mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-09 11:58:37 +02:00
merge upstream/master into andrew/inertia
This commit is contained in:
commit
54153a70a3
54 changed files with 1460 additions and 415 deletions
|
@ -28,6 +28,7 @@ link_hifi_library(audio ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
|
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
|
@ -51,4 +52,4 @@ IF (WIN32)
|
||||||
target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
|
target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
|
||||||
ENDIF(WIN32)
|
ENDIF(WIN32)
|
||||||
|
|
||||||
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}")
|
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}")
|
||||||
|
|
|
@ -11,8 +11,10 @@
|
||||||
|
|
||||||
#include <QtCore/QCoreApplication>
|
#include <QtCore/QCoreApplication>
|
||||||
#include <QtCore/QEventLoop>
|
#include <QtCore/QEventLoop>
|
||||||
|
#include <QtCore/QStandardPaths>
|
||||||
#include <QtCore/QTimer>
|
#include <QtCore/QTimer>
|
||||||
#include <QtNetwork/QNetworkAccessManager>
|
#include <QtNetwork/QNetworkAccessManager>
|
||||||
|
#include <QtNetwork/QNetworkDiskCache>
|
||||||
#include <QtNetwork/QNetworkRequest>
|
#include <QtNetwork/QNetworkRequest>
|
||||||
#include <QtNetwork/QNetworkReply>
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
|
||||||
|
@ -20,6 +22,7 @@
|
||||||
#include <AvatarData.h>
|
#include <AvatarData.h>
|
||||||
#include <NodeList.h>
|
#include <NodeList.h>
|
||||||
#include <PacketHeaders.h>
|
#include <PacketHeaders.h>
|
||||||
|
#include <ResourceCache.h>
|
||||||
#include <UUID.h>
|
#include <UUID.h>
|
||||||
#include <VoxelConstants.h>
|
#include <VoxelConstants.h>
|
||||||
#include <ParticlesScriptingInterface.h>
|
#include <ParticlesScriptingInterface.h>
|
||||||
|
@ -30,7 +33,8 @@ Agent::Agent(const QByteArray& packet) :
|
||||||
ThreadedAssignment(packet),
|
ThreadedAssignment(packet),
|
||||||
_voxelEditSender(),
|
_voxelEditSender(),
|
||||||
_particleEditSender(),
|
_particleEditSender(),
|
||||||
_receivedAudioBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO)
|
_receivedAudioBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO),
|
||||||
|
_avatarHashMap()
|
||||||
{
|
{
|
||||||
// be the parent of the script engine so it gets moved when we do
|
// be the parent of the script engine so it gets moved when we do
|
||||||
_scriptEngine.setParent(this);
|
_scriptEngine.setParent(this);
|
||||||
|
@ -128,6 +132,16 @@ void Agent::readPendingDatagrams() {
|
||||||
// let this continue through to the NodeList so it updates last heard timestamp
|
// let this continue through to the NodeList so it updates last heard timestamp
|
||||||
// for the sending audio mixer
|
// for the sending audio mixer
|
||||||
NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket);
|
NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket);
|
||||||
|
} else if (datagramPacketType == PacketTypeBulkAvatarData
|
||||||
|
|| datagramPacketType == PacketTypeAvatarIdentity
|
||||||
|
|| datagramPacketType == PacketTypeAvatarBillboard
|
||||||
|
|| datagramPacketType == PacketTypeKillAvatar) {
|
||||||
|
// let the avatar hash map process it
|
||||||
|
_avatarHashMap.processAvatarMixerDatagram(receivedPacket, nodeList->sendingNodeForPacket(receivedPacket));
|
||||||
|
|
||||||
|
// let this continue through to the NodeList so it updates last heard timestamp
|
||||||
|
// for the sending avatar-mixer
|
||||||
|
NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket);
|
||||||
} else {
|
} else {
|
||||||
NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket);
|
NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket);
|
||||||
}
|
}
|
||||||
|
@ -148,22 +162,32 @@ void Agent::run() {
|
||||||
<< NodeType::ParticleServer);
|
<< NodeType::ParticleServer);
|
||||||
|
|
||||||
// figure out the URL for the script for this agent assignment
|
// figure out the URL for the script for this agent assignment
|
||||||
QString scriptURLString("http://%1:8080/assignment/%2");
|
QUrl scriptURL;
|
||||||
scriptURLString = scriptURLString.arg(NodeList::getInstance()->getDomainHandler().getIP().toString(),
|
if (_payload.isEmpty()) {
|
||||||
uuidStringWithoutCurlyBraces(_uuid));
|
scriptURL = QUrl(QString("http://%1:8080/assignment/%2")
|
||||||
|
.arg(NodeList::getInstance()->getDomainHandler().getIP().toString(),
|
||||||
|
uuidStringWithoutCurlyBraces(_uuid)));
|
||||||
|
} else {
|
||||||
|
scriptURL = QUrl(_payload);
|
||||||
|
}
|
||||||
|
|
||||||
QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
|
QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
|
||||||
QNetworkReply *reply = networkManager->get(QNetworkRequest(QUrl(scriptURLString)));
|
QNetworkReply *reply = networkManager->get(QNetworkRequest(scriptURL));
|
||||||
|
QNetworkDiskCache* cache = new QNetworkDiskCache(networkManager);
|
||||||
|
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||||
|
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "agentCache");
|
||||||
|
networkManager->setCache(cache);
|
||||||
|
|
||||||
qDebug() << "Downloading script at" << scriptURLString;
|
qDebug() << "Downloading script at" << scriptURL.toString();
|
||||||
|
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||||
|
|
||||||
loop.exec();
|
loop.exec();
|
||||||
|
|
||||||
// let the AvatarData class use our QNetworkAcessManager
|
// let the AvatarData and ResourceCache classes use our QNetworkAccessManager
|
||||||
AvatarData::setNetworkAccessManager(networkManager);
|
AvatarData::setNetworkAccessManager(networkManager);
|
||||||
|
ResourceCache::setNetworkAccessManager(networkManager);
|
||||||
|
|
||||||
QString scriptContents(reply->readAll());
|
QString scriptContents(reply->readAll());
|
||||||
|
|
||||||
|
@ -178,6 +202,7 @@ void Agent::run() {
|
||||||
|
|
||||||
// give this AvatarData object to the script engine
|
// give this AvatarData object to the script engine
|
||||||
_scriptEngine.setAvatarData(&scriptedAvatar, "Avatar");
|
_scriptEngine.setAvatarData(&scriptedAvatar, "Avatar");
|
||||||
|
_scriptEngine.setAvatarHashMap(&_avatarHashMap, "AvatarList");
|
||||||
|
|
||||||
// register ourselves to the script engine
|
// register ourselves to the script engine
|
||||||
_scriptEngine.registerGlobalObject("Agent", this);
|
_scriptEngine.registerGlobalObject("Agent", this);
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <QtCore/QObject>
|
#include <QtCore/QObject>
|
||||||
#include <QtCore/QUrl>
|
#include <QtCore/QUrl>
|
||||||
|
|
||||||
|
#include <AvatarHashMap.h>
|
||||||
#include <MixedAudioRingBuffer.h>
|
#include <MixedAudioRingBuffer.h>
|
||||||
#include <ParticleEditPacketSender.h>
|
#include <ParticleEditPacketSender.h>
|
||||||
#include <ParticleTree.h>
|
#include <ParticleTree.h>
|
||||||
|
@ -65,6 +66,7 @@ private:
|
||||||
VoxelTreeHeadlessViewer _voxelViewer;
|
VoxelTreeHeadlessViewer _voxelViewer;
|
||||||
|
|
||||||
MixedAudioRingBuffer _receivedAudioBuffer;
|
MixedAudioRingBuffer _receivedAudioBuffer;
|
||||||
|
AvatarHashMap _avatarHashMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Agent_h
|
#endif // hifi_Agent_h
|
||||||
|
|
|
@ -42,13 +42,13 @@ body {
|
||||||
background-color: #28FF57;
|
background-color: #28FF57;
|
||||||
}
|
}
|
||||||
|
|
||||||
#instance-field {
|
#extra-fields {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
top: 40px;
|
top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#instance-field input {
|
#extra-fields input {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,13 @@
|
||||||
Run
|
Run
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class='big-field' id='instance-field'>
|
<div id="extra-fields">
|
||||||
<input type='text' name='instances' placeholder='# of instances'>
|
<div class='big-field' id='instance-field'>
|
||||||
|
<input type='text' name='instances' placeholder='# of instances'>
|
||||||
|
</div>
|
||||||
|
<div class='big-field' id='pool-field'>
|
||||||
|
<input type='text' name='pool' placeholder='pool'>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- %div#stop-button.big-button -->
|
<!-- %div#stop-button.big-button -->
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -22,9 +22,14 @@ $(document).ready(function(){
|
||||||
+ '--' + boundary + '--\r\n';
|
+ '--' + boundary + '--\r\n';
|
||||||
|
|
||||||
var headers = {};
|
var headers = {};
|
||||||
|
|
||||||
if ($('#instance-field input').val()) {
|
if ($('#instance-field input').val()) {
|
||||||
headers['ASSIGNMENT-INSTANCES'] = $('#instance-field input').val();
|
headers['ASSIGNMENT-INSTANCES'] = $('#instance-field input').val();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($('#pool-field input').val()) {
|
||||||
|
headers['ASSIGNMENT-POOL'] = $('#pool-field input').val();
|
||||||
|
}
|
||||||
|
|
||||||
// post form to assignment in order to create an assignment
|
// post form to assignment in order to create an assignment
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
|
|
@ -187,7 +187,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QSet<Assignment::Type> parsedTypes(QSet<Assignment::Type>() << Assignment::AgentType);
|
QSet<Assignment::Type> parsedTypes;
|
||||||
parseAssignmentConfigs(parsedTypes);
|
parseAssignmentConfigs(parsedTypes);
|
||||||
|
|
||||||
populateDefaultStaticAssignmentsExcludingTypes(parsedTypes);
|
populateDefaultStaticAssignmentsExcludingTypes(parsedTypes);
|
||||||
|
@ -222,12 +222,19 @@ void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes)
|
||||||
|
|
||||||
if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) {
|
if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) {
|
||||||
QVariant mapValue = _argumentVariantMap[variantMapKeys[configIndex]];
|
QVariant mapValue = _argumentVariantMap[variantMapKeys[configIndex]];
|
||||||
|
QJsonArray assignmentArray;
|
||||||
|
|
||||||
if (mapValue.type() == QVariant::String) {
|
if (mapValue.type() == QVariant::String) {
|
||||||
QJsonDocument deserializedDocument = QJsonDocument::fromJson(mapValue.toString().toUtf8());
|
QJsonDocument deserializedDocument = QJsonDocument::fromJson(mapValue.toString().toUtf8());
|
||||||
createStaticAssignmentsForType(assignmentType, deserializedDocument.array());
|
assignmentArray = deserializedDocument.array();
|
||||||
} else {
|
} else {
|
||||||
createStaticAssignmentsForType(assignmentType, mapValue.toJsonValue().toArray());
|
assignmentArray = mapValue.toJsonValue().toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assignmentType != Assignment::AgentType) {
|
||||||
|
createStaticAssignmentsForType(assignmentType, assignmentArray);
|
||||||
|
} else {
|
||||||
|
createScriptedAssignmentsFromArray(assignmentArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
excludedTypes.insert(assignmentType);
|
excludedTypes.insert(assignmentType);
|
||||||
|
@ -242,6 +249,42 @@ void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment
|
||||||
_staticAssignmentHash.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment));
|
_staticAssignmentHash.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configArray) {
|
||||||
|
foreach(const QJsonValue& jsonValue, configArray) {
|
||||||
|
if (jsonValue.isObject()) {
|
||||||
|
QJsonObject jsonObject = jsonValue.toObject();
|
||||||
|
|
||||||
|
// make sure we were passed a URL, otherwise this is an invalid scripted assignment
|
||||||
|
const QString ASSIGNMENT_URL_KEY = "url";
|
||||||
|
QString assignmentURL = jsonObject[ASSIGNMENT_URL_KEY].toString();
|
||||||
|
|
||||||
|
if (!assignmentURL.isEmpty()) {
|
||||||
|
// check the json for a pool
|
||||||
|
const QString ASSIGNMENT_POOL_KEY = "pool";
|
||||||
|
QString assignmentPool = jsonObject[ASSIGNMENT_POOL_KEY].toString();
|
||||||
|
|
||||||
|
// check for a number of instances, if not passed then default is 1
|
||||||
|
const QString ASSIGNMENT_INSTANCES_KEY = "instances";
|
||||||
|
int numInstances = jsonObject[ASSIGNMENT_INSTANCES_KEY].toInt();
|
||||||
|
numInstances = (numInstances == 0 ? 1 : numInstances);
|
||||||
|
|
||||||
|
for (int i = 0; i < numInstances; i++) {
|
||||||
|
// add a scripted assignment to the queue for this instance
|
||||||
|
Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand,
|
||||||
|
Assignment::AgentType,
|
||||||
|
assignmentPool);
|
||||||
|
scriptAssignment->setPayload(assignmentURL.toUtf8());
|
||||||
|
|
||||||
|
qDebug() << "Adding scripted assignment to queue -" << *scriptAssignment;
|
||||||
|
qDebug() << "URL for script is" << assignmentURL;
|
||||||
|
|
||||||
|
_assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray) {
|
void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray) {
|
||||||
// we have a string for config for this type
|
// we have a string for config for this type
|
||||||
qDebug() << "Parsing config for assignment type" << type;
|
qDebug() << "Parsing config for assignment type" << type;
|
||||||
|
@ -284,8 +327,10 @@ void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const Q
|
||||||
|
|
||||||
void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Assignment::Type>& excludedTypes) {
|
void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Assignment::Type>& excludedTypes) {
|
||||||
// enumerate over all assignment types and see if we've already excluded it
|
// enumerate over all assignment types and see if we've already excluded it
|
||||||
for (int defaultedType = Assignment::AudioMixerType; defaultedType != Assignment::AllTypes; defaultedType++) {
|
for (Assignment::Type defaultedType = Assignment::AudioMixerType;
|
||||||
if (!excludedTypes.contains((Assignment::Type) defaultedType)) {
|
defaultedType != Assignment::AllTypes;
|
||||||
|
defaultedType = static_cast<Assignment::Type>(static_cast<int>(defaultedType) + 1)) {
|
||||||
|
if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) {
|
||||||
// type has not been set from a command line or config file config, use the default
|
// type has not been set from a command line or config file config, use the default
|
||||||
// by clearing whatever exists and writing a single default assignment with no payload
|
// by clearing whatever exists and writing a single default assignment with no payload
|
||||||
Assignment* newAssignment = new Assignment(Assignment::CreateCommand, (Assignment::Type) defaultedType);
|
Assignment* newAssignment = new Assignment(Assignment::CreateCommand, (Assignment::Type) defaultedType);
|
||||||
|
|
|
@ -66,6 +66,7 @@ private:
|
||||||
|
|
||||||
void parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes);
|
void parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes);
|
||||||
void addStaticAssignmentToAssignmentHash(Assignment* newAssignment);
|
void addStaticAssignmentToAssignmentHash(Assignment* newAssignment);
|
||||||
|
void createScriptedAssignmentsFromArray(const QJsonArray& configArray);
|
||||||
void createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray);
|
void createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray);
|
||||||
void populateDefaultStaticAssignmentsExcludingTypes(const QSet<Assignment::Type>& excludedTypes);
|
void populateDefaultStaticAssignmentsExcludingTypes(const QSet<Assignment::Type>& excludedTypes);
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ var reflectiveScale = 100.0;
|
||||||
var diffusionScale = 100.0;
|
var diffusionScale = 100.0;
|
||||||
var absorptionScale = 100.0;
|
var absorptionScale = 100.0;
|
||||||
var combFilterScale = 50.0;
|
var combFilterScale = 50.0;
|
||||||
|
var originalScale = 2.0;
|
||||||
|
var echoesScale = 2.0;
|
||||||
|
|
||||||
// these three properties are bound together, if you change one, the others will also change
|
// these three properties are bound together, if you change one, the others will also change
|
||||||
var reflectiveRatio = AudioReflector.getReflectiveRatio();
|
var reflectiveRatio = AudioReflector.getReflectiveRatio();
|
||||||
|
@ -421,6 +423,84 @@ var absorptionThumb = Overlays.addOverlay("image", {
|
||||||
alpha: 1
|
alpha: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var originalY = topY;
|
||||||
|
topY += sliderHeight;
|
||||||
|
|
||||||
|
var originalLabel = Overlays.addOverlay("text", {
|
||||||
|
x: 40,
|
||||||
|
y: originalY,
|
||||||
|
width: 60,
|
||||||
|
height: sliderHeight,
|
||||||
|
color: { red: 0, green: 0, blue: 0},
|
||||||
|
textColor: { red: 255, green: 255, blue: 255},
|
||||||
|
topMargin: 6,
|
||||||
|
leftMargin: 5,
|
||||||
|
text: "Original\nMix:"
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var originalSlider = Overlays.addOverlay("image", {
|
||||||
|
// alternate form of expressing bounds
|
||||||
|
bounds: { x: 100, y: originalY, width: 150, height: sliderHeight},
|
||||||
|
subImage: { x: 46, y: 0, width: 200, height: 71 },
|
||||||
|
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png",
|
||||||
|
color: { red: 255, green: 255, blue: 255},
|
||||||
|
alpha: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var originalMinThumbX = 110;
|
||||||
|
var originalMaxThumbX = originalMinThumbX + 110;
|
||||||
|
var originalThumbX = originalMinThumbX + ((originalMaxThumbX - originalMinThumbX) * (AudioReflector.getOriginalSourceAttenuation() / originalScale));
|
||||||
|
var originalThumb = Overlays.addOverlay("image", {
|
||||||
|
x: originalThumbX,
|
||||||
|
y: originalY+9,
|
||||||
|
width: 18,
|
||||||
|
height: 17,
|
||||||
|
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png",
|
||||||
|
color: { red: 128, green: 128, blue: 0},
|
||||||
|
alpha: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
var echoesY = topY;
|
||||||
|
topY += sliderHeight;
|
||||||
|
|
||||||
|
var echoesLabel = Overlays.addOverlay("text", {
|
||||||
|
x: 40,
|
||||||
|
y: echoesY,
|
||||||
|
width: 60,
|
||||||
|
height: sliderHeight,
|
||||||
|
color: { red: 0, green: 0, blue: 0},
|
||||||
|
textColor: { red: 255, green: 255, blue: 255},
|
||||||
|
topMargin: 6,
|
||||||
|
leftMargin: 5,
|
||||||
|
text: "Echoes\nMix:"
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var echoesSlider = Overlays.addOverlay("image", {
|
||||||
|
// alternate form of expressing bounds
|
||||||
|
bounds: { x: 100, y: echoesY, width: 150, height: sliderHeight},
|
||||||
|
subImage: { x: 46, y: 0, width: 200, height: 71 },
|
||||||
|
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png",
|
||||||
|
color: { red: 255, green: 255, blue: 255},
|
||||||
|
alpha: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var echoesMinThumbX = 110;
|
||||||
|
var echoesMaxThumbX = echoesMinThumbX + 110;
|
||||||
|
var echoesThumbX = echoesMinThumbX + ((echoesMaxThumbX - echoesMinThumbX) * (AudioReflector.getEchoesAttenuation() / echoesScale));
|
||||||
|
var echoesThumb = Overlays.addOverlay("image", {
|
||||||
|
x: echoesThumbX,
|
||||||
|
y: echoesY+9,
|
||||||
|
width: 18,
|
||||||
|
height: 17,
|
||||||
|
imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png",
|
||||||
|
color: { red: 128, green: 128, blue: 0},
|
||||||
|
alpha: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// When our script shuts down, we should clean up all of our overlays
|
// When our script shuts down, we should clean up all of our overlays
|
||||||
function scriptEnding() {
|
function scriptEnding() {
|
||||||
|
@ -460,6 +540,14 @@ function scriptEnding() {
|
||||||
Overlays.deleteOverlay(absorptionThumb);
|
Overlays.deleteOverlay(absorptionThumb);
|
||||||
Overlays.deleteOverlay(absorptionSlider);
|
Overlays.deleteOverlay(absorptionSlider);
|
||||||
|
|
||||||
|
Overlays.deleteOverlay(echoesLabel);
|
||||||
|
Overlays.deleteOverlay(echoesThumb);
|
||||||
|
Overlays.deleteOverlay(echoesSlider);
|
||||||
|
|
||||||
|
Overlays.deleteOverlay(originalLabel);
|
||||||
|
Overlays.deleteOverlay(originalThumb);
|
||||||
|
Overlays.deleteOverlay(originalSlider);
|
||||||
|
|
||||||
}
|
}
|
||||||
Script.scriptEnding.connect(scriptEnding);
|
Script.scriptEnding.connect(scriptEnding);
|
||||||
|
|
||||||
|
@ -483,6 +571,8 @@ var movingSliderLocalFactor = false;
|
||||||
var movingSliderReflective = false;
|
var movingSliderReflective = false;
|
||||||
var movingSliderDiffusion = false;
|
var movingSliderDiffusion = false;
|
||||||
var movingSliderAbsorption = false;
|
var movingSliderAbsorption = false;
|
||||||
|
var movingSliderOriginal = false;
|
||||||
|
var movingSliderEchoes = false;
|
||||||
|
|
||||||
var thumbClickOffsetX = 0;
|
var thumbClickOffsetX = 0;
|
||||||
function mouseMoveEvent(event) {
|
function mouseMoveEvent(event) {
|
||||||
|
@ -546,7 +636,6 @@ function mouseMoveEvent(event) {
|
||||||
var combFilter = ((newThumbX - combFilterMinThumbX) / (combFilterMaxThumbX - combFilterMinThumbX)) * combFilterScale;
|
var combFilter = ((newThumbX - combFilterMinThumbX) / (combFilterMaxThumbX - combFilterMinThumbX)) * combFilterScale;
|
||||||
AudioReflector.setCombFilterWindow(combFilter);
|
AudioReflector.setCombFilterWindow(combFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (movingSliderLocalFactor) {
|
if (movingSliderLocalFactor) {
|
||||||
newThumbX = event.x - thumbClickOffsetX;
|
newThumbX = event.x - thumbClickOffsetX;
|
||||||
if (newThumbX < localFactorMinThumbX) {
|
if (newThumbX < localFactorMinThumbX) {
|
||||||
|
@ -598,6 +687,30 @@ function mouseMoveEvent(event) {
|
||||||
var diffusion = ((newThumbX - diffusionMinThumbX) / (diffusionMaxThumbX - diffusionMinThumbX)) * diffusionScale;
|
var diffusion = ((newThumbX - diffusionMinThumbX) / (diffusionMaxThumbX - diffusionMinThumbX)) * diffusionScale;
|
||||||
setDiffusionRatio(diffusion);
|
setDiffusionRatio(diffusion);
|
||||||
}
|
}
|
||||||
|
if (movingSliderEchoes) {
|
||||||
|
newThumbX = event.x - thumbClickOffsetX;
|
||||||
|
if (newThumbX < echoesMinThumbX) {
|
||||||
|
newThumbX = echoesMminThumbX;
|
||||||
|
}
|
||||||
|
if (newThumbX > echoesMaxThumbX) {
|
||||||
|
newThumbX = echoesMaxThumbX;
|
||||||
|
}
|
||||||
|
Overlays.editOverlay(echoesThumb, { x: newThumbX } );
|
||||||
|
var echoes = ((newThumbX - echoesMinThumbX) / (echoesMaxThumbX - echoesMinThumbX)) * echoesScale;
|
||||||
|
AudioReflector.setEchoesAttenuation(echoes);
|
||||||
|
}
|
||||||
|
if (movingSliderOriginal) {
|
||||||
|
newThumbX = event.x - thumbClickOffsetX;
|
||||||
|
if (newThumbX < originalMinThumbX) {
|
||||||
|
newThumbX = originalMminThumbX;
|
||||||
|
}
|
||||||
|
if (newThumbX > originalMaxThumbX) {
|
||||||
|
newThumbX = originalMaxThumbX;
|
||||||
|
}
|
||||||
|
Overlays.editOverlay(originalThumb, { x: newThumbX } );
|
||||||
|
var original = ((newThumbX - originalMinThumbX) / (originalMaxThumbX - originalMinThumbX)) * originalScale;
|
||||||
|
AudioReflector.setOriginalSourceAttenuation(original);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -640,7 +753,16 @@ function mousePressEvent(event) {
|
||||||
movingSliderReflective = true;
|
movingSliderReflective = true;
|
||||||
thumbClickOffsetX = event.x - reflectiveThumbX;
|
thumbClickOffsetX = event.x - reflectiveThumbX;
|
||||||
}
|
}
|
||||||
|
if (clickedOverlay == originalThumb) {
|
||||||
|
movingSliderOriginal = true;
|
||||||
|
thumbClickOffsetX = event.x - originalThumbX;
|
||||||
|
}
|
||||||
|
if (clickedOverlay == echoesThumb) {
|
||||||
|
movingSliderEchoes = true;
|
||||||
|
thumbClickOffsetX = event.x - echoesThumbX;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mouseReleaseEvent(event) {
|
function mouseReleaseEvent(event) {
|
||||||
if (movingSliderDelay) {
|
if (movingSliderDelay) {
|
||||||
movingSliderDelay = false;
|
movingSliderDelay = false;
|
||||||
|
@ -672,14 +794,12 @@ function mouseReleaseEvent(event) {
|
||||||
AudioReflector.setCombFilterWindow(combFilter);
|
AudioReflector.setCombFilterWindow(combFilter);
|
||||||
combFilterThumbX = newThumbX;
|
combFilterThumbX = newThumbX;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (movingSliderLocalFactor) {
|
if (movingSliderLocalFactor) {
|
||||||
movingSliderLocalFactor = false;
|
movingSliderLocalFactor = false;
|
||||||
var localFactor = ((newThumbX - localFactorMinThumbX) / (localFactorMaxThumbX - localFactorMinThumbX)) * localFactorScale;
|
var localFactor = ((newThumbX - localFactorMinThumbX) / (localFactorMaxThumbX - localFactorMinThumbX)) * localFactorScale;
|
||||||
AudioReflector.setLocalAudioAttenuationFactor(localFactor);
|
AudioReflector.setLocalAudioAttenuationFactor(localFactor);
|
||||||
localFactorThumbX = newThumbX;
|
localFactorThumbX = newThumbX;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (movingSliderReflective) {
|
if (movingSliderReflective) {
|
||||||
movingSliderReflective = false;
|
movingSliderReflective = false;
|
||||||
var reflective = ((newThumbX - reflectiveMinThumbX) / (reflectiveMaxThumbX - reflectiveMinThumbX)) * reflectiveScale;
|
var reflective = ((newThumbX - reflectiveMinThumbX) / (reflectiveMaxThumbX - reflectiveMinThumbX)) * reflectiveScale;
|
||||||
|
@ -687,7 +807,6 @@ function mouseReleaseEvent(event) {
|
||||||
reflectiveThumbX = newThumbX;
|
reflectiveThumbX = newThumbX;
|
||||||
updateRatioSliders();
|
updateRatioSliders();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (movingSliderDiffusion) {
|
if (movingSliderDiffusion) {
|
||||||
movingSliderDiffusion = false;
|
movingSliderDiffusion = false;
|
||||||
var diffusion = ((newThumbX - diffusionMinThumbX) / (diffusionMaxThumbX - diffusionMinThumbX)) * diffusionScale;
|
var diffusion = ((newThumbX - diffusionMinThumbX) / (diffusionMaxThumbX - diffusionMinThumbX)) * diffusionScale;
|
||||||
|
@ -695,7 +814,6 @@ function mouseReleaseEvent(event) {
|
||||||
diffusionThumbX = newThumbX;
|
diffusionThumbX = newThumbX;
|
||||||
updateRatioSliders();
|
updateRatioSliders();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (movingSliderAbsorption) {
|
if (movingSliderAbsorption) {
|
||||||
movingSliderAbsorption = false;
|
movingSliderAbsorption = false;
|
||||||
var absorption = ((newThumbX - absorptionMinThumbX) / (absorptionMaxThumbX - absorptionMinThumbX)) * absorptionScale;
|
var absorption = ((newThumbX - absorptionMinThumbX) / (absorptionMaxThumbX - absorptionMinThumbX)) * absorptionScale;
|
||||||
|
@ -703,6 +821,18 @@ function mouseReleaseEvent(event) {
|
||||||
absorptionThumbX = newThumbX;
|
absorptionThumbX = newThumbX;
|
||||||
updateRatioSliders();
|
updateRatioSliders();
|
||||||
}
|
}
|
||||||
|
if (movingSliderEchoes) {
|
||||||
|
movingSliderEchoes = false;
|
||||||
|
var echoes = ((newThumbX - echoesMinThumbX) / (echoesMaxThumbX - echoesMinThumbX)) * echoesScale;
|
||||||
|
AudioReflector.setEchoesAttenuation(echoes);
|
||||||
|
echoesThumbX = newThumbX;
|
||||||
|
}
|
||||||
|
if (movingSliderOriginal) {
|
||||||
|
movingSliderOriginal = false;
|
||||||
|
var original = ((newThumbX - originalMinThumbX) / (originalMaxThumbX - originalMinThumbX)) * originalScale;
|
||||||
|
AudioReflector.setOriginalSourceAttenuation(original);
|
||||||
|
originalThumbX = newThumbX;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||||
|
|
|
@ -15,12 +15,12 @@ var AMPLITUDE = 45.0;
|
||||||
|
|
||||||
var cumulativeTime = 0.0;
|
var cumulativeTime = 0.0;
|
||||||
|
|
||||||
print("# Joint list start");
|
|
||||||
var jointList = MyAvatar.getJointNames();
|
var jointList = MyAvatar.getJointNames();
|
||||||
|
var jointMappings = "\n# Joint list start";
|
||||||
for (var i = 0; i < jointList.length; i++) {
|
for (var i = 0; i < jointList.length; i++) {
|
||||||
print("jointIndex = " + jointList[i] + " = " + i);
|
jointMappings = jointMappings + "\njointIndex = " + jointList[i] + " = " + i;
|
||||||
}
|
}
|
||||||
print("# Joint list end");
|
print(jointMappings + "\n# Joint list end");
|
||||||
|
|
||||||
Script.update.connect(function(deltaTime) {
|
Script.update.connect(function(deltaTime) {
|
||||||
cumulativeTime += deltaTime;
|
cumulativeTime += deltaTime;
|
||||||
|
|
49
examples/dancing_bot.js
Normal file
49
examples/dancing_bot.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
//
|
||||||
|
// dancing_bot.js
|
||||||
|
// examples
|
||||||
|
//
|
||||||
|
// Created by Andrzej Kapolka on 4/16/14.
|
||||||
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// This is an example script that demonstrates an NPC avatar running an FBX animation loop.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
var animation = AnimationCache.getAnimation("http://www.fungibleinsight.com/faces/gangnam_style_2.fbx");
|
||||||
|
|
||||||
|
Avatar.skeletonModelURL = "http://www.fungibleinsight.com/faces/beta.fst";
|
||||||
|
|
||||||
|
Agent.isAvatar = true;
|
||||||
|
|
||||||
|
var jointMapping;
|
||||||
|
|
||||||
|
var frameIndex = 0.0;
|
||||||
|
|
||||||
|
var FRAME_RATE = 30.0; // frames per second
|
||||||
|
|
||||||
|
Script.update.connect(function(deltaTime) {
|
||||||
|
if (!jointMapping) {
|
||||||
|
var avatarJointNames = Avatar.jointNames;
|
||||||
|
var animationJointNames = animation.jointNames;
|
||||||
|
if (avatarJointNames === 0 || animationJointNames.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jointMapping = new Array(avatarJointNames.length);
|
||||||
|
for (var i = 0; i < avatarJointNames.length; i++) {
|
||||||
|
jointMapping[i] = animationJointNames.indexOf(avatarJointNames[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frameIndex += deltaTime * FRAME_RATE;
|
||||||
|
var frames = animation.frames;
|
||||||
|
var rotations = frames[Math.floor(frameIndex) % frames.length].rotations;
|
||||||
|
for (var j = 0; j < jointMapping.length; j++) {
|
||||||
|
var rotationIndex = jointMapping[j];
|
||||||
|
if (rotationIndex != -1) {
|
||||||
|
Avatar.setJointData(j, rotations[rotationIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
@ -64,19 +64,92 @@ colors[8] = { red: 31, green: 64, blue: 64 };
|
||||||
var numColors = 9;
|
var numColors = 9;
|
||||||
var whichColor = -1; // Starting color is 'Copy' mode
|
var whichColor = -1; // Starting color is 'Copy' mode
|
||||||
|
|
||||||
// Create sounds for adding, deleting, recoloring voxels
|
// Create sounds for for every script actions that require one
|
||||||
var addSound1 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel+create+2.raw");
|
|
||||||
var addSound2 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel+create+4.raw");
|
|
||||||
|
|
||||||
var addSound3 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel+create+3.raw");
|
|
||||||
var deleteSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel+delete+2.raw");
|
|
||||||
var changeColorSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel+edit+2.raw");
|
|
||||||
var clickSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Switches+and+sliders/toggle+switch+-+medium.raw");
|
|
||||||
var audioOptions = new AudioInjectionOptions();
|
var audioOptions = new AudioInjectionOptions();
|
||||||
|
audioOptions.volume = 1.0;
|
||||||
audioOptions.volume = 0.5;
|
|
||||||
audioOptions.position = Vec3.sum(MyAvatar.position, { x: 0, y: 1, z: 0 } ); // start with audio slightly above the avatar
|
audioOptions.position = Vec3.sum(MyAvatar.position, { x: 0, y: 1, z: 0 } ); // start with audio slightly above the avatar
|
||||||
|
|
||||||
|
function SoundArray() {
|
||||||
|
this.audioOptions = audioOptions
|
||||||
|
this.sounds = new Array();
|
||||||
|
this.addSound = function (soundURL) {
|
||||||
|
this.sounds[this.sounds.length] = new Sound(soundURL);
|
||||||
|
}
|
||||||
|
this.play = function (index) {
|
||||||
|
if (0 <= index && index < this.sounds.length) {
|
||||||
|
Audio.playSound(this.sounds[index], this.audioOptions);
|
||||||
|
} else {
|
||||||
|
print("[ERROR] editVoxels.js:randSound.play() : Index " + index + " out of range.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.playRandom = function () {
|
||||||
|
if (this.sounds.length > 0) {
|
||||||
|
rand = Math.floor(Math.random() * this.sounds.length);
|
||||||
|
Audio.playSound(this.sounds[rand], this.audioOptions);
|
||||||
|
} else {
|
||||||
|
print("[ERROR] editVoxels.js:randSound.playRandom() : Array is empty.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var addVoxelSound = new SoundArray();
|
||||||
|
addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+1.raw");
|
||||||
|
addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+2.raw");
|
||||||
|
addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+3.raw");
|
||||||
|
addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+4.raw");
|
||||||
|
addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+5.raw");
|
||||||
|
addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+6.raw");
|
||||||
|
|
||||||
|
var delVoxelSound = new SoundArray();
|
||||||
|
delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+A1.raw");
|
||||||
|
delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+A2.raw");
|
||||||
|
delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+A3.raw");
|
||||||
|
delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+B1.raw");
|
||||||
|
delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+B2.raw");
|
||||||
|
delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+B3.raw");
|
||||||
|
|
||||||
|
var resizeVoxelSound = new SoundArray();
|
||||||
|
resizeVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Size/V+Size+Minus.raw");
|
||||||
|
resizeVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Size/V+Size+Plus.raw");
|
||||||
|
var voxelSizeMinus = 0;
|
||||||
|
var voxelSizePlus = 1;
|
||||||
|
|
||||||
|
var swatchesSound = new SoundArray();
|
||||||
|
swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+1.raw");
|
||||||
|
swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+2.raw");
|
||||||
|
swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+3.raw");
|
||||||
|
swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+4.raw");
|
||||||
|
swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+5.raw");
|
||||||
|
swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+6.raw");
|
||||||
|
swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+7.raw");
|
||||||
|
swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+8.raw");
|
||||||
|
swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+9.raw");
|
||||||
|
|
||||||
|
var undoSound = new SoundArray();
|
||||||
|
undoSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Undo/Undo+1.raw");
|
||||||
|
undoSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Undo/Undo+2.raw");
|
||||||
|
undoSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Undo/Undo+3.raw");
|
||||||
|
|
||||||
|
var scriptInitSound = new SoundArray();
|
||||||
|
scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+A.raw");
|
||||||
|
scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+B.raw");
|
||||||
|
scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+C.raw");
|
||||||
|
scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+D.raw");
|
||||||
|
|
||||||
|
var modeSwitchSound = new SoundArray();
|
||||||
|
modeSwitchSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Mode+Switch/Mode+1.raw");
|
||||||
|
modeSwitchSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Mode+Switch/Mode+2.raw");
|
||||||
|
modeSwitchSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Mode+Switch/Mode+3.raw");
|
||||||
|
|
||||||
|
var initialVoxelSound = new SoundArray();
|
||||||
|
initialVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Initial+Voxel/Initial+V.raw");
|
||||||
|
|
||||||
|
var colorInheritSound = new SoundArray();
|
||||||
|
colorInheritSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Color+Inherit/Inherit+A.raw");
|
||||||
|
colorInheritSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Color+Inherit/Inherit+B.raw");
|
||||||
|
colorInheritSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Color+Inherit/Inherit+C.raw");
|
||||||
|
|
||||||
|
|
||||||
var editToolsOn = true; // starts out off
|
var editToolsOn = true; // starts out off
|
||||||
|
|
||||||
// previewAsVoxel - by default, we will preview adds/deletes/recolors as just 4 lines on the intersecting face. But if you
|
// previewAsVoxel - by default, we will preview adds/deletes/recolors as just 4 lines on the intersecting face. But if you
|
||||||
|
@ -379,8 +452,17 @@ function calcThumbFromScale(scale) {
|
||||||
if (thumbStep > pointerVoxelScaleSteps) {
|
if (thumbStep > pointerVoxelScaleSteps) {
|
||||||
thumbStep = pointerVoxelScaleSteps;
|
thumbStep = pointerVoxelScaleSteps;
|
||||||
}
|
}
|
||||||
|
var oldThumbX = thumbX;
|
||||||
thumbX = (thumbDeltaPerStep * (thumbStep - 1)) + minThumbX;
|
thumbX = (thumbDeltaPerStep * (thumbStep - 1)) + minThumbX;
|
||||||
Overlays.editOverlay(thumb, { x: thumbX + sliderX } );
|
Overlays.editOverlay(thumb, { x: thumbX + sliderX } );
|
||||||
|
|
||||||
|
if (thumbX > oldThumbX) {
|
||||||
|
resizeVoxelSound.play(voxelSizePlus);
|
||||||
|
print("Plus");
|
||||||
|
} else if (thumbX < oldThumbX) {
|
||||||
|
resizeVoxelSound.play(voxelSizeMinus);
|
||||||
|
print("Minus");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcScaleFromThumb(newThumbX) {
|
function calcScaleFromThumb(newThumbX) {
|
||||||
|
@ -443,15 +525,6 @@ var recolorToolSelected = false;
|
||||||
var eyedropperToolSelected = false;
|
var eyedropperToolSelected = false;
|
||||||
var pasteMode = false;
|
var pasteMode = false;
|
||||||
|
|
||||||
function playRandomAddSound(audioOptions) {
|
|
||||||
if (Math.random() < 0.33) {
|
|
||||||
Audio.playSound(addSound1, audioOptions);
|
|
||||||
} else if (Math.random() < 0.5) {
|
|
||||||
Audio.playSound(addSound2, audioOptions);
|
|
||||||
} else {
|
|
||||||
Audio.playSound(addSound3, audioOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateVoxelFromIntersection(intersection, operation) {
|
function calculateVoxelFromIntersection(intersection, operation) {
|
||||||
//print("calculateVoxelFromIntersection() operation="+operation);
|
//print("calculateVoxelFromIntersection() operation="+operation);
|
||||||
|
@ -744,7 +817,7 @@ function trackKeyReleaseEvent(event) {
|
||||||
moveTools();
|
moveTools();
|
||||||
setAudioPosition(); // make sure we set the audio position before playing sounds
|
setAudioPosition(); // make sure we set the audio position before playing sounds
|
||||||
showPreviewGuides();
|
showPreviewGuides();
|
||||||
Audio.playSound(clickSound, audioOptions);
|
scriptInitSound.playRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.text == "ALT") {
|
if (event.text == "ALT") {
|
||||||
|
@ -808,18 +881,21 @@ function mousePressEvent(event) {
|
||||||
Overlays.editOverlay(thumb, { imageURL: toolIconUrl + "voxel-size-slider-handle.svg", });
|
Overlays.editOverlay(thumb, { imageURL: toolIconUrl + "voxel-size-slider-handle.svg", });
|
||||||
|
|
||||||
} else if (clickedOverlay == voxelTool) {
|
} else if (clickedOverlay == voxelTool) {
|
||||||
|
modeSwitchSound.play(0);
|
||||||
voxelToolSelected = true;
|
voxelToolSelected = true;
|
||||||
recolorToolSelected = false;
|
recolorToolSelected = false;
|
||||||
eyedropperToolSelected = false;
|
eyedropperToolSelected = false;
|
||||||
moveTools();
|
moveTools();
|
||||||
clickedOnSomething = true;
|
clickedOnSomething = true;
|
||||||
} else if (clickedOverlay == recolorTool) {
|
} else if (clickedOverlay == recolorTool) {
|
||||||
|
modeSwitchSound.play(1);
|
||||||
voxelToolSelected = false;
|
voxelToolSelected = false;
|
||||||
recolorToolSelected = true;
|
recolorToolSelected = true;
|
||||||
eyedropperToolSelected = false;
|
eyedropperToolSelected = false;
|
||||||
moveTools();
|
moveTools();
|
||||||
clickedOnSomething = true;
|
clickedOnSomething = true;
|
||||||
} else if (clickedOverlay == eyedropperTool) {
|
} else if (clickedOverlay == eyedropperTool) {
|
||||||
|
modeSwitchSound.play(2);
|
||||||
voxelToolSelected = false;
|
voxelToolSelected = false;
|
||||||
recolorToolSelected = false;
|
recolorToolSelected = false;
|
||||||
eyedropperToolSelected = true;
|
eyedropperToolSelected = true;
|
||||||
|
@ -846,6 +922,7 @@ function mousePressEvent(event) {
|
||||||
whichColor = s;
|
whichColor = s;
|
||||||
moveTools();
|
moveTools();
|
||||||
clickedOnSomething = true;
|
clickedOnSomething = true;
|
||||||
|
swatchesSound.play(whichColor);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -888,7 +965,7 @@ function mousePressEvent(event) {
|
||||||
// Delete voxel
|
// Delete voxel
|
||||||
voxelDetails = calculateVoxelFromIntersection(intersection,"delete");
|
voxelDetails = calculateVoxelFromIntersection(intersection,"delete");
|
||||||
Voxels.eraseVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s);
|
Voxels.eraseVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s);
|
||||||
Audio.playSound(deleteSound, audioOptions);
|
delVoxelSound.playRandom();
|
||||||
Overlays.editOverlay(voxelPreview, { visible: false });
|
Overlays.editOverlay(voxelPreview, { visible: false });
|
||||||
} else if (eyedropperToolSelected || trackAsEyedropper) {
|
} else if (eyedropperToolSelected || trackAsEyedropper) {
|
||||||
if (whichColor != -1) {
|
if (whichColor != -1) {
|
||||||
|
@ -896,6 +973,7 @@ function mousePressEvent(event) {
|
||||||
colors[whichColor].green = intersection.voxel.green;
|
colors[whichColor].green = intersection.voxel.green;
|
||||||
colors[whichColor].blue = intersection.voxel.blue;
|
colors[whichColor].blue = intersection.voxel.blue;
|
||||||
moveTools();
|
moveTools();
|
||||||
|
swatchesSound.play(whichColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (recolorToolSelected || trackAsRecolor) {
|
} else if (recolorToolSelected || trackAsRecolor) {
|
||||||
|
@ -903,10 +981,9 @@ function mousePressEvent(event) {
|
||||||
voxelDetails = calculateVoxelFromIntersection(intersection,"recolor");
|
voxelDetails = calculateVoxelFromIntersection(intersection,"recolor");
|
||||||
|
|
||||||
// doing this erase then set will make sure we only recolor just the target voxel
|
// doing this erase then set will make sure we only recolor just the target voxel
|
||||||
Voxels.eraseVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s);
|
|
||||||
Voxels.setVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s,
|
Voxels.setVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s,
|
||||||
colors[whichColor].red, colors[whichColor].green, colors[whichColor].blue);
|
colors[whichColor].red, colors[whichColor].green, colors[whichColor].blue);
|
||||||
Audio.playSound(changeColorSound, audioOptions);
|
swatchesSound.play(whichColor);
|
||||||
Overlays.editOverlay(voxelPreview, { visible: false });
|
Overlays.editOverlay(voxelPreview, { visible: false });
|
||||||
} else if (voxelToolSelected) {
|
} else if (voxelToolSelected) {
|
||||||
// Add voxel on face
|
// Add voxel on face
|
||||||
|
@ -930,7 +1007,7 @@ function mousePressEvent(event) {
|
||||||
lastVoxelColor = { red: newColor.red, green: newColor.green, blue: newColor.blue };
|
lastVoxelColor = { red: newColor.red, green: newColor.green, blue: newColor.blue };
|
||||||
lastVoxelScale = voxelDetails.s;
|
lastVoxelScale = voxelDetails.s;
|
||||||
|
|
||||||
playRandomAddSound(audioOptions);
|
addVoxelSound.playRandom();
|
||||||
|
|
||||||
Overlays.editOverlay(voxelPreview, { visible: false });
|
Overlays.editOverlay(voxelPreview, { visible: false });
|
||||||
dragStart = { x: event.x, y: event.y };
|
dragStart = { x: event.x, y: event.y };
|
||||||
|
@ -946,12 +1023,12 @@ function keyPressEvent(event) {
|
||||||
if (event.text == "`") {
|
if (event.text == "`") {
|
||||||
print("Color = Copy");
|
print("Color = Copy");
|
||||||
whichColor = -1;
|
whichColor = -1;
|
||||||
Audio.playSound(clickSound, audioOptions);
|
colorInheritSound.playRandom();
|
||||||
moveTools();
|
moveTools();
|
||||||
} else if ((nVal > 0) && (nVal <= numColors)) {
|
} else if ((nVal > 0) && (nVal <= numColors)) {
|
||||||
whichColor = nVal - 1;
|
whichColor = nVal - 1;
|
||||||
print("Color = " + (whichColor + 1));
|
print("Color = " + (whichColor + 1));
|
||||||
Audio.playSound(clickSound, audioOptions);
|
swatchesSound.play(whichColor);
|
||||||
moveTools();
|
moveTools();
|
||||||
} else if (event.text == "0") {
|
} else if (event.text == "0") {
|
||||||
// Create a brand new 1 meter voxel in front of your avatar
|
// Create a brand new 1 meter voxel in front of your avatar
|
||||||
|
@ -969,8 +1046,12 @@ function keyPressEvent(event) {
|
||||||
Voxels.eraseVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s);
|
Voxels.eraseVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s);
|
||||||
Voxels.setVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s, newVoxel.red, newVoxel.green, newVoxel.blue);
|
Voxels.setVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s, newVoxel.red, newVoxel.green, newVoxel.blue);
|
||||||
setAudioPosition();
|
setAudioPosition();
|
||||||
playRandomAddSound(audioOptions);
|
initialVoxelSound.playRandom();
|
||||||
|
} else if (event.text == "z") {
|
||||||
|
undoSound.playRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trackKeyPressEvent(event); // used by preview support
|
trackKeyPressEvent(event); // used by preview support
|
||||||
|
|
|
@ -120,6 +120,7 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake)
|
||||||
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
|
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
|
|
|
@ -1679,8 +1679,12 @@ void Application::init() {
|
||||||
_audioReflector.setMyAvatar(getAvatar());
|
_audioReflector.setMyAvatar(getAvatar());
|
||||||
_audioReflector.setVoxels(_voxels.getTree());
|
_audioReflector.setVoxels(_voxels.getTree());
|
||||||
_audioReflector.setAudio(getAudio());
|
_audioReflector.setAudio(getAudio());
|
||||||
|
_audioReflector.setAvatarManager(&_avatarManager);
|
||||||
|
|
||||||
connect(getAudio(), &Audio::processInboundAudio, &_audioReflector, &AudioReflector::processInboundAudio,Qt::DirectConnection);
|
connect(getAudio(), &Audio::processInboundAudio, &_audioReflector, &AudioReflector::processInboundAudio,Qt::DirectConnection);
|
||||||
connect(getAudio(), &Audio::processLocalAudio, &_audioReflector, &AudioReflector::processLocalAudio,Qt::DirectConnection);
|
connect(getAudio(), &Audio::processLocalAudio, &_audioReflector, &AudioReflector::processLocalAudio,Qt::DirectConnection);
|
||||||
|
connect(getAudio(), &Audio::preProcessOriginalInboundAudio, &_audioReflector,
|
||||||
|
&AudioReflector::preProcessOriginalInboundAudio,Qt::DirectConnection);
|
||||||
|
|
||||||
// save settings when avatar changes
|
// save settings when avatar changes
|
||||||
connect(_myAvatar, &MyAvatar::transformChanged, this, &Application::bumpSettings);
|
connect(_myAvatar, &MyAvatar::transformChanged, this, &Application::bumpSettings);
|
||||||
|
@ -3397,6 +3401,7 @@ void Application::loadScript(const QString& scriptName) {
|
||||||
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
|
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
|
||||||
scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
|
scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
|
||||||
scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance());
|
scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance());
|
||||||
|
scriptEngine->registerGlobalObject("AnimationCache", &_animationCache);
|
||||||
scriptEngine->registerGlobalObject("AudioReflector", &_audioReflector);
|
scriptEngine->registerGlobalObject("AudioReflector", &_audioReflector);
|
||||||
|
|
||||||
QThread* workerThread = new QThread(this);
|
QThread* workerThread = new QThread(this);
|
||||||
|
|
|
@ -470,6 +470,7 @@ private:
|
||||||
QSet<int> _keysPressed;
|
QSet<int> _keysPressed;
|
||||||
|
|
||||||
GeometryCache _geometryCache;
|
GeometryCache _geometryCache;
|
||||||
|
AnimationCache _animationCache;
|
||||||
TextureCache _textureCache;
|
TextureCache _textureCache;
|
||||||
|
|
||||||
GlowEffect _glowEffect;
|
GlowEffect _glowEffect;
|
||||||
|
|
|
@ -784,6 +784,7 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) {
|
||||||
_ringBuffer.readSamples((int16_t*)buffer.data(), numNetworkOutputSamples);
|
_ringBuffer.readSamples((int16_t*)buffer.data(), numNetworkOutputSamples);
|
||||||
// Accumulate direct transmission of audio from sender to receiver
|
// Accumulate direct transmission of audio from sender to receiver
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingIncludeOriginal)) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingIncludeOriginal)) {
|
||||||
|
emit preProcessOriginalInboundAudio(sampleTime, buffer, _desiredOutputFormat);
|
||||||
addSpatialAudioToBuffer(sampleTime, buffer, numNetworkOutputSamples);
|
addSpatialAudioToBuffer(sampleTime, buffer, numNetworkOutputSamples);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,7 @@ public slots:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
bool muteToggled();
|
bool muteToggled();
|
||||||
|
void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format);
|
||||||
void processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
|
void processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
|
||||||
void processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
|
void processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@ const float SLIGHTLY_SHORT = 0.999f; // slightly inside the distance so we're on
|
||||||
|
|
||||||
const float DEFAULT_ABSORPTION_RATIO = 0.125; // 12.5% is absorbed
|
const float DEFAULT_ABSORPTION_RATIO = 0.125; // 12.5% is absorbed
|
||||||
const float DEFAULT_DIFFUSION_RATIO = 0.125; // 12.5% is diffused
|
const float DEFAULT_DIFFUSION_RATIO = 0.125; // 12.5% is diffused
|
||||||
|
const float DEFAULT_ORIGINAL_ATTENUATION = 1.0f;
|
||||||
|
const float DEFAULT_ECHO_ATTENUATION = 1.0f;
|
||||||
|
|
||||||
AudioReflector::AudioReflector(QObject* parent) :
|
AudioReflector::AudioReflector(QObject* parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
|
@ -36,6 +38,8 @@ AudioReflector::AudioReflector(QObject* parent) :
|
||||||
_diffusionFanout(DEFAULT_DIFFUSION_FANOUT),
|
_diffusionFanout(DEFAULT_DIFFUSION_FANOUT),
|
||||||
_absorptionRatio(DEFAULT_ABSORPTION_RATIO),
|
_absorptionRatio(DEFAULT_ABSORPTION_RATIO),
|
||||||
_diffusionRatio(DEFAULT_DIFFUSION_RATIO),
|
_diffusionRatio(DEFAULT_DIFFUSION_RATIO),
|
||||||
|
_originalSourceAttenuation(DEFAULT_ORIGINAL_ATTENUATION),
|
||||||
|
_allEchoesAttenuation(DEFAULT_ECHO_ATTENUATION),
|
||||||
_withDiffusion(false),
|
_withDiffusion(false),
|
||||||
_lastPreDelay(DEFAULT_PRE_DELAY),
|
_lastPreDelay(DEFAULT_PRE_DELAY),
|
||||||
_lastSoundMsPerMeter(DEFAULT_MS_DELAY_PER_METER),
|
_lastSoundMsPerMeter(DEFAULT_MS_DELAY_PER_METER),
|
||||||
|
@ -43,20 +47,29 @@ AudioReflector::AudioReflector(QObject* parent) :
|
||||||
_lastLocalAudioAttenuationFactor(DEFAULT_LOCAL_ATTENUATION_FACTOR),
|
_lastLocalAudioAttenuationFactor(DEFAULT_LOCAL_ATTENUATION_FACTOR),
|
||||||
_lastDiffusionFanout(DEFAULT_DIFFUSION_FANOUT),
|
_lastDiffusionFanout(DEFAULT_DIFFUSION_FANOUT),
|
||||||
_lastAbsorptionRatio(DEFAULT_ABSORPTION_RATIO),
|
_lastAbsorptionRatio(DEFAULT_ABSORPTION_RATIO),
|
||||||
_lastDiffusionRatio(DEFAULT_DIFFUSION_RATIO)
|
_lastDiffusionRatio(DEFAULT_DIFFUSION_RATIO),
|
||||||
|
_lastDontDistanceAttenuate(false),
|
||||||
|
_lastAlternateDistanceAttenuate(false)
|
||||||
{
|
{
|
||||||
_reflections = 0;
|
_reflections = 0;
|
||||||
_diffusionPathCount = 0;
|
_diffusionPathCount = 0;
|
||||||
_averageAttenuation = 0.0f;
|
_officialAverageAttenuation = _averageAttenuation = 0.0f;
|
||||||
_maxAttenuation = 0.0f;
|
_officialMaxAttenuation = _maxAttenuation = 0.0f;
|
||||||
_minAttenuation = 0.0f;
|
_officialMinAttenuation = _minAttenuation = 0.0f;
|
||||||
_averageDelay = 0;
|
_officialAverageDelay = _averageDelay = 0;
|
||||||
_maxDelay = 0;
|
_officialMaxDelay = _maxDelay = 0;
|
||||||
_minDelay = 0;
|
_officialMinDelay = _minDelay = 0;
|
||||||
|
_inboundEchoesCount = 0;
|
||||||
|
_inboundEchoesSuppressedCount = 0;
|
||||||
|
_localEchoesCount = 0;
|
||||||
|
_localEchoesSuppressedCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioReflector::haveAttributesChanged() {
|
bool AudioReflector::haveAttributesChanged() {
|
||||||
bool withDiffusion = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingWithDiffusions);
|
bool withDiffusion = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingWithDiffusions);
|
||||||
|
bool dontDistanceAttenuate = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingDontDistanceAttenuate);
|
||||||
|
bool alternateDistanceAttenuate = Menu::getInstance()->isOptionChecked(
|
||||||
|
MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate);
|
||||||
|
|
||||||
bool attributesChange = (_withDiffusion != withDiffusion
|
bool attributesChange = (_withDiffusion != withDiffusion
|
||||||
|| _lastPreDelay != _preDelay
|
|| _lastPreDelay != _preDelay
|
||||||
|
@ -64,7 +77,9 @@ bool AudioReflector::haveAttributesChanged() {
|
||||||
|| _lastDistanceAttenuationScalingFactor != _distanceAttenuationScalingFactor
|
|| _lastDistanceAttenuationScalingFactor != _distanceAttenuationScalingFactor
|
||||||
|| _lastDiffusionFanout != _diffusionFanout
|
|| _lastDiffusionFanout != _diffusionFanout
|
||||||
|| _lastAbsorptionRatio != _absorptionRatio
|
|| _lastAbsorptionRatio != _absorptionRatio
|
||||||
|| _lastDiffusionRatio != _diffusionRatio);
|
|| _lastDiffusionRatio != _diffusionRatio
|
||||||
|
|| _lastDontDistanceAttenuate != dontDistanceAttenuate
|
||||||
|
|| _lastAlternateDistanceAttenuate != alternateDistanceAttenuate);
|
||||||
|
|
||||||
if (attributesChange) {
|
if (attributesChange) {
|
||||||
_withDiffusion = withDiffusion;
|
_withDiffusion = withDiffusion;
|
||||||
|
@ -74,6 +89,8 @@ bool AudioReflector::haveAttributesChanged() {
|
||||||
_lastDiffusionFanout = _diffusionFanout;
|
_lastDiffusionFanout = _diffusionFanout;
|
||||||
_lastAbsorptionRatio = _absorptionRatio;
|
_lastAbsorptionRatio = _absorptionRatio;
|
||||||
_lastDiffusionRatio = _diffusionRatio;
|
_lastDiffusionRatio = _diffusionRatio;
|
||||||
|
_lastDontDistanceAttenuate = dontDistanceAttenuate;
|
||||||
|
_lastAlternateDistanceAttenuate = alternateDistanceAttenuate;
|
||||||
}
|
}
|
||||||
|
|
||||||
return attributesChange;
|
return attributesChange;
|
||||||
|
@ -107,19 +124,47 @@ float AudioReflector::getDelayFromDistance(float distance) {
|
||||||
|
|
||||||
// attenuation = from the Audio Mixer
|
// attenuation = from the Audio Mixer
|
||||||
float AudioReflector::getDistanceAttenuationCoefficient(float distance) {
|
float AudioReflector::getDistanceAttenuationCoefficient(float distance) {
|
||||||
const float DISTANCE_SCALE = 2.5f;
|
|
||||||
const float GEOMETRIC_AMPLITUDE_SCALAR = 0.3f;
|
|
||||||
const float DISTANCE_LOG_BASE = 2.5f;
|
bool doDistanceAttenuation = !Menu::getInstance()->isOptionChecked(
|
||||||
const float DISTANCE_SCALE_LOG = logf(DISTANCE_SCALE) / logf(DISTANCE_LOG_BASE);
|
MenuOption::AudioSpatialProcessingDontDistanceAttenuate);
|
||||||
|
|
||||||
|
bool originalFormula = !Menu::getInstance()->isOptionChecked(
|
||||||
|
MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate);
|
||||||
|
|
||||||
float distanceSquareToSource = distance * distance;
|
|
||||||
|
float distanceCoefficient = 1.0f;
|
||||||
|
|
||||||
|
if (doDistanceAttenuation) {
|
||||||
|
|
||||||
|
if (originalFormula) {
|
||||||
|
const float DISTANCE_SCALE = 2.5f;
|
||||||
|
const float GEOMETRIC_AMPLITUDE_SCALAR = 0.3f;
|
||||||
|
const float DISTANCE_LOG_BASE = 2.5f;
|
||||||
|
const float DISTANCE_SCALE_LOG = logf(DISTANCE_SCALE) / logf(DISTANCE_LOG_BASE);
|
||||||
|
|
||||||
|
float distanceSquareToSource = distance * distance;
|
||||||
|
|
||||||
// calculate the distance coefficient using the distance to this node
|
// calculate the distance coefficient using the distance to this node
|
||||||
float distanceCoefficient = powf(GEOMETRIC_AMPLITUDE_SCALAR,
|
distanceCoefficient = powf(GEOMETRIC_AMPLITUDE_SCALAR,
|
||||||
DISTANCE_SCALE_LOG +
|
DISTANCE_SCALE_LOG +
|
||||||
(0.5f * logf(distanceSquareToSource) / logf(DISTANCE_LOG_BASE)) - 1);
|
(0.5f * logf(distanceSquareToSource) / logf(DISTANCE_LOG_BASE)) - 1);
|
||||||
|
distanceCoefficient = std::min(1.0f, distanceCoefficient * getDistanceAttenuationScalingFactor());
|
||||||
distanceCoefficient = std::min(1.0f, distanceCoefficient * getDistanceAttenuationScalingFactor());
|
} else {
|
||||||
|
|
||||||
|
// From Fred: If we wanted something that would produce a tail that could go up to 5 seconds in a
|
||||||
|
// really big room, that would suggest the sound still has to be in the audible after traveling about
|
||||||
|
// 1500 meters. If it’s a sound of average volume, we probably have about 30 db, or 5 base2 orders
|
||||||
|
// of magnitude we can drop down before the sound becomes inaudible. (That’s approximate headroom
|
||||||
|
// based on a few sloppy assumptions.) So we could try a factor like 1 / (2^(D/300)) for starters.
|
||||||
|
// 1 / (2^(D/300))
|
||||||
|
const float DISTANCE_BASE = 2.0f;
|
||||||
|
const float DISTANCE_DENOMINATOR = 300.0f;
|
||||||
|
const float DISTANCE_NUMERATOR = 300.0f;
|
||||||
|
distanceCoefficient = DISTANCE_NUMERATOR / powf(DISTANCE_BASE, (distance / DISTANCE_DENOMINATOR ));
|
||||||
|
distanceCoefficient = std::min(1.0f, distanceCoefficient * getDistanceAttenuationScalingFactor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return distanceCoefficient;
|
return distanceCoefficient;
|
||||||
}
|
}
|
||||||
|
@ -236,11 +281,13 @@ void AudioReflector::injectAudiblePoint(AudioSource source, const AudiblePoint&
|
||||||
rightSample = originalSamplesData[(sample * NUMBER_OF_CHANNELS) + 1];
|
rightSample = originalSamplesData[(sample * NUMBER_OF_CHANNELS) + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
attenuatedLeftSamplesData[sample * NUMBER_OF_CHANNELS] = leftSample * leftEarAttenuation;
|
attenuatedLeftSamplesData[sample * NUMBER_OF_CHANNELS] =
|
||||||
|
leftSample * leftEarAttenuation * _allEchoesAttenuation;
|
||||||
attenuatedLeftSamplesData[sample * NUMBER_OF_CHANNELS + 1] = 0;
|
attenuatedLeftSamplesData[sample * NUMBER_OF_CHANNELS + 1] = 0;
|
||||||
|
|
||||||
attenuatedRightSamplesData[sample * NUMBER_OF_CHANNELS] = 0;
|
attenuatedRightSamplesData[sample * NUMBER_OF_CHANNELS] = 0;
|
||||||
attenuatedRightSamplesData[sample * NUMBER_OF_CHANNELS + 1] = rightSample * rightEarAttenuation;
|
attenuatedRightSamplesData[sample * NUMBER_OF_CHANNELS + 1] =
|
||||||
|
rightSample * rightEarAttenuation * _allEchoesAttenuation;
|
||||||
}
|
}
|
||||||
|
|
||||||
// now inject the attenuated array with the appropriate delay
|
// now inject the attenuated array with the appropriate delay
|
||||||
|
@ -249,9 +296,25 @@ void AudioReflector::injectAudiblePoint(AudioSource source, const AudiblePoint&
|
||||||
|
|
||||||
_audio->addSpatialAudioToBuffer(sampleTimeLeft, attenuatedLeftSamples, totalNumberOfSamples);
|
_audio->addSpatialAudioToBuffer(sampleTimeLeft, attenuatedLeftSamples, totalNumberOfSamples);
|
||||||
_audio->addSpatialAudioToBuffer(sampleTimeRight, attenuatedRightSamples, totalNumberOfSamples);
|
_audio->addSpatialAudioToBuffer(sampleTimeRight, attenuatedRightSamples, totalNumberOfSamples);
|
||||||
|
|
||||||
|
_injectedEchoes++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AudioReflector::preProcessOriginalInboundAudio(unsigned int sampleTime,
|
||||||
|
QByteArray& samples, const QAudioFormat& format) {
|
||||||
|
|
||||||
|
if (_originalSourceAttenuation != 1.0f) {
|
||||||
|
int numberOfSamples = (samples.size() / sizeof(int16_t));
|
||||||
|
int16_t* sampleData = (int16_t*)samples.data();
|
||||||
|
for (int i = 0; i < numberOfSamples; i++) {
|
||||||
|
sampleData[i] = sampleData[i] * _originalSourceAttenuation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void AudioReflector::processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) {
|
void AudioReflector::processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) {
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingProcessLocalAudio)) {
|
if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingProcessLocalAudio)) {
|
||||||
const int NUM_CHANNELS_INPUT = 1;
|
const int NUM_CHANNELS_INPUT = 1;
|
||||||
|
@ -272,6 +335,8 @@ void AudioReflector::processLocalAudio(unsigned int sampleTime, const QByteArray
|
||||||
_localAudioDelays.clear();
|
_localAudioDelays.clear();
|
||||||
_localEchoesSuppressed.clear();
|
_localEchoesSuppressed.clear();
|
||||||
echoAudio(LOCAL_AUDIO, sampleTime, stereoInputData, outputFormat);
|
echoAudio(LOCAL_AUDIO, sampleTime, stereoInputData, outputFormat);
|
||||||
|
_localEchoesCount = _localAudioDelays.size();
|
||||||
|
_localEchoesSuppressedCount = _localEchoesSuppressed.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,9 +345,13 @@ void AudioReflector::processInboundAudio(unsigned int sampleTime, const QByteArr
|
||||||
_inboundAudioDelays.clear();
|
_inboundAudioDelays.clear();
|
||||||
_inboundEchoesSuppressed.clear();
|
_inboundEchoesSuppressed.clear();
|
||||||
echoAudio(INBOUND_AUDIO, sampleTime, samples, format);
|
echoAudio(INBOUND_AUDIO, sampleTime, samples, format);
|
||||||
|
_inboundEchoesCount = _inboundAudioDelays.size();
|
||||||
|
_inboundEchoesSuppressedCount = _inboundEchoesSuppressed.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioReflector::echoAudio(AudioSource source, unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) {
|
void AudioReflector::echoAudio(AudioSource source, unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) {
|
||||||
|
QMutexLocker locker(&_mutex);
|
||||||
|
|
||||||
_maxDelay = 0;
|
_maxDelay = 0;
|
||||||
_maxAttenuation = 0.0f;
|
_maxAttenuation = 0.0f;
|
||||||
_minDelay = std::numeric_limits<int>::max();
|
_minDelay = std::numeric_limits<int>::max();
|
||||||
|
@ -292,14 +361,20 @@ void AudioReflector::echoAudio(AudioSource source, unsigned int sampleTime, cons
|
||||||
_totalAttenuation = 0.0f;
|
_totalAttenuation = 0.0f;
|
||||||
_attenuationCount = 0;
|
_attenuationCount = 0;
|
||||||
|
|
||||||
QMutexLocker locker(&_mutex);
|
|
||||||
|
|
||||||
// depending on if we're processing local or external audio, pick the correct points vector
|
// depending on if we're processing local or external audio, pick the correct points vector
|
||||||
QVector<AudiblePoint>& audiblePoints = source == INBOUND_AUDIO ? _inboundAudiblePoints : _localAudiblePoints;
|
QVector<AudiblePoint>& audiblePoints = source == INBOUND_AUDIO ? _inboundAudiblePoints : _localAudiblePoints;
|
||||||
|
|
||||||
|
int injectCalls = 0;
|
||||||
|
_injectedEchoes = 0;
|
||||||
foreach(const AudiblePoint& audiblePoint, audiblePoints) {
|
foreach(const AudiblePoint& audiblePoint, audiblePoints) {
|
||||||
|
injectCalls++;
|
||||||
injectAudiblePoint(source, audiblePoint, samples, sampleTime, format.sampleRate());
|
injectAudiblePoint(source, audiblePoint, samples, sampleTime, format.sampleRate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
qDebug() << "injectCalls=" << injectCalls;
|
||||||
|
qDebug() << "_injectedEchoes=" << _injectedEchoes;
|
||||||
|
*/
|
||||||
|
|
||||||
_averageDelay = _delayCount == 0 ? 0 : _totalDelay / _delayCount;
|
_averageDelay = _delayCount == 0 ? 0 : _totalDelay / _delayCount;
|
||||||
_averageAttenuation = _attenuationCount == 0 ? 0 : _totalAttenuation / _attenuationCount;
|
_averageAttenuation = _attenuationCount == 0 ? 0 : _totalAttenuation / _attenuationCount;
|
||||||
|
@ -308,6 +383,14 @@ void AudioReflector::echoAudio(AudioSource source, unsigned int sampleTime, cons
|
||||||
_minDelay = 0.0f;
|
_minDelay = 0.0f;
|
||||||
_minAttenuation = 0.0f;
|
_minAttenuation = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_officialMaxDelay = _maxDelay;
|
||||||
|
_officialMinDelay = _minDelay;
|
||||||
|
_officialMaxAttenuation = _maxAttenuation;
|
||||||
|
_officialMinAttenuation = _minAttenuation;
|
||||||
|
_officialAverageDelay = _averageDelay;
|
||||||
|
_officialAverageAttenuation = _averageAttenuation;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioReflector::drawVector(const glm::vec3& start, const glm::vec3& end, const glm::vec3& color) {
|
void AudioReflector::drawVector(const glm::vec3& start, const glm::vec3& end, const glm::vec3& color) {
|
||||||
|
@ -359,6 +442,19 @@ void AudioReflector::addAudioPath(AudioSource source, const glm::vec3& origin, c
|
||||||
audioPaths.push_back(path);
|
audioPaths.push_back(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: This is a prototype of an eventual utility that will identify the speaking sources for the inbound audio
|
||||||
|
// stream. It's not currently called but will be added soon.
|
||||||
|
void AudioReflector::identifyAudioSources() {
|
||||||
|
// looking for audio sources....
|
||||||
|
foreach (const AvatarSharedPointer& avatarPointer, _avatarManager->getAvatarHash()) {
|
||||||
|
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
|
||||||
|
if (!avatar->isInitialized()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
qDebug() << "avatar["<< avatar <<"] loudness:" << avatar->getAudioLoudness();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AudioReflector::calculateAllReflections() {
|
void AudioReflector::calculateAllReflections() {
|
||||||
// only recalculate when we've moved, or if the attributes have changed
|
// only recalculate when we've moved, or if the attributes have changed
|
||||||
// TODO: what about case where new voxels are added in front of us???
|
// TODO: what about case where new voxels are added in front of us???
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
#include "Audio.h"
|
#include "Audio.h"
|
||||||
#include "avatar/MyAvatar.h"
|
#include "avatar/MyAvatar.h"
|
||||||
|
#include "avatar/AvatarManager.h"
|
||||||
|
|
||||||
enum AudioSource {
|
enum AudioSource {
|
||||||
LOCAL_AUDIO,
|
LOCAL_AUDIO,
|
||||||
|
@ -69,25 +70,27 @@ public:
|
||||||
void setVoxels(VoxelTree* voxels) { _voxels = voxels; }
|
void setVoxels(VoxelTree* voxels) { _voxels = voxels; }
|
||||||
void setMyAvatar(MyAvatar* myAvatar) { _myAvatar = myAvatar; }
|
void setMyAvatar(MyAvatar* myAvatar) { _myAvatar = myAvatar; }
|
||||||
void setAudio(Audio* audio) { _audio = audio; }
|
void setAudio(Audio* audio) { _audio = audio; }
|
||||||
|
void setAvatarManager(AvatarManager* avatarManager) { _avatarManager = avatarManager; }
|
||||||
|
|
||||||
void render(); /// must be called in the application render loop
|
void render(); /// must be called in the application render loop
|
||||||
|
|
||||||
|
void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format);
|
||||||
void processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
|
void processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
|
||||||
void processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
|
void processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
// statistics
|
// statistics
|
||||||
int getReflections() const { return _reflections; }
|
int getReflections() const { return _reflections; }
|
||||||
float getAverageDelayMsecs() const { return _averageDelay; }
|
float getAverageDelayMsecs() const { return _officialAverageDelay; }
|
||||||
float getAverageAttenuation() const { return _averageAttenuation; }
|
float getAverageAttenuation() const { return _officialAverageAttenuation; }
|
||||||
float getMaxDelayMsecs() const { return _maxDelay; }
|
float getMaxDelayMsecs() const { return _officialMaxDelay; }
|
||||||
float getMaxAttenuation() const { return _maxAttenuation; }
|
float getMaxAttenuation() const { return _officialMaxAttenuation; }
|
||||||
float getMinDelayMsecs() const { return _minDelay; }
|
float getMinDelayMsecs() const { return _officialMinDelay; }
|
||||||
float getMinAttenuation() const { return _minAttenuation; }
|
float getMinAttenuation() const { return _officialMinAttenuation; }
|
||||||
float getDelayFromDistance(float distance);
|
float getDelayFromDistance(float distance);
|
||||||
int getDiffusionPathCount() const { return _diffusionPathCount; }
|
int getDiffusionPathCount() const { return _diffusionPathCount; }
|
||||||
int getEchoesInjected() const { return _inboundAudioDelays.size() + _localAudioDelays.size(); }
|
int getEchoesInjected() const { return _inboundEchoesCount + _localEchoesCount; }
|
||||||
int getEchoesSuppressed() const { return _inboundEchoesSuppressed.size() + _localEchoesSuppressed.size(); }
|
int getEchoesSuppressed() const { return _inboundEchoesSuppressedCount + _localEchoesSuppressedCount; }
|
||||||
|
|
||||||
/// ms of delay added to all echos
|
/// ms of delay added to all echos
|
||||||
float getPreDelay() const { return _preDelay; }
|
float getPreDelay() const { return _preDelay; }
|
||||||
|
@ -126,12 +129,19 @@ public slots:
|
||||||
float getReflectiveRatio() const { return (1.0f - (_absorptionRatio + _diffusionRatio)); }
|
float getReflectiveRatio() const { return (1.0f - (_absorptionRatio + _diffusionRatio)); }
|
||||||
void setReflectiveRatio(float ratio);
|
void setReflectiveRatio(float ratio);
|
||||||
|
|
||||||
|
// wet/dry mix - these don't affect any reflection calculations, only the final mix volumes
|
||||||
|
float getOriginalSourceAttenuation() const { return _originalSourceAttenuation; }
|
||||||
|
void setOriginalSourceAttenuation(float value) { _originalSourceAttenuation = value; }
|
||||||
|
float getEchoesAttenuation() const { return _allEchoesAttenuation; }
|
||||||
|
void setEchoesAttenuation(float value) { _allEchoesAttenuation = value; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VoxelTree* _voxels; // used to access voxel scene
|
VoxelTree* _voxels; // used to access voxel scene
|
||||||
MyAvatar* _myAvatar; // access to listener
|
MyAvatar* _myAvatar; // access to listener
|
||||||
Audio* _audio; // access to audio API
|
Audio* _audio; // access to audio API
|
||||||
|
AvatarManager* _avatarManager; // access to avatar manager API
|
||||||
|
|
||||||
// Helpers for drawing
|
// Helpers for drawing
|
||||||
void drawVector(const glm::vec3& start, const glm::vec3& end, const glm::vec3& color);
|
void drawVector(const glm::vec3& start, const glm::vec3& end, const glm::vec3& color);
|
||||||
|
@ -147,11 +157,18 @@ private:
|
||||||
float _averageDelay;
|
float _averageDelay;
|
||||||
float _maxDelay;
|
float _maxDelay;
|
||||||
float _minDelay;
|
float _minDelay;
|
||||||
|
float _officialAverageDelay;
|
||||||
|
float _officialMaxDelay;
|
||||||
|
float _officialMinDelay;
|
||||||
int _attenuationCount;
|
int _attenuationCount;
|
||||||
float _totalAttenuation;
|
float _totalAttenuation;
|
||||||
float _averageAttenuation;
|
float _averageAttenuation;
|
||||||
float _maxAttenuation;
|
float _maxAttenuation;
|
||||||
float _minAttenuation;
|
float _minAttenuation;
|
||||||
|
float _officialAverageAttenuation;
|
||||||
|
float _officialMaxAttenuation;
|
||||||
|
float _officialMinAttenuation;
|
||||||
|
|
||||||
|
|
||||||
glm::vec3 _listenerPosition;
|
glm::vec3 _listenerPosition;
|
||||||
glm::vec3 _origin;
|
glm::vec3 _origin;
|
||||||
|
@ -161,11 +178,15 @@ private:
|
||||||
QVector<AudiblePoint> _inboundAudiblePoints; /// the audible points that have been calculated from the inbound audio paths
|
QVector<AudiblePoint> _inboundAudiblePoints; /// the audible points that have been calculated from the inbound audio paths
|
||||||
QMap<float, float> _inboundAudioDelays; /// delay times for currently injected audio points
|
QMap<float, float> _inboundAudioDelays; /// delay times for currently injected audio points
|
||||||
QVector<float> _inboundEchoesSuppressed; /// delay times for currently injected audio points
|
QVector<float> _inboundEchoesSuppressed; /// delay times for currently injected audio points
|
||||||
|
int _inboundEchoesCount;
|
||||||
|
int _inboundEchoesSuppressedCount;
|
||||||
|
|
||||||
QVector<AudioPath*> _localAudioPaths; /// audio paths we're processing for local audio
|
QVector<AudioPath*> _localAudioPaths; /// audio paths we're processing for local audio
|
||||||
QVector<AudiblePoint> _localAudiblePoints; /// the audible points that have been calculated from the local audio paths
|
QVector<AudiblePoint> _localAudiblePoints; /// the audible points that have been calculated from the local audio paths
|
||||||
QMap<float, float> _localAudioDelays; /// delay times for currently injected audio points
|
QMap<float, float> _localAudioDelays; /// delay times for currently injected audio points
|
||||||
QVector<float> _localEchoesSuppressed; /// delay times for currently injected audio points
|
QVector<float> _localEchoesSuppressed; /// delay times for currently injected audio points
|
||||||
|
int _localEchoesCount;
|
||||||
|
int _localEchoesSuppressedCount;
|
||||||
|
|
||||||
// adds a sound source to begin an audio path trace, these can be the initial sound sources with their directional properties,
|
// adds a sound source to begin an audio path trace, these can be the initial sound sources with their directional properties,
|
||||||
// as well as diffusion sound sources
|
// as well as diffusion sound sources
|
||||||
|
@ -182,6 +203,7 @@ private:
|
||||||
void calculateAllReflections();
|
void calculateAllReflections();
|
||||||
int countDiffusionPaths();
|
int countDiffusionPaths();
|
||||||
glm::vec3 getFaceNormal(BoxFace face);
|
glm::vec3 getFaceNormal(BoxFace face);
|
||||||
|
void identifyAudioSources();
|
||||||
|
|
||||||
void injectAudiblePoint(AudioSource source, const AudiblePoint& audiblePoint, const QByteArray& samples, unsigned int sampleTime, int sampleRate);
|
void injectAudiblePoint(AudioSource source, const AudiblePoint& audiblePoint, const QByteArray& samples, unsigned int sampleTime, int sampleRate);
|
||||||
void echoAudio(AudioSource source, unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
|
void echoAudio(AudioSource source, unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format);
|
||||||
|
@ -197,13 +219,16 @@ private:
|
||||||
float _distanceAttenuationScalingFactor;
|
float _distanceAttenuationScalingFactor;
|
||||||
float _localAudioAttenuationFactor;
|
float _localAudioAttenuationFactor;
|
||||||
float _combFilterWindow;
|
float _combFilterWindow;
|
||||||
|
|
||||||
int _diffusionFanout; // number of points of diffusion from each reflection point
|
int _diffusionFanout; // number of points of diffusion from each reflection point
|
||||||
|
|
||||||
// all elements have the same material for now...
|
// all elements have the same material for now...
|
||||||
float _absorptionRatio;
|
float _absorptionRatio;
|
||||||
float _diffusionRatio;
|
float _diffusionRatio;
|
||||||
float _reflectiveRatio;
|
float _reflectiveRatio;
|
||||||
|
|
||||||
|
// wet/dry mix - these don't affect any reflection calculations, only the final mix volumes
|
||||||
|
float _originalSourceAttenuation; /// each sample of original signal will be multiplied by this
|
||||||
|
float _allEchoesAttenuation; /// each sample of all echo signals will be multiplied by this
|
||||||
|
|
||||||
// remember the last known values at calculation
|
// remember the last known values at calculation
|
||||||
bool haveAttributesChanged();
|
bool haveAttributesChanged();
|
||||||
|
@ -216,6 +241,10 @@ private:
|
||||||
int _lastDiffusionFanout;
|
int _lastDiffusionFanout;
|
||||||
float _lastAbsorptionRatio;
|
float _lastAbsorptionRatio;
|
||||||
float _lastDiffusionRatio;
|
float _lastDiffusionRatio;
|
||||||
|
bool _lastDontDistanceAttenuate;
|
||||||
|
bool _lastAlternateDistanceAttenuate;
|
||||||
|
|
||||||
|
int _injectedEchoes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -429,6 +429,14 @@ Menu::Menu() :
|
||||||
Qt::CTRL | Qt::SHIFT | Qt::Key_A,
|
Qt::CTRL | Qt::SHIFT | Qt::Key_A,
|
||||||
true);
|
true);
|
||||||
|
|
||||||
|
addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingDontDistanceAttenuate,
|
||||||
|
Qt::CTRL | Qt::SHIFT | Qt::Key_Y,
|
||||||
|
false);
|
||||||
|
|
||||||
|
addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate,
|
||||||
|
Qt::CTRL | Qt::SHIFT | Qt::Key_U,
|
||||||
|
false);
|
||||||
|
|
||||||
addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel,
|
addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel,
|
||||||
Qt::CTRL | Qt::SHIFT | Qt::Key_V,
|
Qt::CTRL | Qt::SHIFT | Qt::Key_V,
|
||||||
this,
|
this,
|
||||||
|
|
|
@ -268,6 +268,10 @@ namespace MenuOption {
|
||||||
const QString AudioSpatialProcessingSlightlyRandomSurfaces = "Slightly Random Surfaces";
|
const QString AudioSpatialProcessingSlightlyRandomSurfaces = "Slightly Random Surfaces";
|
||||||
const QString AudioSpatialProcessingStereoSource = "Stereo Source";
|
const QString AudioSpatialProcessingStereoSource = "Stereo Source";
|
||||||
const QString AudioSpatialProcessingWithDiffusions = "With Diffusions";
|
const QString AudioSpatialProcessingWithDiffusions = "With Diffusions";
|
||||||
|
const QString AudioSpatialProcessingDontDistanceAttenuate = "Don't calculate distance attenuation";
|
||||||
|
const QString AudioSpatialProcessingAlternateDistanceAttenuate = "Alternate distance attenuation";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const QString Avatars = "Avatars";
|
const QString Avatars = "Avatars";
|
||||||
const QString Bandwidth = "Bandwidth Display";
|
const QString Bandwidth = "Bandwidth Display";
|
||||||
|
|
|
@ -22,9 +22,9 @@
|
||||||
|
|
||||||
#include <AccountManager.h>
|
#include <AccountManager.h>
|
||||||
|
|
||||||
#include "Application.h"
|
#include <FBXReader.h>
|
||||||
#include "renderer/FBXReader.h"
|
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include "ModelUploader.h"
|
#include "ModelUploader.h"
|
||||||
|
|
||||||
|
|
||||||
|
@ -306,14 +306,14 @@ bool ModelUploader::addTextures(const QString& texdir, const QString fbxFile) {
|
||||||
|
|
||||||
foreach (FBXMesh mesh, geometry.meshes) {
|
foreach (FBXMesh mesh, geometry.meshes) {
|
||||||
foreach (FBXMeshPart part, mesh.parts) {
|
foreach (FBXMeshPart part, mesh.parts) {
|
||||||
if (!part.diffuseFilename.isEmpty()) {
|
if (!part.diffuseTexture.filename.isEmpty()) {
|
||||||
if (!addPart(texdir + "/" + part.diffuseFilename,
|
if (!addPart(texdir + "/" + part.diffuseTexture.filename,
|
||||||
QString("texture%1").arg(++_texturesCount))) {
|
QString("texture%1").arg(++_texturesCount))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!part.normalFilename.isEmpty()) {
|
if (!part.normalTexture.filename.isEmpty()) {
|
||||||
if (!addPart(texdir + "/" + part.normalFilename,
|
if (!addPart(texdir + "/" + part.normalTexture.filename,
|
||||||
QString("texture%1").arg(++_texturesCount))) {
|
QString("texture%1").arg(++_texturesCount))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,112 +78,6 @@ float angle_to(glm::vec3 head_pos, glm::vec3 source_pos, float render_yaw, float
|
||||||
return atan2(head_pos.x - source_pos.x, head_pos.z - source_pos.z) + render_yaw + head_yaw;
|
return atan2(head_pos.x - source_pos.x, head_pos.z - source_pos.z) + render_yaw + head_yaw;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function returns the positive angle (in radians) between two 3D vectors
|
|
||||||
float angleBetween(const glm::vec3& v1, const glm::vec3& v2) {
|
|
||||||
return acosf((glm::dot(v1, v2)) / (glm::length(v1) * glm::length(v2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function return the rotation from the first vector onto the second
|
|
||||||
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) {
|
|
||||||
float angle = angleBetween(v1, v2);
|
|
||||||
if (glm::isnan(angle) || angle < EPSILON) {
|
|
||||||
return glm::quat();
|
|
||||||
}
|
|
||||||
glm::vec3 axis;
|
|
||||||
if (angle > 179.99f * RADIANS_PER_DEGREE) { // 180 degree rotation; must use another axis
|
|
||||||
axis = glm::cross(v1, glm::vec3(1.0f, 0.0f, 0.0f));
|
|
||||||
float axisLength = glm::length(axis);
|
|
||||||
if (axisLength < EPSILON) { // parallel to x; y will work
|
|
||||||
axis = glm::normalize(glm::cross(v1, glm::vec3(0.0f, 1.0f, 0.0f)));
|
|
||||||
} else {
|
|
||||||
axis /= axisLength;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
axis = glm::normalize(glm::cross(v1, v2));
|
|
||||||
}
|
|
||||||
return glm::angleAxis(angle, axis);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
glm::vec3 extractTranslation(const glm::mat4& matrix) {
|
|
||||||
return glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setTranslation(glm::mat4& matrix, const glm::vec3& translation) {
|
|
||||||
matrix[3][0] = translation.x;
|
|
||||||
matrix[3][1] = translation.y;
|
|
||||||
matrix[3][2] = translation.z;
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) {
|
|
||||||
// uses the iterative polar decomposition algorithm described by Ken Shoemake at
|
|
||||||
// http://www.cs.wisc.edu/graphics/Courses/838-s2002/Papers/polar-decomp.pdf
|
|
||||||
// code adapted from Clyde, https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Matrix4f.java
|
|
||||||
|
|
||||||
// start with the contents of the upper 3x3 portion of the matrix
|
|
||||||
glm::mat3 upper = glm::mat3(matrix);
|
|
||||||
if (!assumeOrthogonal) {
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
// store the results of the previous iteration
|
|
||||||
glm::mat3 previous = upper;
|
|
||||||
|
|
||||||
// compute average of the matrix with its inverse transpose
|
|
||||||
float sd00 = previous[1][1] * previous[2][2] - previous[2][1] * previous[1][2];
|
|
||||||
float sd10 = previous[0][1] * previous[2][2] - previous[2][1] * previous[0][2];
|
|
||||||
float sd20 = previous[0][1] * previous[1][2] - previous[1][1] * previous[0][2];
|
|
||||||
float det = previous[0][0] * sd00 + previous[2][0] * sd20 - previous[1][0] * sd10;
|
|
||||||
if (fabs(det) == 0.0f) {
|
|
||||||
// determinant is zero; matrix is not invertible
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
float hrdet = 0.5f / det;
|
|
||||||
upper[0][0] = +sd00 * hrdet + previous[0][0] * 0.5f;
|
|
||||||
upper[1][0] = -sd10 * hrdet + previous[1][0] * 0.5f;
|
|
||||||
upper[2][0] = +sd20 * hrdet + previous[2][0] * 0.5f;
|
|
||||||
|
|
||||||
upper[0][1] = -(previous[1][0] * previous[2][2] - previous[2][0] * previous[1][2]) * hrdet + previous[0][1] * 0.5f;
|
|
||||||
upper[1][1] = +(previous[0][0] * previous[2][2] - previous[2][0] * previous[0][2]) * hrdet + previous[1][1] * 0.5f;
|
|
||||||
upper[2][1] = -(previous[0][0] * previous[1][2] - previous[1][0] * previous[0][2]) * hrdet + previous[2][1] * 0.5f;
|
|
||||||
|
|
||||||
upper[0][2] = +(previous[1][0] * previous[2][1] - previous[2][0] * previous[1][1]) * hrdet + previous[0][2] * 0.5f;
|
|
||||||
upper[1][2] = -(previous[0][0] * previous[2][1] - previous[2][0] * previous[0][1]) * hrdet + previous[1][2] * 0.5f;
|
|
||||||
upper[2][2] = +(previous[0][0] * previous[1][1] - previous[1][0] * previous[0][1]) * hrdet + previous[2][2] * 0.5f;
|
|
||||||
|
|
||||||
// compute the difference; if it's small enough, we're done
|
|
||||||
glm::mat3 diff = upper - previous;
|
|
||||||
if (diff[0][0] * diff[0][0] + diff[1][0] * diff[1][0] + diff[2][0] * diff[2][0] + diff[0][1] * diff[0][1] +
|
|
||||||
diff[1][1] * diff[1][1] + diff[2][1] * diff[2][1] + diff[0][2] * diff[0][2] + diff[1][2] * diff[1][2] +
|
|
||||||
diff[2][2] * diff[2][2] < EPSILON) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now that we have a nice orthogonal matrix, we can extract the rotation quaternion
|
|
||||||
// using the method described in http://en.wikipedia.org/wiki/Rotation_matrix#Conversions
|
|
||||||
float x2 = fabs(1.0f + upper[0][0] - upper[1][1] - upper[2][2]);
|
|
||||||
float y2 = fabs(1.0f - upper[0][0] + upper[1][1] - upper[2][2]);
|
|
||||||
float z2 = fabs(1.0f - upper[0][0] - upper[1][1] + upper[2][2]);
|
|
||||||
float w2 = fabs(1.0f + upper[0][0] + upper[1][1] + upper[2][2]);
|
|
||||||
return glm::normalize(glm::quat(0.5f * sqrtf(w2),
|
|
||||||
0.5f * sqrtf(x2) * (upper[1][2] >= upper[2][1] ? 1.0f : -1.0f),
|
|
||||||
0.5f * sqrtf(y2) * (upper[2][0] >= upper[0][2] ? 1.0f : -1.0f),
|
|
||||||
0.5f * sqrtf(z2) * (upper[0][1] >= upper[1][0] ? 1.0f : -1.0f)));
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec3 extractScale(const glm::mat4& matrix) {
|
|
||||||
return glm::vec3(glm::length(matrix[0]), glm::length(matrix[1]), glm::length(matrix[2]));
|
|
||||||
}
|
|
||||||
|
|
||||||
float extractUniformScale(const glm::mat4& matrix) {
|
|
||||||
return extractUniformScale(extractScale(matrix));
|
|
||||||
}
|
|
||||||
|
|
||||||
float extractUniformScale(const glm::vec3& scale) {
|
|
||||||
return (scale.x + scale.y + scale.z) / 3.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw a 3D vector floating in space
|
// Draw a 3D vector floating in space
|
||||||
void drawVector(glm::vec3 * vector) {
|
void drawVector(glm::vec3 * vector) {
|
||||||
glDisable(GL_LIGHTING);
|
glDisable(GL_LIGHTING);
|
||||||
|
@ -629,4 +523,4 @@ bool pointInSphere(glm::vec3& point, glm::vec3& sphereCenter, double sphereRadiu
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,22 +44,6 @@ void drawVector(glm::vec3* vector);
|
||||||
|
|
||||||
void printVector(glm::vec3 vec);
|
void printVector(glm::vec3 vec);
|
||||||
|
|
||||||
float angleBetween(const glm::vec3& v1, const glm::vec3& v2);
|
|
||||||
|
|
||||||
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2);
|
|
||||||
|
|
||||||
glm::vec3 extractTranslation(const glm::mat4& matrix);
|
|
||||||
|
|
||||||
void setTranslation(glm::mat4& matrix, const glm::vec3& translation);
|
|
||||||
|
|
||||||
glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal = false);
|
|
||||||
|
|
||||||
glm::vec3 extractScale(const glm::mat4& matrix);
|
|
||||||
|
|
||||||
float extractUniformScale(const glm::mat4& matrix);
|
|
||||||
|
|
||||||
float extractUniformScale(const glm::vec3& scale);
|
|
||||||
|
|
||||||
double diffclock(timeval *clock1,timeval *clock2);
|
double diffclock(timeval *clock1,timeval *clock2);
|
||||||
|
|
||||||
void renderCollisionOverlay(int width, int height, float magnitude, float red = 0, float blue = 0, float green = 0);
|
void renderCollisionOverlay(int width, int height, float magnitude, float red = 0, float blue = 0, float green = 0);
|
||||||
|
|
|
@ -56,7 +56,6 @@ Avatar::Avatar() :
|
||||||
_mouseRayOrigin(0.0f, 0.0f, 0.0f),
|
_mouseRayOrigin(0.0f, 0.0f, 0.0f),
|
||||||
_mouseRayDirection(0.0f, 0.0f, 0.0f),
|
_mouseRayDirection(0.0f, 0.0f, 0.0f),
|
||||||
_moving(false),
|
_moving(false),
|
||||||
_owningAvatarMixer(),
|
|
||||||
_collisionFlags(0),
|
_collisionFlags(0),
|
||||||
_initialized(false),
|
_initialized(false),
|
||||||
_shouldRenderBillboard(true)
|
_shouldRenderBillboard(true)
|
||||||
|
@ -685,6 +684,11 @@ void Avatar::setBillboard(const QByteArray& billboard) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int Avatar::parseDataAtOffset(const QByteArray& packet, int offset) {
|
int Avatar::parseDataAtOffset(const QByteArray& packet, int offset) {
|
||||||
|
if (!_initialized) {
|
||||||
|
// now that we have data for this Avatar we are go for init
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
// change in position implies movement
|
// change in position implies movement
|
||||||
glm::vec3 oldPosition = _position;
|
glm::vec3 oldPosition = _position;
|
||||||
|
|
||||||
|
|
|
@ -99,9 +99,6 @@ public:
|
||||||
|
|
||||||
/// Returns the distance to use as a LOD parameter.
|
/// Returns the distance to use as a LOD parameter.
|
||||||
float getLODDistance() const;
|
float getLODDistance() const;
|
||||||
|
|
||||||
Node* getOwningAvatarMixer() { return _owningAvatarMixer.data(); }
|
|
||||||
void setOwningAvatarMixer(const QWeakPointer<Node>& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; }
|
|
||||||
|
|
||||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const;
|
||||||
|
|
||||||
|
@ -177,7 +174,6 @@ protected:
|
||||||
glm::vec3 _mouseRayDirection;
|
glm::vec3 _mouseRayDirection;
|
||||||
float _stringLength;
|
float _stringLength;
|
||||||
bool _moving; ///< set when position is changing
|
bool _moving; ///< set when position is changing
|
||||||
QWeakPointer<Node> _owningAvatarMixer;
|
|
||||||
|
|
||||||
uint32_t _collisionFlags;
|
uint32_t _collisionFlags;
|
||||||
|
|
||||||
|
|
|
@ -51,14 +51,16 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
||||||
// simulate avatars
|
// simulate avatars
|
||||||
AvatarHash::iterator avatarIterator = _avatarHash.begin();
|
AvatarHash::iterator avatarIterator = _avatarHash.begin();
|
||||||
while (avatarIterator != _avatarHash.end()) {
|
while (avatarIterator != _avatarHash.end()) {
|
||||||
Avatar* avatar = static_cast<Avatar*>(avatarIterator.value().data());
|
AvatarSharedPointer sharedAvatar = avatarIterator.value();
|
||||||
if (avatar == static_cast<Avatar*>(_myAvatar.data()) || !avatar->isInitialized()) {
|
Avatar* avatar = reinterpret_cast<Avatar*>(sharedAvatar.data());
|
||||||
|
|
||||||
|
if (sharedAvatar == _myAvatar || !avatar->isInitialized()) {
|
||||||
// DO NOT update _myAvatar! Its update has already been done earlier in the main loop.
|
// DO NOT update _myAvatar! Its update has already been done earlier in the main loop.
|
||||||
// DO NOT update uninitialized Avatars
|
// DO NOT update uninitialized Avatars
|
||||||
++avatarIterator;
|
++avatarIterator;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (avatar->getOwningAvatarMixer()) {
|
if (!shouldKillAvatar(sharedAvatar)) {
|
||||||
// this avatar's mixer is still around, go ahead and simulate it
|
// this avatar's mixer is still around, go ahead and simulate it
|
||||||
avatar->simulate(deltaTime);
|
avatar->simulate(deltaTime);
|
||||||
avatar->setMouseRay(mouseOrigin, mouseDirection);
|
avatar->setMouseRay(mouseOrigin, mouseDirection);
|
||||||
|
@ -127,127 +129,12 @@ void AvatarManager::renderAvatarFades(const glm::vec3& cameraPosition, Avatar::R
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarSharedPointer AvatarManager::matchingOrNewAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
|
AvatarSharedPointer AvatarManager::newSharedAvatar() {
|
||||||
AvatarSharedPointer matchingAvatar = _avatarHash.value(sessionUUID);
|
return AvatarSharedPointer(new Avatar());
|
||||||
|
|
||||||
if (!matchingAvatar) {
|
|
||||||
// construct a new Avatar for this node
|
|
||||||
Avatar* avatar = new Avatar();
|
|
||||||
avatar->setOwningAvatarMixer(mixerWeakPointer);
|
|
||||||
|
|
||||||
// insert the new avatar into our hash
|
|
||||||
matchingAvatar = AvatarSharedPointer(avatar);
|
|
||||||
_avatarHash.insert(sessionUUID, matchingAvatar);
|
|
||||||
|
|
||||||
qDebug() << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarManager hash.";
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchingAvatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer<Node>& mixerWeakPointer) {
|
|
||||||
switch (packetTypeForPacket(datagram)) {
|
|
||||||
case PacketTypeBulkAvatarData:
|
|
||||||
processAvatarDataPacket(datagram, mixerWeakPointer);
|
|
||||||
break;
|
|
||||||
case PacketTypeAvatarIdentity:
|
|
||||||
processAvatarIdentityPacket(datagram, mixerWeakPointer);
|
|
||||||
break;
|
|
||||||
case PacketTypeAvatarBillboard:
|
|
||||||
processAvatarBillboardPacket(datagram, mixerWeakPointer);
|
|
||||||
break;
|
|
||||||
case PacketTypeKillAvatar:
|
|
||||||
processKillAvatar(datagram);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarManager::processAvatarDataPacket(const QByteArray &datagram, const QWeakPointer<Node> &mixerWeakPointer) {
|
|
||||||
int bytesRead = numBytesForPacketHeader(datagram);
|
|
||||||
|
|
||||||
// enumerate over all of the avatars in this packet
|
|
||||||
// only add them if mixerWeakPointer points to something (meaning that mixer is still around)
|
|
||||||
while (bytesRead < datagram.size() && mixerWeakPointer.data()) {
|
|
||||||
QUuid sessionUUID = QUuid::fromRfc4122(datagram.mid(bytesRead, NUM_BYTES_RFC4122_UUID));
|
|
||||||
bytesRead += NUM_BYTES_RFC4122_UUID;
|
|
||||||
|
|
||||||
AvatarSharedPointer matchingAvatarData = matchingOrNewAvatar(sessionUUID, mixerWeakPointer);
|
|
||||||
|
|
||||||
// have the matching (or new) avatar parse the data from the packet
|
|
||||||
bytesRead += matchingAvatarData->parseDataAtOffset(datagram, bytesRead);
|
|
||||||
|
|
||||||
Avatar* matchingAvatar = reinterpret_cast<Avatar*>(matchingAvatarData.data());
|
|
||||||
|
|
||||||
if (!matchingAvatar->isInitialized()) {
|
|
||||||
// now that we have AvatarData for this Avatar we are go for init
|
|
||||||
matchingAvatar->init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet, const QWeakPointer<Node>& mixerWeakPointer) {
|
|
||||||
// setup a data stream to parse the packet
|
|
||||||
QDataStream identityStream(packet);
|
|
||||||
identityStream.skipRawData(numBytesForPacketHeader(packet));
|
|
||||||
|
|
||||||
QUuid sessionUUID;
|
|
||||||
|
|
||||||
while (!identityStream.atEnd()) {
|
|
||||||
|
|
||||||
QUrl faceMeshURL, skeletonURL;
|
|
||||||
QString displayName;
|
|
||||||
identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> displayName;
|
|
||||||
|
|
||||||
// mesh URL for a UUID, find avatar in our list
|
|
||||||
AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(sessionUUID, mixerWeakPointer);
|
|
||||||
if (matchingAvatar) {
|
|
||||||
Avatar* avatar = static_cast<Avatar*>(matchingAvatar.data());
|
|
||||||
|
|
||||||
if (avatar->getFaceModelURL() != faceMeshURL) {
|
|
||||||
avatar->setFaceModelURL(faceMeshURL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avatar->getSkeletonModelURL() != skeletonURL) {
|
|
||||||
avatar->setSkeletonModelURL(skeletonURL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avatar->getDisplayName() != displayName) {
|
|
||||||
avatar->setDisplayName(displayName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarManager::processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer) {
|
|
||||||
int headerSize = numBytesForPacketHeader(packet);
|
|
||||||
QUuid sessionUUID = QUuid::fromRfc4122(QByteArray::fromRawData(packet.constData() + headerSize, NUM_BYTES_RFC4122_UUID));
|
|
||||||
|
|
||||||
AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(sessionUUID, mixerWeakPointer);
|
|
||||||
if (matchingAvatar) {
|
|
||||||
Avatar* avatar = static_cast<Avatar*>(matchingAvatar.data());
|
|
||||||
QByteArray billboard = packet.mid(headerSize + NUM_BYTES_RFC4122_UUID);
|
|
||||||
if (avatar->getBillboard() != billboard) {
|
|
||||||
avatar->setBillboard(billboard);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarManager::processKillAvatar(const QByteArray& datagram) {
|
|
||||||
// read the node id
|
|
||||||
QUuid sessionUUID = QUuid::fromRfc4122(datagram.mid(numBytesForPacketHeader(datagram), NUM_BYTES_RFC4122_UUID));
|
|
||||||
|
|
||||||
// remove the avatar with that UUID from our hash, if it exists
|
|
||||||
AvatarHash::iterator matchedAvatar = _avatarHash.find(sessionUUID);
|
|
||||||
if (matchedAvatar != _avatarHash.end()) {
|
|
||||||
erase(matchedAvatar);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarHash::iterator AvatarManager::erase(const AvatarHash::iterator& iterator) {
|
AvatarHash::iterator AvatarManager::erase(const AvatarHash::iterator& iterator) {
|
||||||
if (iterator.key() != MY_AVATAR_KEY) {
|
if (iterator.key() != MY_AVATAR_KEY) {
|
||||||
qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash.";
|
|
||||||
if (reinterpret_cast<Avatar*>(iterator.value().data())->isInitialized()) {
|
if (reinterpret_cast<Avatar*>(iterator.value().data())->isInitialized()) {
|
||||||
_avatarFades.push_back(iterator.value());
|
_avatarFades.push_back(iterator.value());
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
class MyAvatar;
|
class MyAvatar;
|
||||||
|
|
||||||
class AvatarManager : public QObject, public AvatarHashMap {
|
class AvatarManager : public AvatarHashMap {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AvatarManager(QObject* parent = 0);
|
AvatarManager(QObject* parent = 0);
|
||||||
|
@ -35,23 +35,15 @@ public:
|
||||||
void renderAvatars(Avatar::RenderMode renderMode, bool selfAvatarOnly = false);
|
void renderAvatars(Avatar::RenderMode renderMode, bool selfAvatarOnly = false);
|
||||||
|
|
||||||
void clearOtherAvatars();
|
void clearOtherAvatars();
|
||||||
|
|
||||||
public slots:
|
|
||||||
void processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer<Node>& mixerWeakPointer);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AvatarManager(const AvatarManager& other);
|
AvatarManager(const AvatarManager& other);
|
||||||
|
|
||||||
AvatarSharedPointer matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer<Node>& mixerWeakPointer);
|
|
||||||
|
|
||||||
void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
|
|
||||||
void processAvatarIdentityPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
|
|
||||||
void processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
|
|
||||||
void processKillAvatar(const QByteArray& datagram);
|
|
||||||
|
|
||||||
void simulateAvatarFades(float deltaTime);
|
void simulateAvatarFades(float deltaTime);
|
||||||
void renderAvatarFades(const glm::vec3& cameraPosition, Avatar::RenderMode renderMode);
|
void renderAvatarFades(const glm::vec3& cameraPosition, Avatar::RenderMode renderMode);
|
||||||
|
|
||||||
|
AvatarSharedPointer newSharedAvatar();
|
||||||
|
|
||||||
// virtual override
|
// virtual override
|
||||||
AvatarHash::iterator erase(const AvatarHash::iterator& iterator);
|
AvatarHash::iterator erase(const AvatarHash::iterator& iterator);
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,10 @@
|
||||||
#include <faceplus.h>
|
#include <faceplus.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <FBXReader.h>
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "Faceplus.h"
|
#include "Faceplus.h"
|
||||||
#include "renderer/FBXReader.h"
|
|
||||||
|
|
||||||
static int floatVectorMetaTypeId = qRegisterMetaType<QVector<float> >();
|
static int floatVectorMetaTypeId = qRegisterMetaType<QVector<float> >();
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,10 @@
|
||||||
|
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
#include <FBXReader.h>
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "Visage.h"
|
#include "Visage.h"
|
||||||
#include "renderer/FBXReader.h"
|
|
||||||
|
|
||||||
// this has to go after our normal includes, because its definition of HANDLE conflicts with Qt's
|
// this has to go after our normal includes, because its definition of HANDLE conflicts with Qt's
|
||||||
#ifdef HAVE_VISAGE
|
#ifdef HAVE_VISAGE
|
||||||
|
|
|
@ -543,14 +543,14 @@ void NetworkGeometry::setGeometry(const FBXGeometry& geometry) {
|
||||||
int totalIndices = 0;
|
int totalIndices = 0;
|
||||||
foreach (const FBXMeshPart& part, mesh.parts) {
|
foreach (const FBXMeshPart& part, mesh.parts) {
|
||||||
NetworkMeshPart networkPart;
|
NetworkMeshPart networkPart;
|
||||||
if (!part.diffuseFilename.isEmpty()) {
|
if (!part.diffuseTexture.filename.isEmpty()) {
|
||||||
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(
|
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(
|
||||||
_textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye);
|
_textureBase.resolved(QUrl(part.diffuseTexture.filename)), false, mesh.isEye, part.diffuseTexture.content);
|
||||||
networkPart.diffuseTexture->setLoadPriorities(_loadPriorities);
|
networkPart.diffuseTexture->setLoadPriorities(_loadPriorities);
|
||||||
}
|
}
|
||||||
if (!part.normalFilename.isEmpty()) {
|
if (!part.normalTexture.filename.isEmpty()) {
|
||||||
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(
|
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(
|
||||||
_textureBase.resolved(QUrl(part.normalFilename)), true);
|
_textureBase.resolved(QUrl(part.normalTexture.filename)), true, false, part.normalTexture.content);
|
||||||
networkPart.normalTexture->setLoadPriorities(_loadPriorities);
|
networkPart.normalTexture->setLoadPriorities(_loadPriorities);
|
||||||
}
|
}
|
||||||
networkMesh.parts.append(networkPart);
|
networkMesh.parts.append(networkPart);
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
#include <ResourceCache.h>
|
#include <ResourceCache.h>
|
||||||
|
|
||||||
#include "FBXReader.h"
|
#include <FBXReader.h>
|
||||||
|
|
||||||
class Model;
|
class Model;
|
||||||
class NetworkGeometry;
|
class NetworkGeometry;
|
||||||
|
|
|
@ -105,13 +105,22 @@ GLuint TextureCache::getBlueTextureID() {
|
||||||
return _blueTextureID;
|
return _blueTextureID;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable) {
|
/// Extra data for creating textures.
|
||||||
|
class TextureExtra {
|
||||||
|
public:
|
||||||
|
bool normalMap;
|
||||||
|
const QByteArray& content;
|
||||||
|
};
|
||||||
|
|
||||||
|
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool normalMap,
|
||||||
|
bool dilatable, const QByteArray& content) {
|
||||||
if (!dilatable) {
|
if (!dilatable) {
|
||||||
return ResourceCache::getResource(url, QUrl(), false, &normalMap).staticCast<NetworkTexture>();
|
TextureExtra extra = { normalMap, content };
|
||||||
|
return ResourceCache::getResource(url, QUrl(), false, &extra).staticCast<NetworkTexture>();
|
||||||
}
|
}
|
||||||
QSharedPointer<NetworkTexture> texture = _dilatableNetworkTextures.value(url);
|
QSharedPointer<NetworkTexture> texture = _dilatableNetworkTextures.value(url);
|
||||||
if (texture.isNull()) {
|
if (texture.isNull()) {
|
||||||
texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url), &Resource::allReferencesCleared);
|
texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url, content), &Resource::allReferencesCleared);
|
||||||
texture->setSelf(texture);
|
texture->setSelf(texture);
|
||||||
texture->setCache(this);
|
texture->setCache(this);
|
||||||
_dilatableNetworkTextures.insert(url, texture);
|
_dilatableNetworkTextures.insert(url, texture);
|
||||||
|
@ -215,7 +224,9 @@ bool TextureCache::eventFilter(QObject* watched, QEvent* event) {
|
||||||
|
|
||||||
QSharedPointer<Resource> TextureCache::createResource(const QUrl& url,
|
QSharedPointer<Resource> TextureCache::createResource(const QUrl& url,
|
||||||
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
|
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
|
||||||
return QSharedPointer<Resource>(new NetworkTexture(url, *(const bool*)extra), &Resource::allReferencesCleared);
|
const TextureExtra* textureExtra = static_cast<const TextureExtra*>(extra);
|
||||||
|
return QSharedPointer<Resource>(new NetworkTexture(url, textureExtra->normalMap, textureExtra->content),
|
||||||
|
&Resource::allReferencesCleared);
|
||||||
}
|
}
|
||||||
|
|
||||||
QOpenGLFramebufferObject* TextureCache::createFramebufferObject() {
|
QOpenGLFramebufferObject* TextureCache::createFramebufferObject() {
|
||||||
|
@ -238,8 +249,8 @@ Texture::~Texture() {
|
||||||
glDeleteTextures(1, &_id);
|
glDeleteTextures(1, &_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) :
|
NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content) :
|
||||||
Resource(url),
|
Resource(url, !content.isEmpty()),
|
||||||
_translucent(false) {
|
_translucent(false) {
|
||||||
|
|
||||||
if (!url.isValid()) {
|
if (!url.isValid()) {
|
||||||
|
@ -250,12 +261,19 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) :
|
||||||
glBindTexture(GL_TEXTURE_2D, getID());
|
glBindTexture(GL_TEXTURE_2D, getID());
|
||||||
loadSingleColorTexture(normalMap ? OPAQUE_BLUE : OPAQUE_WHITE);
|
loadSingleColorTexture(normalMap ? OPAQUE_BLUE : OPAQUE_WHITE);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
// if we have content, load it after we have our self pointer
|
||||||
|
if (!content.isEmpty()) {
|
||||||
|
_startedLoading = true;
|
||||||
|
QMetaObject::invokeMethod(this, "loadContent", Qt::QueuedConnection, Q_ARG(const QByteArray&, content));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImageReader : public QRunnable {
|
class ImageReader : public QRunnable {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply);
|
ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply, const QUrl& url = QUrl(),
|
||||||
|
const QByteArray& content = QByteArray());
|
||||||
|
|
||||||
virtual void run();
|
virtual void run();
|
||||||
|
|
||||||
|
@ -263,27 +281,37 @@ private:
|
||||||
|
|
||||||
QWeakPointer<Resource> _texture;
|
QWeakPointer<Resource> _texture;
|
||||||
QNetworkReply* _reply;
|
QNetworkReply* _reply;
|
||||||
|
QUrl _url;
|
||||||
|
QByteArray _content;
|
||||||
};
|
};
|
||||||
|
|
||||||
ImageReader::ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply) :
|
ImageReader::ImageReader(const QWeakPointer<Resource>& texture, QNetworkReply* reply,
|
||||||
|
const QUrl& url, const QByteArray& content) :
|
||||||
_texture(texture),
|
_texture(texture),
|
||||||
_reply(reply) {
|
_reply(reply),
|
||||||
|
_url(url),
|
||||||
|
_content(content) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageReader::run() {
|
void ImageReader::run() {
|
||||||
QSharedPointer<Resource> texture = _texture.toStrongRef();
|
QSharedPointer<Resource> texture = _texture.toStrongRef();
|
||||||
if (texture.isNull()) {
|
if (texture.isNull()) {
|
||||||
_reply->deleteLater();
|
if (_reply) {
|
||||||
|
_reply->deleteLater();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QUrl url = _reply->url();
|
if (_reply) {
|
||||||
QImage image = QImage::fromData(_reply->readAll());
|
_url = _reply->url();
|
||||||
_reply->deleteLater();
|
_content = _reply->readAll();
|
||||||
|
_reply->deleteLater();
|
||||||
|
}
|
||||||
|
QImage image = QImage::fromData(_content);
|
||||||
|
|
||||||
// enforce a fixed maximum
|
// enforce a fixed maximum
|
||||||
const int MAXIMUM_SIZE = 1024;
|
const int MAXIMUM_SIZE = 1024;
|
||||||
if (image.width() > MAXIMUM_SIZE || image.height() > MAXIMUM_SIZE) {
|
if (image.width() > MAXIMUM_SIZE || image.height() > MAXIMUM_SIZE) {
|
||||||
qDebug() << "Image greater than maximum size:" << url << image.width() << image.height();
|
qDebug() << "Image greater than maximum size:" << _url << image.width() << image.height();
|
||||||
image = image.scaled(MAXIMUM_SIZE, MAXIMUM_SIZE, Qt::KeepAspectRatio);
|
image = image.scaled(MAXIMUM_SIZE, MAXIMUM_SIZE, Qt::KeepAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,7 +343,7 @@ void ImageReader::run() {
|
||||||
}
|
}
|
||||||
int imageArea = image.width() * image.height();
|
int imageArea = image.width() * image.height();
|
||||||
if (opaquePixels == imageArea) {
|
if (opaquePixels == imageArea) {
|
||||||
qDebug() << "Image with alpha channel is completely opaque:" << url;
|
qDebug() << "Image with alpha channel is completely opaque:" << _url;
|
||||||
image = image.convertToFormat(QImage::Format_RGB888);
|
image = image.convertToFormat(QImage::Format_RGB888);
|
||||||
}
|
}
|
||||||
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image),
|
QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image),
|
||||||
|
@ -327,6 +355,10 @@ void NetworkTexture::downloadFinished(QNetworkReply* reply) {
|
||||||
QThreadPool::globalInstance()->start(new ImageReader(_self, reply));
|
QThreadPool::globalInstance()->start(new ImageReader(_self, reply));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetworkTexture::loadContent(const QByteArray& content) {
|
||||||
|
QThreadPool::globalInstance()->start(new ImageReader(_self, NULL, _url, content));
|
||||||
|
}
|
||||||
|
|
||||||
void NetworkTexture::setImage(const QImage& image, bool translucent) {
|
void NetworkTexture::setImage(const QImage& image, bool translucent) {
|
||||||
_translucent = translucent;
|
_translucent = translucent;
|
||||||
|
|
||||||
|
@ -348,8 +380,8 @@ void NetworkTexture::imageLoaded(const QImage& image) {
|
||||||
// nothing by default
|
// nothing by default
|
||||||
}
|
}
|
||||||
|
|
||||||
DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url) :
|
DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url, const QByteArray& content) :
|
||||||
NetworkTexture(url, false),
|
NetworkTexture(url, false, content),
|
||||||
_innerRadius(0),
|
_innerRadius(0),
|
||||||
_outerRadius(0)
|
_outerRadius(0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -44,7 +44,8 @@ public:
|
||||||
GLuint getBlueTextureID();
|
GLuint getBlueTextureID();
|
||||||
|
|
||||||
/// Loads a texture from the specified URL.
|
/// Loads a texture from the specified URL.
|
||||||
QSharedPointer<NetworkTexture> getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false);
|
QSharedPointer<NetworkTexture> getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false,
|
||||||
|
const QByteArray& content = QByteArray());
|
||||||
|
|
||||||
/// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is
|
/// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is
|
||||||
/// used for scene rendering.
|
/// used for scene rendering.
|
||||||
|
@ -115,7 +116,7 @@ class NetworkTexture : public Resource, public Texture {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
NetworkTexture(const QUrl& url, bool normalMap);
|
NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content);
|
||||||
|
|
||||||
/// Checks whether it "looks like" this texture is translucent
|
/// Checks whether it "looks like" this texture is translucent
|
||||||
/// (majority of pixels neither fully opaque or fully transparent).
|
/// (majority of pixels neither fully opaque or fully transparent).
|
||||||
|
@ -124,10 +125,12 @@ public:
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
virtual void downloadFinished(QNetworkReply* reply);
|
virtual void downloadFinished(QNetworkReply* reply);
|
||||||
virtual void imageLoaded(const QImage& image);
|
|
||||||
|
Q_INVOKABLE void loadContent(const QByteArray& content);
|
||||||
Q_INVOKABLE void setImage(const QImage& image, bool translucent);
|
Q_INVOKABLE void setImage(const QImage& image, bool translucent);
|
||||||
|
|
||||||
|
virtual void imageLoaded(const QImage& image);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
bool _translucent;
|
bool _translucent;
|
||||||
|
@ -139,7 +142,7 @@ class DilatableNetworkTexture : public NetworkTexture {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
DilatableNetworkTexture(const QUrl& url);
|
DilatableNetworkTexture(const QUrl& url, const QByteArray& content);
|
||||||
|
|
||||||
/// Returns a pointer to a texture with the requested amount of dilation.
|
/// Returns a pointer to a texture with the requested amount of dilation.
|
||||||
QSharedPointer<Texture> getDilatedTexture(float dilation);
|
QSharedPointer<Texture> getDilatedTexture(float dilation);
|
||||||
|
|
|
@ -343,7 +343,7 @@ void Stats::display(
|
||||||
|
|
||||||
lines = _expanded ? 12 : 3;
|
lines = _expanded ? 12 : 3;
|
||||||
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) {
|
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) {
|
||||||
lines += 8; // spatial audio processing adds 1 spacing line and 7 extra lines of info
|
lines += 9; // spatial audio processing adds 1 spacing line and 8 extra lines of info
|
||||||
}
|
}
|
||||||
|
|
||||||
drawBackground(backgroundColor, horizontalOffset, 0, glWidget->width() - horizontalOffset, lines * STATS_PELS_PER_LINE + 10);
|
drawBackground(backgroundColor, horizontalOffset, 0, glWidget->width() - horizontalOffset, lines * STATS_PELS_PER_LINE + 10);
|
||||||
|
@ -540,11 +540,19 @@ void Stats::display(
|
||||||
|
|
||||||
verticalOffset += STATS_PELS_PER_LINE;
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color);
|
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color);
|
||||||
|
|
||||||
|
bool distanceAttenuationDisabled = Menu::getInstance()->isOptionChecked(
|
||||||
|
MenuOption::AudioSpatialProcessingDontDistanceAttenuate);
|
||||||
|
|
||||||
sprintf(reflectionsStatus, "Attenuation: average %5.3f, max %5.3f, min %5.3f, Factor: %5.3f",
|
bool alternateDistanceAttenuationEnabled = Menu::getInstance()->isOptionChecked(
|
||||||
|
MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate);
|
||||||
|
|
||||||
|
sprintf(reflectionsStatus, "Attenuation: average %5.3f, max %5.3f, min %5.3f, %s: %5.3f",
|
||||||
audioReflector->getAverageAttenuation(),
|
audioReflector->getAverageAttenuation(),
|
||||||
audioReflector->getMaxAttenuation(),
|
audioReflector->getMaxAttenuation(),
|
||||||
audioReflector->getMinAttenuation(),
|
audioReflector->getMinAttenuation(),
|
||||||
|
(distanceAttenuationDisabled ? "Distance Factor [DISABLED]" :
|
||||||
|
alternateDistanceAttenuationEnabled ? "Distance Factor [ALTERNATE]" : "Distance Factor [STANARD]"),
|
||||||
audioReflector->getDistanceAttenuationScalingFactor());
|
audioReflector->getDistanceAttenuationScalingFactor());
|
||||||
|
|
||||||
verticalOffset += STATS_PELS_PER_LINE;
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
|
@ -585,6 +593,13 @@ void Stats::display(
|
||||||
verticalOffset += STATS_PELS_PER_LINE;
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color);
|
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color);
|
||||||
|
|
||||||
|
sprintf(reflectionsStatus, "Wet/Dry Mix: Original: %5.3f Echoes: %5.3f",
|
||||||
|
audioReflector->getOriginalSourceAttenuation(),
|
||||||
|
audioReflector->getEchoesAttenuation());
|
||||||
|
|
||||||
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,9 @@ AvatarData::AvatarData() :
|
||||||
_displayNameTargetAlpha(0.0f),
|
_displayNameTargetAlpha(0.0f),
|
||||||
_displayNameAlpha(0.0f),
|
_displayNameAlpha(0.0f),
|
||||||
_billboard(),
|
_billboard(),
|
||||||
_errorLogExpiry(0)
|
_errorLogExpiry(0),
|
||||||
|
_owningAvatarMixer(),
|
||||||
|
_lastUpdateTimer()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -193,6 +195,10 @@ bool AvatarData::shouldLogError(const quint64& now) {
|
||||||
|
|
||||||
// read data in packet starting at byte offset and return number of bytes parsed
|
// read data in packet starting at byte offset and return number of bytes parsed
|
||||||
int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
|
int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
|
||||||
|
|
||||||
|
// reset the last heard timer since we have new data for this AvatarData
|
||||||
|
_lastUpdateTimer.restart();
|
||||||
|
|
||||||
// lazily allocate memory for HeadData in case we're not an Avatar instance
|
// lazily allocate memory for HeadData in case we're not an Avatar instance
|
||||||
if (!_headData) {
|
if (!_headData) {
|
||||||
_headData = new HeadData(this);
|
_headData = new HeadData(this);
|
||||||
|
|
|
@ -32,6 +32,7 @@ typedef unsigned long long quint64;
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/quaternion.hpp>
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
|
||||||
|
#include <QtCore/QElapsedTimer>
|
||||||
#include <QtCore/QByteArray>
|
#include <QtCore/QByteArray>
|
||||||
#include <QtCore/QHash>
|
#include <QtCore/QHash>
|
||||||
#include <QtCore/QObject>
|
#include <QtCore/QObject>
|
||||||
|
@ -44,6 +45,7 @@ typedef unsigned long long quint64;
|
||||||
|
|
||||||
#include <CollisionInfo.h>
|
#include <CollisionInfo.h>
|
||||||
#include <RegisteredMetaTypes.h>
|
#include <RegisteredMetaTypes.h>
|
||||||
|
#include <Node.h>
|
||||||
|
|
||||||
#include "HeadData.h"
|
#include "HeadData.h"
|
||||||
#include "HandData.h"
|
#include "HandData.h"
|
||||||
|
@ -99,6 +101,8 @@ class AvatarData : public QObject {
|
||||||
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
|
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
|
||||||
Q_PROPERTY(QString billboardURL READ getBillboardURL WRITE setBillboardFromURL)
|
Q_PROPERTY(QString billboardURL READ getBillboardURL WRITE setBillboardFromURL)
|
||||||
|
|
||||||
|
Q_PROPERTY(QStringList jointNames READ getJointNames)
|
||||||
|
|
||||||
Q_PROPERTY(QUuid sessionUUID READ getSessionUUID);
|
Q_PROPERTY(QUuid sessionUUID READ getSessionUUID);
|
||||||
public:
|
public:
|
||||||
AvatarData();
|
AvatarData();
|
||||||
|
@ -218,6 +222,11 @@ public:
|
||||||
QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); }
|
QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); }
|
||||||
void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); }
|
void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); }
|
||||||
|
|
||||||
|
Node* getOwningAvatarMixer() { return _owningAvatarMixer.data(); }
|
||||||
|
void setOwningAvatarMixer(const QWeakPointer<Node>& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; }
|
||||||
|
|
||||||
|
QElapsedTimer& getLastUpdateTimer() { return _lastUpdateTimer; }
|
||||||
|
|
||||||
virtual float getBoundingRadius() const { return 1.f; }
|
virtual float getBoundingRadius() const { return 1.f; }
|
||||||
|
|
||||||
static void setNetworkAccessManager(QNetworkAccessManager* sharedAccessManager) { networkAccessManager = sharedAccessManager; }
|
static void setNetworkAccessManager(QNetworkAccessManager* sharedAccessManager) { networkAccessManager = sharedAccessManager; }
|
||||||
|
@ -276,7 +285,10 @@ protected:
|
||||||
static QNetworkAccessManager* networkAccessManager;
|
static QNetworkAccessManager* networkAccessManager;
|
||||||
|
|
||||||
quint64 _errorLogExpiry; ///< time in future when to log an error
|
quint64 _errorLogExpiry; ///< time in future when to log an error
|
||||||
|
|
||||||
|
QWeakPointer<Node> _owningAvatarMixer;
|
||||||
|
QElapsedTimer _lastUpdateTimer;
|
||||||
|
|
||||||
/// Loads the joint indices, names from the FST file (if any)
|
/// Loads the joint indices, names from the FST file (if any)
|
||||||
virtual void updateJointMappings();
|
virtual void updateJointMappings();
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,14 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include <PacketHeaders.h>
|
||||||
|
|
||||||
#include "AvatarHashMap.h"
|
#include "AvatarHashMap.h"
|
||||||
|
|
||||||
AvatarHashMap::AvatarHashMap() :
|
AvatarHashMap::AvatarHashMap() :
|
||||||
_avatarHash()
|
_avatarHash()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarHashMap::insert(const QUuid& id, AvatarSharedPointer avatar) {
|
void AvatarHashMap::insert(const QUuid& id, AvatarSharedPointer avatar) {
|
||||||
|
@ -22,6 +25,150 @@ void AvatarHashMap::insert(const QUuid& id, AvatarSharedPointer avatar) {
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarHash::iterator AvatarHashMap::erase(const AvatarHash::iterator& iterator) {
|
AvatarHash::iterator AvatarHashMap::erase(const AvatarHash::iterator& iterator) {
|
||||||
|
qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarHashMap.";
|
||||||
return _avatarHash.erase(iterator);
|
return _avatarHash.erase(iterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const qint64 AVATAR_SILENCE_THRESHOLD_MSECS = 5 * 1000;
|
||||||
|
|
||||||
|
bool AvatarHashMap::shouldKillAvatar(const AvatarSharedPointer& sharedAvatar) {
|
||||||
|
return (sharedAvatar->getOwningAvatarMixer() == NULL
|
||||||
|
|| sharedAvatar->getLastUpdateTimer().elapsed() > AVATAR_SILENCE_THRESHOLD_MSECS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarHashMap::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer<Node>& mixerWeakPointer) {
|
||||||
|
switch (packetTypeForPacket(datagram)) {
|
||||||
|
case PacketTypeBulkAvatarData:
|
||||||
|
processAvatarDataPacket(datagram, mixerWeakPointer);
|
||||||
|
break;
|
||||||
|
case PacketTypeAvatarIdentity:
|
||||||
|
processAvatarIdentityPacket(datagram, mixerWeakPointer);
|
||||||
|
break;
|
||||||
|
case PacketTypeAvatarBillboard:
|
||||||
|
processAvatarBillboardPacket(datagram, mixerWeakPointer);
|
||||||
|
break;
|
||||||
|
case PacketTypeKillAvatar:
|
||||||
|
processKillAvatar(datagram);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AvatarHashMap::containsAvatarWithDisplayName(const QString& displayName) {
|
||||||
|
|
||||||
|
AvatarHash::iterator avatarIterator = _avatarHash.begin();
|
||||||
|
while (avatarIterator != _avatarHash.end()) {
|
||||||
|
AvatarSharedPointer sharedAvatar = avatarIterator.value();
|
||||||
|
if (avatarIterator.value()->getDisplayName() == displayName) {
|
||||||
|
// this is a match
|
||||||
|
// check if this avatar should still be around
|
||||||
|
if (!shouldKillAvatar(sharedAvatar)) {
|
||||||
|
// we have a match, return true
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// we should remove this avatar, do that now
|
||||||
|
erase(avatarIterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
++avatarIterator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return false, no match
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AvatarSharedPointer AvatarHashMap::newSharedAvatar() {
|
||||||
|
return AvatarSharedPointer(new AvatarData());
|
||||||
|
}
|
||||||
|
|
||||||
|
AvatarSharedPointer AvatarHashMap::matchingOrNewAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
|
||||||
|
AvatarSharedPointer matchingAvatar = _avatarHash.value(sessionUUID);
|
||||||
|
|
||||||
|
if (!matchingAvatar) {
|
||||||
|
// insert the new avatar into our hash
|
||||||
|
matchingAvatar = newSharedAvatar();
|
||||||
|
|
||||||
|
qDebug() << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap.";
|
||||||
|
_avatarHash.insert(sessionUUID, matchingAvatar);
|
||||||
|
|
||||||
|
matchingAvatar->setOwningAvatarMixer(mixerWeakPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchingAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarHashMap::processAvatarDataPacket(const QByteArray &datagram, const QWeakPointer<Node> &mixerWeakPointer) {
|
||||||
|
int bytesRead = numBytesForPacketHeader(datagram);
|
||||||
|
|
||||||
|
// enumerate over all of the avatars in this packet
|
||||||
|
// only add them if mixerWeakPointer points to something (meaning that mixer is still around)
|
||||||
|
while (bytesRead < datagram.size() && mixerWeakPointer.data()) {
|
||||||
|
QUuid sessionUUID = QUuid::fromRfc4122(datagram.mid(bytesRead, NUM_BYTES_RFC4122_UUID));
|
||||||
|
bytesRead += NUM_BYTES_RFC4122_UUID;
|
||||||
|
|
||||||
|
AvatarSharedPointer matchingAvatarData = matchingOrNewAvatar(sessionUUID, mixerWeakPointer);
|
||||||
|
|
||||||
|
// have the matching (or new) avatar parse the data from the packet
|
||||||
|
bytesRead += matchingAvatarData->parseDataAtOffset(datagram, bytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarHashMap::processAvatarIdentityPacket(const QByteArray &packet, const QWeakPointer<Node>& mixerWeakPointer) {
|
||||||
|
// setup a data stream to parse the packet
|
||||||
|
QDataStream identityStream(packet);
|
||||||
|
identityStream.skipRawData(numBytesForPacketHeader(packet));
|
||||||
|
|
||||||
|
QUuid sessionUUID;
|
||||||
|
|
||||||
|
while (!identityStream.atEnd()) {
|
||||||
|
|
||||||
|
QUrl faceMeshURL, skeletonURL;
|
||||||
|
QString displayName;
|
||||||
|
identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> displayName;
|
||||||
|
|
||||||
|
// mesh URL for a UUID, find avatar in our list
|
||||||
|
AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(sessionUUID, mixerWeakPointer);
|
||||||
|
if (matchingAvatar) {
|
||||||
|
|
||||||
|
if (matchingAvatar->getFaceModelURL() != faceMeshURL) {
|
||||||
|
matchingAvatar->setFaceModelURL(faceMeshURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchingAvatar->getSkeletonModelURL() != skeletonURL) {
|
||||||
|
matchingAvatar->setSkeletonModelURL(skeletonURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchingAvatar->getDisplayName() != displayName) {
|
||||||
|
matchingAvatar->setDisplayName(displayName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarHashMap::processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer) {
|
||||||
|
int headerSize = numBytesForPacketHeader(packet);
|
||||||
|
QUuid sessionUUID = QUuid::fromRfc4122(QByteArray::fromRawData(packet.constData() + headerSize, NUM_BYTES_RFC4122_UUID));
|
||||||
|
|
||||||
|
AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(sessionUUID, mixerWeakPointer);
|
||||||
|
if (matchingAvatar) {
|
||||||
|
QByteArray billboard = packet.mid(headerSize + NUM_BYTES_RFC4122_UUID);
|
||||||
|
if (matchingAvatar->getBillboard() != billboard) {
|
||||||
|
matchingAvatar->setBillboard(billboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarHashMap::processKillAvatar(const QByteArray& datagram) {
|
||||||
|
// read the node id
|
||||||
|
QUuid sessionUUID = QUuid::fromRfc4122(datagram.mid(numBytesForPacketHeader(datagram), NUM_BYTES_RFC4122_UUID));
|
||||||
|
|
||||||
|
// remove the avatar with that UUID from our hash, if it exists
|
||||||
|
AvatarHash::iterator matchedAvatar = _avatarHash.find(sessionUUID);
|
||||||
|
if (matchedAvatar != _avatarHash.end()) {
|
||||||
|
erase(matchedAvatar);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,12 +16,15 @@
|
||||||
#include <QtCore/QSharedPointer>
|
#include <QtCore/QSharedPointer>
|
||||||
#include <QtCore/QUuid>
|
#include <QtCore/QUuid>
|
||||||
|
|
||||||
|
#include <Node.h>
|
||||||
|
|
||||||
#include "AvatarData.h"
|
#include "AvatarData.h"
|
||||||
|
|
||||||
typedef QSharedPointer<AvatarData> AvatarSharedPointer;
|
typedef QSharedPointer<AvatarData> AvatarSharedPointer;
|
||||||
typedef QHash<QUuid, AvatarSharedPointer> AvatarHash;
|
typedef QHash<QUuid, AvatarSharedPointer> AvatarHash;
|
||||||
|
|
||||||
class AvatarHashMap {
|
class AvatarHashMap : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AvatarHashMap();
|
AvatarHashMap();
|
||||||
|
|
||||||
|
@ -29,9 +32,23 @@ public:
|
||||||
int size() const { return _avatarHash.size(); }
|
int size() const { return _avatarHash.size(); }
|
||||||
|
|
||||||
virtual void insert(const QUuid& id, AvatarSharedPointer avatar);
|
virtual void insert(const QUuid& id, AvatarSharedPointer avatar);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer<Node>& mixerWeakPointer);
|
||||||
|
bool containsAvatarWithDisplayName(const QString& displayName);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual AvatarHash::iterator erase(const AvatarHash::iterator& iterator);
|
virtual AvatarHash::iterator erase(const AvatarHash::iterator& iterator);
|
||||||
|
|
||||||
|
bool shouldKillAvatar(const AvatarSharedPointer& sharedAvatar);
|
||||||
|
|
||||||
|
virtual AvatarSharedPointer newSharedAvatar();
|
||||||
|
AvatarSharedPointer matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer<Node>& mixerWeakPointer);
|
||||||
|
|
||||||
|
void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
|
||||||
|
void processAvatarIdentityPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
|
||||||
|
void processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
|
||||||
|
void processKillAvatar(const QByteArray& datagram);
|
||||||
|
|
||||||
AvatarHash _avatarHash;
|
AvatarHash _avatarHash;
|
||||||
};
|
};
|
||||||
|
|
38
libraries/fbx/CMakeLists.txt
Normal file
38
libraries/fbx/CMakeLists.txt
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
cmake_minimum_required(VERSION 2.8)
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
cmake_policy (SET CMP0020 NEW)
|
||||||
|
endif (WIN32)
|
||||||
|
|
||||||
|
set(ROOT_DIR ../..)
|
||||||
|
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||||
|
|
||||||
|
# setup for find modules
|
||||||
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
|
||||||
|
|
||||||
|
set(TARGET_NAME fbx)
|
||||||
|
|
||||||
|
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
|
||||||
|
setup_hifi_library(${TARGET_NAME})
|
||||||
|
|
||||||
|
include(${MACRO_DIR}/IncludeGLM.cmake)
|
||||||
|
include_glm(${TARGET_NAME} "${ROOT_DIR}")
|
||||||
|
|
||||||
|
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
|
||||||
|
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
|
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
|
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
|
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
|
|
||||||
|
# link ZLIB and GnuTLS
|
||||||
|
find_package(ZLIB)
|
||||||
|
find_package(GnuTLS REQUIRED)
|
||||||
|
|
||||||
|
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
|
||||||
|
if (WIN32)
|
||||||
|
add_definitions(-Dssize_t=long)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
|
||||||
|
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}")
|
|
@ -22,14 +22,14 @@
|
||||||
#include <glm/gtx/quaternion.hpp>
|
#include <glm/gtx/quaternion.hpp>
|
||||||
#include <glm/gtx/transform.hpp>
|
#include <glm/gtx/transform.hpp>
|
||||||
|
|
||||||
#include <OctalCode.h>
|
|
||||||
|
|
||||||
#include <GeometryUtil.h>
|
#include <GeometryUtil.h>
|
||||||
|
#include <OctalCode.h>
|
||||||
#include <Shape.h>
|
#include <Shape.h>
|
||||||
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
#include <VoxelTree.h>
|
#include <VoxelTree.h>
|
||||||
|
|
||||||
#include "FBXReader.h"
|
#include "FBXReader.h"
|
||||||
#include "Util.h"
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
@ -72,6 +72,8 @@ bool FBXGeometry::hasBlendedMeshes() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int fbxGeometryMetaTypeId = qRegisterMetaType<FBXGeometry>();
|
static int fbxGeometryMetaTypeId = qRegisterMetaType<FBXGeometry>();
|
||||||
|
static int fbxAnimationFrameMetaTypeId = qRegisterMetaType<FBXAnimationFrame>();
|
||||||
|
static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType<QVector<FBXAnimationFrame> >();
|
||||||
|
|
||||||
template<class T> QVariant readBinaryArray(QDataStream& in) {
|
template<class T> QVariant readBinaryArray(QDataStream& in) {
|
||||||
quint32 arrayLength;
|
quint32 arrayLength;
|
||||||
|
@ -444,6 +446,34 @@ QVector<int> getIntVector(const QVariantList& properties, int index) {
|
||||||
return vector;
|
return vector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVector<qlonglong> getLongVector(const QVariantList& properties, int index) {
|
||||||
|
if (index >= properties.size()) {
|
||||||
|
return QVector<qlonglong>();
|
||||||
|
}
|
||||||
|
QVector<qlonglong> vector = properties.at(index).value<QVector<qlonglong> >();
|
||||||
|
if (!vector.isEmpty()) {
|
||||||
|
return vector;
|
||||||
|
}
|
||||||
|
for (; index < properties.size(); index++) {
|
||||||
|
vector.append(properties.at(index).toLongLong());
|
||||||
|
}
|
||||||
|
return vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<float> getFloatVector(const QVariantList& properties, int index) {
|
||||||
|
if (index >= properties.size()) {
|
||||||
|
return QVector<float>();
|
||||||
|
}
|
||||||
|
QVector<float> vector = properties.at(index).value<QVector<float> >();
|
||||||
|
if (!vector.isEmpty()) {
|
||||||
|
return vector;
|
||||||
|
}
|
||||||
|
for (; index < properties.size(); index++) {
|
||||||
|
vector.append(properties.at(index).toFloat());
|
||||||
|
}
|
||||||
|
return vector;
|
||||||
|
}
|
||||||
|
|
||||||
QVector<double> getDoubleVector(const QVariantList& properties, int index) {
|
QVector<double> getDoubleVector(const QVariantList& properties, int index) {
|
||||||
if (index >= properties.size()) {
|
if (index >= properties.size()) {
|
||||||
return QVector<double>();
|
return QVector<double>();
|
||||||
|
@ -478,8 +508,7 @@ glm::vec3 parseVec3(const QString& string) {
|
||||||
|
|
||||||
QString processID(const QString& id) {
|
QString processID(const QString& id) {
|
||||||
// Blender (at least) prepends a type to the ID, so strip it out
|
// Blender (at least) prepends a type to the ID, so strip it out
|
||||||
int index = id.indexOf("::");
|
return id.mid(id.lastIndexOf(':') + 1);
|
||||||
return (index == -1) ? id : id.mid(index + 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString getID(const QVariantList& properties, int index = 0) {
|
QString getID(const QVariantList& properties, int index = 0) {
|
||||||
|
@ -901,6 +930,19 @@ public:
|
||||||
float averageRadius; // average distance from mesh points to averageVertex
|
float averageRadius; // average distance from mesh points to averageVertex
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AnimationCurve {
|
||||||
|
public:
|
||||||
|
QVector<float> values;
|
||||||
|
};
|
||||||
|
|
||||||
|
FBXTexture getTexture(const QString& textureID, const QHash<QString, QByteArray>& textureFilenames,
|
||||||
|
const QHash<QByteArray, QByteArray>& textureContent) {
|
||||||
|
FBXTexture texture;
|
||||||
|
texture.filename = textureFilenames.value(textureID);
|
||||||
|
texture.content = textureContent.value(texture.filename);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
|
FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) {
|
||||||
QHash<QString, ExtractedMesh> meshes;
|
QHash<QString, ExtractedMesh> meshes;
|
||||||
QVector<ExtractedBlendshape> blendshapes;
|
QVector<ExtractedBlendshape> blendshapes;
|
||||||
|
@ -908,10 +950,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
QMultiHash<QString, QString> childMap;
|
QMultiHash<QString, QString> childMap;
|
||||||
QHash<QString, FBXModel> models;
|
QHash<QString, FBXModel> models;
|
||||||
QHash<QString, Cluster> clusters;
|
QHash<QString, Cluster> clusters;
|
||||||
|
QHash<QString, AnimationCurve> animationCurves;
|
||||||
QHash<QString, QByteArray> textureFilenames;
|
QHash<QString, QByteArray> textureFilenames;
|
||||||
|
QHash<QByteArray, QByteArray> textureContent;
|
||||||
QHash<QString, Material> materials;
|
QHash<QString, Material> materials;
|
||||||
QHash<QString, QString> diffuseTextures;
|
QHash<QString, QString> diffuseTextures;
|
||||||
QHash<QString, QString> bumpTextures;
|
QHash<QString, QString> bumpTextures;
|
||||||
|
QHash<QString, QString> localRotations;
|
||||||
|
QHash<QString, QString> xComponents;
|
||||||
|
QHash<QString, QString> yComponents;
|
||||||
|
QHash<QString, QString> zComponents;
|
||||||
|
|
||||||
QVariantHash joints = mapping.value("joint").toHash();
|
QVariantHash joints = mapping.value("joint").toHash();
|
||||||
QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft")));
|
QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft")));
|
||||||
|
@ -974,7 +1022,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
QString name;
|
QString name;
|
||||||
if (object.properties.size() == 3) {
|
if (object.properties.size() == 3) {
|
||||||
name = object.properties.at(1).toString();
|
name = object.properties.at(1).toString();
|
||||||
name = name.left(name.indexOf(QChar('\0')));
|
name = processID(name.left(name.indexOf(QChar('\0'))));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
name = getID(object.properties);
|
name = getID(object.properties);
|
||||||
|
@ -1124,7 +1172,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
model.postRotation = glm::quat(glm::radians(postRotation));
|
model.postRotation = glm::quat(glm::radians(postRotation));
|
||||||
model.postTransform = glm::translate(-rotationPivot) * glm::translate(scalePivot) *
|
model.postTransform = glm::translate(-rotationPivot) * glm::translate(scalePivot) *
|
||||||
glm::scale(scale) * glm::translate(-scalePivot);
|
glm::scale(scale) * glm::translate(-scalePivot);
|
||||||
// NOTE: anbgles from the FBX file are in degrees
|
// NOTE: angles from the FBX file are in degrees
|
||||||
// so we convert them to radians for the FBXModel class
|
// so we convert them to radians for the FBXModel class
|
||||||
model.rotationMin = glm::radians(glm::vec3(rotationMinX ? rotationMin.x : -180.0f,
|
model.rotationMin = glm::radians(glm::vec3(rotationMinX ? rotationMin.x : -180.0f,
|
||||||
rotationMinY ? rotationMin.y : -180.0f, rotationMinZ ? rotationMin.z : -180.0f));
|
rotationMinY ? rotationMin.y : -180.0f, rotationMinZ ? rotationMin.z : -180.0f));
|
||||||
|
@ -1141,6 +1189,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
textureFilenames.insert(getID(object.properties), filename);
|
textureFilenames.insert(getID(object.properties), filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (object.name == "Video") {
|
||||||
|
QByteArray filename;
|
||||||
|
QByteArray content;
|
||||||
|
foreach (const FBXNode& subobject, object.children) {
|
||||||
|
if (subobject.name == "RelativeFilename") {
|
||||||
|
filename = subobject.properties.at(0).toByteArray();
|
||||||
|
filename = filename.mid(qMax(filename.lastIndexOf('\\'), filename.lastIndexOf('/')) + 1);
|
||||||
|
|
||||||
|
} else if (subobject.name == "Content" && !subobject.properties.isEmpty()) {
|
||||||
|
content = subobject.properties.at(0).toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!content.isEmpty()) {
|
||||||
|
textureContent.insert(filename, content);
|
||||||
|
}
|
||||||
} else if (object.name == "Material") {
|
} else if (object.name == "Material") {
|
||||||
Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), 96.0f };
|
Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), 96.0f };
|
||||||
foreach (const FBXNode& subobject, object.children) {
|
foreach (const FBXNode& subobject, object.children) {
|
||||||
|
@ -1204,6 +1267,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
blendshapeChannelIndices.insert(id, index);
|
blendshapeChannelIndices.insert(id, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (object.name == "AnimationCurve") {
|
||||||
|
AnimationCurve curve;
|
||||||
|
foreach (const FBXNode& subobject, object.children) {
|
||||||
|
if (subobject.name == "KeyValueFloat") {
|
||||||
|
curve.values = getFloatVector(subobject.properties, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
animationCurves.insert(getID(object.properties), curve);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (child.name == "Connections") {
|
} else if (child.name == "Connections") {
|
||||||
|
@ -1214,8 +1285,20 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
if (type.contains("diffuse")) {
|
if (type.contains("diffuse")) {
|
||||||
diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||||
|
|
||||||
} else if (type.contains("bump")) {
|
} else if (type.contains("bump") || type.contains("normal")) {
|
||||||
bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||||
|
|
||||||
|
} else if (type == "lcl rotation") {
|
||||||
|
localRotations.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||||
|
|
||||||
|
} else if (type == "d|x") {
|
||||||
|
xComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||||
|
|
||||||
|
} else if (type == "d|y") {
|
||||||
|
yComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||||
|
|
||||||
|
} else if (type == "d|z") {
|
||||||
|
zComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2));
|
parentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2));
|
||||||
|
@ -1239,7 +1322,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
glm::quat offsetRotation = glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(),
|
glm::quat offsetRotation = glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(),
|
||||||
mapping.value("ry").toFloat(), mapping.value("rz").toFloat())));
|
mapping.value("ry").toFloat(), mapping.value("rz").toFloat())));
|
||||||
geometry.offset = glm::translate(glm::vec3(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(),
|
geometry.offset = glm::translate(glm::vec3(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(),
|
||||||
mapping.value("tz").toFloat())) * glm::mat4_cast(offsetRotation) * glm::scale(glm::vec3(offsetScale, offsetScale, offsetScale));
|
mapping.value("tz").toFloat())) * glm::mat4_cast(offsetRotation) *
|
||||||
|
glm::scale(glm::vec3(offsetScale, offsetScale, offsetScale));
|
||||||
|
|
||||||
// get the list of models in depth-first traversal order
|
// get the list of models in depth-first traversal order
|
||||||
QVector<QString> modelIDs;
|
QVector<QString> modelIDs;
|
||||||
|
@ -1277,6 +1361,17 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
appendModelIDs(parentMap.value(topID), childMap, models, remainingModels, modelIDs);
|
appendModelIDs(parentMap.value(topID), childMap, models, remainingModels, modelIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// figure the number of animation frames from the curves
|
||||||
|
int frameCount = 0;
|
||||||
|
foreach (const AnimationCurve& curve, animationCurves) {
|
||||||
|
frameCount = qMax(frameCount, curve.values.size());
|
||||||
|
}
|
||||||
|
for (int i = 0; i < frameCount; i++) {
|
||||||
|
FBXAnimationFrame frame;
|
||||||
|
frame.rotations.resize(modelIDs.size());
|
||||||
|
geometry.animationFrames.append(frame);
|
||||||
|
}
|
||||||
|
|
||||||
// convert the models to joints
|
// convert the models to joints
|
||||||
QVariantList freeJoints = mapping.values("freeJoint");
|
QVariantList freeJoints = mapping.values("freeJoint");
|
||||||
foreach (const QString& modelID, modelIDs) {
|
foreach (const QString& modelID, modelIDs) {
|
||||||
|
@ -1286,7 +1381,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
joint.parentIndex = model.parentIndex;
|
joint.parentIndex = model.parentIndex;
|
||||||
|
|
||||||
// get the indices of all ancestors starting with the first free one (if any)
|
// get the indices of all ancestors starting with the first free one (if any)
|
||||||
joint.freeLineage.append(geometry.joints.size());
|
int jointIndex = geometry.joints.size();
|
||||||
|
joint.freeLineage.append(jointIndex);
|
||||||
int lastFreeIndex = joint.isFree ? 0 : -1;
|
int lastFreeIndex = joint.isFree ? 0 : -1;
|
||||||
for (int index = joint.parentIndex; index != -1; index = geometry.joints.at(index).parentIndex) {
|
for (int index = joint.parentIndex; index != -1; index = geometry.joints.at(index).parentIndex) {
|
||||||
if (geometry.joints.at(index).isFree) {
|
if (geometry.joints.at(index).isFree) {
|
||||||
|
@ -1325,6 +1421,18 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
joint.shapeType = Shape::UNKNOWN_SHAPE;
|
joint.shapeType = Shape::UNKNOWN_SHAPE;
|
||||||
geometry.joints.append(joint);
|
geometry.joints.append(joint);
|
||||||
geometry.jointIndices.insert(model.name, geometry.joints.size());
|
geometry.jointIndices.insert(model.name, geometry.joints.size());
|
||||||
|
|
||||||
|
QString rotationID = localRotations.value(modelID);
|
||||||
|
AnimationCurve xCurve = animationCurves.value(xComponents.value(rotationID));
|
||||||
|
AnimationCurve yCurve = animationCurves.value(yComponents.value(rotationID));
|
||||||
|
AnimationCurve zCurve = animationCurves.value(zComponents.value(rotationID));
|
||||||
|
glm::vec3 defaultValues = glm::degrees(safeEulerAngles(joint.rotation));
|
||||||
|
for (int i = 0; i < frameCount; i++) {
|
||||||
|
geometry.animationFrames[i].rotations[jointIndex] = glm::quat(glm::radians(glm::vec3(
|
||||||
|
xCurve.values.isEmpty() ? defaultValues.x : xCurve.values.at(i % xCurve.values.size()),
|
||||||
|
yCurve.values.isEmpty() ? defaultValues.y : yCurve.values.at(i % yCurve.values.size()),
|
||||||
|
zCurve.values.isEmpty() ? defaultValues.z : zCurve.values.at(i % zCurve.values.size()))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// for each joint we allocate a JointShapeInfo in which we'll store collision shape info
|
// for each joint we allocate a JointShapeInfo in which we'll store collision shape info
|
||||||
QVector<JointShapeInfo> jointShapeInfos;
|
QVector<JointShapeInfo> jointShapeInfos;
|
||||||
|
@ -1377,23 +1485,23 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
if (materials.contains(childID)) {
|
if (materials.contains(childID)) {
|
||||||
Material material = materials.value(childID);
|
Material material = materials.value(childID);
|
||||||
|
|
||||||
QByteArray diffuseFilename;
|
FBXTexture diffuseTexture;
|
||||||
QString diffuseTextureID = diffuseTextures.value(childID);
|
QString diffuseTextureID = diffuseTextures.value(childID);
|
||||||
if (!diffuseTextureID.isNull()) {
|
if (!diffuseTextureID.isNull()) {
|
||||||
diffuseFilename = textureFilenames.value(diffuseTextureID);
|
diffuseTexture = getTexture(diffuseTextureID, textureFilenames, textureContent);
|
||||||
|
|
||||||
// FBX files generated by 3DSMax have an intermediate texture parent, apparently
|
// FBX files generated by 3DSMax have an intermediate texture parent, apparently
|
||||||
foreach (const QString& childTextureID, childMap.values(diffuseTextureID)) {
|
foreach (const QString& childTextureID, childMap.values(diffuseTextureID)) {
|
||||||
if (textureFilenames.contains(childTextureID)) {
|
if (textureFilenames.contains(childTextureID)) {
|
||||||
diffuseFilename = textureFilenames.value(childTextureID);
|
diffuseTexture = getTexture(diffuseTextureID, textureFilenames, textureContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray normalFilename;
|
FBXTexture normalTexture;
|
||||||
QString bumpTextureID = bumpTextures.value(childID);
|
QString bumpTextureID = bumpTextures.value(childID);
|
||||||
if (!bumpTextureID.isNull()) {
|
if (!bumpTextureID.isNull()) {
|
||||||
normalFilename = textureFilenames.value(bumpTextureID);
|
normalTexture = getTexture(bumpTextureID, textureFilenames, textureContent);
|
||||||
generateTangents = true;
|
generateTangents = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1403,21 +1511,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
part.diffuseColor = material.diffuse;
|
part.diffuseColor = material.diffuse;
|
||||||
part.specularColor = material.specular;
|
part.specularColor = material.specular;
|
||||||
part.shininess = material.shininess;
|
part.shininess = material.shininess;
|
||||||
if (!diffuseFilename.isNull()) {
|
if (!diffuseTexture.filename.isNull()) {
|
||||||
part.diffuseFilename = diffuseFilename;
|
part.diffuseTexture = diffuseTexture;
|
||||||
}
|
}
|
||||||
if (!normalFilename.isNull()) {
|
if (!normalTexture.filename.isNull()) {
|
||||||
part.normalFilename = normalFilename;
|
part.normalTexture = normalTexture;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
materialIndex++;
|
materialIndex++;
|
||||||
|
|
||||||
} else if (textureFilenames.contains(childID)) {
|
} else if (textureFilenames.contains(childID)) {
|
||||||
QByteArray filename = textureFilenames.value(childID);
|
FBXTexture texture = getTexture(childID, textureFilenames, textureContent);
|
||||||
for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
|
for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
|
||||||
if (extracted.partMaterialTextures.at(j).second == textureIndex) {
|
if (extracted.partMaterialTextures.at(j).second == textureIndex) {
|
||||||
extracted.mesh.parts[j].diffuseFilename = filename;
|
extracted.mesh.parts[j].diffuseTexture = texture;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
textureIndex++;
|
textureIndex++;
|
|
@ -105,6 +105,14 @@ public:
|
||||||
glm::mat4 inverseBindMatrix;
|
glm::mat4 inverseBindMatrix;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A texture map in an FBX document.
|
||||||
|
class FBXTexture {
|
||||||
|
public:
|
||||||
|
|
||||||
|
QByteArray filename;
|
||||||
|
QByteArray content;
|
||||||
|
};
|
||||||
|
|
||||||
/// A single part of a mesh (with the same material).
|
/// A single part of a mesh (with the same material).
|
||||||
class FBXMeshPart {
|
class FBXMeshPart {
|
||||||
public:
|
public:
|
||||||
|
@ -116,8 +124,8 @@ public:
|
||||||
glm::vec3 specularColor;
|
glm::vec3 specularColor;
|
||||||
float shininess;
|
float shininess;
|
||||||
|
|
||||||
QByteArray diffuseFilename;
|
FBXTexture diffuseTexture;
|
||||||
QByteArray normalFilename;
|
FBXTexture normalTexture;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A single mesh (with optional blendshapes) extracted from an FBX document.
|
/// A single mesh (with optional blendshapes) extracted from an FBX document.
|
||||||
|
@ -141,6 +149,16 @@ public:
|
||||||
QVector<FBXBlendshape> blendshapes;
|
QVector<FBXBlendshape> blendshapes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// A single animation frame extracted from an FBX document.
|
||||||
|
class FBXAnimationFrame {
|
||||||
|
public:
|
||||||
|
|
||||||
|
QVector<glm::quat> rotations;
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(FBXAnimationFrame)
|
||||||
|
Q_DECLARE_METATYPE(QVector<FBXAnimationFrame>)
|
||||||
|
|
||||||
/// An attachment to an FBX document.
|
/// An attachment to an FBX document.
|
||||||
class FBXAttachment {
|
class FBXAttachment {
|
||||||
public:
|
public:
|
||||||
|
@ -185,6 +203,8 @@ public:
|
||||||
Extents bindExtents;
|
Extents bindExtents;
|
||||||
Extents meshExtents;
|
Extents meshExtents;
|
||||||
|
|
||||||
|
QVector<FBXAnimationFrame> animationFrames;
|
||||||
|
|
||||||
QVector<FBXAttachment> attachments;
|
QVector<FBXAttachment> attachments;
|
||||||
|
|
||||||
int getJointIndex(const QString& name) const { return jointIndices.value(name) - 1; }
|
int getJointIndex(const QString& name) const { return jointIndices.value(name) - 1; }
|
|
@ -23,6 +23,7 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}")
|
||||||
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
|
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
|
||||||
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
|
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
|
|
||||||
# link ZLIB and GnuTLS
|
# link ZLIB and GnuTLS
|
||||||
|
@ -35,4 +36,4 @@ if (WIN32)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
|
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
|
||||||
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}")
|
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}")
|
||||||
|
|
|
@ -24,6 +24,7 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake)
|
||||||
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
|
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
|
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
|
||||||
|
|
||||||
# link ZLIB
|
# link ZLIB
|
||||||
|
@ -36,4 +37,4 @@ if (WIN32)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
|
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
|
||||||
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets)
|
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets)
|
||||||
|
|
102
libraries/script-engine/src/AnimationCache.cpp
Normal file
102
libraries/script-engine/src/AnimationCache.cpp
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
//
|
||||||
|
// AnimationCache.cpp
|
||||||
|
// libraries/script-engine/src/
|
||||||
|
//
|
||||||
|
// Created by Andrzej Kapolka on 4/14/14.
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <QRunnable>
|
||||||
|
#include <QThreadPool>
|
||||||
|
|
||||||
|
#include "AnimationCache.h"
|
||||||
|
|
||||||
|
static int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();
|
||||||
|
|
||||||
|
AnimationCache::AnimationCache(QObject* parent) :
|
||||||
|
ResourceCache(parent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationPointer AnimationCache::getAnimation(const QUrl& url) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
AnimationPointer result;
|
||||||
|
QMetaObject::invokeMethod(this, "getAnimation", Qt::BlockingQueuedConnection,
|
||||||
|
Q_RETURN_ARG(AnimationPointer, result), Q_ARG(const QUrl&, url));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return getResource(url).staticCast<Animation>();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSharedPointer<Resource> AnimationCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
|
||||||
|
bool delayLoad, const void* extra) {
|
||||||
|
return QSharedPointer<Resource>(new Animation(url), &Resource::allReferencesCleared);
|
||||||
|
}
|
||||||
|
|
||||||
|
Animation::Animation(const QUrl& url) :
|
||||||
|
Resource(url) {
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnimationReader : public QRunnable {
|
||||||
|
public:
|
||||||
|
|
||||||
|
AnimationReader(const QWeakPointer<Resource>& animation, QNetworkReply* reply);
|
||||||
|
|
||||||
|
virtual void run();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QWeakPointer<Resource> _animation;
|
||||||
|
QNetworkReply* _reply;
|
||||||
|
};
|
||||||
|
|
||||||
|
AnimationReader::AnimationReader(const QWeakPointer<Resource>& animation, QNetworkReply* reply) :
|
||||||
|
_animation(animation),
|
||||||
|
_reply(reply) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationReader::run() {
|
||||||
|
QSharedPointer<Resource> animation = _animation.toStrongRef();
|
||||||
|
if (!animation.isNull()) {
|
||||||
|
QMetaObject::invokeMethod(animation.data(), "setGeometry",
|
||||||
|
Q_ARG(const FBXGeometry&, readFBX(_reply->readAll(), QVariantHash())));
|
||||||
|
}
|
||||||
|
_reply->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList Animation::getJointNames() const {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QStringList result;
|
||||||
|
QMetaObject::invokeMethod(const_cast<Animation*>(this), "getJointNames", Qt::BlockingQueuedConnection,
|
||||||
|
Q_RETURN_ARG(QStringList, result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
QStringList names;
|
||||||
|
foreach (const FBXJoint& joint, _geometry.joints) {
|
||||||
|
names.append(joint.name);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<FBXAnimationFrame> Animation::getFrames() const {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QVector<FBXAnimationFrame> result;
|
||||||
|
QMetaObject::invokeMethod(const_cast<Animation*>(this), "getFrames", Qt::BlockingQueuedConnection,
|
||||||
|
Q_RETURN_ARG(QVector<FBXAnimationFrame>, result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return _geometry.animationFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::setGeometry(const FBXGeometry& geometry) {
|
||||||
|
_geometry = geometry;
|
||||||
|
finishedLoading(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::downloadFinished(QNetworkReply* reply) {
|
||||||
|
// send the reader off to the thread pool
|
||||||
|
QThreadPool::globalInstance()->start(new AnimationReader(_self, reply));
|
||||||
|
}
|
||||||
|
|
68
libraries/script-engine/src/AnimationCache.h
Normal file
68
libraries/script-engine/src/AnimationCache.h
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
//
|
||||||
|
// AnimationCache.h
|
||||||
|
// libraries/script-engine/src/
|
||||||
|
//
|
||||||
|
// Created by Andrzej Kapolka on 4/14/14.
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_AnimationCache_h
|
||||||
|
#define hifi_AnimationCache_h
|
||||||
|
|
||||||
|
#include <ResourceCache.h>
|
||||||
|
|
||||||
|
#include <FBXReader.h>
|
||||||
|
|
||||||
|
class Animation;
|
||||||
|
|
||||||
|
typedef QSharedPointer<Animation> AnimationPointer;
|
||||||
|
|
||||||
|
/// Scriptable interface for FBX animation loading.
|
||||||
|
class AnimationCache : public ResourceCache {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
AnimationCache(QObject* parent = NULL);
|
||||||
|
|
||||||
|
Q_INVOKABLE AnimationPointer getAnimation(const QString& url) { return getAnimation(QUrl(url)); }
|
||||||
|
|
||||||
|
Q_INVOKABLE AnimationPointer getAnimation(const QUrl& url);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual QSharedPointer<Resource> createResource(const QUrl& url,
|
||||||
|
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra);
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(AnimationPointer)
|
||||||
|
|
||||||
|
/// An animation loaded from the network.
|
||||||
|
class Animation : public Resource {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Animation(const QUrl& url);
|
||||||
|
|
||||||
|
const FBXGeometry& getGeometry() const { return _geometry; }
|
||||||
|
|
||||||
|
Q_INVOKABLE QStringList getJointNames() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE QVector<FBXAnimationFrame> getFrames() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
Q_INVOKABLE void setGeometry(const FBXGeometry& geometry);
|
||||||
|
|
||||||
|
virtual void downloadFinished(QNetworkReply* reply);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
FBXGeometry _geometry;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_AnimationCache_h
|
36
libraries/script-engine/src/AnimationObject.cpp
Normal file
36
libraries/script-engine/src/AnimationObject.cpp
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// AnimationObject.cpp
|
||||||
|
// libraries/script-engine/src/
|
||||||
|
//
|
||||||
|
// Created by Andrzej Kapolka on 4/17/14.
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <QScriptEngine>
|
||||||
|
|
||||||
|
#include "AnimationCache.h"
|
||||||
|
#include "AnimationObject.h"
|
||||||
|
|
||||||
|
QStringList AnimationObject::getJointNames() const {
|
||||||
|
return qscriptvalue_cast<AnimationPointer>(thisObject())->getJointNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<FBXAnimationFrame> AnimationObject::getFrames() const {
|
||||||
|
return qscriptvalue_cast<AnimationPointer>(thisObject())->getFrames();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<glm::quat> AnimationFrameObject::getRotations() const {
|
||||||
|
return qscriptvalue_cast<FBXAnimationFrame>(thisObject()).rotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerAnimationTypes(QScriptEngine* engine) {
|
||||||
|
qScriptRegisterSequenceMetaType<QVector<FBXAnimationFrame> >(engine);
|
||||||
|
engine->setDefaultPrototype(qMetaTypeId<FBXAnimationFrame>(), engine->newQObject(
|
||||||
|
new AnimationFrameObject(), QScriptEngine::ScriptOwnership));
|
||||||
|
engine->setDefaultPrototype(qMetaTypeId<AnimationPointer>(), engine->newQObject(
|
||||||
|
new AnimationObject(), QScriptEngine::ScriptOwnership));
|
||||||
|
}
|
||||||
|
|
47
libraries/script-engine/src/AnimationObject.h
Normal file
47
libraries/script-engine/src/AnimationObject.h
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// AnimationObject.h
|
||||||
|
// libraries/script-engine/src/
|
||||||
|
//
|
||||||
|
// Created by Andrzej Kapolka on 4/17/14.
|
||||||
|
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_AnimationObject_h
|
||||||
|
#define hifi_AnimationObject_h
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QScriptable>
|
||||||
|
|
||||||
|
#include <FBXReader.h>
|
||||||
|
|
||||||
|
class QScriptEngine;
|
||||||
|
|
||||||
|
/// Scriptable wrapper for animation pointers.
|
||||||
|
class AnimationObject : public QObject, protected QScriptable {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QStringList jointNames READ getJointNames)
|
||||||
|
Q_PROPERTY(QVector<FBXAnimationFrame> frames READ getFrames)
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Q_INVOKABLE QStringList getJointNames() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE QVector<FBXAnimationFrame> getFrames() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Scriptable wrapper for animation frames.
|
||||||
|
class AnimationFrameObject : public QObject, protected QScriptable {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QVector<glm::quat> rotations READ getRotations)
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Q_INVOKABLE QVector<glm::quat> getRotations() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
void registerAnimationTypes(QScriptEngine* engine);
|
||||||
|
|
||||||
|
#endif // hifi_AnimationObject_h
|
|
@ -28,6 +28,7 @@
|
||||||
|
|
||||||
#include <Sound.h>
|
#include <Sound.h>
|
||||||
|
|
||||||
|
#include "AnimationObject.h"
|
||||||
#include "MenuItemProperties.h"
|
#include "MenuItemProperties.h"
|
||||||
#include "LocalVoxels.h"
|
#include "LocalVoxels.h"
|
||||||
#include "ScriptEngine.h"
|
#include "ScriptEngine.h"
|
||||||
|
@ -64,7 +65,8 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
|
||||||
_fileNameString(fileNameString),
|
_fileNameString(fileNameString),
|
||||||
_quatLibrary(),
|
_quatLibrary(),
|
||||||
_vec3Library(),
|
_vec3Library(),
|
||||||
_uuidLibrary()
|
_uuidLibrary(),
|
||||||
|
_animationCache(this)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +90,8 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL,
|
||||||
_fileNameString(),
|
_fileNameString(),
|
||||||
_quatLibrary(),
|
_quatLibrary(),
|
||||||
_vec3Library(),
|
_vec3Library(),
|
||||||
_uuidLibrary()
|
_uuidLibrary(),
|
||||||
|
_animationCache(this)
|
||||||
{
|
{
|
||||||
QString scriptURLString = scriptURL.toString();
|
QString scriptURLString = scriptURL.toString();
|
||||||
_fileNameString = scriptURLString;
|
_fileNameString = scriptURLString;
|
||||||
|
@ -153,6 +156,14 @@ void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectNa
|
||||||
registerGlobalObject(objectName, _avatarData);
|
registerGlobalObject(objectName, _avatarData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName) {
|
||||||
|
// remove the old Avatar property, if it exists
|
||||||
|
_engine.globalObject().setProperty(objectName, QScriptValue());
|
||||||
|
|
||||||
|
// give the script engine the new avatar hash map
|
||||||
|
registerGlobalObject(objectName, avatarHashMap);
|
||||||
|
}
|
||||||
|
|
||||||
bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) {
|
bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) {
|
||||||
if (_isRunning) {
|
if (_isRunning) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -180,11 +191,13 @@ void ScriptEngine::init() {
|
||||||
registerVoxelMetaTypes(&_engine);
|
registerVoxelMetaTypes(&_engine);
|
||||||
registerEventTypes(&_engine);
|
registerEventTypes(&_engine);
|
||||||
registerMenuItemProperties(&_engine);
|
registerMenuItemProperties(&_engine);
|
||||||
|
registerAnimationTypes(&_engine);
|
||||||
|
|
||||||
qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue);
|
qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue);
|
||||||
qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue);
|
qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue);
|
||||||
qScriptRegisterSequenceMetaType<QVector<ParticleID> >(&_engine);
|
qScriptRegisterSequenceMetaType<QVector<ParticleID> >(&_engine);
|
||||||
qScriptRegisterSequenceMetaType<QVector<glm::vec2> >(&_engine);
|
qScriptRegisterSequenceMetaType<QVector<glm::vec2> >(&_engine);
|
||||||
|
qScriptRegisterSequenceMetaType<QVector<glm::quat> >(&_engine);
|
||||||
qScriptRegisterSequenceMetaType<QVector<QString> >(&_engine);
|
qScriptRegisterSequenceMetaType<QVector<QString> >(&_engine);
|
||||||
|
|
||||||
QScriptValue soundConstructorValue = _engine.newFunction(soundConstructor);
|
QScriptValue soundConstructorValue = _engine.newFunction(soundConstructor);
|
||||||
|
@ -204,7 +217,8 @@ void ScriptEngine::init() {
|
||||||
registerGlobalObject("Quat", &_quatLibrary);
|
registerGlobalObject("Quat", &_quatLibrary);
|
||||||
registerGlobalObject("Vec3", &_vec3Library);
|
registerGlobalObject("Vec3", &_vec3Library);
|
||||||
registerGlobalObject("Uuid", &_uuidLibrary);
|
registerGlobalObject("Uuid", &_uuidLibrary);
|
||||||
|
registerGlobalObject("AnimationCache", &_animationCache);
|
||||||
|
|
||||||
registerGlobalObject("Voxels", &_voxelsScriptingInterface);
|
registerGlobalObject("Voxels", &_voxelsScriptingInterface);
|
||||||
|
|
||||||
QScriptValue treeScaleValue = _engine.newVariant(QVariant(TREE_SCALE));
|
QScriptValue treeScaleValue = _engine.newVariant(QVariant(TREE_SCALE));
|
||||||
|
|
|
@ -22,7 +22,9 @@
|
||||||
#include <VoxelsScriptingInterface.h>
|
#include <VoxelsScriptingInterface.h>
|
||||||
|
|
||||||
#include <AvatarData.h>
|
#include <AvatarData.h>
|
||||||
|
#include <AvatarHashMap.h>
|
||||||
|
|
||||||
|
#include "AnimationCache.h"
|
||||||
#include "AbstractControllerScriptingInterface.h"
|
#include "AbstractControllerScriptingInterface.h"
|
||||||
#include "Quat.h"
|
#include "Quat.h"
|
||||||
#include "ScriptUUID.h"
|
#include "ScriptUUID.h"
|
||||||
|
@ -62,6 +64,7 @@ public:
|
||||||
bool isAvatar() const { return _isAvatar; }
|
bool isAvatar() const { return _isAvatar; }
|
||||||
|
|
||||||
void setAvatarData(AvatarData* avatarData, const QString& objectName);
|
void setAvatarData(AvatarData* avatarData, const QString& objectName);
|
||||||
|
void setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName);
|
||||||
|
|
||||||
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
||||||
void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; }
|
void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; }
|
||||||
|
@ -125,6 +128,7 @@ private:
|
||||||
Quat _quatLibrary;
|
Quat _quatLibrary;
|
||||||
Vec3 _vec3Library;
|
Vec3 _vec3Library;
|
||||||
ScriptUUID _uuidLibrary;
|
ScriptUUID _uuidLibrary;
|
||||||
|
AnimationCache _animationCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ScriptEngine_h
|
#endif // hifi_ScriptEngine_h
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <QThread>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
|
|
||||||
|
@ -174,6 +175,10 @@ float Resource::getLoadPriority() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Resource::allReferencesCleared() {
|
void Resource::allReferencesCleared() {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "allReferencesCleared");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (_cache) {
|
if (_cache) {
|
||||||
// create and reinsert new shared pointer
|
// create and reinsert new shared pointer
|
||||||
QSharedPointer<Resource> self(this, &Resource::allReferencesCleared);
|
QSharedPointer<Resource> self(this, &Resource::allReferencesCleared);
|
||||||
|
|
|
@ -123,7 +123,7 @@ public:
|
||||||
|
|
||||||
void setCache(ResourceCache* cache) { _cache = cache; }
|
void setCache(ResourceCache* cache) { _cache = cache; }
|
||||||
|
|
||||||
void allReferencesCleared();
|
Q_INVOKABLE void allReferencesCleared();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
|
|
||||||
|
|
|
@ -663,6 +663,110 @@ glm::vec3 safeEulerAngles(const glm::quat& q) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function returns the positive angle (in radians) between two 3D vectors
|
||||||
|
float angleBetween(const glm::vec3& v1, const glm::vec3& v2) {
|
||||||
|
return acosf((glm::dot(v1, v2)) / (glm::length(v1) * glm::length(v2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function return the rotation from the first vector onto the second
|
||||||
|
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) {
|
||||||
|
float angle = angleBetween(v1, v2);
|
||||||
|
if (glm::isnan(angle) || angle < EPSILON) {
|
||||||
|
return glm::quat();
|
||||||
|
}
|
||||||
|
glm::vec3 axis;
|
||||||
|
if (angle > 179.99f * RADIANS_PER_DEGREE) { // 180 degree rotation; must use another axis
|
||||||
|
axis = glm::cross(v1, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||||
|
float axisLength = glm::length(axis);
|
||||||
|
if (axisLength < EPSILON) { // parallel to x; y will work
|
||||||
|
axis = glm::normalize(glm::cross(v1, glm::vec3(0.0f, 1.0f, 0.0f)));
|
||||||
|
} else {
|
||||||
|
axis /= axisLength;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
axis = glm::normalize(glm::cross(v1, v2));
|
||||||
|
}
|
||||||
|
return glm::angleAxis(angle, axis);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 extractTranslation(const glm::mat4& matrix) {
|
||||||
|
return glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTranslation(glm::mat4& matrix, const glm::vec3& translation) {
|
||||||
|
matrix[3][0] = translation.x;
|
||||||
|
matrix[3][1] = translation.y;
|
||||||
|
matrix[3][2] = translation.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) {
|
||||||
|
// uses the iterative polar decomposition algorithm described by Ken Shoemake at
|
||||||
|
// http://www.cs.wisc.edu/graphics/Courses/838-s2002/Papers/polar-decomp.pdf
|
||||||
|
// code adapted from Clyde, https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Matrix4f.java
|
||||||
|
|
||||||
|
// start with the contents of the upper 3x3 portion of the matrix
|
||||||
|
glm::mat3 upper = glm::mat3(matrix);
|
||||||
|
if (!assumeOrthogonal) {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
// store the results of the previous iteration
|
||||||
|
glm::mat3 previous = upper;
|
||||||
|
|
||||||
|
// compute average of the matrix with its inverse transpose
|
||||||
|
float sd00 = previous[1][1] * previous[2][2] - previous[2][1] * previous[1][2];
|
||||||
|
float sd10 = previous[0][1] * previous[2][2] - previous[2][1] * previous[0][2];
|
||||||
|
float sd20 = previous[0][1] * previous[1][2] - previous[1][1] * previous[0][2];
|
||||||
|
float det = previous[0][0] * sd00 + previous[2][0] * sd20 - previous[1][0] * sd10;
|
||||||
|
if (fabs(det) == 0.0f) {
|
||||||
|
// determinant is zero; matrix is not invertible
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
float hrdet = 0.5f / det;
|
||||||
|
upper[0][0] = +sd00 * hrdet + previous[0][0] * 0.5f;
|
||||||
|
upper[1][0] = -sd10 * hrdet + previous[1][0] * 0.5f;
|
||||||
|
upper[2][0] = +sd20 * hrdet + previous[2][0] * 0.5f;
|
||||||
|
|
||||||
|
upper[0][1] = -(previous[1][0] * previous[2][2] - previous[2][0] * previous[1][2]) * hrdet + previous[0][1] * 0.5f;
|
||||||
|
upper[1][1] = +(previous[0][0] * previous[2][2] - previous[2][0] * previous[0][2]) * hrdet + previous[1][1] * 0.5f;
|
||||||
|
upper[2][1] = -(previous[0][0] * previous[1][2] - previous[1][0] * previous[0][2]) * hrdet + previous[2][1] * 0.5f;
|
||||||
|
|
||||||
|
upper[0][2] = +(previous[1][0] * previous[2][1] - previous[2][0] * previous[1][1]) * hrdet + previous[0][2] * 0.5f;
|
||||||
|
upper[1][2] = -(previous[0][0] * previous[2][1] - previous[2][0] * previous[0][1]) * hrdet + previous[1][2] * 0.5f;
|
||||||
|
upper[2][2] = +(previous[0][0] * previous[1][1] - previous[1][0] * previous[0][1]) * hrdet + previous[2][2] * 0.5f;
|
||||||
|
|
||||||
|
// compute the difference; if it's small enough, we're done
|
||||||
|
glm::mat3 diff = upper - previous;
|
||||||
|
if (diff[0][0] * diff[0][0] + diff[1][0] * diff[1][0] + diff[2][0] * diff[2][0] + diff[0][1] * diff[0][1] +
|
||||||
|
diff[1][1] * diff[1][1] + diff[2][1] * diff[2][1] + diff[0][2] * diff[0][2] + diff[1][2] * diff[1][2] +
|
||||||
|
diff[2][2] * diff[2][2] < EPSILON) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now that we have a nice orthogonal matrix, we can extract the rotation quaternion
|
||||||
|
// using the method described in http://en.wikipedia.org/wiki/Rotation_matrix#Conversions
|
||||||
|
float x2 = fabs(1.0f + upper[0][0] - upper[1][1] - upper[2][2]);
|
||||||
|
float y2 = fabs(1.0f - upper[0][0] + upper[1][1] - upper[2][2]);
|
||||||
|
float z2 = fabs(1.0f - upper[0][0] - upper[1][1] + upper[2][2]);
|
||||||
|
float w2 = fabs(1.0f + upper[0][0] + upper[1][1] + upper[2][2]);
|
||||||
|
return glm::normalize(glm::quat(0.5f * sqrtf(w2),
|
||||||
|
0.5f * sqrtf(x2) * (upper[1][2] >= upper[2][1] ? 1.0f : -1.0f),
|
||||||
|
0.5f * sqrtf(y2) * (upper[2][0] >= upper[0][2] ? 1.0f : -1.0f),
|
||||||
|
0.5f * sqrtf(z2) * (upper[0][1] >= upper[1][0] ? 1.0f : -1.0f)));
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 extractScale(const glm::mat4& matrix) {
|
||||||
|
return glm::vec3(glm::length(matrix[0]), glm::length(matrix[1]), glm::length(matrix[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
float extractUniformScale(const glm::mat4& matrix) {
|
||||||
|
return extractUniformScale(extractScale(matrix));
|
||||||
|
}
|
||||||
|
|
||||||
|
float extractUniformScale(const glm::vec3& scale) {
|
||||||
|
return (scale.x + scale.y + scale.z) / 3.0f;
|
||||||
|
}
|
||||||
|
|
||||||
bool isNaN(float value) {
|
bool isNaN(float value) {
|
||||||
return value != value;
|
return value != value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,6 +167,22 @@ int unpackFloatVec3FromSignedTwoByteFixed(const unsigned char* sourceBuffer, glm
|
||||||
/// \return vec3 with euler angles in radians
|
/// \return vec3 with euler angles in radians
|
||||||
glm::vec3 safeEulerAngles(const glm::quat& q);
|
glm::vec3 safeEulerAngles(const glm::quat& q);
|
||||||
|
|
||||||
|
float angleBetween(const glm::vec3& v1, const glm::vec3& v2);
|
||||||
|
|
||||||
|
glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2);
|
||||||
|
|
||||||
|
glm::vec3 extractTranslation(const glm::mat4& matrix);
|
||||||
|
|
||||||
|
void setTranslation(glm::mat4& matrix, const glm::vec3& translation);
|
||||||
|
|
||||||
|
glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal = false);
|
||||||
|
|
||||||
|
glm::vec3 extractScale(const glm::mat4& matrix);
|
||||||
|
|
||||||
|
float extractUniformScale(const glm::mat4& matrix);
|
||||||
|
|
||||||
|
float extractUniformScale(const glm::vec3& scale);
|
||||||
|
|
||||||
/// \return bool are two orientations similar to each other
|
/// \return bool are two orientations similar to each other
|
||||||
const float ORIENTATION_SIMILAR_ENOUGH = 5.0f; // 10 degrees in any direction
|
const float ORIENTATION_SIMILAR_ENOUGH = 5.0f; // 10 degrees in any direction
|
||||||
bool isSimilarOrientation(const glm::quat& orientionA, const glm::quat& orientionB,
|
bool isSimilarOrientation(const glm::quat& orientionA, const glm::quat& orientionB,
|
||||||
|
|
Loading…
Reference in a new issue