mirror of
https://github.com/overte-org/overte.git
synced 2025-04-08 21:33:00 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi
This commit is contained in:
commit
dda99bfea6
386 changed files with 10959 additions and 5994 deletions
|
@ -16,6 +16,7 @@
|
|||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include <AssetClient.h>
|
||||
#include <AvatarHashMap.h>
|
||||
#include <AudioInjectorManager.h>
|
||||
#include <AssetClient.h>
|
||||
|
@ -43,8 +44,8 @@
|
|||
|
||||
static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10;
|
||||
|
||||
Agent::Agent(NLPacket& packet) :
|
||||
ThreadedAssignment(packet),
|
||||
Agent::Agent(ReceivedMessage& message) :
|
||||
ThreadedAssignment(message),
|
||||
_entityEditSender(),
|
||||
_receivedAudioStream(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES,
|
||||
InboundAudioStream::Settings(0, false, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, false,
|
||||
|
@ -79,46 +80,46 @@ Agent::Agent(NLPacket& packet) :
|
|||
packetReceiver.registerListener(PacketType::Jurisdiction, this, "handleJurisdictionPacket");
|
||||
}
|
||||
|
||||
void Agent::handleOctreePacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
|
||||
auto packetType = packet->getType();
|
||||
void Agent::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
auto packetType = message->getType();
|
||||
|
||||
if (packetType == PacketType::OctreeStats) {
|
||||
|
||||
int statsMessageLength = OctreeHeadlessViewer::parseOctreeStats(packet, senderNode);
|
||||
if (packet->getPayloadSize() > statsMessageLength) {
|
||||
int statsMessageLength = OctreeHeadlessViewer::parseOctreeStats(message, senderNode);
|
||||
if (message->getSize() > statsMessageLength) {
|
||||
// pull out the piggybacked packet and create a new QSharedPointer<NLPacket> for it
|
||||
int piggyBackedSizeWithHeader = packet->getPayloadSize() - statsMessageLength;
|
||||
|
||||
int piggyBackedSizeWithHeader = message->getSize() - statsMessageLength;
|
||||
|
||||
auto buffer = std::unique_ptr<char[]>(new char[piggyBackedSizeWithHeader]);
|
||||
memcpy(buffer.get(), packet->getPayload() + statsMessageLength, piggyBackedSizeWithHeader);
|
||||
memcpy(buffer.get(), message->getRawMessage() + statsMessageLength, piggyBackedSizeWithHeader);
|
||||
|
||||
auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, packet->getSenderSockAddr());
|
||||
packet = QSharedPointer<NLPacket>(newPacket.release());
|
||||
auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, message->getSenderSockAddr());
|
||||
message = QSharedPointer<ReceivedMessage>::create(*newPacket);
|
||||
} else {
|
||||
return; // bail since no piggyback data
|
||||
}
|
||||
|
||||
packetType = packet->getType();
|
||||
packetType = message->getType();
|
||||
} // fall through to piggyback message
|
||||
|
||||
if (packetType == PacketType::EntityData || packetType == PacketType::EntityErase) {
|
||||
_entityViewer.processDatagram(*packet, senderNode);
|
||||
_entityViewer.processDatagram(*message, senderNode);
|
||||
}
|
||||
}
|
||||
|
||||
void Agent::handleJurisdictionPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
|
||||
void Agent::handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
NodeType_t nodeType;
|
||||
packet->peekPrimitive(&nodeType);
|
||||
message->peekPrimitive(&nodeType);
|
||||
|
||||
// PacketType_JURISDICTION, first byte is the node type...
|
||||
if (nodeType == NodeType::EntityServer) {
|
||||
DependencyManager::get<EntityScriptingInterface>()->getJurisdictionListener()->
|
||||
queueReceivedPacket(packet, senderNode);
|
||||
queueReceivedPacket(message, senderNode);
|
||||
}
|
||||
}
|
||||
|
||||
void Agent::handleAudioPacket(QSharedPointer<NLPacket> packet) {
|
||||
_receivedAudioStream.parseData(*packet);
|
||||
void Agent::handleAudioPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
_receivedAudioStream.parseData(*message);
|
||||
|
||||
_lastReceivedAudioLoudness = _receivedAudioStream.getNextOutputFrameLoudness();
|
||||
|
||||
|
@ -131,6 +132,7 @@ void Agent::run() {
|
|||
|
||||
// make sure we request our script once the agent connects to the domain
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain, this, &Agent::requestScript);
|
||||
|
||||
ThreadedAssignment::commonInit(AGENT_LOGGING_NAME, NodeType::Agent);
|
||||
|
@ -223,6 +225,7 @@ void Agent::executeScript() {
|
|||
// call model URL setters with empty URLs so our avatar, if user, will have the default models
|
||||
scriptedAvatar->setFaceModelURL(QUrl());
|
||||
scriptedAvatar->setSkeletonModelURL(QUrl());
|
||||
|
||||
// give this AvatarData object to the script engine
|
||||
_scriptEngine->registerGlobalObject("Avatar", scriptedAvatar.data());
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class Agent : public ThreadedAssignment {
|
|||
Q_PROPERTY(QUuid sessionUUID READ getSessionUUID)
|
||||
|
||||
public:
|
||||
Agent(NLPacket& packet);
|
||||
Agent(ReceivedMessage& message);
|
||||
|
||||
void setIsAvatar(bool isAvatar);
|
||||
bool isAvatar() const { return _isAvatar; }
|
||||
|
@ -63,9 +63,10 @@ private slots:
|
|||
void scriptRequestFinished();
|
||||
void executeScript();
|
||||
|
||||
void handleAudioPacket(QSharedPointer<NLPacket> packet);
|
||||
void handleOctreePacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
void handleJurisdictionPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
void handleAudioPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
||||
void processAgentAvatarAndAudio(float deltaTime);
|
||||
|
||||
private:
|
||||
|
|
|
@ -225,11 +225,11 @@ void AssignmentClient::sendAssignmentRequest() {
|
|||
}
|
||||
}
|
||||
|
||||
void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<NLPacket> packet) {
|
||||
void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
qDebug() << "Received a PacketType::CreateAssignment - attempting to unpack.";
|
||||
|
||||
// construct the deployed assignment from the packet data
|
||||
_currentAssignment = AssignmentFactory::unpackAssignment(*packet);
|
||||
_currentAssignment = AssignmentFactory::unpackAssignment(*message);
|
||||
|
||||
if (_currentAssignment && !_isAssigned) {
|
||||
qDebug() << "Received an assignment -" << *_currentAssignment;
|
||||
|
@ -239,7 +239,7 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<NLPacket> pac
|
|||
|
||||
// switch our DomainHandler hostname and port to whoever sent us the assignment
|
||||
|
||||
nodeList->getDomainHandler().setSockAddr(packet->getSenderSockAddr(), _assignmentServerHostname);
|
||||
nodeList->getDomainHandler().setSockAddr(message->getSenderSockAddr(), _assignmentServerHostname);
|
||||
nodeList->getDomainHandler().setAssignmentUUID(_currentAssignment->getUUID());
|
||||
|
||||
qDebug() << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString();
|
||||
|
@ -274,8 +274,8 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<NLPacket> pac
|
|||
}
|
||||
}
|
||||
|
||||
void AssignmentClient::handleStopNodePacket(QSharedPointer<NLPacket> packet) {
|
||||
const HifiSockAddr& senderSockAddr = packet->getSenderSockAddr();
|
||||
void AssignmentClient::handleStopNodePacket(QSharedPointer<ReceivedMessage> message) {
|
||||
const HifiSockAddr& senderSockAddr = message->getSenderSockAddr();
|
||||
|
||||
if (senderSockAddr.getAddress() == QHostAddress::LocalHost ||
|
||||
senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) {
|
||||
|
|
|
@ -38,8 +38,8 @@ public slots:
|
|||
void aboutToQuit();
|
||||
|
||||
private slots:
|
||||
void handleCreateAssignmentPacket(QSharedPointer<NLPacket> packet);
|
||||
void handleStopNodePacket(QSharedPointer<NLPacket> packet);
|
||||
void handleCreateAssignmentPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void handleStopNodePacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
private:
|
||||
void setUpStatusToMonitor();
|
||||
|
|
|
@ -207,14 +207,14 @@ void AssignmentClientMonitor::checkSpares() {
|
|||
}
|
||||
}
|
||||
|
||||
void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer<NLPacket> packet) {
|
||||
void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
// read out the sender ID
|
||||
QUuid senderID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||
QUuid senderID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
SharedNodePointer matchingNode = nodeList->nodeWithUUID(senderID);
|
||||
const HifiSockAddr& senderSockAddr = packet->getSenderSockAddr();
|
||||
const HifiSockAddr& senderSockAddr = message->getSenderSockAddr();
|
||||
|
||||
AssignmentClientChildData* childData = nullptr;
|
||||
|
||||
|
@ -251,7 +251,7 @@ void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer<NLPacket> p
|
|||
|
||||
// get child's assignment type out of the packet
|
||||
quint8 assignmentType;
|
||||
packet->readPrimitive(&assignmentType);
|
||||
message->readPrimitive(&assignmentType);
|
||||
|
||||
childData->setChildType((Assignment::Type) assignmentType);
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ public:
|
|||
private slots:
|
||||
void checkSpares();
|
||||
void childProcessFinished();
|
||||
void handleChildStatusPacket(QSharedPointer<NLPacket> packet);
|
||||
void handleChildStatusPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
public slots:
|
||||
void aboutToQuit();
|
||||
|
|
|
@ -19,26 +19,26 @@
|
|||
#include "assets/AssetServer.h"
|
||||
#include "messages/MessagesMixer.h"
|
||||
|
||||
ThreadedAssignment* AssignmentFactory::unpackAssignment(NLPacket& packet) {
|
||||
ThreadedAssignment* AssignmentFactory::unpackAssignment(ReceivedMessage& message) {
|
||||
|
||||
quint8 packedType;
|
||||
packet.peekPrimitive(&packedType);
|
||||
message.peekPrimitive(&packedType);
|
||||
|
||||
Assignment::Type unpackedType = (Assignment::Type) packedType;
|
||||
|
||||
switch (unpackedType) {
|
||||
case Assignment::AudioMixerType:
|
||||
return new AudioMixer(packet);
|
||||
return new AudioMixer(message);
|
||||
case Assignment::AvatarMixerType:
|
||||
return new AvatarMixer(packet);
|
||||
return new AvatarMixer(message);
|
||||
case Assignment::AgentType:
|
||||
return new Agent(packet);
|
||||
return new Agent(message);
|
||||
case Assignment::EntityServerType:
|
||||
return new EntityServer(packet);
|
||||
return new EntityServer(message);
|
||||
case Assignment::AssetServerType:
|
||||
return new AssetServer(packet);
|
||||
return new AssetServer(message);
|
||||
case Assignment::MessagesMixerType:
|
||||
return new MessagesMixer(packet);
|
||||
return new MessagesMixer(message);
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
class AssignmentFactory {
|
||||
public:
|
||||
static ThreadedAssignment* unpackAssignment(NLPacket& packet);
|
||||
static ThreadedAssignment* unpackAssignment(ReceivedMessage& message);
|
||||
};
|
||||
|
||||
#endif // hifi_AssignmentFactory_h
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
|
||||
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
||||
|
||||
AssetServer::AssetServer(NLPacket& packet) :
|
||||
ThreadedAssignment(packet),
|
||||
AssetServer::AssetServer(ReceivedMessage& message) :
|
||||
ThreadedAssignment(message),
|
||||
_taskPool(this)
|
||||
{
|
||||
|
||||
|
@ -40,7 +40,7 @@ AssetServer::AssetServer(NLPacket& packet) :
|
|||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet");
|
||||
packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo");
|
||||
packetReceiver.registerMessageListener(PacketType::AssetUpload, this, "handleAssetUpload");
|
||||
packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload");
|
||||
}
|
||||
|
||||
void AssetServer::run() {
|
||||
|
@ -84,20 +84,20 @@ void AssetServer::run() {
|
|||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleAssetGetInfo(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
|
||||
void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
QByteArray assetHash;
|
||||
MessageID messageID;
|
||||
uint8_t extensionLength;
|
||||
|
||||
if (packet->getPayloadSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID) + sizeof(extensionLength))) {
|
||||
if (message->getSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID) + sizeof(extensionLength))) {
|
||||
qDebug() << "ERROR bad file request";
|
||||
return;
|
||||
}
|
||||
|
||||
packet->readPrimitive(&messageID);
|
||||
assetHash = packet->readWithoutCopy(SHA256_HASH_LENGTH);
|
||||
packet->readPrimitive(&extensionLength);
|
||||
QByteArray extension = packet->read(extensionLength);
|
||||
message->readPrimitive(&messageID);
|
||||
assetHash = message->readWithoutCopy(SHA256_HASH_LENGTH);
|
||||
message->readPrimitive(&extensionLength);
|
||||
QByteArray extension = message->read(extensionLength);
|
||||
|
||||
auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply);
|
||||
|
||||
|
@ -122,26 +122,26 @@ void AssetServer::handleAssetGetInfo(QSharedPointer<NLPacket> packet, SharedNode
|
|||
nodeList->sendPacket(std::move(replyPacket), *senderNode);
|
||||
}
|
||||
|
||||
void AssetServer::handleAssetGet(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
|
||||
void AssetServer::handleAssetGet(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
|
||||
auto minSize = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(uint8_t) + sizeof(DataOffset) + sizeof(DataOffset));
|
||||
|
||||
if (packet->getPayloadSize() < minSize) {
|
||||
if (message->getSize() < minSize) {
|
||||
qDebug() << "ERROR bad file request";
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue task
|
||||
auto task = new SendAssetTask(packet, senderNode, _resourcesDirectory);
|
||||
auto task = new SendAssetTask(message, senderNode, _resourcesDirectory);
|
||||
_taskPool.start(task);
|
||||
}
|
||||
|
||||
void AssetServer::handleAssetUpload(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
|
||||
void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
|
||||
if (senderNode->getCanRez()) {
|
||||
qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID());
|
||||
|
||||
auto task = new UploadAssetTask(packetList, senderNode, _resourcesDirectory);
|
||||
auto task = new UploadAssetTask(message, senderNode, _resourcesDirectory);
|
||||
_taskPool.start(task);
|
||||
} else {
|
||||
// this is a node the domain told us is not allowed to rez entities
|
||||
|
@ -151,7 +151,7 @@ void AssetServer::handleAssetUpload(QSharedPointer<NLPacketList> packetList, Sha
|
|||
auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetServerError));
|
||||
|
||||
MessageID messageID;
|
||||
packetList->readPrimitive(&messageID);
|
||||
message->readPrimitive(&messageID);
|
||||
|
||||
// write the message ID and a permission denied error
|
||||
permissionErrorPacket->writePrimitive(messageID);
|
||||
|
|
|
@ -18,19 +18,20 @@
|
|||
#include <QThreadPool>
|
||||
|
||||
#include "AssetUtils.h"
|
||||
#include "ReceivedMessage.h"
|
||||
|
||||
class AssetServer : public ThreadedAssignment {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AssetServer(NLPacket& packet);
|
||||
AssetServer(ReceivedMessage& message);
|
||||
|
||||
public slots:
|
||||
void run();
|
||||
|
||||
private slots:
|
||||
void handleAssetGetInfo(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
void handleAssetGet(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
void handleAssetUpload(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode);
|
||||
void handleAssetGetInfo(QSharedPointer<ReceivedMessage> packet, SharedNodePointer senderNode);
|
||||
void handleAssetGet(QSharedPointer<ReceivedMessage> packet, SharedNodePointer senderNode);
|
||||
void handleAssetUpload(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer senderNode);
|
||||
|
||||
void sendStatsPacket();
|
||||
|
||||
|
|
|
@ -22,9 +22,9 @@
|
|||
|
||||
#include "AssetUtils.h"
|
||||
|
||||
SendAssetTask::SendAssetTask(QSharedPointer<NLPacket> packet, const SharedNodePointer& sendToNode, const QDir& resourcesDir) :
|
||||
SendAssetTask::SendAssetTask(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& sendToNode, const QDir& resourcesDir) :
|
||||
QRunnable(),
|
||||
_packet(packet),
|
||||
_message(message),
|
||||
_senderNode(sendToNode),
|
||||
_resourcesDir(resourcesDir)
|
||||
{
|
||||
|
@ -36,16 +36,16 @@ void SendAssetTask::run() {
|
|||
uint8_t extensionLength;
|
||||
DataOffset start, end;
|
||||
|
||||
_packet->readPrimitive(&messageID);
|
||||
QByteArray assetHash = _packet->read(SHA256_HASH_LENGTH);
|
||||
_packet->readPrimitive(&extensionLength);
|
||||
QByteArray extension = _packet->read(extensionLength);
|
||||
_message->readPrimitive(&messageID);
|
||||
QByteArray assetHash = _message->read(SHA256_HASH_LENGTH);
|
||||
_message->readPrimitive(&extensionLength);
|
||||
QByteArray extension = _message->read(extensionLength);
|
||||
|
||||
// `start` and `end` indicate the range of data to retrieve for the asset identified by `assetHash`.
|
||||
// `start` is inclusive, `end` is exclusive. Requesting `start` = 1, `end` = 10 will retrieve 9 bytes of data,
|
||||
// starting at index 1.
|
||||
_packet->readPrimitive(&start);
|
||||
_packet->readPrimitive(&end);
|
||||
_message->readPrimitive(&start);
|
||||
_message->readPrimitive(&end);
|
||||
|
||||
QString hexHash = assetHash.toHex();
|
||||
|
||||
|
|
|
@ -25,12 +25,12 @@ class NLPacket;
|
|||
|
||||
class SendAssetTask : public QRunnable {
|
||||
public:
|
||||
SendAssetTask(QSharedPointer<NLPacket> packet, const SharedNodePointer& sendToNode, const QDir& resourcesDir);
|
||||
SendAssetTask(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& sendToNode, const QDir& resourcesDir);
|
||||
|
||||
void run();
|
||||
|
||||
private:
|
||||
QSharedPointer<NLPacket> _packet;
|
||||
QSharedPointer<ReceivedMessage> _message;
|
||||
SharedNodePointer _senderNode;
|
||||
QDir _resourcesDir;
|
||||
};
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
#include <NLPacketList.h>
|
||||
|
||||
|
||||
UploadAssetTask::UploadAssetTask(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode,
|
||||
UploadAssetTask::UploadAssetTask(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode,
|
||||
const QDir& resourcesDir) :
|
||||
_packetList(packetList),
|
||||
_receivedMessage(receivedMessage),
|
||||
_senderNode(senderNode),
|
||||
_resourcesDir(resourcesDir)
|
||||
{
|
||||
|
@ -29,7 +29,7 @@ UploadAssetTask::UploadAssetTask(QSharedPointer<NLPacketList> packetList, Shared
|
|||
}
|
||||
|
||||
void UploadAssetTask::run() {
|
||||
auto data = _packetList->getMessage();
|
||||
auto data = _receivedMessage->getMessage();
|
||||
|
||||
QBuffer buffer { &data };
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
|
|
|
@ -19,17 +19,19 @@
|
|||
#include <QtCore/QRunnable>
|
||||
#include <QtCore/QSharedPointer>
|
||||
|
||||
#include "ReceivedMessage.h"
|
||||
|
||||
class NLPacketList;
|
||||
class Node;
|
||||
|
||||
class UploadAssetTask : public QRunnable {
|
||||
public:
|
||||
UploadAssetTask(QSharedPointer<NLPacketList> packetList, QSharedPointer<Node> senderNode, const QDir& resourcesDir);
|
||||
UploadAssetTask(QSharedPointer<ReceivedMessage> message, QSharedPointer<Node> senderNode, const QDir& resourcesDir);
|
||||
|
||||
void run();
|
||||
|
||||
private:
|
||||
QSharedPointer<NLPacketList> _packetList;
|
||||
QSharedPointer<ReceivedMessage> _receivedMessage;
|
||||
QSharedPointer<Node> _senderNode;
|
||||
QDir _resourcesDir;
|
||||
};
|
||||
|
|
|
@ -75,8 +75,8 @@ bool AudioMixer::shouldMute(float quietestFrame) {
|
|||
return (quietestFrame > _noiseMutingThreshold);
|
||||
}
|
||||
|
||||
AudioMixer::AudioMixer(NLPacket& packet) :
|
||||
ThreadedAssignment(packet),
|
||||
AudioMixer::AudioMixer(ReceivedMessage& message) :
|
||||
ThreadedAssignment(message),
|
||||
_trailingSleepRatio(1.0f),
|
||||
_minAudibilityThreshold(LOUDNESS_TO_DISTANCE_RATIO / 2.0f),
|
||||
_performanceThrottlingRatio(0.0f),
|
||||
|
@ -438,7 +438,6 @@ int AudioMixer::prepareMixForListeningNode(Node* node) {
|
|||
AudioMixerClientData* listenerNodeData = static_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
|
||||
// zero out the client mix for this node
|
||||
memset(_preMixSamples, 0, sizeof(_preMixSamples));
|
||||
memset(_mixSamples, 0, sizeof(_mixSamples));
|
||||
|
||||
// loop through all other nodes that have sufficient audio to mix
|
||||
|
@ -459,6 +458,9 @@ int AudioMixer::prepareMixForListeningNode(Node* node) {
|
|||
if (otherNodeStream->getType() == PositionalAudioStream::Microphone) {
|
||||
streamUUID = otherNode->getUUID();
|
||||
}
|
||||
|
||||
// clear out the pre-mix samples before filling it up with this source
|
||||
memset(_preMixSamples, 0, sizeof(_preMixSamples));
|
||||
|
||||
if (*otherNode != *node || otherNodeStream->shouldLoopbackForNode()) {
|
||||
streamsMixed += addStreamToMixForListeningNodeWithStream(listenerNodeData, streamUUID,
|
||||
|
@ -542,17 +544,17 @@ void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) {
|
|||
}
|
||||
}
|
||||
|
||||
void AudioMixer::handleNodeAudioPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) {
|
||||
DependencyManager::get<NodeList>()->updateNodeWithDataFromPacket(packet, sendingNode);
|
||||
void AudioMixer::handleNodeAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
DependencyManager::get<NodeList>()->updateNodeWithDataFromPacket(message, sendingNode);
|
||||
}
|
||||
|
||||
void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) {
|
||||
void AudioMixer::handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
if (sendingNode->getCanAdjustLocks()) {
|
||||
auto newPacket = NLPacket::create(PacketType::MuteEnvironment, packet->getPayloadSize());
|
||||
auto newPacket = NLPacket::create(PacketType::MuteEnvironment, message->getSize());
|
||||
// Copy payload
|
||||
newPacket->write(packet->getPayload(), packet->getPayloadSize());
|
||||
newPacket->write(message->getRawMessage(), message->getSize());
|
||||
|
||||
nodeList->eachNode([&](const SharedNodePointer& node){
|
||||
if (node->getType() == NodeType::Agent && node->getActiveSocket() &&
|
||||
|
|
|
@ -28,7 +28,7 @@ const int READ_DATAGRAMS_STATS_WINDOW_SECONDS = 30;
|
|||
class AudioMixer : public ThreadedAssignment {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioMixer(NLPacket& packet);
|
||||
AudioMixer(ReceivedMessage& message);
|
||||
|
||||
void deleteLater() { qDebug() << "DELETE LATER CALLED?"; QObject::deleteLater(); }
|
||||
public slots:
|
||||
|
@ -41,8 +41,8 @@ public slots:
|
|||
|
||||
private slots:
|
||||
void broadcastMixes();
|
||||
void handleNodeAudioPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode);
|
||||
void handleMuteEnvironmentPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode);
|
||||
void handleNodeAudioPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
|
||||
private:
|
||||
void domainSettingsRequestComplete();
|
||||
|
|
|
@ -49,18 +49,18 @@ AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() const {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
int AudioMixerClientData::parseData(NLPacket& packet) {
|
||||
PacketType packetType = packet.getType();
|
||||
int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
||||
PacketType packetType = message.getType();
|
||||
|
||||
if (packetType == PacketType::AudioStreamStats) {
|
||||
|
||||
// skip over header, appendFlag, and num stats packed
|
||||
packet.seek(sizeof(quint8) + sizeof(quint16));
|
||||
message.seek(sizeof(quint8) + sizeof(quint16));
|
||||
|
||||
// read the downstream audio stream stats
|
||||
packet.readPrimitive(&_downstreamAudioStreamStats);
|
||||
message.readPrimitive(&_downstreamAudioStreamStats);
|
||||
|
||||
return packet.pos();
|
||||
return message.getPosition();
|
||||
|
||||
} else {
|
||||
PositionalAudioStream* matchingStream = NULL;
|
||||
|
@ -74,10 +74,10 @@ int AudioMixerClientData::parseData(NLPacket& packet) {
|
|||
// we don't have a mic stream yet, so add it
|
||||
|
||||
// read the channel flag to see if our stream is stereo or not
|
||||
packet.seek(sizeof(quint16));
|
||||
message.seek(sizeof(quint16));
|
||||
|
||||
quint8 channelFlag;
|
||||
packet.readPrimitive(&channelFlag);
|
||||
message.readPrimitive(&channelFlag);
|
||||
|
||||
bool isStereo = channelFlag == 1;
|
||||
|
||||
|
@ -89,11 +89,11 @@ int AudioMixerClientData::parseData(NLPacket& packet) {
|
|||
// this is injected audio
|
||||
|
||||
// grab the stream identifier for this injected audio
|
||||
packet.seek(sizeof(quint16));
|
||||
QUuid streamIdentifier = QUuid::fromRfc4122(packet.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||
message.seek(sizeof(quint16));
|
||||
QUuid streamIdentifier = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||
|
||||
bool isStereo;
|
||||
packet.readPrimitive(&isStereo);
|
||||
message.readPrimitive(&isStereo);
|
||||
|
||||
if (!_audioStreams.contains(streamIdentifier)) {
|
||||
// we don't have this injected stream yet, so add it
|
||||
|
@ -105,9 +105,9 @@ int AudioMixerClientData::parseData(NLPacket& packet) {
|
|||
}
|
||||
|
||||
// seek to the beginning of the packet so that the next reader is in the right spot
|
||||
packet.seek(0);
|
||||
message.seek(0);
|
||||
|
||||
return matchingStream->parseData(packet);
|
||||
return matchingStream->parseData(message);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ public:
|
|||
const QHash<QUuid, PositionalAudioStream*>& getAudioStreams() const { return _audioStreams; }
|
||||
AvatarAudioStream* getAvatarAudioStream() const;
|
||||
|
||||
int parseData(NLPacket& packet);
|
||||
int parseData(ReceivedMessage& message);
|
||||
|
||||
void checkBuffersBeforeFrameSend();
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
|
|||
const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 60;
|
||||
const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / (float) AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) * 1000;
|
||||
|
||||
AvatarMixer::AvatarMixer(NLPacket& packet) :
|
||||
ThreadedAssignment(packet),
|
||||
AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
||||
ThreadedAssignment(message),
|
||||
_broadcastThread(),
|
||||
_lastFrameTimestamp(QDateTime::currentMSecsSinceEpoch()),
|
||||
_trailingSleepRatio(1.0f),
|
||||
|
@ -157,7 +157,7 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
++_sumListeners;
|
||||
|
||||
AvatarData& avatar = nodeData->getAvatar();
|
||||
glm::vec3 myPosition = avatar.getPosition();
|
||||
glm::vec3 myPosition = avatar.getClientGlobalPosition();
|
||||
|
||||
// reset the internal state for correct random number distribution
|
||||
distribution.reset();
|
||||
|
@ -290,7 +290,7 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
|
||||
// The full rate distance is the distance at which EVERY update will be sent for this avatar
|
||||
// at twice the full rate distance, there will be a 50% chance of sending this avatar's update
|
||||
glm::vec3 otherPosition = otherAvatar.getPosition();
|
||||
glm::vec3 otherPosition = otherAvatar.getClientGlobalPosition();
|
||||
float distanceToAvatar = glm::length(myPosition - otherPosition);
|
||||
|
||||
// potentially update the max full rate distance for this frame
|
||||
|
@ -424,19 +424,19 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::handleAvatarDataPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
|
||||
void AvatarMixer::handleAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->updateNodeWithDataFromPacket(packet, senderNode);
|
||||
nodeList->updateNodeWithDataFromPacket(message, senderNode);
|
||||
}
|
||||
|
||||
void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
|
||||
void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
if (senderNode->getLinkedData()) {
|
||||
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
|
||||
if (nodeData != nullptr) {
|
||||
AvatarData& avatar = nodeData->getAvatar();
|
||||
|
||||
// parse the identity packet and update the change timestamp if appropriate
|
||||
if (avatar.hasIdentityChangedAfterParsing(*packet)) {
|
||||
if (avatar.hasIdentityChangedAfterParsing(message->getMessage())) {
|
||||
QMutexLocker nodeDataLocker(&nodeData->getMutex());
|
||||
nodeData->setIdentityChangeTimestamp(QDateTime::currentMSecsSinceEpoch());
|
||||
}
|
||||
|
@ -444,13 +444,13 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<NLPacket> packet, Sh
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::handleAvatarBillboardPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
|
||||
void AvatarMixer::handleAvatarBillboardPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
|
||||
if (nodeData) {
|
||||
AvatarData& avatar = nodeData->getAvatar();
|
||||
|
||||
// parse the billboard packet and update the change timestamp if appropriate
|
||||
if (avatar.hasBillboardChangedAfterParsing(*packet)) {
|
||||
if (avatar.hasBillboardChangedAfterParsing(message->getMessage())) {
|
||||
QMutexLocker nodeDataLocker(&nodeData->getMutex());
|
||||
nodeData->setBillboardChangeTimestamp(QDateTime::currentMSecsSinceEpoch());
|
||||
}
|
||||
|
@ -458,8 +458,8 @@ void AvatarMixer::handleAvatarBillboardPacket(QSharedPointer<NLPacket> packet, S
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::handleKillAvatarPacket(QSharedPointer<NLPacket> packet) {
|
||||
DependencyManager::get<NodeList>()->processKillNode(*packet);
|
||||
void AvatarMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
DependencyManager::get<NodeList>()->processKillNode(*message);
|
||||
}
|
||||
|
||||
void AvatarMixer::sendStatsPacket() {
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
class AvatarMixer : public ThreadedAssignment {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AvatarMixer(NLPacket& packet);
|
||||
AvatarMixer(ReceivedMessage& message);
|
||||
~AvatarMixer();
|
||||
public slots:
|
||||
/// runs the avatar mixer
|
||||
|
@ -32,10 +32,10 @@ public slots:
|
|||
void sendStatsPacket();
|
||||
|
||||
private slots:
|
||||
void handleAvatarDataPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
void handleAvatarIdentityPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
void handleAvatarBillboardPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
void handleKillAvatarPacket(QSharedPointer<NLPacket> packet);
|
||||
void handleAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleAvatarBillboardPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void domainSettingsRequestComplete();
|
||||
|
||||
private:
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
|
||||
#include "AvatarMixerClientData.h"
|
||||
|
||||
int AvatarMixerClientData::parseData(NLPacket& packet) {
|
||||
int AvatarMixerClientData::parseData(ReceivedMessage& message) {
|
||||
// pull the sequence number from the data first
|
||||
packet.readPrimitive(&_lastReceivedSequenceNumber);
|
||||
|
||||
message.readPrimitive(&_lastReceivedSequenceNumber);
|
||||
|
||||
// compute the offset to the data payload
|
||||
return _avatar.parseDataFromBuffer(packet.readWithoutCopy(packet.bytesLeftToRead()));
|
||||
return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()));
|
||||
}
|
||||
|
||||
bool AvatarMixerClientData::checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid) {
|
||||
|
@ -40,7 +40,7 @@ uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& node
|
|||
}
|
||||
|
||||
void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {
|
||||
jsonObject["display_name"] = _avatar.getDisplayName();
|
||||
jsonObject["display_name"] = _avatar->getDisplayName();
|
||||
jsonObject["full_rate_distance"] = _fullRateDistance;
|
||||
jsonObject["max_av_distance"] = _maxAvatarDistance;
|
||||
jsonObject["num_avs_sent_last_frame"] = _numAvatarsSentLastFrame;
|
||||
|
@ -49,7 +49,7 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {
|
|||
jsonObject["total_num_out_of_order_sends"] = _numOutOfOrderSends;
|
||||
|
||||
jsonObject[OUTBOUND_AVATAR_DATA_STATS_KEY] = getOutboundAvatarDataKbps();
|
||||
jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar.getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT;
|
||||
jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar->getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT;
|
||||
|
||||
jsonObject["av_data_receive_rate"] = _avatar.getReceiveRate();
|
||||
jsonObject["av_data_receive_rate"] = _avatar->getReceiveRate();
|
||||
}
|
||||
|
|
|
@ -33,8 +33,8 @@ const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps";
|
|||
class AvatarMixerClientData : public NodeData {
|
||||
Q_OBJECT
|
||||
public:
|
||||
int parseData(NLPacket& packet);
|
||||
AvatarData& getAvatar() { return _avatar; }
|
||||
int parseData(ReceivedMessage& message) override;
|
||||
AvatarData& getAvatar() { return *_avatar; }
|
||||
|
||||
bool checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid);
|
||||
|
||||
|
@ -80,7 +80,7 @@ public:
|
|||
|
||||
void loadJSONStats(QJsonObject& jsonObject) const;
|
||||
private:
|
||||
AvatarData _avatar;
|
||||
AvatarSharedPointer _avatar { new AvatarData() };
|
||||
|
||||
uint16_t _lastReceivedSequenceNumber { 0 };
|
||||
std::unordered_map<QUuid, uint16_t> _lastBroadcastSequenceNumbers;
|
||||
|
|
19
assignment-client/src/entities/AssignmentParentFinder.cpp
Normal file
19
assignment-client/src/entities/AssignmentParentFinder.cpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// AssignmentParentFinder.cpp
|
||||
// assignment-client/src/entities
|
||||
//
|
||||
// Created by Seth Alves on 2015-10-22
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AssignmentParentFinder.h"
|
||||
|
||||
SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID) const {
|
||||
SpatiallyNestableWeakPointer parent;
|
||||
// search entities
|
||||
parent = _tree->findEntityByEntityItemID(parentID);
|
||||
return parent;
|
||||
}
|
34
assignment-client/src/entities/AssignmentParentFinder.h
Normal file
34
assignment-client/src/entities/AssignmentParentFinder.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// AssignmentParentFinder.h
|
||||
// interface/src/entities
|
||||
//
|
||||
// Created by Seth Alves on 2015-10-21
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AssignmentParentFinder_h
|
||||
#define hifi_AssignmentParentFinder_h
|
||||
|
||||
#include <memory>
|
||||
#include <QUuid>
|
||||
|
||||
#include <EntityTree.h>
|
||||
#include <SpatialParentFinder.h>
|
||||
|
||||
// This interface is used to turn a QUuid into a pointer to a "parent" -- something that children can
|
||||
// be spatially relative to. At this point, this means either an EntityItem or an Avatar.
|
||||
|
||||
class AssignmentParentFinder : public SpatialParentFinder {
|
||||
public:
|
||||
AssignmentParentFinder(EntityTreePointer tree) : _tree(tree) { }
|
||||
virtual ~AssignmentParentFinder() { }
|
||||
virtual SpatiallyNestableWeakPointer find(QUuid parentID) const;
|
||||
|
||||
protected:
|
||||
EntityTreePointer _tree;
|
||||
};
|
||||
|
||||
#endif // hifi_AssignmentParentFinder_h
|
|
@ -16,13 +16,14 @@
|
|||
#include "EntityServer.h"
|
||||
#include "EntityServerConsts.h"
|
||||
#include "EntityNodeData.h"
|
||||
#include "AssignmentParentFinder.h"
|
||||
|
||||
const char* MODEL_SERVER_NAME = "Entity";
|
||||
const char* MODEL_SERVER_LOGGING_TARGET_NAME = "entity-server";
|
||||
const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo";
|
||||
|
||||
EntityServer::EntityServer(NLPacket& packet) :
|
||||
OctreeServer(packet),
|
||||
EntityServer::EntityServer(ReceivedMessage& message) :
|
||||
OctreeServer(message),
|
||||
_entitySimulation(NULL)
|
||||
{
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
|
@ -40,9 +41,9 @@ EntityServer::~EntityServer() {
|
|||
tree->removeNewlyCreatedHook(this);
|
||||
}
|
||||
|
||||
void EntityServer::handleEntityPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
|
||||
void EntityServer::handleEntityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
if (_octreeInboundPacketProcessor) {
|
||||
_octreeInboundPacketProcessor->queueReceivedPacket(packet, senderNode);
|
||||
_octreeInboundPacketProcessor->queueReceivedPacket(message, senderNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,6 +61,10 @@ OctreePointer EntityServer::createTree() {
|
|||
tree->setSimulation(simpleSimulation);
|
||||
_entitySimulation = simpleSimulation;
|
||||
}
|
||||
|
||||
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
|
||||
DependencyManager::set<AssignmentParentFinder>(tree);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
|
@ -266,3 +271,72 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
|||
tree->setWantEditLogging(wantEditLogging);
|
||||
tree->setWantTerseEditLogging(wantTerseEditLogging);
|
||||
}
|
||||
|
||||
|
||||
// FIXME - this stats tracking is somewhat temporary to debug the Whiteboard issues. It's not a bad
|
||||
// set of stats to have, but we'd probably want a different data structure if we keep it very long.
|
||||
// Since this version uses a single shared QMap for all senders, there could be some lock contention
|
||||
// on this QWriteLocker
|
||||
void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) {
|
||||
QWriteLocker locker(&_viewerSendingStatsLock);
|
||||
_viewerSendingStats[viewerNode][dataID] = { usecTimestampNow(), dataLastEdited };
|
||||
}
|
||||
|
||||
void EntityServer::trackViewerGone(const QUuid& viewerNode) {
|
||||
QWriteLocker locker(&_viewerSendingStatsLock);
|
||||
_viewerSendingStats.remove(viewerNode);
|
||||
}
|
||||
|
||||
QString EntityServer::serverSubclassStats() {
|
||||
QLocale locale(QLocale::English);
|
||||
QString statsString;
|
||||
|
||||
// display memory usage stats
|
||||
statsString += "<b>Entity Server Memory Statistics</b>\r\n";
|
||||
statsString += QString().sprintf("EntityTreeElement size... %ld bytes\r\n", sizeof(EntityTreeElement));
|
||||
statsString += QString().sprintf(" EntityItem size... %ld bytes\r\n", sizeof(EntityItem));
|
||||
statsString += "\r\n\r\n";
|
||||
|
||||
statsString += "<b>Entity Server Sending to Viewer Statistics</b>\r\n";
|
||||
statsString += "----- Viewer Node ID ----------------- ----- Entity ID ---------------------- "
|
||||
"---------- Last Sent To ---------- ---------- Last Edited -----------\r\n";
|
||||
|
||||
int viewers = 0;
|
||||
const int COLUMN_WIDTH = 24;
|
||||
|
||||
{
|
||||
QReadLocker locker(&_viewerSendingStatsLock);
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
for (auto viewerID : _viewerSendingStats.keys()) {
|
||||
statsString += viewerID.toString() + "\r\n";
|
||||
|
||||
auto viewerData = _viewerSendingStats[viewerID];
|
||||
for (auto entityID : viewerData.keys()) {
|
||||
ViewerSendingStats stats = viewerData[entityID];
|
||||
|
||||
quint64 elapsedSinceSent = now - stats.lastSent;
|
||||
double sentMsecsAgo = (double)(elapsedSinceSent / USECS_PER_MSEC);
|
||||
|
||||
quint64 elapsedSinceEdit = now - stats.lastEdited;
|
||||
double editMsecsAgo = (double)(elapsedSinceEdit / USECS_PER_MSEC);
|
||||
|
||||
statsString += " "; // the viewerID spacing
|
||||
statsString += entityID.toString();
|
||||
statsString += " ";
|
||||
statsString += QString("%1 msecs ago")
|
||||
.arg(locale.toString((double)sentMsecsAgo).rightJustified(COLUMN_WIDTH, ' '));
|
||||
statsString += QString("%1 msecs ago")
|
||||
.arg(locale.toString((double)editMsecsAgo).rightJustified(COLUMN_WIDTH, ' '));
|
||||
statsString += "\r\n";
|
||||
}
|
||||
viewers++;
|
||||
}
|
||||
}
|
||||
if (viewers < 1) {
|
||||
statsString += " no viewers... \r\n";
|
||||
}
|
||||
statsString += "\r\n\r\n";
|
||||
|
||||
return statsString;
|
||||
}
|
||||
|
|
|
@ -21,10 +21,16 @@
|
|||
#include "EntityTree.h"
|
||||
|
||||
/// Handles assignments of type EntityServer - sending entities to various clients.
|
||||
|
||||
struct ViewerSendingStats {
|
||||
quint64 lastSent;
|
||||
quint64 lastEdited;
|
||||
};
|
||||
|
||||
class EntityServer : public OctreeServer, public NewlyCreatedEntityHook {
|
||||
Q_OBJECT
|
||||
public:
|
||||
EntityServer(NLPacket& packet);
|
||||
EntityServer(ReceivedMessage& message);
|
||||
~EntityServer();
|
||||
|
||||
// Subclasses must implement these methods
|
||||
|
@ -44,6 +50,10 @@ public:
|
|||
|
||||
virtual void entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) override;
|
||||
virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject) override;
|
||||
virtual QString serverSubclassStats() override;
|
||||
|
||||
virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) override;
|
||||
virtual void trackViewerGone(const QUuid& viewerNode) override;
|
||||
|
||||
public slots:
|
||||
void pruneDeletedEntities();
|
||||
|
@ -52,11 +62,14 @@ protected:
|
|||
virtual OctreePointer createTree() override;
|
||||
|
||||
private slots:
|
||||
void handleEntityPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
void handleEntityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
||||
private:
|
||||
EntitySimulation* _entitySimulation;
|
||||
QTimer* _pruneDeletedEntitiesTimer = nullptr;
|
||||
|
||||
QReadWriteLock _viewerSendingStatsLock;
|
||||
QMap<QUuid, QMap<QUuid, ViewerSendingStats>> _viewerSendingStats;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityServer_h
|
||||
|
|
|
@ -20,13 +20,13 @@
|
|||
|
||||
const QString MESSAGES_MIXER_LOGGING_NAME = "messages-mixer";
|
||||
|
||||
MessagesMixer::MessagesMixer(NLPacket& packet) : ThreadedAssignment(packet)
|
||||
MessagesMixer::MessagesMixer(ReceivedMessage& message) : ThreadedAssignment(message)
|
||||
{
|
||||
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &MessagesMixer::nodeKilled);
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
packetReceiver.registerMessageListener(PacketType::MessagesData, this, "handleMessages");
|
||||
packetReceiver.registerMessageListener(PacketType::MessagesSubscribe, this, "handleMessagesSubscribe");
|
||||
packetReceiver.registerMessageListener(PacketType::MessagesUnsubscribe, this, "handleMessagesUnsubscribe");
|
||||
packetReceiver.registerListener(PacketType::MessagesData, this, "handleMessages");
|
||||
packetReceiver.registerListener(PacketType::MessagesSubscribe, this, "handleMessagesSubscribe");
|
||||
packetReceiver.registerListener(PacketType::MessagesUnsubscribe, this, "handleMessagesUnsubscribe");
|
||||
}
|
||||
|
||||
void MessagesMixer::nodeKilled(SharedNodePointer killedNode) {
|
||||
|
@ -35,10 +35,10 @@ void MessagesMixer::nodeKilled(SharedNodePointer killedNode) {
|
|||
}
|
||||
}
|
||||
|
||||
void MessagesMixer::handleMessages(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
|
||||
void MessagesMixer::handleMessages(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode) {
|
||||
QString channel, message;
|
||||
QUuid senderID;
|
||||
MessagesClient::decodeMessagesPacket(packetList, channel, message, senderID);
|
||||
MessagesClient::decodeMessagesPacket(receivedMessage, channel, message, senderID);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
@ -53,13 +53,13 @@ void MessagesMixer::handleMessages(QSharedPointer<NLPacketList> packetList, Shar
|
|||
});
|
||||
}
|
||||
|
||||
void MessagesMixer::handleMessagesSubscribe(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
|
||||
QString channel = QString::fromUtf8(packetList->getMessage());
|
||||
void MessagesMixer::handleMessagesSubscribe(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
QString channel = QString::fromUtf8(message->getMessage());
|
||||
_channelSubscribers[channel] << senderNode->getUUID();
|
||||
}
|
||||
|
||||
void MessagesMixer::handleMessagesUnsubscribe(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
|
||||
QString channel = QString::fromUtf8(packetList->getMessage());
|
||||
void MessagesMixer::handleMessagesUnsubscribe(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
QString channel = QString::fromUtf8(message->getMessage());
|
||||
if (_channelSubscribers.contains(channel)) {
|
||||
_channelSubscribers[channel].remove(senderNode->getUUID());
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
class MessagesMixer : public ThreadedAssignment {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MessagesMixer(NLPacket& packet);
|
||||
MessagesMixer(ReceivedMessage& message);
|
||||
|
||||
public slots:
|
||||
void run();
|
||||
|
@ -29,9 +29,9 @@ public slots:
|
|||
void sendStatsPacket();
|
||||
|
||||
private slots:
|
||||
void handleMessages(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode);
|
||||
void handleMessagesSubscribe(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode);
|
||||
void handleMessagesUnsubscribe(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode);
|
||||
void handleMessages(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleMessagesSubscribe(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleMessagesUnsubscribe(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
||||
private:
|
||||
QHash<QString,QSet<QUuid>> _channelSubscribers;
|
||||
|
|
|
@ -75,7 +75,7 @@ void OctreeInboundPacketProcessor::midProcess() {
|
|||
}
|
||||
}
|
||||
|
||||
void OctreeInboundPacketProcessor::processPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) {
|
||||
void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
if (_shuttingDown) {
|
||||
qDebug() << "OctreeInboundPacketProcessor::processPacket() while shutting down... ignoring incoming packet";
|
||||
return;
|
||||
|
@ -85,22 +85,22 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<NLPacket> packet
|
|||
|
||||
if (debugProcessPacket) {
|
||||
qDebug("OctreeInboundPacketProcessor::processPacket() payload=%p payloadLength=%lld",
|
||||
packet->getPayload(),
|
||||
packet->getPayloadSize());
|
||||
message->getRawMessage(),
|
||||
message->getSize());
|
||||
}
|
||||
|
||||
// Ask our tree subclass if it can handle the incoming packet...
|
||||
PacketType packetType = packet->getType();
|
||||
PacketType packetType = message->getType();
|
||||
|
||||
if (_myServer->getOctree()->handlesEditPacketType(packetType)) {
|
||||
PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket);
|
||||
_receivedPacketCount++;
|
||||
|
||||
unsigned short int sequence;
|
||||
packet->readPrimitive(&sequence);
|
||||
message->readPrimitive(&sequence);
|
||||
|
||||
quint64 sentAt;
|
||||
packet->readPrimitive(&sentAt);
|
||||
message->readPrimitive(&sentAt);
|
||||
|
||||
quint64 arrivedAt = usecTimestampNow();
|
||||
if (sentAt > arrivedAt) {
|
||||
|
@ -118,7 +118,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<NLPacket> packet
|
|||
|
||||
if (debugProcessPacket || _myServer->wantsDebugReceiving()) {
|
||||
qDebug() << "PROCESSING THREAD: got '" << packetType << "' packet - " << _receivedPacketCount << " command from client";
|
||||
qDebug() << " receivedBytes=" << packet->getDataSize();
|
||||
qDebug() << " receivedBytes=" << message->getSize();
|
||||
qDebug() << " sequence=" << sequence;
|
||||
qDebug() << " sentAt=" << sentAt << " usecs";
|
||||
qDebug() << " arrivedAt=" << arrivedAt << " usecs";
|
||||
|
@ -132,29 +132,29 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<NLPacket> packet
|
|||
qDebug() << " numBytesPacketHeader=" << NLPacket::totalHeaderSize(packetType);
|
||||
qDebug() << " sizeof(sequence)=" << sizeof(sequence);
|
||||
qDebug() << " sizeof(sentAt)=" << sizeof(sentAt);
|
||||
qDebug() << " atByte (in payload)=" << packet->pos();
|
||||
qDebug() << " payload size=" << packet->getPayloadSize();
|
||||
qDebug() << " atByte (in payload)=" << message->getPosition();
|
||||
qDebug() << " payload size=" << message->getSize();
|
||||
|
||||
if (!packet->bytesLeftToRead()) {
|
||||
if (!message->getBytesLeftToRead()) {
|
||||
qDebug() << " ----- UNEXPECTED ---- got a packet without any edit details!!!! --------";
|
||||
}
|
||||
}
|
||||
|
||||
const unsigned char* editData = nullptr;
|
||||
|
||||
while (packet->bytesLeftToRead() > 0) {
|
||||
while (message->getBytesLeftToRead() > 0) {
|
||||
|
||||
editData = reinterpret_cast<const unsigned char*>(packet->getPayload() + packet->pos());
|
||||
editData = reinterpret_cast<const unsigned char*>(message->getRawMessage() + message->getPosition());
|
||||
|
||||
int maxSize = packet->bytesLeftToRead();
|
||||
int maxSize = message->getBytesLeftToRead();
|
||||
|
||||
if (debugProcessPacket) {
|
||||
qDebug() << " --- inside while loop ---";
|
||||
qDebug() << " maxSize=" << maxSize;
|
||||
qDebug("OctreeInboundPacketProcessor::processPacket() %hhu "
|
||||
"payload=%p payloadLength=%lld editData=%p payloadPosition=%lld maxSize=%d",
|
||||
packetType, packet->getPayload(), packet->getPayloadSize(), editData,
|
||||
packet->pos(), maxSize);
|
||||
packetType, message->getRawMessage(), message->getSize(), editData,
|
||||
message->getPosition(), maxSize);
|
||||
}
|
||||
|
||||
quint64 startProcess, startLock = usecTimestampNow();
|
||||
|
@ -162,7 +162,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<NLPacket> packet
|
|||
_myServer->getOctree()->withWriteLock([&] {
|
||||
startProcess = usecTimestampNow();
|
||||
editDataBytesRead =
|
||||
_myServer->getOctree()->processEditPacketData(*packet, editData, maxSize, sendingNode);
|
||||
_myServer->getOctree()->processEditPacketData(*message, editData, maxSize, sendingNode);
|
||||
});
|
||||
quint64 endProcess = usecTimestampNow();
|
||||
|
||||
|
@ -178,12 +178,12 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<NLPacket> packet
|
|||
lockWaitTime += thisLockWaitTime;
|
||||
|
||||
// skip to next edit record in the packet
|
||||
packet->seek(packet->pos() + editDataBytesRead);
|
||||
message->seek(message->getPosition() + editDataBytesRead);
|
||||
|
||||
if (debugProcessPacket) {
|
||||
qDebug() << " editDataBytesRead=" << editDataBytesRead;
|
||||
qDebug() << " AFTER processEditPacketData payload position=" << packet->pos();
|
||||
qDebug() << " AFTER processEditPacketData payload size=" << packet->getPayloadSize();
|
||||
qDebug() << " AFTER processEditPacketData payload position=" << message->getPosition();
|
||||
qDebug() << " AFTER processEditPacketData payload size=" << message->getSize();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -191,7 +191,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<NLPacket> packet
|
|||
if (debugProcessPacket) {
|
||||
qDebug("OctreeInboundPacketProcessor::processPacket() DONE LOOPING FOR %hhu "
|
||||
"payload=%p payloadLength=%lld editData=%p payloadPosition=%lld",
|
||||
packetType, packet->getPayload(), packet->getPayloadSize(), editData, packet->pos());
|
||||
packetType, message->getRawMessage(), message->getSize(), editData, message->getPosition());
|
||||
}
|
||||
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
|
|
|
@ -78,7 +78,7 @@ public:
|
|||
|
||||
protected:
|
||||
|
||||
virtual void processPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode);
|
||||
virtual void processPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
virtual unsigned long getMaxWait() const;
|
||||
virtual void preProcess();
|
||||
|
|
|
@ -33,8 +33,6 @@ OctreeQueryNode::OctreeQueryNode() :
|
|||
_lastTimeBagEmpty(0),
|
||||
_viewFrustumChanging(false),
|
||||
_viewFrustumJustStoppedChanging(true),
|
||||
_currentPacketIsColor(true),
|
||||
_currentPacketIsCompressed(false),
|
||||
_octreeSendThread(NULL),
|
||||
_lastClientBoundaryLevelAdjust(0),
|
||||
_lastClientOctreeSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
|
||||
|
@ -59,7 +57,6 @@ OctreeQueryNode::~OctreeQueryNode() {
|
|||
|
||||
void OctreeQueryNode::nodeKilled() {
|
||||
_isShuttingDown = true;
|
||||
elementBag.unhookNotifications(); // if our node is shutting down, then we no longer need octree element notifications
|
||||
if (_octreeSendThread) {
|
||||
// just tell our thread we want to shutdown, this is asynchronous, and fast, we don't need or want it to block
|
||||
// while the thread actually shuts down
|
||||
|
@ -69,7 +66,6 @@ void OctreeQueryNode::nodeKilled() {
|
|||
|
||||
void OctreeQueryNode::forceNodeShutdown() {
|
||||
_isShuttingDown = true;
|
||||
elementBag.unhookNotifications(); // if our node is shutting down, then we no longer need octree element notifications
|
||||
if (_octreeSendThread) {
|
||||
// we really need to force our thread to shutdown, this is synchronous, we will block while the thread actually
|
||||
// shuts down because we really need it to shutdown, and it's ok if we wait for it to complete
|
||||
|
@ -181,15 +177,9 @@ void OctreeQueryNode::resetOctreePacket() {
|
|||
|
||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
||||
// the clients requested color state.
|
||||
_currentPacketIsColor = getWantColor();
|
||||
_currentPacketIsCompressed = getWantCompression();
|
||||
OCTREE_PACKET_FLAGS flags = 0;
|
||||
if (_currentPacketIsColor) {
|
||||
setAtBit(flags, PACKET_IS_COLOR_BIT);
|
||||
}
|
||||
if (_currentPacketIsCompressed) {
|
||||
setAtBit(flags, PACKET_IS_COMPRESSED_BIT);
|
||||
}
|
||||
setAtBit(flags, PACKET_IS_COLOR_BIT); // always color
|
||||
setAtBit(flags, PACKET_IS_COMPRESSED_BIT); // always compressed
|
||||
|
||||
_octreePacket->reset();
|
||||
|
||||
|
@ -214,10 +204,9 @@ void OctreeQueryNode::writeToPacket(const unsigned char* buffer, unsigned int by
|
|||
|
||||
// compressed packets include lead bytes which contain compressed size, this allows packing of
|
||||
// multiple compressed portions together
|
||||
if (_currentPacketIsCompressed) {
|
||||
OCTREE_PACKET_INTERNAL_SECTION_SIZE sectionSize = bytes;
|
||||
_octreePacket->writePrimitive(sectionSize);
|
||||
}
|
||||
OCTREE_PACKET_INTERNAL_SECTION_SIZE sectionSize = bytes;
|
||||
_octreePacket->writePrimitive(sectionSize);
|
||||
|
||||
if (bytes <= _octreePacket->bytesAvailableForWrite()) {
|
||||
_octreePacket->write(reinterpret_cast<const char*>(buffer), bytes);
|
||||
_octreePacketWaiting = true;
|
||||
|
@ -338,8 +327,7 @@ void OctreeQueryNode::dumpOutOfView() {
|
|||
int stillInView = 0;
|
||||
int outOfView = 0;
|
||||
OctreeElementBag tempBag;
|
||||
while (!elementBag.isEmpty()) {
|
||||
OctreeElementPointer elementToCheck = elementBag.extract();
|
||||
while (OctreeElementPointer elementToCheck = elementBag.extract()) {
|
||||
if (elementToCheck->isInView(_currentViewFrustum)) {
|
||||
tempBag.insert(elementToCheck);
|
||||
stillInView++;
|
||||
|
@ -348,8 +336,7 @@ void OctreeQueryNode::dumpOutOfView() {
|
|||
}
|
||||
}
|
||||
if (stillInView > 0) {
|
||||
while (!tempBag.isEmpty()) {
|
||||
OctreeElementPointer elementToKeepInBag = tempBag.extract();
|
||||
while (OctreeElementPointer elementToKeepInBag = tempBag.extract()) {
|
||||
if (elementToKeepInBag->isInView(_currentViewFrustum)) {
|
||||
elementBag.insert(elementToKeepInBag);
|
||||
}
|
||||
|
@ -375,11 +362,11 @@ const NLPacket* OctreeQueryNode::getNextNackedPacket() {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void OctreeQueryNode::parseNackPacket(NLPacket& packet) {
|
||||
void OctreeQueryNode::parseNackPacket(ReceivedMessage& message) {
|
||||
// read sequence numbers
|
||||
while (packet.bytesLeftToRead()) {
|
||||
while (message.getBytesLeftToRead()) {
|
||||
OCTREE_PACKET_SEQUENCE sequenceNumber;
|
||||
packet.readPrimitive(&sequenceNumber);
|
||||
message.readPrimitive(&sequenceNumber);
|
||||
_nackedSequenceNumbers.enqueue(sequenceNumber);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
#include <iostream>
|
||||
|
||||
#include <CoverageMap.h>
|
||||
#include <NodeData.h>
|
||||
#include <OctreeConstants.h>
|
||||
#include <OctreeElementBag.h>
|
||||
|
@ -55,7 +54,6 @@ public:
|
|||
void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; }
|
||||
|
||||
OctreeElementBag elementBag;
|
||||
CoverageMap map;
|
||||
OctreeElementExtraEncodeData extraEncodeData;
|
||||
|
||||
ViewFrustum& getCurrentViewFrustum() { return _currentViewFrustum; }
|
||||
|
@ -77,12 +75,6 @@ public:
|
|||
quint64 getLastTimeBagEmpty() const { return _lastTimeBagEmpty; }
|
||||
void setLastTimeBagEmpty() { _lastTimeBagEmpty = _sceneSendStartTime; }
|
||||
|
||||
bool getCurrentPacketIsColor() const { return _currentPacketIsColor; }
|
||||
bool getCurrentPacketIsCompressed() const { return _currentPacketIsCompressed; }
|
||||
bool getCurrentPacketFormatMatches() {
|
||||
return (getCurrentPacketIsColor() == getWantColor() && getCurrentPacketIsCompressed() == getWantCompression());
|
||||
}
|
||||
|
||||
bool hasLodChanged() const { return _lodChanged; }
|
||||
|
||||
OctreeSceneStats stats;
|
||||
|
@ -108,7 +100,7 @@ public:
|
|||
|
||||
OCTREE_PACKET_SEQUENCE getSequenceNumber() const { return _sequenceNumber; }
|
||||
|
||||
void parseNackPacket(NLPacket& packet);
|
||||
void parseNackPacket(ReceivedMessage& message);
|
||||
bool hasNextNackedPacket() const;
|
||||
const NLPacket* getNextNackedPacket();
|
||||
|
||||
|
@ -135,8 +127,6 @@ private:
|
|||
quint64 _lastTimeBagEmpty;
|
||||
bool _viewFrustumChanging;
|
||||
bool _viewFrustumJustStoppedChanging;
|
||||
bool _currentPacketIsColor;
|
||||
bool _currentPacketIsCompressed;
|
||||
|
||||
OctreeSendThread* _octreeSendThread;
|
||||
|
||||
|
|
|
@ -309,37 +309,29 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
int truePacketsSent = 0;
|
||||
int trueBytesSent = 0;
|
||||
int packetsSentThisInterval = 0;
|
||||
bool isFullScene = ((!viewFrustumChanged || !nodeData->getWantDelta()) && nodeData->getViewFrustumJustStoppedChanging())
|
||||
bool isFullScene = ((!viewFrustumChanged) && nodeData->getViewFrustumJustStoppedChanging())
|
||||
|| nodeData->hasLodChanged();
|
||||
|
||||
bool somethingToSend = true; // assume we have something
|
||||
|
||||
// FOR NOW... node tells us if it wants to receive only view frustum deltas
|
||||
bool wantDelta = viewFrustumChanged && nodeData->getWantDelta();
|
||||
|
||||
// If our packet already has content in it, then we must use the color choice of the waiting packet.
|
||||
// If we're starting a fresh packet, then...
|
||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
||||
// the clients requested color state.
|
||||
bool wantColor = nodeData->getWantColor();
|
||||
bool wantCompression = nodeData->getWantCompression();
|
||||
|
||||
// If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color
|
||||
// then let's just send that waiting packet.
|
||||
if (!nodeData->getCurrentPacketFormatMatches()) {
|
||||
if (nodeData->isPacketWaiting()) {
|
||||
packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent);
|
||||
} else {
|
||||
nodeData->resetOctreePacket();
|
||||
}
|
||||
int targetSize = MAX_OCTREE_PACKET_DATA_SIZE;
|
||||
if (wantCompression) {
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
}
|
||||
_packetData.changeSettings(wantCompression, targetSize);
|
||||
if (nodeData->isPacketWaiting()) {
|
||||
packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent);
|
||||
} else {
|
||||
nodeData->resetOctreePacket();
|
||||
}
|
||||
int targetSize = MAX_OCTREE_PACKET_DATA_SIZE;
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
|
||||
const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL;
|
||||
_packetData.changeSettings(true, targetSize); // FIXME - eventually support only compressed packets
|
||||
|
||||
const ViewFrustum* lastViewFrustum = viewFrustumChanged ? &nodeData->getLastKnownViewFrustum() : NULL;
|
||||
|
||||
// If the current view frustum has changed OR we have nothing to send, then search against
|
||||
// the current view frustum for things to send.
|
||||
|
@ -350,12 +342,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
if (nodeData->moveShouldDump() || nodeData->hasLodChanged()) {
|
||||
nodeData->dumpOutOfView();
|
||||
}
|
||||
nodeData->map.erase();
|
||||
}
|
||||
|
||||
if (!viewFrustumChanged && !nodeData->getWantDelta()) {
|
||||
// only set our last sent time if we weren't resetting due to frustum change
|
||||
nodeData->setLastTimeBagEmpty();
|
||||
}
|
||||
|
||||
// track completed scenes and send out the stats packet accordingly
|
||||
|
@ -424,8 +410,11 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
quint64 lockWaitEnd = usecTimestampNow();
|
||||
lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart);
|
||||
quint64 encodeStart = usecTimestampNow();
|
||||
|
||||
|
||||
OctreeElementPointer subTree = nodeData->elementBag.extract();
|
||||
if (!subTree) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* TODO: Looking for a way to prevent locking and encoding a tree that is not
|
||||
// going to result in any packets being sent...
|
||||
|
@ -448,22 +437,25 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
}
|
||||
*/
|
||||
|
||||
bool wantOcclusionCulling = nodeData->getWantOcclusionCulling();
|
||||
CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP;
|
||||
|
||||
float octreeSizeScale = nodeData->getOctreeSizeScale();
|
||||
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
|
||||
|
||||
int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving()
|
||||
? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
|
||||
int boundaryLevelAdjust = boundaryLevelAdjustClient +
|
||||
(viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
|
||||
|
||||
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor,
|
||||
WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum,
|
||||
wantOcclusionCulling, coverageMap, boundaryLevelAdjust, octreeSizeScale,
|
||||
EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(),
|
||||
WANT_EXISTS_BITS, DONT_CHOP, viewFrustumChanged, lastViewFrustum,
|
||||
boundaryLevelAdjust, octreeSizeScale,
|
||||
nodeData->getLastTimeBagEmpty(),
|
||||
isFullScene, &nodeData->stats, _myServer->getJurisdiction(),
|
||||
&nodeData->extraEncodeData);
|
||||
|
||||
// Our trackSend() function is implemented by the server subclass, and will be called back
|
||||
// during the encodeTreeBitstream() as new entities/data elements are sent
|
||||
params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
|
||||
_myServer->trackSend(dataID, dataEdited, _nodeUUID);
|
||||
};
|
||||
|
||||
// TODO: should this include the lock time or not? This stat is sent down to the client,
|
||||
// it seems like it may be a good idea to include the lock time as part of the encode time
|
||||
// are reported to client. Since you can encode without the lock
|
||||
|
@ -518,8 +510,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
// if for some reason the finalized size is greater than our available size, then probably the "compressed"
|
||||
// form actually inflated beyond our padding, and in this case we will send the current packet, then
|
||||
// write to out new packet...
|
||||
unsigned int writtenSize = _packetData.getFinalizedSize()
|
||||
+ (nodeData->getCurrentPacketIsCompressed() ? sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) : 0);
|
||||
unsigned int writtenSize = _packetData.getFinalizedSize() + sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
|
||||
if (writtenSize > nodeData->getAvailable()) {
|
||||
packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent);
|
||||
|
@ -535,8 +526,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
// the packet doesn't have enough space to bother attempting to pack more...
|
||||
bool sendNow = true;
|
||||
|
||||
if (nodeData->getCurrentPacketIsCompressed() &&
|
||||
nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING &&
|
||||
if (nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING &&
|
||||
extraPackingAttempts <= REASONABLE_NUMBER_OF_PACKING_ATTEMPTS) {
|
||||
sendNow = false; // try to pack more
|
||||
}
|
||||
|
@ -548,9 +538,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
quint64 packetSendingEnd = usecTimestampNow();
|
||||
packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart);
|
||||
|
||||
if (wantCompression) {
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
}
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
|
||||
} else {
|
||||
// If we're in compressed mode, then we want to see if we have room for more in this wire packet.
|
||||
// but we've finalized the _packetData, so we want to start a new section, we will do that by
|
||||
|
@ -560,7 +548,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
// a larger compressed size then uncompressed size
|
||||
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING;
|
||||
}
|
||||
_packetData.changeSettings(nodeData->getWantCompression(), targetSize); // will do reset
|
||||
_packetData.changeSettings(true, targetSize); // will do reset - NOTE: Always compressed
|
||||
|
||||
}
|
||||
OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec);
|
||||
|
@ -625,7 +613,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
|
|||
if (nodeData->elementBag.isEmpty()) {
|
||||
nodeData->updateLastKnownViewFrustum();
|
||||
nodeData->setViewSent(true);
|
||||
nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes
|
||||
}
|
||||
|
||||
} // end if bag wasn't empty, and so we sent stuff...
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
#include <atomic>
|
||||
|
||||
#include <GenericThread.h>
|
||||
#include <OctreeElementBag.h>
|
||||
|
||||
#include "OctreeQueryNode.h"
|
||||
|
||||
|
|
|
@ -210,8 +210,8 @@ void OctreeServer::trackProcessWaitTime(float time) {
|
|||
_averageProcessWaitTime.updateAverage(time);
|
||||
}
|
||||
|
||||
OctreeServer::OctreeServer(NLPacket& packet) :
|
||||
ThreadedAssignment(packet),
|
||||
OctreeServer::OctreeServer(ReceivedMessage& message) :
|
||||
ThreadedAssignment(message),
|
||||
_argc(0),
|
||||
_argv(NULL),
|
||||
_parsedArgV(NULL),
|
||||
|
@ -821,6 +821,11 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
.arg(locale.toString((uint)checkSum).rightJustified(16, ' '));
|
||||
|
||||
statsString += "\r\n\r\n";
|
||||
|
||||
statsString += serverSubclassStats();
|
||||
|
||||
statsString += "\r\n\r\n";
|
||||
|
||||
statsString += "</pre>\r\n";
|
||||
statsString += "</doc></html>";
|
||||
|
||||
|
@ -873,12 +878,12 @@ void OctreeServer::parsePayload() {
|
|||
}
|
||||
}
|
||||
|
||||
void OctreeServer::handleOctreeQueryPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
|
||||
void OctreeServer::handleOctreeQueryPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
if (!_isFinished) {
|
||||
// If we got a query packet, then we're talking to an agent, and we
|
||||
// need to make sure we have it in our nodeList.
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->updateNodeWithDataFromPacket(packet, senderNode);
|
||||
nodeList->updateNodeWithDataFromPacket(message, senderNode);
|
||||
|
||||
OctreeQueryNode* nodeData = dynamic_cast<OctreeQueryNode*>(senderNode->getLinkedData());
|
||||
if (nodeData && !nodeData->isOctreeSendThreadInitalized()) {
|
||||
|
@ -887,17 +892,17 @@ void OctreeServer::handleOctreeQueryPacket(QSharedPointer<NLPacket> packet, Shar
|
|||
}
|
||||
}
|
||||
|
||||
void OctreeServer::handleOctreeDataNackPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
|
||||
void OctreeServer::handleOctreeDataNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
// If we got a nack packet, then we're talking to an agent, and we
|
||||
// need to make sure we have it in our nodeList.
|
||||
OctreeQueryNode* nodeData = dynamic_cast<OctreeQueryNode*>(senderNode->getLinkedData());
|
||||
if (nodeData) {
|
||||
nodeData->parseNackPacket(*packet);
|
||||
nodeData->parseNackPacket(*message);
|
||||
}
|
||||
}
|
||||
|
||||
void OctreeServer::handleJurisdictionRequestPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
|
||||
_jurisdictionSender->queueReceivedPacket(packet, senderNode);
|
||||
void OctreeServer::handleJurisdictionRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
_jurisdictionSender->queueReceivedPacket(message, senderNode);
|
||||
}
|
||||
|
||||
bool OctreeServer::readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result) {
|
||||
|
@ -1179,6 +1184,8 @@ void OctreeServer::nodeKilled(SharedNodePointer node) {
|
|||
if (usecsElapsed > 1000) {
|
||||
qDebug() << qPrintable(_safeServerName) << "server nodeKilled() took: " << usecsElapsed << " usecs for node:" << *node;
|
||||
}
|
||||
|
||||
trackViewerGone(node->getUUID());
|
||||
}
|
||||
|
||||
void OctreeServer::forceNodeShutdown(SharedNodePointer node) {
|
||||
|
|
|
@ -34,7 +34,7 @@ const int DEFAULT_PACKETS_PER_INTERVAL = 2000; // some 120,000 packets per secon
|
|||
class OctreeServer : public ThreadedAssignment, public HTTPRequestHandler {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OctreeServer(NLPacket& packet);
|
||||
OctreeServer(ReceivedMessage& message);
|
||||
~OctreeServer();
|
||||
|
||||
/// allows setting of run arguments
|
||||
|
@ -79,6 +79,9 @@ public:
|
|||
virtual void beforeRun() { }
|
||||
virtual bool hasSpecialPacketsToSend(const SharedNodePointer& node) { return false; }
|
||||
virtual int sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { return 0; }
|
||||
virtual QString serverSubclassStats() { return QString(); }
|
||||
virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) { }
|
||||
virtual void trackViewerGone(const QUuid& viewerNode) { }
|
||||
|
||||
static float SKIP_TIME; // use this for trackXXXTime() calls for non-times
|
||||
|
||||
|
@ -132,9 +135,9 @@ public slots:
|
|||
|
||||
private slots:
|
||||
void domainSettingsRequestComplete();
|
||||
void handleOctreeQueryPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
void handleOctreeDataNackPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
void handleJurisdictionRequestPacket(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
void handleOctreeQueryPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleOctreeDataNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleJurisdictionRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
||||
protected:
|
||||
virtual OctreePointer createTree() = 0;
|
||||
|
|
31
cmake/externals/gverb/CMakeLists.txt
vendored
31
cmake/externals/gverb/CMakeLists.txt
vendored
|
@ -1,31 +0,0 @@
|
|||
set(EXTERNAL_NAME gverb)
|
||||
|
||||
if (ANDROID)
|
||||
set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19")
|
||||
endif ()
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/gverb-master.zip
|
||||
URL_MD5 8b16d586390a2102804e46b87820dfc6
|
||||
CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
|
||||
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
)
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE FILEPATH "Path to gverb include directory")
|
||||
|
||||
if (WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${INSTALL_DIR}/lib/gverb.lib CACHE FILEPATH "List of gverb libraries")
|
||||
else ()
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${INSTALL_DIR}/lib/libgverb.a CACHE FILEPATH "List of gverb libraries")
|
||||
endif ()
|
|
@ -18,10 +18,16 @@ hifi_library_search_hints("leapmotion")
|
|||
find_path(LEAPMOTION_INCLUDE_DIRS Leap.h PATH_SUFFIXES include HINTS ${LEAPMOTION_SEARCH_DIRS})
|
||||
|
||||
if (WIN32)
|
||||
find_library(LEAPMOTION_LIBRARY_DEBUG Leapd PATH_SUFFIXES lib/x86 HINTS ${LEAPMOTION_SEARCH_DIRS})
|
||||
find_library(LEAPMOTION_LIBRARY_RELEASE Leap PATH_SUFFIXES lib/x86 HINTS ${LEAPMOTION_SEARCH_DIRS})
|
||||
|
||||
find_path(LEAPMOTION_DLL_PATH Leap.dll PATH_SUFFIXES lib/x86 HINTS ${LEAPMOTION_SEARCH_DIRS})
|
||||
|
||||
if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
|
||||
set(ARCH_DIR "x64")
|
||||
else()
|
||||
set(ARCH_DIR "x86")
|
||||
endif()
|
||||
|
||||
find_library(LEAPMOTION_LIBRARY_DEBUG Leapd PATH_SUFFIXES "lib/${ARCH_DIR}" HINTS ${LEAPMOTION_SEARCH_DIRS})
|
||||
find_library(LEAPMOTION_LIBRARY_RELEASE Leap PATH_SUFFIXES "lib/${ARCH_DIR}" HINTS ${LEAPMOTION_SEARCH_DIRS})
|
||||
find_path(LEAPMOTION_DLL_PATH Leap.dll PATH_SUFFIXES "lib/${ARCH_DIR}" HINTS ${LEAPMOTION_SEARCH_DIRS})
|
||||
elseif (APPLE)
|
||||
find_library(LEAPMOTION_LIBRARY_RELEASE Leap PATH_SUFFIXES lib HINTS ${LEAPMOTION_SEARCH_DIRS})
|
||||
endif ()
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
# Try to find the RSSDK library
|
||||
#
|
||||
# You must provide a RSSDK_ROOT_DIR which contains lib and include directories
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# RSSDK_FOUND - system found RSSDK
|
||||
# RSSDK_INCLUDE_DIRS - the RSSDK include directory
|
||||
# RSSDK_LIBRARIES - Link this to use RSSDK
|
||||
#
|
||||
# Created on 12/7/2014 by Thijs Wenker
|
||||
# Copyright (c) 2014 High Fidelity
|
||||
#
|
||||
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("rssdk")
|
||||
|
||||
find_path(RSSDK_INCLUDE_DIRS pxcbase.h PATH_SUFFIXES include HINTS ${RSSDK_SEARCH_DIRS})
|
||||
|
||||
if (WIN32)
|
||||
find_library(RSSDK_LIBRARY_DEBUG libpxc_d PATH_SUFFIXES lib/Win32 HINTS ${RSSDK_SEARCH_DIRS})
|
||||
find_library(RSSDK_LIBRARY_RELEASE libpxc PATH_SUFFIXES lib/Win32 HINTS ${RSSDK_SEARCH_DIRS})
|
||||
endif ()
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(RSSDK)
|
||||
|
||||
set(RSSDK_LIBRARIES "${RSSDK_LIBRARY}")
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(RSSDK DEFAULT_MSG RSSDK_INCLUDE_DIRS RSSDK_LIBRARIES)
|
||||
|
||||
mark_as_advanced(RSSDK_INCLUDE_DIRS RSSDK_LIBRARIES RSSDK_SEARCH_DIRS)
|
|
@ -1,30 +0,0 @@
|
|||
#
|
||||
# FindRtMidi.cmake
|
||||
#
|
||||
# Try to find the RtMidi library
|
||||
#
|
||||
# You can provide a RTMIDI_ROOT_DIR which contains lib and include directories
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# RTMIDI_FOUND - system found RtMidi
|
||||
# RTMIDI_INCLUDE_DIRS - the RtMidi include directory
|
||||
# RTMIDI_LIBRARIES - link to this to use RtMidi
|
||||
#
|
||||
# Created on 6/30/2014 by Stephen Birarda
|
||||
# Copyright 2014 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("rtmidi")
|
||||
|
||||
find_path(RTMIDI_INCLUDE_DIRS RtMidi.h PATH_SUFFIXES include HINTS ${RTMIDI_SEARCH_DIRS})
|
||||
find_library(RTMIDI_LIBRARIES NAMES rtmidi PATH_SUFFIXES lib HINTS ${RTMIDI_SEARCH_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(RtMidi DEFAULT_MSG RTMIDI_INCLUDE_DIRS RTMIDI_LIBRARIES)
|
||||
|
||||
mark_as_advanced(RTMIDI_INCLUDE_DIRS RTMIDI_LIBRARIES RTMIDI_SEARCH_DIRS)
|
|
@ -51,15 +51,15 @@ const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
|
|||
<< NodeType::AssetServer
|
||||
<< NodeType::MessagesMixer;
|
||||
|
||||
void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<NLPacket> packet) {
|
||||
if (packet->getPayloadSize() == 0) {
|
||||
void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
if (message->getSize() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QDataStream packetStream(packet.data());
|
||||
QDataStream packetStream(message->getMessage());
|
||||
|
||||
// read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it
|
||||
NodeConnectionData nodeConnection = NodeConnectionData::fromDataStream(packetStream, packet->getSenderSockAddr());
|
||||
NodeConnectionData nodeConnection = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr());
|
||||
|
||||
if (nodeConnection.localSockAddr.isNull() || nodeConnection.publicSockAddr.isNull()) {
|
||||
qDebug() << "Unexpected data received for node local socket or public socket. Will not allow connection.";
|
||||
|
@ -72,7 +72,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<NLPacket> pack
|
|||
|
||||
if (!VALID_NODE_TYPES.contains(nodeConnection.nodeType)) {
|
||||
qDebug() << "Received an invalid node type with connect request. Will not allow connection from"
|
||||
<< nodeConnection.senderSockAddr;
|
||||
<< nodeConnection.senderSockAddr << ": " << nodeConnection.nodeType;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -87,11 +87,11 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<NLPacket> pack
|
|||
QString username;
|
||||
QByteArray usernameSignature;
|
||||
|
||||
if (packet->bytesLeftToRead() > 0) {
|
||||
if (message->getBytesLeftToRead() > 0) {
|
||||
// read username from packet
|
||||
packetStream >> username;
|
||||
|
||||
if (packet->bytesLeftToRead() > 0) {
|
||||
if (message->getBytesLeftToRead() > 0) {
|
||||
// read user signature from packet
|
||||
packetStream >> usernameSignature;
|
||||
}
|
||||
|
@ -103,14 +103,14 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<NLPacket> pack
|
|||
if (node) {
|
||||
// set the sending sock addr and node interest set on this node
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
nodeData->setSendingSockAddr(packet->getSenderSockAddr());
|
||||
nodeData->setSendingSockAddr(message->getSenderSockAddr());
|
||||
nodeData->setNodeInterestSet(nodeConnection.interestList.toSet());
|
||||
|
||||
// signal that we just connected a node so the DomainServer can get it a list
|
||||
// and broadcast its presence right away
|
||||
emit connectedNode(node);
|
||||
} else {
|
||||
qDebug() << "Refusing connection from node at" << packet->getSenderSockAddr();
|
||||
qDebug() << "Refusing connection from node at" << message->getSenderSockAddr();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -572,10 +572,10 @@ void DomainGatekeeper::handlePeerPingTimeout() {
|
|||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer<NLPacket> packet) {
|
||||
void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
// loop through the packet and pull out network peers
|
||||
// any peer we don't have we add to the hash, otherwise we update
|
||||
QDataStream iceResponseStream(packet.data());
|
||||
QDataStream iceResponseStream(message->getMessage());
|
||||
|
||||
NetworkPeer* receivedPeer = new NetworkPeer;
|
||||
iceResponseStream >> *receivedPeer;
|
||||
|
@ -600,15 +600,15 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer<NLPacket>
|
|||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::processICEPingPacket(QSharedPointer<NLPacket> packet) {
|
||||
void DomainGatekeeper::processICEPingPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*packet, limitedNodeList->getSessionUUID());
|
||||
auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*message, limitedNodeList->getSessionUUID());
|
||||
|
||||
limitedNodeList->sendPacket(std::move(pingReplyPacket), packet->getSenderSockAddr());
|
||||
limitedNodeList->sendPacket(std::move(pingReplyPacket), message->getSenderSockAddr());
|
||||
}
|
||||
|
||||
void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<NLPacket> packet) {
|
||||
QDataStream packetStream(packet.data());
|
||||
void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
QDataStream packetStream(message->getMessage());
|
||||
|
||||
QUuid nodeUUID;
|
||||
packetStream >> nodeUUID;
|
||||
|
@ -617,6 +617,6 @@ void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<NLPacket> packet
|
|||
|
||||
if (sendingPeer) {
|
||||
// we had this NetworkPeer in our connecting list - add the right sock addr to our connected list
|
||||
sendingPeer->activateMatchingOrNewSymmetricSocket(packet->getSenderSockAddr());
|
||||
sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,10 +41,10 @@ public:
|
|||
|
||||
void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); }
|
||||
public slots:
|
||||
void processConnectRequestPacket(QSharedPointer<NLPacket> packet);
|
||||
void processICEPingPacket(QSharedPointer<NLPacket> packet);
|
||||
void processICEPingReplyPacket(QSharedPointer<NLPacket> packet);
|
||||
void processICEPeerInformationPacket(QSharedPointer<NLPacket> packet);
|
||||
void processConnectRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEPingPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEPeerInformationPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void publicKeyJSONCallback(QNetworkReply& requestReply);
|
||||
|
||||
|
|
|
@ -273,7 +273,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
|||
packetReceiver.registerListener(PacketType::RequestAssignment, this, "processRequestAssignmentPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainListRequest, this, "processListRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket");
|
||||
packetReceiver.registerMessageListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainDisconnectRequest, this, "processNodeDisconnectRequestPacket");
|
||||
|
||||
// NodeList won't be available to the settings manager when it is created, so call registerListener here
|
||||
|
@ -578,10 +578,10 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
|
|||
}
|
||||
}
|
||||
|
||||
void DomainServer::processListRequestPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) {
|
||||
void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
|
||||
QDataStream packetStream(packet.data());
|
||||
NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, packet->getSenderSockAddr(), false);
|
||||
QDataStream packetStream(message->getMessage());
|
||||
NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr(), false);
|
||||
|
||||
// update this node's sockets in case they have changed
|
||||
sendingNode->setPublicSocket(nodeRequestData.publicSockAddr);
|
||||
|
@ -591,7 +591,7 @@ void DomainServer::processListRequestPacket(QSharedPointer<NLPacket> packet, Sha
|
|||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
|
||||
nodeData->setNodeInterestSet(nodeRequestData.interestList.toSet());
|
||||
|
||||
sendDomainListToNode(sendingNode, packet->getSenderSockAddr());
|
||||
sendDomainListToNode(sendingNode, message->getSenderSockAddr());
|
||||
}
|
||||
|
||||
unsigned int DomainServer::countConnectedUsers() {
|
||||
|
@ -764,9 +764,9 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) {
|
|||
);
|
||||
}
|
||||
|
||||
void DomainServer::processRequestAssignmentPacket(QSharedPointer<NLPacket> packet) {
|
||||
void DomainServer::processRequestAssignmentPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
// construct the requested assignment from the packet data
|
||||
Assignment requestAssignment(*packet);
|
||||
Assignment requestAssignment(*message);
|
||||
|
||||
// Suppress these for Assignment::AgentType to once per 5 seconds
|
||||
static QElapsedTimer noisyMessageTimer;
|
||||
|
@ -784,14 +784,14 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer<NLPacket> packe
|
|||
static QString repeatedMessage = LogHandler::getInstance().addOnlyOnceMessageRegex
|
||||
("Received a request for assignment type [^ ]+ from [^ ]+");
|
||||
qDebug() << "Received a request for assignment type" << requestAssignment.getType()
|
||||
<< "from" << packet->getSenderSockAddr();
|
||||
<< "from" << message->getSenderSockAddr();
|
||||
noisyMessageTimer.restart();
|
||||
}
|
||||
|
||||
SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment);
|
||||
|
||||
if (assignmentToDeploy) {
|
||||
qDebug() << "Deploying assignment -" << *assignmentToDeploy.data() << "- to" << packet->getSenderSockAddr();
|
||||
qDebug() << "Deploying assignment -" << *assignmentToDeploy.data() << "- to" << message->getSenderSockAddr();
|
||||
|
||||
// give this assignment out, either the type matches or the requestor said they will take any
|
||||
static std::unique_ptr<NLPacket> assignmentPacket;
|
||||
|
@ -812,7 +812,7 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer<NLPacket> packe
|
|||
assignmentStream << uniqueAssignment;
|
||||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
limitedNodeList->sendUnreliablePacket(*assignmentPacket, packet->getSenderSockAddr());
|
||||
limitedNodeList->sendUnreliablePacket(*assignmentPacket, message->getSenderSockAddr());
|
||||
|
||||
// give the information for that deployed assignment to the gatekeeper so it knows to that that node
|
||||
// in when it comes back around
|
||||
|
@ -824,7 +824,7 @@ void DomainServer::processRequestAssignmentPacket(QSharedPointer<NLPacket> packe
|
|||
static QString repeatedMessage = LogHandler::getInstance().addOnlyOnceMessageRegex
|
||||
("Unable to fulfill assignment request of type [^ ]+ from [^ ]+");
|
||||
qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType()
|
||||
<< "from" << packet->getSenderSockAddr();
|
||||
<< "from" << message->getSenderSockAddr();
|
||||
noisyMessageTimer.restart();
|
||||
}
|
||||
}
|
||||
|
@ -993,7 +993,7 @@ void DomainServer::sendHeartbeatToIceServer() {
|
|||
DependencyManager::get<LimitedNodeList>()->sendHeartbeatToIceServer(_iceServerSocket);
|
||||
}
|
||||
|
||||
void DomainServer::processNodeJSONStatsPacket(QSharedPointer<NLPacketList> packetList, SharedNodePointer sendingNode) {
|
||||
void DomainServer::processNodeJSONStatsPacket(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer sendingNode) {
|
||||
auto nodeData = dynamic_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
|
||||
if (nodeData) {
|
||||
nodeData->updateJSONStats(packetList->getMessage());
|
||||
|
@ -1767,17 +1767,17 @@ void DomainServer::addStaticAssignmentsToQueue() {
|
|||
}
|
||||
}
|
||||
|
||||
void DomainServer::processPathQueryPacket(QSharedPointer<NLPacket> packet) {
|
||||
void DomainServer::processPathQueryPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
// this is a query for the viewpoint resulting from a path
|
||||
// first pull the query path from the packet
|
||||
|
||||
// figure out how many bytes the sender said this path is
|
||||
quint16 numPathBytes;
|
||||
packet->readPrimitive(&numPathBytes);
|
||||
message->readPrimitive(&numPathBytes);
|
||||
|
||||
if (numPathBytes <= packet->bytesLeftToRead()) {
|
||||
if (numPathBytes <= message->getBytesLeftToRead()) {
|
||||
// the number of path bytes makes sense for the sent packet - pull out the path
|
||||
QString pathQuery = QString::fromUtf8(packet->getPayload() + packet->pos(), numPathBytes);
|
||||
QString pathQuery = QString::fromUtf8(message->getRawMessage() + message->getPosition(), numPathBytes);
|
||||
|
||||
// our settings contain paths that start with a leading slash, so make sure this query has that
|
||||
if (!pathQuery.startsWith("/")) {
|
||||
|
@ -1825,7 +1825,7 @@ void DomainServer::processPathQueryPacket(QSharedPointer<NLPacket> packet) {
|
|||
|
||||
// send off the packet - see if we can associate this outbound data to a particular node
|
||||
// TODO: does this senderSockAddr always work for a punched DS client?
|
||||
nodeList->sendPacket(std::move(pathResponsePacket), packet->getSenderSockAddr());
|
||||
nodeList->sendPacket(std::move(pathResponsePacket), message->getSenderSockAddr());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1837,11 +1837,11 @@ void DomainServer::processPathQueryPacket(QSharedPointer<NLPacket> packet) {
|
|||
}
|
||||
}
|
||||
|
||||
void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<NLPacket> packet) {
|
||||
void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
// This packet has been matched to a source node and they're asking not to be in the domain anymore
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
const QUuid& nodeUUID = packet->getSourceID();
|
||||
const QUuid& nodeUUID = message->getSourceID();
|
||||
|
||||
qDebug() << "Received a disconnect request from node with UUID" << nodeUUID;
|
||||
|
||||
|
|
|
@ -56,11 +56,11 @@ public slots:
|
|||
|
||||
void restart();
|
||||
|
||||
void processRequestAssignmentPacket(QSharedPointer<NLPacket> packet);
|
||||
void processListRequestPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode);
|
||||
void processNodeJSONStatsPacket(QSharedPointer<NLPacketList> packetList, SharedNodePointer sendingNode);
|
||||
void processPathQueryPacket(QSharedPointer<NLPacket> packet);
|
||||
void processNodeDisconnectRequestPacket(QSharedPointer<NLPacket> packet);
|
||||
void processRequestAssignmentPacket(QSharedPointer<ReceivedMessage> packet);
|
||||
void processListRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void processNodeJSONStatsPacket(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer sendingNode);
|
||||
void processPathQueryPacket(QSharedPointer<ReceivedMessage> packet);
|
||||
void processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
private slots:
|
||||
void aboutToQuit();
|
||||
|
|
|
@ -67,9 +67,9 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
|
|||
QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<NLPacket> packet) {
|
||||
void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
Assignment::Type type;
|
||||
packet->readPrimitive(&type);
|
||||
message->readPrimitive(&type);
|
||||
|
||||
QJsonObject responseObject = responseObjectForType(QString::number(type));
|
||||
auto json = QJsonDocument(responseObject).toJson();
|
||||
|
@ -79,7 +79,7 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<NL
|
|||
packetList->write(json);
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
nodeList->sendPacketList(std::move(packetList), packet->getSenderSockAddr());
|
||||
nodeList->sendPacketList(std::move(packetList), message->getSenderSockAddr());
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <HifiConfigVariantMap.h>
|
||||
#include <HTTPManager.h>
|
||||
|
||||
#include <NLPacket.h>
|
||||
#include <ReceivedMessage.h>
|
||||
|
||||
const QString SETTINGS_PATHS_KEY = "paths";
|
||||
|
||||
|
@ -42,7 +42,7 @@ public:
|
|||
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
|
||||
|
||||
private slots:
|
||||
void processSettingsRequestPacket(QSharedPointer<NLPacket> packet);
|
||||
void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
private:
|
||||
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
|
||||
|
|
416
examples/acScripts/AgentPoolController.js
Normal file
416
examples/acScripts/AgentPoolController.js
Normal file
|
@ -0,0 +1,416 @@
|
|||
//
|
||||
// AgentPoolController.js
|
||||
// acScripts
|
||||
//
|
||||
// Created by Sam Gateau on 11/23/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
function printDebug(message) {
|
||||
print(message);
|
||||
}
|
||||
|
||||
(function() {
|
||||
var SERVICE_CHANNEL = "com.highfidelity.playback.service";
|
||||
var COMMAND_CHANNEL = "com.highfidelity.playback.command";
|
||||
|
||||
// The time between alive messages on the command channel
|
||||
var ALIVE_PERIOD = 3;
|
||||
var NUM_CYCLES_BEFORE_RESET = 8;
|
||||
|
||||
// Service Actions
|
||||
var MASTER_ID = -1;
|
||||
var INVALID_AGENT = -2;
|
||||
|
||||
var BROADCAST_AGENTS = -3;
|
||||
|
||||
var MASTER_ALIVE = "MASTER_ALIVE";
|
||||
var AGENT_ALIVE = "AGENT_ALIVE";
|
||||
var AGENT_READY = "READY";
|
||||
var MASTER_HIRE_AGENT = "HIRE"
|
||||
var MASTER_FIRE_AGENT = "FIRE";
|
||||
|
||||
var makeUniqueUUID = function(SEUUID) {
|
||||
//return SEUUID + Math.random();
|
||||
// forget complexity, just give me a four digit pin
|
||||
return (Math.random() * 10000).toFixed(0);
|
||||
}
|
||||
|
||||
var packServiceMessage = function(dest, command, src) {
|
||||
var message = {
|
||||
dest: dest,
|
||||
command: command,
|
||||
src: src
|
||||
};
|
||||
return JSON.stringify(message);
|
||||
};
|
||||
|
||||
var unpackServiceMessage = function(message) {
|
||||
return JSON.parse(message);
|
||||
};
|
||||
|
||||
var packCommandMessage = function(dest, action, argument) {
|
||||
var message = {
|
||||
dest_key: dest,
|
||||
action_key: action,
|
||||
argument_key: argument
|
||||
};
|
||||
return JSON.stringify(message);
|
||||
};
|
||||
|
||||
var unpackCommandMessage = function(message) {
|
||||
return JSON.parse(message);
|
||||
};
|
||||
|
||||
// Actor
|
||||
//---------------------------------
|
||||
var Actor = function() {
|
||||
this.agentID = INVALID_AGENT;
|
||||
this.lastAliveCycle = 0;
|
||||
this.onHired = function(actor) {};
|
||||
this.onFired = function(actor) {};
|
||||
};
|
||||
|
||||
Actor.prototype.isConnected = function () {
|
||||
return (this.agentID != INVALID_AGENT);
|
||||
}
|
||||
|
||||
Actor.prototype.alive = function () {
|
||||
printDebug("Agent UUID =" + this.agentID + " Alive was " + this.lastAliveCycle);
|
||||
this.lastAliveCycle = 0;
|
||||
}
|
||||
Actor.prototype.incrementAliveCycle = function () {
|
||||
printDebug("Actor.prototype.incrementAliveCycle UUID =" + this.agentID + " Alive pre increment " + this.lastAliveCycle);
|
||||
if (this.isConnected()) {
|
||||
this.lastAliveCycle++;
|
||||
printDebug("Agent UUID =" + this.agentID + " Alive incremented " + this.lastAliveCycle);
|
||||
}
|
||||
return this.lastAliveCycle;
|
||||
}
|
||||
|
||||
this.Actor = Actor;
|
||||
|
||||
// master side
|
||||
//---------------------------------
|
||||
var MasterController = function() {
|
||||
this.timeSinceLastAlive = 0;
|
||||
this.knownAgents = new Array;
|
||||
this.hiredActors = new Array;
|
||||
this.hiringAgentsQueue = new Array;
|
||||
this.subscribed = false;
|
||||
};
|
||||
|
||||
MasterController.prototype.destroy = function() {
|
||||
if (this.subscribed) {
|
||||
Messages.unsubscribe(SERVICE_CHANNEL);
|
||||
Messages.unsubscribe(COMMAND_CHANNEL);
|
||||
this.subscribed = true;
|
||||
}
|
||||
};
|
||||
|
||||
MasterController.prototype.reset = function() {
|
||||
this.timeSinceLastAlive = 0;
|
||||
|
||||
if (!this.subscribed) {
|
||||
Messages.subscribe(COMMAND_CHANNEL);
|
||||
Messages.subscribe(SERVICE_CHANNEL);
|
||||
var localThis = this;
|
||||
Messages.messageReceived.connect(function (channel, message, senderID) {
|
||||
if (channel == SERVICE_CHANNEL) {
|
||||
localThis._processServiceMessage(message, senderID);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
// ready to roll, enable
|
||||
this.subscribed = true;
|
||||
printDebug("Master Started");
|
||||
};
|
||||
|
||||
MasterController.prototype._processServiceMessage = function(message, senderID) {
|
||||
var service = unpackServiceMessage(message);
|
||||
if (service.dest == MASTER_ID) {
|
||||
if (service.command == AGENT_READY) {
|
||||
// check to see if we know about this agent
|
||||
var agentIndex = this.knownAgents.indexOf(service.src);
|
||||
if (agentIndex < 0) {
|
||||
this._onAgentAvailableForHiring(service.src);
|
||||
} else {
|
||||
// Master think the agent is hired but not the other way around, forget about it
|
||||
printDebug("New agent still sending ready ? " + service.src + " " + agentIndex + " Forgeting about it");
|
||||
// this._removeHiredAgent(agentIndex);
|
||||
}
|
||||
} else if (service.command == AGENT_ALIVE) {
|
||||
// check to see if we know about this agent
|
||||
var agentIndex = this.knownAgents.indexOf(service.src);
|
||||
if (agentIndex >= 0) {
|
||||
// yes so reset its alive beat
|
||||
this.hiredActors[agentIndex].alive();
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MasterController.prototype._onAgentAvailableForHiring = function(agentID) {
|
||||
if (this.hiringAgentsQueue.length == 0) {
|
||||
printDebug("No Actor on the hiring queue");
|
||||
return;
|
||||
}
|
||||
|
||||
printDebug("MasterController.prototype._onAgentAvailableForHiring " + agentID);
|
||||
var newActor = this.hiringAgentsQueue.pop();
|
||||
|
||||
var indexOfNewAgent = this.knownAgents.push(agentID);
|
||||
newActor.alive();
|
||||
newActor.agentID = agentID;
|
||||
this.hiredActors.push(newActor);
|
||||
|
||||
printDebug("New agent available to be hired " + agentID + " " + indexOfNewAgent);
|
||||
var serviceMessage = packServiceMessage(agentID, MASTER_HIRE_AGENT, MASTER_ID);
|
||||
printDebug("serviceMessage = " + serviceMessage);
|
||||
Messages.sendMessage(SERVICE_CHANNEL, serviceMessage);
|
||||
printDebug("message sent calling the actor" + JSON.stringify(newActor) );
|
||||
|
||||
newActor.onHired(newActor);
|
||||
}
|
||||
|
||||
MasterController.prototype.sendCommand = function(target, action, argument) {
|
||||
if (this.subscribed) {
|
||||
var command = packCommandMessage(target, action, argument);
|
||||
printDebug(command);
|
||||
Messages.sendMessage(COMMAND_CHANNEL, command);
|
||||
}
|
||||
};
|
||||
|
||||
MasterController.prototype.update = function(deltaTime) {
|
||||
this.timeSinceLastAlive += deltaTime;
|
||||
if (this.timeSinceLastAlive > ALIVE_PERIOD) {
|
||||
this.timeSinceLastAlive = 0;
|
||||
Messages.sendMessage(SERVICE_CHANNEL, packServiceMessage(BROADCAST_AGENTS, MASTER_ALIVE, MASTER_ID));
|
||||
|
||||
{
|
||||
// Check for alive connected agents
|
||||
var lostAgents = new Array();
|
||||
for (var i = 0; i < this.hiredActors.length; i++) {
|
||||
var actor = this.hiredActors[i];
|
||||
var lastAlive = actor.incrementAliveCycle()
|
||||
if (lastAlive > NUM_CYCLES_BEFORE_RESET) {
|
||||
printDebug("Agent Lost, firing Agent #" + i + " ID " + actor.agentID);
|
||||
lostAgents.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
// now fire gathered lost agents from end to begin
|
||||
while (lostAgents.length > 0) {
|
||||
printDebug("Firing " + lostAgents.length + " agents" + JSON.stringify(lostAgents));
|
||||
this.fireAgent(this.hiredActors[lostAgents.pop()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
MasterController.prototype.hireAgent = function(actor) {
|
||||
if (actor == null) {
|
||||
printDebug("trying to hire an agent with a null actor, abort");
|
||||
return;
|
||||
}
|
||||
if (actor.isConnected()) {
|
||||
printDebug("trying to hire an agent already connected, abort");
|
||||
return;
|
||||
}
|
||||
this.hiringAgentsQueue.unshift(actor);
|
||||
};
|
||||
|
||||
MasterController.prototype.fireAgent = function(actor) {
|
||||
// check to see if we know about this agent
|
||||
printDebug("MasterController.prototype.fireAgent" + actor.agentID);
|
||||
|
||||
// Try the waiting list first
|
||||
var waitingIndex = this.hiringAgentsQueue.indexOf(actor);
|
||||
if (waitingIndex >= 0) {
|
||||
printDebug("fireAgent found actor on waiting queue #" + waitingIndex);
|
||||
var lostActor = this.hiringAgentsQueue.splice(waitingIndex, 1);
|
||||
if (lostActor.length) {
|
||||
lostActor[0].onFired(lostActor[0]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// then the hired agents
|
||||
var actorIndex = this.knownAgents.indexOf(actor.agentID);
|
||||
if (actorIndex >= 0) {
|
||||
printDebug("fired actor found #" + actorIndex);
|
||||
this._removeHiredAgent(actorIndex);
|
||||
}
|
||||
}
|
||||
|
||||
MasterController.prototype._removeHiredAgent = function(actorIndex) {
|
||||
// check to see if we know about this agent
|
||||
if (actorIndex >= 0) {
|
||||
printDebug("MasterController.prototype._removeHiredAgent #" + this.knownAgents[actorIndex])
|
||||
this.knownAgents.splice(actorIndex, 1);
|
||||
|
||||
var lostActor = this.hiredActors[actorIndex];
|
||||
this.hiredActors.splice(actorIndex, 1);
|
||||
|
||||
var lostAgentID = lostActor.agentID;
|
||||
lostActor.agentID = INVALID_AGENT;
|
||||
|
||||
if (lostAgentID != INVALID_AGENT) {
|
||||
printDebug("fired actor is still connected, send fire command");
|
||||
Messages.sendMessage(SERVICE_CHANNEL, packServiceMessage(lostAgentID, MASTER_FIRE_AGENT, MASTER_ID));
|
||||
}
|
||||
|
||||
lostActor.onFired(lostActor);
|
||||
}
|
||||
}
|
||||
|
||||
this.MasterController = MasterController;
|
||||
|
||||
// agent side
|
||||
//---------------------------------
|
||||
var AgentController = function() {
|
||||
this.subscribed = false;
|
||||
this._init();
|
||||
|
||||
this.onHired = function() {};
|
||||
this.onCommand = function(command) {};
|
||||
this.onFired = function() {};
|
||||
};
|
||||
|
||||
AgentController.prototype._init = function() {
|
||||
this.isHired= false;
|
||||
this.timeSinceLastAlive = 0;
|
||||
this.numCyclesWithoutAlive = 0;
|
||||
this.agentUUID = makeUniqueUUID(Agent.sessionUUID);
|
||||
printDebug("this.agentUUID = " + this.agentUUID);
|
||||
}
|
||||
|
||||
AgentController.prototype.destroy = function() {
|
||||
if (this.subscribed) {
|
||||
this.fired();
|
||||
Messages.unsubscribe(SERVICE_CHANNEL);
|
||||
Messages.unsubscribe(COMMAND_CHANNEL);
|
||||
this.subscribed = true;
|
||||
}
|
||||
};
|
||||
|
||||
AgentController.prototype.reset = function() {
|
||||
// If already hired, fire
|
||||
this.fired();
|
||||
|
||||
if (!this.subscribed) {
|
||||
Messages.subscribe(COMMAND_CHANNEL);
|
||||
Messages.subscribe(SERVICE_CHANNEL);
|
||||
var localThis = this;
|
||||
Messages.messageReceived.connect(function (channel, message, senderID) {
|
||||
if (channel == SERVICE_CHANNEL) {
|
||||
localThis._processServiceMessage(message, senderID);
|
||||
return;
|
||||
}
|
||||
if (channel == COMMAND_CHANNEL) {
|
||||
localThis._processCommandMessage(message, senderID);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.subscribed = true;
|
||||
printDebug("Client Started");
|
||||
};
|
||||
|
||||
AgentController.prototype._processServiceMessage = function(message, senderID) {
|
||||
var service = unpackServiceMessage(message);
|
||||
printDebug("Client " + this.agentUUID + " Received message = " + message);
|
||||
if (service.dest == this.agentUUID) {
|
||||
if (service.command != AGENT_READY) {
|
||||
|
||||
// this is potentially a message to hire me if i m not already
|
||||
if (!this.isHired && (service.command == MASTER_HIRE_AGENT)) {
|
||||
printDebug(service.command);
|
||||
this.isHired = true;
|
||||
printDebug("Client Hired by master UUID" + service.src);
|
||||
this.onHired();
|
||||
this.alive();
|
||||
return;
|
||||
}
|
||||
|
||||
// Or maybe a message to fire me if i m not hired
|
||||
if (this.isHired && (service.command == MASTER_FIRE_AGENT)) {
|
||||
printDebug("Client Fired by master UUID" + senderID);
|
||||
this.fired();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if ((service.src == MASTER_ID) && (service.command == MASTER_ALIVE)) {
|
||||
this.numCyclesWithoutAlive = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AgentController.prototype._processCommandMessage = function(message, senderID) {
|
||||
// ONly work if hired
|
||||
if (this.isHired) {
|
||||
var command = unpackCommandMessage(message);
|
||||
if ((command.dest_key == this.agentUUID) || (command.dest_key == BROADCAST_AGENTS)) {
|
||||
printDebug("Command received = " + JSON.stringify(command) + senderID);
|
||||
this.onCommand(command);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
AgentController.prototype.update = function(deltaTime) {
|
||||
this.timeSinceLastAlive += deltaTime;
|
||||
if (this.timeSinceLastAlive > ALIVE_PERIOD) {
|
||||
if (this.subscribed) {
|
||||
if (!this.isHired) {
|
||||
Messages.sendMessage(SERVICE_CHANNEL, packServiceMessage(MASTER_ID, AGENT_READY, this.agentUUID));
|
||||
//printDebug("Client Ready" + SERVICE_CHANNEL + AGENT_READY);
|
||||
} else {
|
||||
// Send alive beat
|
||||
printDebug("beat !");
|
||||
Messages.sendMessage(SERVICE_CHANNEL, packServiceMessage(MASTER_ID, AGENT_ALIVE, this.agentUUID));
|
||||
|
||||
// Listen for master beat
|
||||
this.numCyclesWithoutAlive++;
|
||||
if (this.numCyclesWithoutAlive > NUM_CYCLES_BEFORE_RESET) {
|
||||
printDebug("Master Lost, self firing Agent");
|
||||
this.fired();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.timeSinceLastAlive = 0;
|
||||
}
|
||||
};
|
||||
|
||||
AgentController.prototype.fired = function() {
|
||||
// clear the state first
|
||||
var wasHired = this.isHired;
|
||||
|
||||
// reset
|
||||
this._init();
|
||||
|
||||
// then custom fire if was hired
|
||||
if (wasHired) {
|
||||
this.onFired();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.AgentController = AgentController;
|
||||
|
||||
|
||||
|
||||
this.BROADCAST_AGENTS = BROADCAST_AGENTS;
|
||||
})();
|
||||
|
||||
|
||||
|
|
@ -10,20 +10,17 @@
|
|||
//
|
||||
|
||||
|
||||
var filename = "http://your.recording.url";
|
||||
var recordingFile = "http://your.recording.url";
|
||||
var playFromCurrentLocation = true;
|
||||
var loop = true;
|
||||
|
||||
Avatar.skeletonModelURL = "https://hifi-public.s3.amazonaws.com/marketplace/contents/e21c0b95-e502-4d15-8c41-ea2fc40f1125/3585ddf674869a67d31d5964f7b52de1.fst?1427169998";
|
||||
|
||||
// Set position here if playFromCurrentLocation is true
|
||||
Avatar.position = { x:1, y: 1, z: 1 };
|
||||
Avatar.orientation = Quat.fromPitchYawRollDegrees(0, 0, 0);
|
||||
Avatar.scale = 1.0;
|
||||
|
||||
Agent.isAvatar = true;
|
||||
|
||||
Avatar.loadRecording(filename);
|
||||
Recording.loadRecording(recordingFile);
|
||||
|
||||
count = 300; // This is necessary to wait for the audio mixer to connect
|
||||
function update(event) {
|
||||
|
@ -32,22 +29,18 @@ function update(event) {
|
|||
return;
|
||||
}
|
||||
if (count == 0) {
|
||||
Avatar.setPlayFromCurrentLocation(playFromCurrentLocation);
|
||||
Avatar.setPlayerLoop(loop);
|
||||
Avatar.setPlayerUseDisplayName(true);
|
||||
Avatar.setPlayerUseAttachments(true);
|
||||
Avatar.setPlayerUseHeadModel(false);
|
||||
Avatar.setPlayerUseSkeletonModel(true);
|
||||
Avatar.startPlaying();
|
||||
Avatar.play();
|
||||
Recording.setPlayFromCurrentLocation(playFromCurrentLocation);
|
||||
Recording.setPlayerLoop(loop);
|
||||
Recording.setPlayerUseDisplayName(true);
|
||||
Recording.setPlayerUseAttachments(true);
|
||||
Recording.setPlayerUseHeadModel(false);
|
||||
Recording.setPlayerUseSkeletonModel(true);
|
||||
Recording.startPlaying();
|
||||
Vec3.print("Playing from ", Avatar.position);
|
||||
|
||||
count--;
|
||||
}
|
||||
|
||||
if (Avatar.isPlaying()) {
|
||||
Avatar.play();
|
||||
} else {
|
||||
if (!Recording.isPlaying()) {
|
||||
Script.update.disconnect(update);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,18 +9,15 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
Script.include("./AgentPoolController.js");
|
||||
var agentController = new AgentController();
|
||||
|
||||
// Set the following variables to the values needed
|
||||
var commandChannel = "com.highfidelity.PlaybackChannel1";
|
||||
var clip_url = null;
|
||||
var playFromCurrentLocation = true;
|
||||
var useDisplayName = true;
|
||||
var useAttachments = true;
|
||||
var useAvatarModel = true;
|
||||
|
||||
// ID of the agent. Two agents can't have the same ID.
|
||||
var announceIDChannel = "com.highfidelity.playbackAgent.announceID";
|
||||
var UNKNOWN_AGENT_ID = -2;
|
||||
var id = UNKNOWN_AGENT_ID; // unknown until aknowledged
|
||||
|
||||
// Set position/orientation/scale here if playFromCurrentLocation is true
|
||||
Avatar.position = { x:0, y: 0, z: 0 };
|
||||
|
@ -28,16 +25,12 @@ Avatar.orientation = Quat.fromPitchYawRollDegrees(0, 0, 0);
|
|||
Avatar.scale = 1.0;
|
||||
|
||||
var totalTime = 0;
|
||||
var subscribed = false;
|
||||
var WAIT_FOR_AUDIO_MIXER = 1;
|
||||
|
||||
// Script. DO NOT MODIFY BEYOND THIS LINE.
|
||||
var DO_NOTHING = 0;
|
||||
var PLAY = 1;
|
||||
var PLAY_LOOP = 2;
|
||||
var STOP = 3;
|
||||
var SHOW = 4;
|
||||
var HIDE = 5;
|
||||
var LOAD = 6;
|
||||
|
||||
Recording.setPlayFromCurrentLocation(playFromCurrentLocation);
|
||||
|
@ -46,29 +39,15 @@ Recording.setPlayerUseAttachments(useAttachments);
|
|||
Recording.setPlayerUseHeadModel(false);
|
||||
Recording.setPlayerUseSkeletonModel(useAvatarModel);
|
||||
|
||||
function getAction(channel, message, senderID) {
|
||||
if(subscribed) {
|
||||
var command = JSON.parse(message);
|
||||
print("I'm the agent " + id + " and I received this: ID: " + command.id_key + " Action: " + command.action_key + " URL: " + command.clip_url_key);
|
||||
|
||||
if (command.id_key == id || command.id_key == -1) {
|
||||
if (command.action_key === 6) {
|
||||
clip_url = command.clip_url_key;
|
||||
}
|
||||
|
||||
action = command.action_key;
|
||||
print("That command was for me!");
|
||||
print("My clip is: " + clip_url);
|
||||
} else {
|
||||
action = DO_NOTHING;
|
||||
}
|
||||
|
||||
switch(action) {
|
||||
function agentCommand(command) {
|
||||
if(true) {
|
||||
|
||||
// var command = JSON.parse(message);
|
||||
print("I'm the agent " + this.agentUUID + " and I received this: Dest: " + command.dest_key + " Action: " + command.action_key + " URL: " + command.argument_key);
|
||||
|
||||
switch(command.action_key) {
|
||||
case PLAY:
|
||||
print("Play");
|
||||
if (!Agent.isAvatar) {
|
||||
Agent.isAvatar = true;
|
||||
}
|
||||
if (!Recording.isPlaying()) {
|
||||
Recording.startPlaying();
|
||||
}
|
||||
|
@ -76,9 +55,6 @@ function getAction(channel, message, senderID) {
|
|||
break;
|
||||
case PLAY_LOOP:
|
||||
print("Play loop");
|
||||
if (!Agent.isAvatar) {
|
||||
Agent.isAvatar = true;
|
||||
}
|
||||
if (!Recording.isPlaying()) {
|
||||
Recording.startPlaying();
|
||||
}
|
||||
|
@ -90,75 +66,60 @@ function getAction(channel, message, senderID) {
|
|||
Recording.stopPlaying();
|
||||
}
|
||||
break;
|
||||
case SHOW:
|
||||
print("Show");
|
||||
if (!Agent.isAvatar) {
|
||||
Agent.isAvatar = true;
|
||||
}
|
||||
break;
|
||||
case HIDE:
|
||||
print("Hide");
|
||||
if (Recording.isPlaying()) {
|
||||
Recording.stopPlaying();
|
||||
}
|
||||
Agent.isAvatar = false;
|
||||
break;
|
||||
case LOAD:
|
||||
print("Load");
|
||||
if(clip_url !== null) {
|
||||
Recording.loadRecording(clip_url);
|
||||
}
|
||||
break;
|
||||
case DO_NOTHING:
|
||||
{
|
||||
print("Load" + command.argument_key);
|
||||
print("Agent #" + command.dest_key + " loading clip URL: " + command.argument_key);
|
||||
Recording.loadRecording(command.argument_key);
|
||||
print("After Load" + command.argument_key);
|
||||
Recording.setPlayerTime(0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print("Unknown action: " + action);
|
||||
print("Unknown action: " + command.action_key);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (Recording.isPlaying()) {
|
||||
Recording.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function agentHired() {
|
||||
print("Agent Hired from playbackAgents.js");
|
||||
Agent.isAvatar = true;
|
||||
Recording.stopPlaying();
|
||||
Recording.setPlayerLoop(false);
|
||||
Recording.setPlayerTime(0);
|
||||
}
|
||||
|
||||
function agentFired() {
|
||||
print("Agent Fired from playbackAgents.js");
|
||||
Recording.stopPlaying();
|
||||
Recording.setPlayerTime(0);
|
||||
Recording.setPlayerLoop(false);
|
||||
Agent.isAvatar = false;
|
||||
}
|
||||
|
||||
|
||||
function update(deltaTime) {
|
||||
|
||||
totalTime += deltaTime;
|
||||
|
||||
if (totalTime > WAIT_FOR_AUDIO_MIXER) {
|
||||
if (!subscribed) {
|
||||
Messages.subscribe(commandChannel); // command channel
|
||||
Messages.subscribe(announceIDChannel); // id announce channel
|
||||
subscribed = true;
|
||||
print("I'm the agent and I am ready to receive!");
|
||||
}
|
||||
if (subscribed && id == UNKNOWN_AGENT_ID) {
|
||||
print("sending ready, id:" + id);
|
||||
Messages.sendMessage(announceIDChannel, "ready");
|
||||
if (!agentController.subscribed) {
|
||||
agentController.reset();
|
||||
agentController.onCommand = agentCommand;
|
||||
agentController.onHired = agentHired;
|
||||
agentController.onFired = agentFired;
|
||||
}
|
||||
}
|
||||
|
||||
agentController.update(deltaTime);
|
||||
}
|
||||
|
||||
|
||||
function scriptEnding() {
|
||||
|
||||
agentController.destroy();
|
||||
}
|
||||
|
||||
Messages.messageReceived.connect(function (channel, message, senderID) {
|
||||
if (channel == announceIDChannel && message != "ready") {
|
||||
// If I don't yet know if my ID has been recieved, then check to see if the master has acknowledged me
|
||||
if (id == UNKNOWN_AGENT_ID) {
|
||||
var parts = message.split(".");
|
||||
var agentID = parts[0];
|
||||
var agentIndex = parts[1];
|
||||
if (agentID == Agent.sessionUUID) {
|
||||
id = agentIndex;
|
||||
Messages.unsubscribe(announceIDChannel); // id announce channel
|
||||
}
|
||||
}
|
||||
}
|
||||
if (channel == commandChannel) {
|
||||
getAction(channel, message, senderID);
|
||||
}
|
||||
});
|
||||
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
|
|
@ -8,31 +8,24 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
Script.include("./AgentPoolController.js");
|
||||
|
||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
||||
var masterController = new MasterController();
|
||||
|
||||
var ac_number = 1; // This is the default number of ACs. Their ID need to be unique and between 0 (included) and ac_number (excluded)
|
||||
var names = new Array(); // It is possible to specify the name of the ACs in this array. ACs names ordered by IDs (Default name is "ACx", x = ID + 1))
|
||||
var channel = "com.highfidelity.PlaybackChannel1";
|
||||
var subscribed = false;
|
||||
var clip_url = null;
|
||||
var input_text = null;
|
||||
|
||||
var knownAgents = new Array; // We will add our known agents here when we discover them
|
||||
|
||||
// available playbackAgents will announce their sessionID here.
|
||||
var announceIDChannel = "com.highfidelity.playbackAgent.announceID";
|
||||
|
||||
// Script. DO NOT MODIFY BEYOND THIS LINE.
|
||||
Script.include("../libraries/toolBars.js");
|
||||
//Script.include("../libraries/toolBars.js");
|
||||
Script.include(HIFI_PUBLIC_BUCKET + "scripts/libraries/toolBars.js");
|
||||
// We want small icons
|
||||
Tool.IMAGE_HEIGHT /= 2;
|
||||
Tool.IMAGE_WIDTH /= 2;
|
||||
|
||||
var DO_NOTHING = 0;
|
||||
var PLAY = 1;
|
||||
var PLAY_LOOP = 2;
|
||||
var STOP = 3;
|
||||
var SHOW = 4;
|
||||
var HIDE = 5;
|
||||
var LOAD = 6;
|
||||
|
||||
var windowDimensions = Controller.getViewportDimensions();
|
||||
|
@ -44,241 +37,381 @@ var COLOR_MASTER = { red: 0, green: 0, blue: 0 };
|
|||
var TEXT_HEIGHT = 12;
|
||||
var TEXT_MARGIN = 3;
|
||||
|
||||
var toolBars = new Array();
|
||||
var nameOverlays = new Array();
|
||||
var onOffIcon = new Array();
|
||||
var playIcon = new Array();
|
||||
var playLoopIcon = new Array();
|
||||
var stopIcon = new Array();
|
||||
var loadIcon = new Array();
|
||||
|
||||
setupPlayback();
|
||||
|
||||
function setupPlayback() {
|
||||
ac_number = Window.prompt("Insert number of agents: ","1");
|
||||
if (ac_number === "" || ac_number === null) {
|
||||
ac_number = 1;
|
||||
}
|
||||
Messages.subscribe(channel);
|
||||
subscribed = true;
|
||||
setupToolBars();
|
||||
// Add new features to Actor class:
|
||||
Actor.prototype.destroy = function() {
|
||||
print("Actor.prototype.destroy");
|
||||
print("Need to fire myself" + this.agentID);
|
||||
masterController.fireAgent(this);
|
||||
}
|
||||
|
||||
function setupToolBars() {
|
||||
if (toolBars.length > 0) {
|
||||
print("Multiple calls to Recorder.js:setupToolBars()");
|
||||
return;
|
||||
}
|
||||
Tool.IMAGE_HEIGHT /= 2;
|
||||
Tool.IMAGE_WIDTH /= 2;
|
||||
|
||||
for (i = 0; i <= ac_number; i++) {
|
||||
toolBars.push(new ToolBar(0, 0, ToolBar.HORIZONTAL));
|
||||
toolBars[i].setBack((i == ac_number) ? COLOR_MASTER : COLOR_TOOL_BAR, ALPHA_OFF);
|
||||
|
||||
onOffIcon.push(toolBars[i].addTool({
|
||||
imageURL: TOOL_ICON_URL + "ac-on-off.svg",
|
||||
subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
x: 0, y: 0,
|
||||
width: Tool.IMAGE_WIDTH,
|
||||
height: Tool.IMAGE_HEIGHT,
|
||||
alpha: ALPHA_ON,
|
||||
visible: true
|
||||
}, true, true));
|
||||
|
||||
playIcon[i] = toolBars[i].addTool({
|
||||
imageURL: TOOL_ICON_URL + "play.svg",
|
||||
subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
width: Tool.IMAGE_WIDTH,
|
||||
height: Tool.IMAGE_HEIGHT,
|
||||
alpha: ALPHA_OFF,
|
||||
visible: true
|
||||
}, false);
|
||||
|
||||
var playLoopWidthFactor = 1.65;
|
||||
playLoopIcon[i] = toolBars[i].addTool({
|
||||
imageURL: TOOL_ICON_URL + "play-and-loop.svg",
|
||||
subImage: { x: 0, y: 0, width: playLoopWidthFactor * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
width: playLoopWidthFactor * Tool.IMAGE_WIDTH,
|
||||
height: Tool.IMAGE_HEIGHT,
|
||||
alpha: ALPHA_OFF,
|
||||
visible: true
|
||||
}, false);
|
||||
|
||||
stopIcon[i] = toolBars[i].addTool({
|
||||
imageURL: TOOL_ICON_URL + "recording-stop.svg",
|
||||
width: Tool.IMAGE_WIDTH,
|
||||
height: Tool.IMAGE_HEIGHT,
|
||||
alpha: ALPHA_OFF,
|
||||
visible: true
|
||||
}, false);
|
||||
|
||||
loadIcon[i] = toolBars[i].addTool({
|
||||
imageURL: TOOL_ICON_URL + "recording-upload.svg",
|
||||
width: Tool.IMAGE_WIDTH,
|
||||
height: Tool.IMAGE_HEIGHT,
|
||||
alpha: ALPHA_OFF,
|
||||
visible: true
|
||||
}, false);
|
||||
|
||||
nameOverlays.push(Overlays.addOverlay("text", {
|
||||
backgroundColor: { red: 0, green: 0, blue: 0 },
|
||||
font: { size: TEXT_HEIGHT },
|
||||
text: (i == ac_number) ? "Master" : i + ". " +
|
||||
((i < names.length) ? names[i] :
|
||||
"AC" + i),
|
||||
x: 0, y: 0,
|
||||
width: toolBars[i].width + ToolBar.SPACING,
|
||||
height: TEXT_HEIGHT + TEXT_MARGIN,
|
||||
leftMargin: TEXT_MARGIN,
|
||||
topMargin: TEXT_MARGIN,
|
||||
alpha: ALPHA_OFF,
|
||||
backgroundAlpha: ALPHA_OFF,
|
||||
visible: true
|
||||
}));
|
||||
|
||||
Actor.prototype.resetClip = function(clipURL, onLoadClip) {
|
||||
this.clipURL = clipURL;
|
||||
this.onLoadClip = onLoadClip;
|
||||
if (this.isConnected()) {
|
||||
this.onLoadClip(this);
|
||||
}
|
||||
}
|
||||
|
||||
function sendCommand(id, action) {
|
||||
if (action === SHOW) {
|
||||
toolBars[id].selectTool(onOffIcon[id], false);
|
||||
toolBars[id].setAlpha(ALPHA_ON, playIcon[id]);
|
||||
toolBars[id].setAlpha(ALPHA_ON, playLoopIcon[id]);
|
||||
toolBars[id].setAlpha(ALPHA_ON, stopIcon[id]);
|
||||
toolBars[id].setAlpha(ALPHA_ON, loadIcon[id]);
|
||||
} else if (action === HIDE) {
|
||||
toolBars[id].selectTool(onOffIcon[id], true);
|
||||
toolBars[id].setAlpha(ALPHA_OFF, playIcon[id]);
|
||||
toolBars[id].setAlpha(ALPHA_OFF, playLoopIcon[id]);
|
||||
toolBars[id].setAlpha(ALPHA_OFF, stopIcon[id]);
|
||||
toolBars[id].setAlpha(ALPHA_OFF, loadIcon[id]);
|
||||
} else if (toolBars[id].toolSelected(onOffIcon[id])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (id == (toolBars.length - 1)) {
|
||||
id = -1; // Master command becomes broadcast.
|
||||
}
|
||||
|
||||
var message = {
|
||||
id_key: id,
|
||||
action_key: action,
|
||||
clip_url_key: clip_url
|
||||
};
|
||||
|
||||
if(subscribed){
|
||||
Messages.sendMessage(channel, JSON.stringify(message));
|
||||
print("Message sent!");
|
||||
clip_url = null;
|
||||
|
||||
Actor.prototype.onMousePressEvent = function(clickedOverlay) {
|
||||
if (this.playIcon === this.toolbar.clicked(clickedOverlay, false)) {
|
||||
masterController.sendCommand(this.agentID, PLAY);
|
||||
} else if (this.playLoopIcon === this.toolbar.clicked(clickedOverlay, false)) {
|
||||
masterController.sendCommand(this.agentID, PLAY_LOOP);
|
||||
} else if (this.stopIcon === this.toolbar.clicked(clickedOverlay, false)) {
|
||||
masterController.sendCommand(this.agentID, STOP);
|
||||
} else if (this.nameOverlay === clickedOverlay) {
|
||||
print("Actor: " + JSON.stringify(this));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function mousePressEvent(event) {
|
||||
clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
|
||||
Actor.prototype._buildUI = function() {
|
||||
print("Actor.prototype._buildUI = " + JSON.stringify(this));
|
||||
|
||||
// Check master control
|
||||
var i = toolBars.length - 1;
|
||||
if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||
if (toolBars[i].toolSelected(onOffIcon[i])) {
|
||||
sendCommand(i, SHOW);
|
||||
} else {
|
||||
sendCommand(i, HIDE);
|
||||
}
|
||||
} else if (playIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||
sendCommand(i, PLAY);
|
||||
} else if (playLoopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||
sendCommand(i, PLAY_LOOP);
|
||||
} else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||
sendCommand(i, STOP);
|
||||
} else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||
this.toolbar = new ToolBar(0, 0, ToolBar.HORIZONTAL);
|
||||
|
||||
this.toolbar.setBack(COLOR_TOOL_BAR, ALPHA_OFF);
|
||||
|
||||
this.playIcon = this.toolbar.addTool({
|
||||
imageURL: TOOL_ICON_URL + "play.svg",
|
||||
subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
width: Tool.IMAGE_WIDTH,
|
||||
height: Tool.IMAGE_HEIGHT,
|
||||
alpha: ALPHA_OFF,
|
||||
visible: true
|
||||
}, false);
|
||||
|
||||
var playLoopWidthFthis = 1.65;
|
||||
this.playLoopIcon = this.toolbar.addTool({
|
||||
imageURL: TOOL_ICON_URL + "play-and-loop.svg",
|
||||
subImage: { x: 0, y: 0, width: playLoopWidthFthis * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
width: playLoopWidthFthis * Tool.IMAGE_WIDTH,
|
||||
height: Tool.IMAGE_HEIGHT,
|
||||
alpha: ALPHA_OFF,
|
||||
visible: true
|
||||
}, false);
|
||||
|
||||
this.stopIcon = this.toolbar.addTool({
|
||||
imageURL: TOOL_ICON_URL + "recording-stop.svg",
|
||||
width: Tool.IMAGE_WIDTH,
|
||||
height: Tool.IMAGE_HEIGHT,
|
||||
alpha: ALPHA_OFF,
|
||||
visible: true
|
||||
}, false);
|
||||
|
||||
this.nameOverlay = Overlays.addOverlay("text", {
|
||||
backgroundColor: { red: 0, green: 0, blue: 0 },
|
||||
font: { size: TEXT_HEIGHT },
|
||||
text: "AC offline",
|
||||
x: 0, y: 0,
|
||||
width: this.toolbar.width + ToolBar.SPACING,
|
||||
height: TEXT_HEIGHT + TEXT_MARGIN,
|
||||
leftMargin: TEXT_MARGIN,
|
||||
topMargin: TEXT_MARGIN,
|
||||
alpha: ALPHA_OFF,
|
||||
backgroundAlpha: ALPHA_OFF,
|
||||
visible: true
|
||||
});
|
||||
}
|
||||
|
||||
Actor.prototype._destroyUI = function() {
|
||||
this.toolbar.cleanup();
|
||||
Overlays.deleteOverlay(this.nameOverlay);
|
||||
}
|
||||
|
||||
Actor.prototype.moveUI = function(pos) {
|
||||
var textSize = TEXT_HEIGHT + 2 * TEXT_MARGIN;
|
||||
this.toolbar.move(pos.x, pos.y);
|
||||
|
||||
Overlays.editOverlay(this.nameOverlay, {
|
||||
x: this.toolbar.x - ToolBar.SPACING,
|
||||
y: this.toolbar.y - textSize
|
||||
});
|
||||
}
|
||||
|
||||
Director = function() {
|
||||
this.actors = new Array();
|
||||
this.toolbar = null;
|
||||
this._buildUI();
|
||||
this.requestPerformanceLoad = false;
|
||||
this.performanceURL = "";
|
||||
};
|
||||
|
||||
Director.prototype.destroy = function () {
|
||||
print("Director.prototype.destroy")
|
||||
this.clearActors();
|
||||
this.toolbar.cleanup();
|
||||
Overlays.deleteOverlay(this.nameOverlay);
|
||||
}
|
||||
|
||||
Director.prototype.clearActors = function () {
|
||||
print("Director.prototype.clearActors")
|
||||
while (this.actors.length > 0) {
|
||||
this.actors.pop().destroy();
|
||||
}
|
||||
|
||||
this.actors = new Array();// Brand new actors
|
||||
}
|
||||
|
||||
Director.prototype._buildUI = function () {
|
||||
this.toolbar = new ToolBar(0, 0, ToolBar.HORIZONTAL);
|
||||
|
||||
this.toolbar.setBack(COLOR_MASTER, ALPHA_OFF);
|
||||
|
||||
this.onOffIcon = this.toolbar.addTool({
|
||||
imageURL: TOOL_ICON_URL + "ac-on-off.svg",
|
||||
subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
x: 0, y: 0,
|
||||
width: Tool.IMAGE_WIDTH,
|
||||
height: Tool.IMAGE_HEIGHT,
|
||||
alpha: ALPHA_ON,
|
||||
visible: true
|
||||
}, true, true);
|
||||
|
||||
this.playIcon = this.toolbar.addTool({
|
||||
imageURL: TOOL_ICON_URL + "play.svg",
|
||||
subImage: { x: 0, y: 0, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
width: Tool.IMAGE_WIDTH,
|
||||
height: Tool.IMAGE_HEIGHT,
|
||||
alpha: ALPHA_OFF,
|
||||
visible: true
|
||||
}, false);
|
||||
|
||||
var playLoopWidthFthis = 1.65;
|
||||
this.playLoopIcon = this.toolbar.addTool({
|
||||
imageURL: TOOL_ICON_URL + "play-and-loop.svg",
|
||||
subImage: { x: 0, y: 0, width: playLoopWidthFthis * Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
|
||||
width: playLoopWidthFthis * Tool.IMAGE_WIDTH,
|
||||
height: Tool.IMAGE_HEIGHT,
|
||||
alpha: ALPHA_OFF,
|
||||
visible: true
|
||||
}, false);
|
||||
|
||||
this.stopIcon = this.toolbar.addTool({
|
||||
imageURL: TOOL_ICON_URL + "recording-stop.svg",
|
||||
width: Tool.IMAGE_WIDTH,
|
||||
height: Tool.IMAGE_HEIGHT,
|
||||
alpha: ALPHA_OFF,
|
||||
visible: true
|
||||
}, false);
|
||||
|
||||
this.loadIcon = this.toolbar.addTool({
|
||||
imageURL: TOOL_ICON_URL + "recording-upload.svg",
|
||||
width: Tool.IMAGE_WIDTH,
|
||||
height: Tool.IMAGE_HEIGHT,
|
||||
alpha: ALPHA_OFF,
|
||||
visible: true
|
||||
}, false);
|
||||
|
||||
this.nameOverlay = Overlays.addOverlay("text", {
|
||||
backgroundColor: { red: 0, green: 0, blue: 0 },
|
||||
font: { size: TEXT_HEIGHT },
|
||||
text: "Master",
|
||||
x: 0, y: 0,
|
||||
width: this.toolbar.width + ToolBar.SPACING,
|
||||
height: TEXT_HEIGHT + TEXT_MARGIN,
|
||||
leftMargin: TEXT_MARGIN,
|
||||
topMargin: TEXT_MARGIN,
|
||||
alpha: ALPHA_OFF,
|
||||
backgroundAlpha: ALPHA_OFF,
|
||||
visible: true
|
||||
});
|
||||
}
|
||||
|
||||
Director.prototype.onMousePressEvent = function(clickedOverlay) {
|
||||
if (this.playIcon === this.toolbar.clicked(clickedOverlay, false)) {
|
||||
print("master play");
|
||||
masterController.sendCommand(BROADCAST_AGENTS, PLAY);
|
||||
} else if (this.onOffIcon === this.toolbar.clicked(clickedOverlay, false)) {
|
||||
this.clearActors();
|
||||
return true;
|
||||
} else if (this.playLoopIcon === this.toolbar.clicked(clickedOverlay, false)) {
|
||||
masterController.sendCommand(BROADCAST_AGENTS, PLAY_LOOP);
|
||||
} else if (this.stopIcon === this.toolbar.clicked(clickedOverlay, false)) {
|
||||
masterController.sendCommand(BROADCAST_AGENTS, STOP);
|
||||
} else if (this.loadIcon === this.toolbar.clicked(clickedOverlay, false)) {
|
||||
input_text = Window.prompt("Insert the url of the clip: ","");
|
||||
if (!(input_text === "" || input_text === null)) {
|
||||
clip_url = input_text;
|
||||
sendCommand(i, LOAD);
|
||||
}
|
||||
print("Performance file ready to be loaded url = " + input_text);
|
||||
this.requestPerformanceLoad = true;
|
||||
this.performanceURL = input_text;
|
||||
}
|
||||
} else if (this.nameOverlay === clickedOverlay) {
|
||||
print("Director: " + JSON.stringify(this));
|
||||
} else {
|
||||
// Check individual controls
|
||||
for (i = 0; i < ac_number; i++) {
|
||||
if (onOffIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||
if (toolBars[i].toolSelected(onOffIcon[i], false)) {
|
||||
sendCommand(i, SHOW);
|
||||
} else {
|
||||
sendCommand(i, HIDE);
|
||||
}
|
||||
} else if (playIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||
sendCommand(i, PLAY);
|
||||
} else if (playLoopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||
sendCommand(i, PLAY_LOOP);
|
||||
} else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||
sendCommand(i, STOP);
|
||||
} else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
|
||||
input_text = Window.prompt("Insert the url of the clip: ","");
|
||||
if (!(input_text === "" || input_text === null)) {
|
||||
clip_url = input_text;
|
||||
sendCommand(i, LOAD);
|
||||
}
|
||||
} else {
|
||||
|
||||
for (var i = 0; i < this.actors.length; i++) {
|
||||
if (this.actors[i].onMousePressEvent(clickedOverlay)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false; // nothing clicked from our known overlays
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Director.prototype.moveUI = function(pos) {
|
||||
var textSize = TEXT_HEIGHT + 2 * TEXT_MARGIN;
|
||||
var relative = { x: pos.x, y: pos.y + (this.actors.length + 1) * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize) };
|
||||
|
||||
this.toolbar.move(relative.x, windowDimensions.y - relative.y);
|
||||
Overlays.editOverlay(this.nameOverlay, {
|
||||
x: this.toolbar.x - ToolBar.SPACING,
|
||||
y: this.toolbar.y - textSize
|
||||
});
|
||||
|
||||
for (var i = 0; i < this.actors.length; i++) {
|
||||
this.actors[i].moveUI({x: relative.x, y: windowDimensions.y - relative.y +
|
||||
(i + 1) * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize)});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Director.prototype.reloadPerformance = function() {
|
||||
this.requestPerformanceLoad = false;
|
||||
|
||||
if (this.performanceURL[0] == '{') {
|
||||
var jsonPerformance = JSON.parse(this.performanceURL);
|
||||
this.onPerformanceLoaded(jsonPerformance);
|
||||
} else {
|
||||
|
||||
var urlpartition = this.performanceURL.split(".");
|
||||
print(urlpartition[0]);
|
||||
print(urlpartition[1]);
|
||||
|
||||
if ((urlpartition.length > 1) && (urlpartition[urlpartition.length - 1] === "hfr")) {
|
||||
print("detected a unique clip url");
|
||||
var oneClipPerformance = new Object();
|
||||
oneClipPerformance.avatarClips = new Array();
|
||||
oneClipPerformance.avatarClips[0] = input_text;
|
||||
|
||||
print(JSON.stringify(oneClipPerformance));
|
||||
|
||||
// we make a local simple performance file with a single clip and pipe in directly
|
||||
this.onPerformanceLoaded(oneClipPerformance);
|
||||
return true;
|
||||
} else {
|
||||
// FIXME: I cannot pass directly this.onPerformanceLoaded, is that exepected ?
|
||||
var localThis = this;
|
||||
Assets.downloadData(input_text, function(data) { localThis.onPerformanceLoaded(JSON.parse(data)); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Director.prototype.onPerformanceLoaded = function(performanceJSON) {
|
||||
// First fire all the current actors
|
||||
this.clearActors();
|
||||
|
||||
print("Director.prototype.onPerformanceLoaded = " + JSON.stringify(performanceJSON));
|
||||
if (performanceJSON.avatarClips != null) {
|
||||
var numClips = performanceJSON.avatarClips.length;
|
||||
print("Found " + numClips + "in the performance file, and currently using " + this.actors.length + " actor(s)");
|
||||
|
||||
for (var i = 0; i < numClips; i++) {
|
||||
this.hireActor(performanceJSON.avatarClips[i]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Director.prototype.hireActor = function(clipURL) {
|
||||
print("new actor = " + this.actors.length );
|
||||
var newActor = new Actor();
|
||||
newActor.clipURL = null;
|
||||
newActor.onLoadClip = function(clip) {};
|
||||
|
||||
var localThis = this;
|
||||
newActor.onHired = function(actor) {
|
||||
print("agent hired from Director! " + actor.agentID)
|
||||
Overlays.editOverlay(actor.nameOverlay, {
|
||||
text: "AC " + actor.agentID,
|
||||
backgroundColor: { red: 0, green: 255, blue: 0 }
|
||||
});
|
||||
|
||||
if (actor.clipURL != null) {
|
||||
print("agent hired, calling load clip for url " + actor.clipURL);
|
||||
actor.onLoadClip(actor);
|
||||
}
|
||||
};
|
||||
|
||||
newActor.onFired = function(actor) {
|
||||
print("agent fired from playbackMaster! " + actor.agentID);
|
||||
var index = localThis.actors.indexOf(actor);
|
||||
if (index >= 0) {
|
||||
localThis.actors.splice(index, 1);
|
||||
}
|
||||
|
||||
actor._destroyUI();
|
||||
|
||||
actor.destroy();
|
||||
moveUI();
|
||||
}
|
||||
|
||||
newActor.resetClip(clipURL, function(actor) {
|
||||
print("Load clip for agent" + actor.agentID + " calling load clip for url " + actor.clipURL);
|
||||
masterController.sendCommand(actor.agentID, LOAD, actor.clipURL);
|
||||
});
|
||||
|
||||
|
||||
masterController.hireAgent(newActor);
|
||||
newActor._buildUI();
|
||||
|
||||
this.actors.push(newActor);
|
||||
|
||||
moveUI();
|
||||
}
|
||||
|
||||
|
||||
masterController.reset();
|
||||
var director = new Director();
|
||||
|
||||
moveUI();
|
||||
|
||||
|
||||
function mousePressEvent(event) {
|
||||
print("mousePressEvent");
|
||||
clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y });
|
||||
|
||||
// Check director and actors
|
||||
director.onMousePressEvent(clickedOverlay);
|
||||
}
|
||||
|
||||
function moveUI() {
|
||||
var textSize = TEXT_HEIGHT + 2 * TEXT_MARGIN;
|
||||
var relative = { x: 70, y: 75 + (ac_number) * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize) };
|
||||
|
||||
for (i = 0; i <= ac_number; i++) {
|
||||
toolBars[i].move(relative.x,
|
||||
windowDimensions.y - relative.y +
|
||||
i * (Tool.IMAGE_HEIGHT + ToolBar.SPACING + textSize));
|
||||
|
||||
Overlays.editOverlay(nameOverlays[i], {
|
||||
x: toolBars[i].x - ToolBar.SPACING,
|
||||
y: toolBars[i].y - textSize
|
||||
});
|
||||
}
|
||||
director.moveUI({ x: 70, y: 75});
|
||||
|
||||
}
|
||||
|
||||
function update() {
|
||||
function update(deltaTime) {
|
||||
var newDimensions = Controller.getViewportDimensions();
|
||||
if (windowDimensions.x != newDimensions.x ||
|
||||
windowDimensions.y != newDimensions.y) {
|
||||
windowDimensions = newDimensions;
|
||||
moveUI();
|
||||
}
|
||||
|
||||
if (director.requestPerformanceLoad) {
|
||||
print("reloadPerformance " + director.performanceURL);
|
||||
director.reloadPerformance();
|
||||
}
|
||||
|
||||
masterController.update(deltaTime);
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
for (i = 0; i <= ac_number; i++) {
|
||||
toolBars[i].cleanup();
|
||||
Overlays.deleteOverlay(nameOverlays[i]);
|
||||
}
|
||||
|
||||
if (subscribed) {
|
||||
Messages.unsubscribe(channel);
|
||||
}
|
||||
Messages.unsubscribe(announceIDChannel);
|
||||
print("cleanup")
|
||||
director.destroy();
|
||||
masterController.destroy();
|
||||
}
|
||||
|
||||
Controller.mousePressEvent.connect(mousePressEvent);
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
|
||||
|
||||
Messages.subscribe(announceIDChannel);
|
||||
Messages.messageReceived.connect(function (channel, message, senderID) {
|
||||
if (channel == announceIDChannel && message == "ready") {
|
||||
// check to see if we know about this agent
|
||||
if (knownAgents.indexOf(senderID) < 0) {
|
||||
var indexOfNewAgent = knownAgents.length;
|
||||
knownAgents[indexOfNewAgent] = senderID;
|
||||
var acknowledgeMessage = senderID + "." + indexOfNewAgent;
|
||||
Messages.sendMessage(announceIDChannel, acknowledgeMessage);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
moveUI();
|
105
examples/baseball/assets.json
Normal file
105
examples/baseball/assets.json
Normal file
|
@ -0,0 +1,105 @@
|
|||
{
|
||||
"assets": {
|
||||
"crowd-boos.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/crowd-boos.wav",
|
||||
"atp_url": "atp:c632c92b166ade60aa16b23ff1dfdf712856caeb83bd9311980b2d5edac821af.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
|
||||
},
|
||||
"crowd-cheers-organ.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/crowd-cheers-organ.wav",
|
||||
"atp_url": "atp:b8044401a846ed29f881a0b9b80cf1ba41f26327180c28fc9c70d144f9b70045.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
|
||||
},
|
||||
"crowd-medium.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/crowd-medium.wav",
|
||||
"atp_url": "atp:0821bf2ac60dd2f356dfdd948e8bb89c23984dc3584612f6c815765154f02cae.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
|
||||
},
|
||||
"baseball-hitting-bat-1.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-1.wav",
|
||||
"atp_url": "atp:6f0b691a0c9c9ece6557d97fe242b1faec4020fe26efc9c17327993b513c5fe5.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: https://www.freesound.org/people/SocializedArtist45/"
|
||||
},
|
||||
"baseball-hitting-bat-set-1.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-set-1.wav",
|
||||
"atp_url": "atp:5be5806205158ebdc5c3623ceb7ae73315028b51ffeae24292aff7042e3fa6a9.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: CGEffex - https://www.freesound.org/people/CGEffex/"
|
||||
},
|
||||
"baseball-hitting-bat-set-2.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-set-2.wav",
|
||||
"atp_url": "atp:e68661374e2145c480809c26134782aad11e0de456c7802170c7abccc4028873.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: CGEffex - https://www.freesound.org/people/CGEffex/"
|
||||
},
|
||||
"baseball-hitting-bat-set-3.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-set-3.wav",
|
||||
"atp_url": "atp:787e3c9af17dd3929527787176ede83d6806260e63ddd5a4cef48cd22e32c6f7.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: CGEffex - https://www.freesound.org/people/CGEffex/"
|
||||
},
|
||||
"baseball-hitting-bat-set-4.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/baseball-hitting-bat-set-4.wav",
|
||||
"atp_url": "atp:fc65383431a6238c7a4749f0f6f061f75a604ed5e17d775ab1b2955609e67ebb.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: CGEffex - https://www.freesound.org/people/CGEffex/"
|
||||
},
|
||||
"chatter-loop.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/chatter-loop.wav",
|
||||
"atp_url": "atp:d9978e693035d4e2b5c7b546c8cccfb2dde5677834d9eed5206ccb2da55b4732.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
|
||||
},
|
||||
"zorba-organ.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/zorba-organ.wav",
|
||||
"atp_url": "atp:1ee58f4d929fdef7c2989cd8be964952a24cdd653d80f57b6a89a2ae8e3029e1.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
|
||||
},
|
||||
"charge-organ.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/charge-organ.wav",
|
||||
"atp_url": "atp:cefaba2d5f1a378382ee046716bffcf6b6a40676649b23a1e81a996efe22d7d3.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
|
||||
},
|
||||
"ball-game.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/ball-game.wav",
|
||||
"atp_url": "atp:fb41e37f8f8f7b78e546ac78800df6e39edaa09b2df4bfa0afdd8d749dac38b8.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
|
||||
},
|
||||
"clapping.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/clapping.wav",
|
||||
"atp_url": "atp:44a83a788ccfd2924e35c902c34808b24dbd0309d000299ce01a355f91cf8115.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: Alyssa Galindo - http://freesound.org/people/AshFox/"
|
||||
},
|
||||
"pop1.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/pop1.wav",
|
||||
"atp_url": "atp:a2bf79c95fe74c2c6c9188acc7230f7cd1b0f6008f2c81954ecd93eca0497ec6.wav"
|
||||
},
|
||||
"pop2.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/pop2.wav",
|
||||
"atp_url": "atp:901067ebc2cda4c0d86ec02fcca2ed901e85f9097ad68bbde78b4cad8eaf2ed7.wav"
|
||||
},
|
||||
"pop3.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/pop3.wav",
|
||||
"atp_url": "atp:830312930577cb1ea36ba2d743e957debbacceb441b20addead5a6faa05a3771.wav"
|
||||
},
|
||||
"pop4.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/pop4.wav",
|
||||
"atp_url": "atp:62e80d0a9f084cf731bcc66ca6e9020ee88587417071a281eee3167307b53560.wav"
|
||||
},
|
||||
"fire1.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/fire1.wav",
|
||||
"atp_url": "atp:ee6afe565576c4546c6d6cd89c1af532484c9b60ab30574d6b40c2df022f7260.wav",
|
||||
"attribution": "CC BY 3.0 - Credir: dcsimon - https://www.freesound.org/people/dcsimon/sounds/160720/"
|
||||
},
|
||||
"fire2.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/fire2.wav",
|
||||
"atp_url": "atp:91ef19ba1c78be82d3fd06530cd05ceb90d1e75f4204c66819c208c55da049ef.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: dcsimon - https://www.freesound.org/people/dcsimon/sounds/160720/"
|
||||
},
|
||||
"fire3.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/fire3.wav",
|
||||
"atp_url": "atp:ee56993daf775012cf49293bfd5971eec7e5c396642f8bfbea902ba8f47b56cd.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: dcsimon - https://www.freesound.org/people/dcsimon/sounds/160720/"
|
||||
},
|
||||
"fire4.wav": {
|
||||
"s3_url": "http://hifi-public.s3.amazonaws.com/birarda/baseball/fireworks/fire4.wav",
|
||||
"atp_url": "atp:37775d267f00f82242a7e7f61f3f3d7bf64a54c5a3799e7f2540fa5f6b79bd02.wav",
|
||||
"attribution": "CC BY 3.0 - Credit: dcsimon - https://www.freesound.org/people/dcsimon/sounds/160720/"
|
||||
}
|
||||
}
|
||||
}
|
43
examples/baseball/baseballCrowd.js
Normal file
43
examples/baseball/baseballCrowd.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// baseballCrowd.js
|
||||
// examples/acScripts
|
||||
//
|
||||
// Created by Stephen Birarda on 10/20/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var chatter = SoundCache.getSound("atp:d9978e693035d4e2b5c7b546c8cccfb2dde5677834d9eed5206ccb2da55b4732.wav");
|
||||
var extras = [
|
||||
SoundCache.getSound("atp:1ee58f4d929fdef7c2989cd8be964952a24cdd653d80f57b6a89a2ae8e3029e1.wav"), // zorba
|
||||
SoundCache.getSound("atp:cefaba2d5f1a378382ee046716bffcf6b6a40676649b23a1e81a996efe22d7d3.wav"), // charge
|
||||
SoundCache.getSound("atp:fb41e37f8f8f7b78e546ac78800df6e39edaa09b2df4bfa0afdd8d749dac38b8.wav"), // take me out to the ball game
|
||||
SoundCache.getSound("atp:44a83a788ccfd2924e35c902c34808b24dbd0309d000299ce01a355f91cf8115.wav") // clapping
|
||||
];
|
||||
|
||||
var CHATTER_VOLUME = 0.20
|
||||
var EXTRA_VOLUME = 0.25
|
||||
|
||||
function playChatter() {
|
||||
if (chatter.downloaded && !chatter.isPlaying) {
|
||||
Audio.playSound(chatter, { loop: true, volume: CHATTER_VOLUME });
|
||||
}
|
||||
}
|
||||
|
||||
chatter.ready.connect(playChatter);
|
||||
|
||||
var currentInjector = null;
|
||||
|
||||
function playRandomExtras() {
|
||||
if ((!currentInjector || !currentInjector.isPlaying) && (Math.random() < (1.0 / 1800.0))) {
|
||||
// play a random extra sound about every 30s
|
||||
currentInjector = Audio.playSound(
|
||||
extras[Math.floor(Math.random() * extras.length)],
|
||||
{ volume: EXTRA_VOLUME }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Script.update.connect(playRandomExtras);
|
43
examples/baseball/bat.js
Normal file
43
examples/baseball/bat.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// bat.js
|
||||
// examples/baseball/
|
||||
//
|
||||
// Created by Ryan Huffman on Nov 9, 2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() {
|
||||
Script.include("pitching.js");
|
||||
|
||||
var pitchingMachine = null;
|
||||
|
||||
this.pitchAndHideAvatar = function() {
|
||||
if (!pitchingMachine) {
|
||||
pitchingMachine = getOrCreatePitchingMachine();
|
||||
Script.update.connect(function(dt) { pitchingMachine.update(dt); });
|
||||
}
|
||||
pitchingMachine.start();
|
||||
MyAvatar.shouldRenderLocally = false;
|
||||
};
|
||||
|
||||
this.startNearGrab = function() {
|
||||
// send the avatar to the baseball location so that they're ready to bat
|
||||
location = "/baseball"
|
||||
|
||||
this.pitchAndHideAvatar()
|
||||
};
|
||||
|
||||
this.continueNearGrab = function() {
|
||||
this.pitchAndHideAvatar()
|
||||
};
|
||||
|
||||
this.releaseGrab = function() {
|
||||
if (pitchingMachine) {
|
||||
pitchingMachine.stop();
|
||||
}
|
||||
MyAvatar.shouldRenderLocally = true;
|
||||
};
|
||||
});
|
76
examples/baseball/createBatButton.js
Normal file
76
examples/baseball/createBatButton.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// createBatButton.js
|
||||
// examples/baseball/moreBatsButton.js
|
||||
//
|
||||
// Created by Stephen Birarda on 10/28/2015.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function(){
|
||||
this.clickReleaseOnEntity = function(entityID, mouseEvent) {
|
||||
if (!mouseEvent.isLeftButton) {
|
||||
return;
|
||||
}
|
||||
this.dropBats();
|
||||
};
|
||||
this.startNearTrigger = function() {
|
||||
this.dropBats();
|
||||
};
|
||||
this.startFarTrigger = function() {
|
||||
this.dropBats();
|
||||
};
|
||||
this.dropBats = function() {
|
||||
// if the bat box is near us, grab it's position
|
||||
var nearby = Entities.findEntities(this.position, 20);
|
||||
|
||||
nearby.forEach(function(id) {
|
||||
var properties = Entities.getEntityProperties(id, ["name", "position"]);
|
||||
if (properties.name && properties.name == "Bat Box") {
|
||||
boxPosition = properties.position;
|
||||
}
|
||||
});
|
||||
|
||||
var BAT_DROP_HEIGHT = 2.0;
|
||||
|
||||
var dropPosition;
|
||||
|
||||
if (!boxPosition) {
|
||||
// we got no bat box position, drop in front of the avatar instead
|
||||
} else {
|
||||
// drop the bat above the bat box
|
||||
dropPosition = Vec3.sum(boxPosition, { x: 0.0, y: BAT_DROP_HEIGHT, z: 0.0});
|
||||
}
|
||||
|
||||
var BAT_MODEL = "atp:c47deaae09cca927f6bc9cca0e8bbe77fc618f8c3f2b49899406a63a59f885cb.fbx";
|
||||
var BAT_COLLISION_HULL = "atp:9eafceb7510c41d50661130090de7e0632aa4da236ebda84a0059a4be2130e0c.obj";
|
||||
var SCRIPT_URL = "http://rawgit.com/birarda/hifi/baseball/examples/baseball/bat.js"
|
||||
|
||||
var batUserData = {
|
||||
grabbableKey: {
|
||||
spatialKey: {
|
||||
leftRelativePosition: { x: 0.9, y: 0.05, z: -0.05 },
|
||||
rightRelativePosition: { x: 0.9, y: 0.05, z: 0.05 },
|
||||
relativeRotation: Quat.fromPitchYawRollDegrees(0, 0, 45)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add the fresh bat at the drop position
|
||||
var bat = Entities.addEntity({
|
||||
name: 'Bat',
|
||||
type: "Model",
|
||||
modelURL: BAT_MODEL,
|
||||
position: dropPosition,
|
||||
compoundShapeURL: BAT_COLLISION_HULL,
|
||||
collisionsWillMove: true,
|
||||
velocity: { x: 0, y: 0.05, z: 0}, // workaround for gravity not taking effect on add
|
||||
gravity: { x: 0, y: -9.81, z: 0},
|
||||
rotation: Quat.fromPitchYawRollDegrees(0.0, 0.0, -90.0),
|
||||
script: SCRIPT_URL,
|
||||
userData: JSON.stringify(batUserData)
|
||||
});
|
||||
};
|
||||
});
|
138
examples/baseball/firework.js
Normal file
138
examples/baseball/firework.js
Normal file
|
@ -0,0 +1,138 @@
|
|||
//
|
||||
// firework.js
|
||||
// examples/baseball/
|
||||
//
|
||||
// Created by Ryan Huffman on Nov 9, 2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
Script.include("utils.js");
|
||||
|
||||
var emitters = [];
|
||||
|
||||
var smokeTrailSettings = {
|
||||
"name":"ParticlesTest Emitter",
|
||||
"type": "ParticleEffect",
|
||||
"color":{"red":205,"green":84.41176470588235,"blue":84.41176470588235},
|
||||
"maxParticles":1000,
|
||||
"velocity": { x: 0, y: 18.0, z: 0 },
|
||||
"lifetime": 20,
|
||||
"lifespan":3,
|
||||
"emitRate":100,
|
||||
"emitSpeed":0.5,
|
||||
"speedSpread":0,
|
||||
"emitOrientation":{"x":0,"y":0,"z":0,"w":1},
|
||||
"emitDimensions":{"x":0,"y":0,"z":0},
|
||||
"emitRadiusStart":0.5,
|
||||
"polarStart":1,
|
||||
"polarFinish":1,
|
||||
"azimuthStart":0,
|
||||
"azimuthFinish":0,
|
||||
"emitAcceleration":{"x":0,"y":-0.70000001192092896,"z":0},
|
||||
"accelerationSpread":{"x":0,"y":0,"z":0},
|
||||
"particleRadius":0.03999999910593033,
|
||||
"radiusSpread":0,
|
||||
"radiusStart":0.13999999910593033,
|
||||
"radiusFinish":0.14,
|
||||
"colorSpread":{"red":0,"green":0,"blue":0},
|
||||
"colorStart":{"red":255,"green":255,"blue":255},
|
||||
"colorFinish":{"red":255,"green":255,"blue":255},
|
||||
"alpha":1,
|
||||
"alphaSpread":0,
|
||||
"alphaStart":0,
|
||||
"alphaFinish":1,
|
||||
"textures":"https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png"
|
||||
};
|
||||
|
||||
var fireworkSettings = {
|
||||
"name":"ParticlesTest Emitter",
|
||||
"type": "ParticleEffect",
|
||||
"color":{"red":205,"green":84.41176470588235,"blue":84.41176470588235},
|
||||
"maxParticles":1000,
|
||||
"lifetime": 20,
|
||||
"lifespan":4,
|
||||
"emitRate":1000,
|
||||
"emitSpeed":1.5,
|
||||
"speedSpread":1.0,
|
||||
"emitOrientation":{"x":-0.2,"y":0,"z":0,"w":0.7000000000000001},
|
||||
"emitDimensions":{"x":0,"y":0,"z":0},
|
||||
"emitRadiusStart":0.5,
|
||||
"polarStart":1,
|
||||
"polarFinish":1.2,
|
||||
"azimuthStart":-Math.PI,
|
||||
"azimuthFinish":Math.PI,
|
||||
"emitAcceleration":{"x":0,"y":-0.70000001192092896,"z":0},
|
||||
"accelerationSpread":{"x":0,"y":0,"z":0},
|
||||
"particleRadius":0.03999999910593033,
|
||||
"radiusSpread":0,
|
||||
"radiusStart":0.13999999910593033,
|
||||
"radiusFinish":0.14,
|
||||
"colorSpread":{"red":0,"green":0,"blue":0},
|
||||
"colorStart":{"red":255,"green":255,"blue":255},
|
||||
"colorFinish":{"red":255,"green":255,"blue":255},
|
||||
"alpha":1,
|
||||
"alphaSpread":0,
|
||||
"alphaStart":0,
|
||||
"alphaFinish":1,
|
||||
"textures":"https://hifi-public.s3.amazonaws.com/alan/Particles/spark_2.png",
|
||||
};
|
||||
|
||||
var popSounds = getSounds([
|
||||
"atp:a2bf79c95fe74c2c6c9188acc7230f7cd1b0f6008f2c81954ecd93eca0497ec6.wav",
|
||||
"atp:901067ebc2cda4c0d86ec02fcca2ed901e85f9097ad68bbde78b4cad8eaf2ed7.wav",
|
||||
"atp:830312930577cb1ea36ba2d743e957debbacceb441b20addead5a6faa05a3771.wav",
|
||||
"atp:62e80d0a9f084cf731bcc66ca6e9020ee88587417071a281eee3167307b53560.wav"
|
||||
]);
|
||||
|
||||
var launchSounds = getSounds([
|
||||
"atp:ee6afe565576c4546c6d6cd89c1af532484c9b60ab30574d6b40c2df022f7260.wav",
|
||||
"atp:91ef19ba1c78be82d3fd06530cd05ceb90d1e75f4204c66819c208c55da049ef.wav",
|
||||
"atp:ee56993daf775012cf49293bfd5971eec7e5c396642f8bfbea902ba8f47b56cd.wav",
|
||||
"atp:37775d267f00f82242a7e7f61f3f3d7bf64a54c5a3799e7f2540fa5f6b79bd02.wav"
|
||||
]);
|
||||
|
||||
function playRandomSound(sounds, options) {
|
||||
Audio.playSound(sounds[randomInt(sounds.length)], options);
|
||||
}
|
||||
|
||||
function shootFirework(position, color, options) {
|
||||
smokeTrailSettings.position = position;
|
||||
smokeTrailSettings.velocity = randomVec3(-5, 5, 10, 20, 10, 15);
|
||||
smokeTrailSettings.gravity = randomVec3(-5, 5, -9.8, -9.8, 20, 40);
|
||||
|
||||
playRandomSound(launchSounds, { position: {x: 0, y: 0 , z: 0}, volume: 3.0 });
|
||||
var smokeID = Entities.addEntity(smokeTrailSettings);
|
||||
|
||||
Script.setTimeout(function() {
|
||||
Entities.editEntity(smokeID, { emitRate: 0 });
|
||||
var position = Entities.getEntityProperties(smokeID, ['position']).position;
|
||||
fireworkSettings.position = position;
|
||||
fireworkSettings.colorStart = color;
|
||||
fireworkSettings.colorFinish = color;
|
||||
var burstID = Entities.addEntity(fireworkSettings);
|
||||
playRandomSound(popSounds, { position: {x: 0, y: 0 , z: 0}, volume: 3.0 });
|
||||
Script.setTimeout(function() {
|
||||
Entities.editEntity(burstID, { emitRate: 0 });
|
||||
}, 500);
|
||||
Script.setTimeout(function() {
|
||||
Entities.deleteEntity(smokeID);
|
||||
Entities.deleteEntity(burstID);
|
||||
}, 10000);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
playFireworkShow = function(position, numberOfFireworks, duration) {
|
||||
for (var i = 0; i < numberOfFireworks; i++) {
|
||||
var randomOffset = randomVec3(-15, 15, -3, 3, -1, 1);
|
||||
var randomPosition = Vec3.sum(position, randomOffset);
|
||||
Script.setTimeout(function(position) {
|
||||
return function() {
|
||||
var color = randomColor(128, 255, 128, 255, 128, 255);
|
||||
shootFirework(position, color, fireworkSettings);
|
||||
}
|
||||
}(randomPosition), Math.random() * duration)
|
||||
}
|
||||
}
|
165
examples/baseball/line.js
Normal file
165
examples/baseball/line.js
Normal file
|
@ -0,0 +1,165 @@
|
|||
function info(message) {
|
||||
print("[INFO] " + message);
|
||||
}
|
||||
|
||||
function error(message) {
|
||||
print("[ERROR] " + message);
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* PolyLine
|
||||
*****************************************************************************/
|
||||
var LINE_DIMENSIONS = { x: 2000, y: 2000, z: 2000 };
|
||||
var MAX_LINE_LENGTH = 40; // This must be 2 or greater;
|
||||
var PolyLine = function(position, color, defaultStrokeWidth) {
|
||||
//info("Creating polyline");
|
||||
//Vec3.print("New line at", position);
|
||||
this.position = position;
|
||||
this.color = color;
|
||||
this.defaultStrokeWidth = 0.10;
|
||||
this.points = [
|
||||
{ x: 0, y: 0, z: 0 },
|
||||
];
|
||||
this.strokeWidths = [
|
||||
this.defaultStrokeWidth,
|
||||
]
|
||||
this.normals = [
|
||||
{ x: 1, y: 0, z: 0 },
|
||||
]
|
||||
this.entityID = Entities.addEntity({
|
||||
type: "PolyLine",
|
||||
position: position,
|
||||
linePoints: this.points,
|
||||
normals: this.normals,
|
||||
strokeWidths: this.strokeWidths,
|
||||
dimensions: LINE_DIMENSIONS,
|
||||
color: color,
|
||||
lifetime: 20,
|
||||
});
|
||||
};
|
||||
|
||||
PolyLine.prototype.enqueuePoint = function(position) {
|
||||
if (this.isFull()) {
|
||||
error("Hit max PolyLine size");
|
||||
return;
|
||||
}
|
||||
|
||||
//Vec3.print("pos", position);
|
||||
//info("Number of points: " + this.points.length);
|
||||
|
||||
position = Vec3.subtract(position, this.position);
|
||||
this.points.push(position);
|
||||
this.normals.push({ x: 1, y: 0, z: 0 });
|
||||
this.strokeWidths.push(this.defaultStrokeWidth);
|
||||
Entities.editEntity(this.entityID, {
|
||||
linePoints: this.points,
|
||||
normals: this.normals,
|
||||
strokeWidths: this.strokeWidths,
|
||||
});
|
||||
};
|
||||
|
||||
PolyLine.prototype.dequeuePoint = function() {
|
||||
if (this.points.length == 0) {
|
||||
error("Hit min PolyLine size");
|
||||
return;
|
||||
}
|
||||
|
||||
this.points = this.points.slice(1);
|
||||
this.normals = this.normals.slice(1);
|
||||
this.strokeWidths = this.strokeWidths.slice(1);
|
||||
|
||||
Entities.editEntity(this.entityID, {
|
||||
linePoints: this.points,
|
||||
normals: this.normals,
|
||||
strokeWidths: this.strokeWidths,
|
||||
});
|
||||
};
|
||||
|
||||
PolyLine.prototype.getFirstPoint = function() {
|
||||
return Vec3.sum(this.position, this.points[0]);
|
||||
};
|
||||
|
||||
PolyLine.prototype.getLastPoint = function() {
|
||||
return Vec3.sum(this.position, this.points[this.points.length - 1]);
|
||||
};
|
||||
|
||||
PolyLine.prototype.getSize = function() {
|
||||
return this.points.length;
|
||||
}
|
||||
|
||||
PolyLine.prototype.isFull = function() {
|
||||
return this.points.length >= MAX_LINE_LENGTH;
|
||||
};
|
||||
|
||||
PolyLine.prototype.destroy = function() {
|
||||
Entities.deleteEntity(this.entityID);
|
||||
this.points = [];
|
||||
};
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* InfiniteLine
|
||||
*****************************************************************************/
|
||||
InfiniteLine = function(position, color) {
|
||||
this.position = position;
|
||||
this.color = color;
|
||||
this.lines = [new PolyLine(position, color)];
|
||||
this.size = 0;
|
||||
};
|
||||
|
||||
InfiniteLine.prototype.enqueuePoint = function(position) {
|
||||
var currentLine;
|
||||
|
||||
if (this.lines.length == 0) {
|
||||
currentLine = new PolyLine(position, this.color);
|
||||
this.lines.push(currentLine);
|
||||
} else {
|
||||
currentLine = this.lines[this.lines.length - 1];
|
||||
}
|
||||
|
||||
if (currentLine.isFull()) {
|
||||
//info("Current line is full, creating new line");
|
||||
//Vec3.print("Last line is", currentLine.getLastPoint());
|
||||
//Vec3.print("New line is", position);
|
||||
var newLine = new PolyLine(currentLine.getLastPoint(), this.color);
|
||||
this.lines.push(newLine);
|
||||
currentLine = newLine;
|
||||
}
|
||||
|
||||
currentLine.enqueuePoint(position);
|
||||
|
||||
++this.size;
|
||||
};
|
||||
|
||||
InfiniteLine.prototype.dequeuePoint = function() {
|
||||
if (this.lines.length == 0) {
|
||||
error("Trying to dequeue from InfiniteLine when no points are left");
|
||||
return;
|
||||
}
|
||||
|
||||
var lastLine = this.lines[0];
|
||||
lastLine.dequeuePoint();
|
||||
|
||||
if (lastLine.getSize() <= 1) {
|
||||
this.lines = this.lines.slice(1);
|
||||
}
|
||||
|
||||
--this.size;
|
||||
};
|
||||
|
||||
InfiniteLine.prototype.getFirstPoint = function() {
|
||||
return this.lines.length > 0 ? this.lines[0].getFirstPoint() : null;
|
||||
};
|
||||
|
||||
InfiniteLine.prototype.getLastPoint = function() {
|
||||
return this.lines.length > 0 ? this.lines[lines.length - 1].getLastPoint() : null;
|
||||
};
|
||||
|
||||
InfiniteLine.prototype.destroy = function() {
|
||||
for (var i = 0; i < this.lines.length; ++i) {
|
||||
this.lines[i].destroy();
|
||||
}
|
||||
|
||||
this.size = 0;
|
||||
};
|
554
examples/baseball/pitching.js
Normal file
554
examples/baseball/pitching.js
Normal file
|
@ -0,0 +1,554 @@
|
|||
//
|
||||
// pitching.js
|
||||
// examples/baseball/
|
||||
//
|
||||
// Created by Ryan Huffman on Nov 9, 2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
print("Loading pitching");
|
||||
|
||||
Script.include("../libraries/line.js");
|
||||
Script.include("firework.js");
|
||||
Script.include("utils.js");
|
||||
|
||||
var DISTANCE_BILLBOARD_NAME = "CurrentScore";
|
||||
var HIGH_SCORE_BILLBOARD_NAME = "HighScore";
|
||||
|
||||
var distanceBillboardEntityID = null;
|
||||
var highScoreBillboardEntityID = null;
|
||||
|
||||
function getDistanceBillboardEntityID() {
|
||||
if (distanceBillboardEntityID === null) {
|
||||
distanceBillboardEntityID = findEntity({name: DISTANCE_BILLBOARD_NAME }, 1000);
|
||||
}
|
||||
return distanceBillboardEntityID;
|
||||
}
|
||||
function getHighScoreBillboardEntityID() {
|
||||
if (highScoreBillboardEntityID === null) {
|
||||
highScoreBillboardEntityID = findEntity({name: HIGH_SCORE_BILLBOARD_NAME }, 1000);
|
||||
}
|
||||
return highScoreBillboardEntityID;
|
||||
}
|
||||
|
||||
var METERS_TO_FEET = 3.28084;
|
||||
|
||||
var AUDIO = {
|
||||
crowdBoos: [
|
||||
SoundCache.getSound("atp:c632c92b166ade60aa16b23ff1dfdf712856caeb83bd9311980b2d5edac821af.wav", false)
|
||||
],
|
||||
crowdCheers: [
|
||||
SoundCache.getSound("atp:0821bf2ac60dd2f356dfdd948e8bb89c23984dc3584612f6c815765154f02cae.wav", false),
|
||||
SoundCache.getSound("atp:b8044401a846ed29f881a0b9b80cf1ba41f26327180c28fc9c70d144f9b70045.wav", false),
|
||||
],
|
||||
batHit: [
|
||||
SoundCache.getSound("atp:6f0b691a0c9c9ece6557d97fe242b1faec4020fe26efc9c17327993b513c5fe5.wav", false),
|
||||
SoundCache.getSound("atp:5be5806205158ebdc5c3623ceb7ae73315028b51ffeae24292aff7042e3fa6a9.wav", false),
|
||||
SoundCache.getSound("atp:e68661374e2145c480809c26134782aad11e0de456c7802170c7abccc4028873.wav", false),
|
||||
SoundCache.getSound("atp:787e3c9af17dd3929527787176ede83d6806260e63ddd5a4cef48cd22e32c6f7.wav", false),
|
||||
SoundCache.getSound("atp:fc65383431a6238c7a4749f0f6f061f75a604ed5e17d775ab1b2955609e67ebb.wav", false),
|
||||
],
|
||||
strike: [
|
||||
SoundCache.getSound("atp:2a258076a85fffde4ba04b5ddc1de9034c7ae7d2af8c5d93d4fed0bcdef3472a.wav", false),
|
||||
SoundCache.getSound("atp:518363524af3ed9b9ae4ca2ceee61f01aecd37e266a51c5a5f5487efe2520fd5.wav", false),
|
||||
SoundCache.getSound("atp:d51d38b089574acbdfdf53ef733bfb3ab41d848fb8c0b6659c7790a785240009.wav", false),
|
||||
],
|
||||
foul: [
|
||||
SoundCache.getSound("atp:316fa18ff9eef457f670452b449a8dc5a41ccabd4e948781c50aaafaae63b0ab.wav", false),
|
||||
SoundCache.getSound("atp:c84d88352d38437edd7414b26dc74e567618712caeb59fec70822398b0c5a279.wav", false),
|
||||
]
|
||||
}
|
||||
|
||||
var PITCH_THUNK_SOUND_URL = "http://hifi-public.s3.amazonaws.com/sounds/ping_pong_gun/pong_sound.wav";
|
||||
var pitchSound = SoundCache.getSound(PITCH_THUNK_SOUND_URL, false);
|
||||
updateBillboard("");
|
||||
|
||||
var PITCHING_MACHINE_URL = "atp:87d4879530b698741ecc45f6f31789aac11f7865a2c3bec5fe9b061a182c80d4.fbx";
|
||||
// This defines an offset to pitch a ball from with respect to the machine's position. The offset is a
|
||||
// percentage of the machine's dimensions. So, { x: 0.5, y: -1.0, z: 0.0 } would offset on 50% on the
|
||||
// machine's x axis, -100% on the y axis, and 0% on the z-axis. For the dimensions { x: 100, y: 100, z: 100 },
|
||||
// that would result in an offset of { x: 50, y: -100, z: 0 }. This makes it easy to calculate an offset if
|
||||
// the machine's dimensions change.
|
||||
var PITCHING_MACHINE_OUTPUT_OFFSET_PCT = {
|
||||
x: 0.0,
|
||||
y: 0.25,
|
||||
z: -1.05,
|
||||
};
|
||||
var PITCHING_MACHINE_PROPERTIES = {
|
||||
name: "Pitching Machine",
|
||||
type: "Model",
|
||||
position: {
|
||||
x: -0.93,
|
||||
y: 0.8,
|
||||
z: -19.8
|
||||
},
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: -0.01,
|
||||
z: 0
|
||||
},
|
||||
gravity: {
|
||||
x: 0.0,
|
||||
y: -9.8,
|
||||
z: 0.0
|
||||
},
|
||||
registrationPoint: {
|
||||
x: 0.5,
|
||||
y: 0.5,
|
||||
z: 0.5
|
||||
},
|
||||
rotation: Quat.fromPitchYawRollDegrees(0, 180, 0),
|
||||
modelURL: PITCHING_MACHINE_URL,
|
||||
dimensions: {
|
||||
x: 0.4,
|
||||
y: 0.61,
|
||||
z: 0.39
|
||||
},
|
||||
collisionsWillMove: false,
|
||||
shapeType: "Box"
|
||||
};
|
||||
PITCHING_MACHINE_PROPERTIES.dimensions = Vec3.multiply(2.5, PITCHING_MACHINE_PROPERTIES.dimensions);
|
||||
var DISTANCE_FROM_PLATE = PITCHING_MACHINE_PROPERTIES.position.z;
|
||||
|
||||
var PITCH_RATE = 5000;
|
||||
|
||||
getOrCreatePitchingMachine = function() {
|
||||
// Search for pitching machine
|
||||
var entities = findEntities({ name: PITCHING_MACHINE_PROPERTIES.name }, 1000);
|
||||
var pitchingMachineID = null;
|
||||
|
||||
// Create if it doesn't exist
|
||||
if (entities.length == 0) {
|
||||
pitchingMachineID = Entities.addEntity(PITCHING_MACHINE_PROPERTIES);
|
||||
} else {
|
||||
pitchingMachineID = entities[0];
|
||||
}
|
||||
|
||||
// Wrap with PitchingMachine object and return
|
||||
return new PitchingMachine(pitchingMachineID);
|
||||
}
|
||||
|
||||
// The pitching machine wraps an entity ID and uses it's position & rotation to determin where to
|
||||
// pitch the ball from and in which direction, and uses the dimensions to determine the scale of them ball.
|
||||
function PitchingMachine(pitchingMachineID) {
|
||||
this.pitchingMachineID = pitchingMachineID;
|
||||
this.enabled = false;
|
||||
this.baseball = null;
|
||||
this.injector = null;
|
||||
}
|
||||
|
||||
PitchingMachine.prototype = {
|
||||
pitchBall: function() {
|
||||
cleanupTrail();
|
||||
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
print("Pitching ball");
|
||||
var machineProperties = Entities.getEntityProperties(this.pitchingMachineID, ["dimensions", "position", "rotation"]);
|
||||
var pitchFromPositionBase = machineProperties.position;
|
||||
var pitchFromOffset = vec3Mult(machineProperties.dimensions, PITCHING_MACHINE_OUTPUT_OFFSET_PCT);
|
||||
pitchFromOffset = Vec3.multiplyQbyV(machineProperties.rotation, pitchFromOffset);
|
||||
var pitchFromPosition = Vec3.sum(pitchFromPositionBase, pitchFromOffset);
|
||||
var pitchDirection = Quat.getFront(machineProperties.rotation);
|
||||
var ballScale = machineProperties.dimensions.x / PITCHING_MACHINE_PROPERTIES.dimensions.x;
|
||||
|
||||
var speed = randomFloat(BASEBALL_MIN_SPEED, BASEBALL_MAX_SPEED);
|
||||
var velocity = Vec3.multiply(speed, pitchDirection);
|
||||
|
||||
this.baseball = new Baseball(pitchFromPosition, velocity, ballScale);
|
||||
|
||||
if (!this.injector) {
|
||||
this.injector = Audio.playSound(pitchSound, {
|
||||
position: pitchFromPosition,
|
||||
volume: 1.0
|
||||
});
|
||||
} else {
|
||||
this.injector.restart();
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
if (this.enabled) {
|
||||
return;
|
||||
}
|
||||
print("Starting Pitching Machine");
|
||||
this.enabled = true;
|
||||
this.pitchBall();
|
||||
},
|
||||
stop: function() {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
print("Stopping Pitching Machine");
|
||||
this.enabled = false;
|
||||
},
|
||||
update: function(dt) {
|
||||
if (this.baseball) {
|
||||
this.baseball.update(dt);
|
||||
if (this.baseball.finished()) {
|
||||
this.baseball = null;
|
||||
var self = this;
|
||||
Script.setTimeout(function() { self.pitchBall(); }, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var BASEBALL_MODEL_URL = "atp:7185099f1f650600ca187222573a88200aeb835454bd2f578f12c7fb4fd190fa.fbx";
|
||||
var BASEBALL_MIN_SPEED = 7.0;
|
||||
var BASEBALL_MAX_SPEED = 15.0;
|
||||
var BASEBALL_RADIUS = 0.07468;
|
||||
var BASEBALL_PROPERTIES = {
|
||||
name: "Baseball",
|
||||
type: "Model",
|
||||
modelURL: BASEBALL_MODEL_URL,
|
||||
shapeType: "Sphere",
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
dimensions: {
|
||||
x: BASEBALL_RADIUS,
|
||||
y: BASEBALL_RADIUS,
|
||||
z: BASEBALL_RADIUS
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
angularVelocity: {
|
||||
x: 17.0,
|
||||
y: 0,
|
||||
z: -8.0,
|
||||
|
||||
x: 0.0,
|
||||
y: 0,
|
||||
z: 0.0,
|
||||
},
|
||||
angularDamping: 0.0,
|
||||
damping: 0.0,
|
||||
restitution: 0.5,
|
||||
friction: 0.0,
|
||||
friction: 0.5,
|
||||
lifetime: 20,
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
}
|
||||
};
|
||||
var BASEBALL_STATE = {
|
||||
PITCHING: 0,
|
||||
HIT: 1,
|
||||
HIT_LANDED: 2,
|
||||
STRIKE: 3,
|
||||
FOUL: 4
|
||||
};
|
||||
|
||||
|
||||
|
||||
function vec3Mult(a, b) {
|
||||
return {
|
||||
x: a.x * b.x,
|
||||
y: a.y * b.y,
|
||||
z: a.z * b.z,
|
||||
};
|
||||
}
|
||||
|
||||
function map(value, min1, max1, min2, max2) {
|
||||
return min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
|
||||
}
|
||||
|
||||
function orientationOf(vector) {
|
||||
var RAD_TO_DEG = 180.0 / Math.PI;
|
||||
var Y_AXIS = { x: 0, y: 1, z: 0 };
|
||||
var X_AXIS = { x: 1, y: 0, z: 0 };
|
||||
var direction = Vec3.normalize(vector);
|
||||
|
||||
var yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS);
|
||||
var pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS);
|
||||
|
||||
return Quat.multiply(yaw, pitch);
|
||||
}
|
||||
|
||||
var ACCELERATION_SPREAD = 0.35;
|
||||
|
||||
var TRAIL_COLOR = { red: 128, green: 255, blue: 89 };
|
||||
var TRAIL_LIFETIME = 20;
|
||||
|
||||
var trail = null;
|
||||
var trailInterval = null;
|
||||
function cleanupTrail() {
|
||||
if (trail) {
|
||||
Script.clearInterval(this.trailInterval);
|
||||
trailInterval = null;
|
||||
|
||||
trail.destroy();
|
||||
trail = null;
|
||||
}
|
||||
}
|
||||
|
||||
function setupTrail(entityID, position) {
|
||||
cleanupTrail();
|
||||
|
||||
var lastPosition = position;
|
||||
trail = new InfiniteLine(position, { red: 128, green: 255, blue: 89 }, 20);
|
||||
trailInterval = Script.setInterval(function() {
|
||||
var properties = Entities.getEntityProperties(entityID, ['position']);
|
||||
if (Vec3.distance(properties.position, lastPosition)) {
|
||||
var strokeWidth = Math.log(1 + trail.size) * 0.05;
|
||||
trail.enqueuePoint(properties.position, strokeWidth);
|
||||
lastPosition = properties.position;
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
function Baseball(position, velocity, ballScale) {
|
||||
var self = this;
|
||||
|
||||
this.state = BASEBALL_STATE.PITCHING;
|
||||
|
||||
// Setup entity properties
|
||||
var properties = shallowCopy(BASEBALL_PROPERTIES);
|
||||
properties.position = position;
|
||||
properties.velocity = velocity;
|
||||
properties.dimensions = Vec3.multiply(ballScale, properties.dimensions);
|
||||
/*
|
||||
properties.gravity = {
|
||||
x: randomFloat(-ACCELERATION_SPREAD, ACCELERATION_SPREAD),
|
||||
y: randomFloat(-ACCELERATION_SPREAD, ACCELERATION_SPREAD),
|
||||
z: 0.0,
|
||||
};
|
||||
*/
|
||||
|
||||
// Create entity
|
||||
this.entityID = Entities.addEntity(properties);
|
||||
|
||||
this.timeSincePitched = 0;
|
||||
this.timeSinceHit = 0;
|
||||
this.hitBallAtPosition = null;
|
||||
this.distanceTravelled = 0;
|
||||
this.wasHighScore = false;
|
||||
this.landed = false;
|
||||
|
||||
// Listen for collision for the lifetime of the entity
|
||||
Script.addEventHandler(this.entityID, "collisionWithEntity", function(entityA, entityB, collision) {
|
||||
self.collisionCallback(entityA, entityB, collision);
|
||||
});
|
||||
if (Math.random() < 0.5) {
|
||||
for (var i = 0; i < 50; i++) {
|
||||
Script.setTimeout(function() {
|
||||
Entities.editEntity(entityID, {
|
||||
gravity: {
|
||||
x: randomFloat(-ACCELERATION_SPREAD, ACCELERATION_SPREAD),
|
||||
y: randomFloat(-ACCELERATION_SPREAD, ACCELERATION_SPREAD),
|
||||
z: 0.0,
|
||||
}
|
||||
})
|
||||
}, i * 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the stadium billboard with the current distance or a text message and update the high score
|
||||
// if it has been beaten.
|
||||
function updateBillboard(distanceOrMessage) {
|
||||
var distanceBillboardEntityID = getDistanceBillboardEntityID();
|
||||
if (distanceBillboardEntityID) {
|
||||
Entities.editEntity(distanceBillboardEntityID, {
|
||||
text: distanceOrMessage
|
||||
});
|
||||
}
|
||||
|
||||
var highScoreBillboardEntityID = getHighScoreBillboardEntityID();
|
||||
// If a number was passed in, let's see if it is larger than the current high score
|
||||
// and update it if so.
|
||||
if (!isNaN(distanceOrMessage) && highScoreBillboardEntityID) {
|
||||
var properties = Entities.getEntityProperties(highScoreBillboardEntityID, ["text"]);
|
||||
var bestDistance = parseInt(properties.text);
|
||||
if (distanceOrMessage >= bestDistance) {
|
||||
Entities.editEntity(highScoreBillboardEntityID, {
|
||||
text: distanceOrMessage,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var FIREWORKS_SHOW_POSITION = { x: 0, y: 0, z: -78.0 };
|
||||
var FIREWORK_PER_X_FEET = 100;
|
||||
var MAX_FIREWORKS = 10;
|
||||
|
||||
Baseball.prototype = {
|
||||
finished: function() {
|
||||
return this.state == BASEBALL_STATE.FOUL
|
||||
|| this.state == BASEBALL_STATE.STRIKE
|
||||
|| this.state == BASEBALL_STATE.HIT_LANDED;
|
||||
},
|
||||
update: function(dt) {
|
||||
this.timeSincePitched += dt;
|
||||
if (this.state == BASEBALL_STATE.HIT) {
|
||||
this.timeSinceHit += dt;
|
||||
var myProperties = Entities.getEntityProperties(this.entityID, ['position', 'velocity']);
|
||||
var speed = Vec3.length(myProperties.velocity);
|
||||
this.distanceTravelled = Vec3.distance(this.hitBallAtPosition, myProperties.position) * METERS_TO_FEET;
|
||||
var wasHighScore = updateBillboard(Math.ceil(this.distanceTravelled));
|
||||
if (this.landed || this.timeSinceHit > 10 || speed < 1) {
|
||||
this.wasHighScore = wasHighScore;
|
||||
this.ballLanded();
|
||||
}
|
||||
} else if (this.state == BASEBALL_STATE.PITCHING) {
|
||||
if (this.timeSincePitched > 10) {
|
||||
print("TIMED OUT WHILE PITCHING");
|
||||
this.state = BASEBALL_STATE.STRIKE;
|
||||
}
|
||||
}
|
||||
},
|
||||
ballLanded: function() {
|
||||
this.state = BASEBALL_STATE.HIT_LANDED;
|
||||
var numberOfFireworks = Math.floor(this.distanceTravelled / FIREWORK_PER_X_FEET);
|
||||
if (numberOfFireworks > 0) {
|
||||
numberOfFireworks = Math.min(MAX_FIREWORKS, numberOfFireworks);
|
||||
playFireworkShow(FIREWORKS_SHOW_POSITION, numberOfFireworks, 2000);
|
||||
}
|
||||
print("Ball took " + this.timeSinceHit.toFixed(3) + " seconds to land");
|
||||
print("Ball travelled " + this.distanceTravelled + " feet")
|
||||
},
|
||||
collisionCallback: function(entityA, entityB, collision) {
|
||||
var self = this;
|
||||
var myProperties = Entities.getEntityProperties(this.entityID, ['position', 'velocity']);
|
||||
var myPosition = myProperties.position;
|
||||
var myVelocity = myProperties.velocity;
|
||||
|
||||
// Activate gravity
|
||||
Entities.editEntity(self.entityID, {
|
||||
gravity: { x: 0, y: -9.8, z: 0 }
|
||||
});
|
||||
|
||||
var name = Entities.getEntityProperties(entityB, ["name"]).name;
|
||||
if (name == "Bat") {
|
||||
if (this.state == BASEBALL_STATE.PITCHING) {
|
||||
print("HIT");
|
||||
|
||||
var FOUL_MIN_YAW = -135.0;
|
||||
var FOUL_MAX_YAW = 135.0;
|
||||
|
||||
var yaw = Math.atan2(myVelocity.x, myVelocity.z) * 180 / Math.PI;
|
||||
var foul = yaw > FOUL_MIN_YAW && yaw < FOUL_MAX_YAW;
|
||||
|
||||
var speedMultiplier = 2;
|
||||
|
||||
if (foul && myVelocity.z > 0) {
|
||||
var TUNNELED_PITCH_RANGE = 15.0;
|
||||
var xzDist = Math.sqrt(myVelocity.x * myVelocity.x + myVelocity.z * myVelocity.z);
|
||||
var pitch = Math.atan2(myVelocity.y, xzDist) * 180 / Math.PI;
|
||||
print("Pitch: ", pitch);
|
||||
// If the pitch is going straight out the back and has a pitch in the range TUNNELED_PITCH_RANGE,
|
||||
// let's assume the ball tunneled through the bat and reverse its direction.
|
||||
if (Math.abs(pitch) < TUNNELED_PITCH_RANGE) {
|
||||
print("Reversing hit");
|
||||
myVelocity.x *= -1;
|
||||
myVelocity.y *= -1;
|
||||
myVelocity.z *= -1;
|
||||
|
||||
yaw = Math.atan2(myVelocity.x, myVelocity.z) * 180 / Math.PI;
|
||||
foul = yaw > FOUL_MIN_YAW && yaw < FOUL_MAX_YAW;
|
||||
|
||||
speedMultiplier = 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Update ball velocity
|
||||
Entities.editEntity(self.entityID, {
|
||||
velocity: Vec3.multiply(speedMultiplier, myVelocity)
|
||||
});
|
||||
|
||||
// Setup line update interval
|
||||
setupTrail(self.entityID, myPosition);
|
||||
|
||||
// Setup bat hit sound
|
||||
playRandomSound(AUDIO.batHit, {
|
||||
position: myPosition,
|
||||
volume: 2.0
|
||||
});
|
||||
|
||||
// Setup crowd reaction sound
|
||||
var speed = Vec3.length(myVelocity);
|
||||
Script.setTimeout(function() {
|
||||
playRandomSound((speed < 5.0) ? AUDIO.crowdBoos : AUDIO.crowdCheers, {
|
||||
position: { x: 0 ,y: 0, z: 0 },
|
||||
volume: 1.0
|
||||
});
|
||||
}, 500);
|
||||
|
||||
if (foul) {
|
||||
print("FOUL, yaw: ", yaw);
|
||||
updateBillboard("FOUL");
|
||||
this.state = BASEBALL_STATE.FOUL;
|
||||
playRandomSound(AUDIO.foul, {
|
||||
position: myPosition,
|
||||
volume: 2.0
|
||||
});
|
||||
} else {
|
||||
print("HIT ", yaw);
|
||||
this.state = BASEBALL_STATE.HIT;
|
||||
}
|
||||
}
|
||||
} else if (name == "stadium") {
|
||||
//entityCollisionWithGround(entityB, this.entityID, collision);
|
||||
this.landed = true;
|
||||
} else if (name == "backstop") {
|
||||
if (this.state == BASEBALL_STATE.PITCHING) {
|
||||
print("STRIKE");
|
||||
this.state = BASEBALL_STATE.STRIKE;
|
||||
updateBillboard("STRIKE");
|
||||
playRandomSound(AUDIO.strike, {
|
||||
position: myPosition,
|
||||
volume: 2.0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function entityCollisionWithGround(ground, entity, collision) {
|
||||
var ZERO_VEC = { x: 0, y: 0, z: 0 };
|
||||
var dVelocityMagnitude = Vec3.length(collision.velocityChange);
|
||||
var position = Entities.getEntityProperties(entity, "position").position;
|
||||
var particleRadius = 0.3;
|
||||
var speed = map(dVelocityMagnitude, 0.05, 3, 0.02, 0.09);
|
||||
var displayTime = 400;
|
||||
var orientationChange = orientationOf(collision.velocityChange);
|
||||
|
||||
var dustEffect = Entities.addEntity({
|
||||
type: "ParticleEffect",
|
||||
name: "Dust-Puff",
|
||||
position: position,
|
||||
color: {red: 195, green: 170, blue: 185},
|
||||
lifespan: 3,
|
||||
lifetime: 2,//displayTime/1000 * 2, //So we can fade particle system out gracefully
|
||||
emitRate: 5,
|
||||
emitSpeed: speed,
|
||||
emitAcceleration: ZERO_VEC,
|
||||
accelerationSpread: ZERO_VEC,
|
||||
isEmitting: true,
|
||||
polarStart: Math.PI/2,
|
||||
polarFinish: Math.PI/2,
|
||||
emitOrientation: orientationChange,
|
||||
radiusSpread: 0.1,
|
||||
radiusStart: particleRadius,
|
||||
radiusFinish: particleRadius + particleRadius / 2,
|
||||
particleRadius: particleRadius,
|
||||
alpha: 0.45,
|
||||
alphaFinish: 0.001,
|
||||
textures: "https://hifi-public.s3.amazonaws.com/alan/Playa/Particles/Particle-Sprite-Gen.png"
|
||||
});
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
cleanupTrail();
|
||||
Entities.deleteEntity(pitchingMachineID);
|
||||
});
|
92
examples/baseball/utils.js
Normal file
92
examples/baseball/utils.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// utils.js
|
||||
// examples/baseball/
|
||||
//
|
||||
// Created by Ryan Huffman on Nov 9, 2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
randomInt = function(low, high) {
|
||||
return Math.floor(randomFloat(low, high));
|
||||
};
|
||||
|
||||
randomFloat = function(low, high) {
|
||||
if (high === undefined) {
|
||||
high = low;
|
||||
low = 0;
|
||||
}
|
||||
return low + Math.random() * (high - low);
|
||||
};
|
||||
|
||||
randomColor = function(redMin, redMax, greenMin, greenMax, blueMin, blueMax) {
|
||||
return {
|
||||
red: Math.ceil(randomFloat(redMin, redMax)),
|
||||
green: Math.ceil(randomFloat(greenMin, greenMax)),
|
||||
blue: Math.ceil(randomFloat(blueMin, blueMax)),
|
||||
}
|
||||
};
|
||||
|
||||
randomVec3 = function(xMin, xMax, yMin, yMax, zMin, zMax) {
|
||||
return {
|
||||
x: randomFloat(xMin, xMax),
|
||||
y: randomFloat(yMin, yMax),
|
||||
z: randomFloat(zMin, zMax),
|
||||
}
|
||||
};
|
||||
|
||||
getSounds = function(soundURLs) {
|
||||
var sounds = [];
|
||||
for (var i = 0; i < soundURLs.length; ++i) {
|
||||
sounds.push(SoundCache.getSound(soundURLs[i], false));
|
||||
}
|
||||
return sounds;
|
||||
};
|
||||
|
||||
playRandomSound = function(sounds, options) {
|
||||
if (options === undefined) {
|
||||
options = {
|
||||
volume: 1.0,
|
||||
position: MyAvatar.position,
|
||||
}
|
||||
}
|
||||
return Audio.playSound(sounds[randomInt(sounds.length)], options);
|
||||
}
|
||||
|
||||
shallowCopy = function(obj) {
|
||||
var copy = {}
|
||||
for (var key in obj) {
|
||||
copy[key] = obj[key];
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
findEntity = function(properties, searchRadius) {
|
||||
var entities = findEntities(properties, searchRadius);
|
||||
return entities.length > 0 ? entities[0] : null;
|
||||
}
|
||||
|
||||
// Return all entities with properties `properties` within radius `searchRadius`
|
||||
findEntities = function(properties, searchRadius) {
|
||||
var entities = Entities.findEntities(MyAvatar.position, searchRadius);
|
||||
var matchedEntities = [];
|
||||
var keys = Object.keys(properties);
|
||||
for (var i = 0; i < entities.length; ++i) {
|
||||
var match = true;
|
||||
var candidateProperties = Entities.getEntityProperties(entities[i], keys);
|
||||
for (var key in properties) {
|
||||
if (candidateProperties[key] != properties[key]) {
|
||||
// This isn't a match, move to next entity
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
matchedEntities.push(entities[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return matchedEntities;
|
||||
}
|
|
@ -34,9 +34,10 @@ var BUMPER_ON_VALUE = 0.5;
|
|||
// distant manipulation
|
||||
//
|
||||
|
||||
var DISTANCE_HOLDING_RADIUS_FACTOR = 5; // multiplied by distance between hand and object
|
||||
var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object
|
||||
var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position
|
||||
var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did
|
||||
var MOVE_WITH_HEAD = true; // experimental head-controll of distantly held objects
|
||||
|
||||
var NO_INTERSECT_COLOR = {
|
||||
red: 10,
|
||||
|
@ -200,6 +201,33 @@ function entityIsGrabbedByOther(entityID) {
|
|||
return false;
|
||||
}
|
||||
|
||||
function getSpatialOffsetPosition(hand, spatialKey) {
|
||||
if (hand !== RIGHT_HAND && spatialKey.leftRelativePosition) {
|
||||
return spatialKey.leftRelativePosition;
|
||||
}
|
||||
if (hand === RIGHT_HAND && spatialKey.rightRelativePosition) {
|
||||
return spatialKey.rightRelativePosition;
|
||||
}
|
||||
if (spatialKey.relativePosition) {
|
||||
return spatialKey.relativePosition;
|
||||
}
|
||||
|
||||
return Vec3.ZERO;
|
||||
}
|
||||
|
||||
function getSpatialOffsetRotation(hand, spatialKey) {
|
||||
if (hand !== RIGHT_HAND && spatialKey.leftRelativeRotation) {
|
||||
return spatialKey.leftRelativeRotation;
|
||||
}
|
||||
if (hand === RIGHT_HAND && spatialKey.rightRelativeRotation) {
|
||||
return spatialKey.rightRelativeRotation;
|
||||
}
|
||||
if (spatialKey.relativeRotation) {
|
||||
return spatialKey.relativeRotation;
|
||||
}
|
||||
|
||||
return Quat.IDENTITY;
|
||||
}
|
||||
|
||||
function MyController(hand) {
|
||||
this.hand = hand;
|
||||
|
@ -223,20 +251,11 @@ function MyController(hand) {
|
|||
this.triggerValue = 0; // rolling average of trigger value
|
||||
this.rawTriggerValue = 0;
|
||||
this.rawBumperValue = 0;
|
||||
|
||||
|
||||
this.overlayLine = null;
|
||||
|
||||
this.offsetPosition = {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
};
|
||||
this.offsetRotation = {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
w: 1.0
|
||||
};
|
||||
|
||||
this.offsetPosition = Vec3.ZERO;
|
||||
this.offsetRotation = Quat.IDENTITY;
|
||||
|
||||
var _this = this;
|
||||
|
||||
|
@ -658,6 +677,13 @@ function MyController(hand) {
|
|||
this.currentObjectTime = now;
|
||||
this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position);
|
||||
this.handPreviousRotation = handRotation;
|
||||
this.currentCameraOrientation = Camera.orientation;
|
||||
|
||||
// compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object
|
||||
this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0);
|
||||
if (this.radiusScalar < 1.0) {
|
||||
this.radiusScalar = 1.0;
|
||||
}
|
||||
|
||||
this.actionID = NULL_ACTION_ID;
|
||||
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
|
||||
|
@ -689,8 +715,6 @@ function MyController(hand) {
|
|||
this.currentAvatarOrientation = MyAvatar.orientation;
|
||||
|
||||
this.overlayLineOff();
|
||||
|
||||
|
||||
};
|
||||
|
||||
this.continueDistanceHolding = function() {
|
||||
|
@ -719,8 +743,12 @@ function MyController(hand) {
|
|||
this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR);
|
||||
|
||||
// the action was set up on a previous call. update the targets.
|
||||
var radius = Math.max(Vec3.distance(this.currentObjectPosition, handControllerPosition) *
|
||||
DISTANCE_HOLDING_RADIUS_FACTOR, DISTANCE_HOLDING_RADIUS_FACTOR);
|
||||
var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) *
|
||||
this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR;
|
||||
if (radius < 1.0) {
|
||||
radius = 1.0;
|
||||
}
|
||||
|
||||
// how far did avatar move this timestep?
|
||||
var currentPosition = MyAvatar.position;
|
||||
var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition);
|
||||
|
@ -751,11 +779,11 @@ function MyController(hand) {
|
|||
var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition);
|
||||
this.handRelativePreviousPosition = handToAvatar;
|
||||
|
||||
// magnify the hand movement but not the change from avatar movement & rotation
|
||||
// magnify the hand movement but not the change from avatar movement & rotation
|
||||
handMoved = Vec3.subtract(handMoved, handMovementFromTurning);
|
||||
var superHandMoved = Vec3.multiply(handMoved, radius);
|
||||
|
||||
// Move the object by the magnified amount and then by amount from avatar movement & rotation
|
||||
// Move the object by the magnified amount and then by amount from avatar movement & rotation
|
||||
var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved);
|
||||
newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition);
|
||||
newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning);
|
||||
|
@ -777,6 +805,16 @@ function MyController(hand) {
|
|||
|
||||
Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab");
|
||||
|
||||
// mix in head motion
|
||||
if (MOVE_WITH_HEAD) {
|
||||
var objDistance = Vec3.length(objectToAvatar);
|
||||
var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { x: 0.0, y: 0.0, z: objDistance });
|
||||
var after = Vec3.multiplyQbyV(Camera.orientation, { x: 0.0, y: 0.0, z: objDistance });
|
||||
var change = Vec3.subtract(before, after);
|
||||
this.currentCameraOrientation = Camera.orientation;
|
||||
this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change);
|
||||
}
|
||||
|
||||
Entities.updateAction(this.grabbedEntity, this.actionID, {
|
||||
targetPosition: this.currentObjectPosition,
|
||||
linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
||||
|
@ -815,12 +853,8 @@ function MyController(hand) {
|
|||
|
||||
if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) {
|
||||
// if an object is "equipped" and has a spatialKey, use it.
|
||||
if (grabbableData.spatialKey.relativePosition) {
|
||||
this.offsetPosition = grabbableData.spatialKey.relativePosition;
|
||||
}
|
||||
if (grabbableData.spatialKey.relativeRotation) {
|
||||
this.offsetRotation = grabbableData.spatialKey.relativeRotation;
|
||||
}
|
||||
this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey);
|
||||
this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey);
|
||||
} else {
|
||||
var objectRotation = grabbedProperties.rotation;
|
||||
this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
|
||||
|
@ -946,23 +980,8 @@ function MyController(hand) {
|
|||
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
|
||||
|
||||
// use a spring to pull the object to where it will be when equipped
|
||||
var relativeRotation = {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
w: 1.0
|
||||
};
|
||||
var relativePosition = {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
};
|
||||
if (grabbableData.spatialKey.relativePosition) {
|
||||
relativePosition = grabbableData.spatialKey.relativePosition;
|
||||
}
|
||||
if (grabbableData.spatialKey.relativeRotation) {
|
||||
relativeRotation = grabbableData.spatialKey.relativeRotation;
|
||||
}
|
||||
var relativeRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey);
|
||||
var relativePosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey);
|
||||
var handRotation = this.getHandRotation();
|
||||
var handPosition = this.getHandPosition();
|
||||
var targetRotation = Quat.multiply(handRotation, relativeRotation);
|
||||
|
|
|
@ -20,6 +20,9 @@ var leapHands = (function () {
|
|||
hasHandAndWristJoints,
|
||||
handToWristOffset = [], // For avatars without a wrist joint we control an estimate of a proper hand joint position
|
||||
HAND_OFFSET = 0.4, // Relative distance of wrist to hand versus wrist to index finger knuckle
|
||||
handAnimationStateHandlers,
|
||||
handAnimationStateFunctions,
|
||||
handAnimationStateProperties,
|
||||
hands,
|
||||
wrists,
|
||||
NUM_HANDS = 2, // 0 = left; 1 = right
|
||||
|
@ -37,7 +40,14 @@ var leapHands = (function () {
|
|||
avatarScale,
|
||||
avatarFaceModelURL,
|
||||
avatarSkeletonModelURL,
|
||||
settingsTimer;
|
||||
settingsTimer,
|
||||
HMD_CAMERA_TO_AVATAR_ROTATION = [
|
||||
Quat.angleAxis(180.0, { x: 0, y: 0, z: 1 }),
|
||||
Quat.angleAxis(-180.0, { x: 0, y: 0, z: 1 })
|
||||
],
|
||||
DESKTOP_CAMERA_TO_AVATAR_ROTATION =
|
||||
Quat.multiply(Quat.angleAxis(180.0, { x: 0, y: 1, z: 0 }), Quat.angleAxis(90.0, { x: 0, y: 0, z: 1 })),
|
||||
LEAP_THUMB_ROOT_ADJUST = [Quat.fromPitchYawRollDegrees(0, 0, 20), Quat.fromPitchYawRollDegrees(0, 0, -20)];
|
||||
|
||||
function printSkeletonJointNames() {
|
||||
var jointNames,
|
||||
|
@ -125,6 +135,26 @@ var leapHands = (function () {
|
|||
*/
|
||||
}
|
||||
|
||||
function animateLeftHand() {
|
||||
var ROTATION_AND_POSITION = 0;
|
||||
|
||||
return {
|
||||
leftHandType: ROTATION_AND_POSITION,
|
||||
leftHandPosition: hands[0].position,
|
||||
leftHandRotation: hands[0].rotation
|
||||
};
|
||||
}
|
||||
|
||||
function animateRightHand() {
|
||||
var ROTATION_AND_POSITION = 0;
|
||||
|
||||
return {
|
||||
rightHandType: ROTATION_AND_POSITION,
|
||||
rightHandPosition: hands[1].position,
|
||||
rightHandRotation: hands[1].rotation
|
||||
};
|
||||
}
|
||||
|
||||
function finishCalibration() {
|
||||
var avatarPosition,
|
||||
handPosition,
|
||||
|
@ -166,10 +196,8 @@ var leapHands = (function () {
|
|||
|
||||
MyAvatar.clearJointData("LeftHand");
|
||||
MyAvatar.clearJointData("LeftForeArm");
|
||||
MyAvatar.clearJointData("LeftArm");
|
||||
MyAvatar.clearJointData("RightHand");
|
||||
MyAvatar.clearJointData("RightForeArm");
|
||||
MyAvatar.clearJointData("RightArm");
|
||||
|
||||
calibrationStatus = CALIBRATED;
|
||||
print("Leap Motion: Calibrated");
|
||||
|
@ -193,12 +221,10 @@ var leapHands = (function () {
|
|||
}
|
||||
|
||||
// Set avatar arms vertical, forearms horizontal, as "zero" position for calibration
|
||||
MyAvatar.setJointRotation("LeftArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 0.0));
|
||||
MyAvatar.setJointRotation("LeftForeArm", Quat.fromPitchYawRollDegrees(0.0, 90.0, 90.0));
|
||||
MyAvatar.setJointRotation("LeftHand", Quat.fromPitchYawRollRadians(0.0, 0.0, 0.0));
|
||||
MyAvatar.setJointRotation("RightArm", Quat.fromPitchYawRollDegrees(90.0, 0.0, 0.0));
|
||||
MyAvatar.setJointRotation("RightForeArm", Quat.fromPitchYawRollDegrees(0.0, -90.0, -90.0));
|
||||
MyAvatar.setJointRotation("RightHand", Quat.fromPitchYawRollRadians(0.0, 0.0, 0.0));
|
||||
MyAvatar.setJointRotation("LeftForeArm", Quat.fromPitchYawRollDegrees(0.0, 0.0, 90.0));
|
||||
MyAvatar.setJointRotation("LeftHand", Quat.fromPitchYawRollDegrees(0.0, 90.0, 0.0));
|
||||
MyAvatar.setJointRotation("RightForeArm", Quat.fromPitchYawRollDegrees(0.0, 0.0, -90.0));
|
||||
MyAvatar.setJointRotation("RightHand", Quat.fromPitchYawRollDegrees(0.0, -90.0, 0.0));
|
||||
|
||||
// Wait for arms to assume their positions before calculating
|
||||
Script.setTimeout(finishCalibration, CALIBRATION_TIME);
|
||||
|
@ -319,6 +345,13 @@ var leapHands = (function () {
|
|||
]
|
||||
];
|
||||
|
||||
handAnimationStateHandlers = [null, null];
|
||||
handAnimationStateFunctions = [animateLeftHand, animateRightHand];
|
||||
handAnimationStateProperties = [
|
||||
["leftHandType", "leftHandPosition", "leftHandRotation"],
|
||||
["rightHandType", "rightHandPosition", "rightHandPosition"]
|
||||
];
|
||||
|
||||
setIsOnHMD();
|
||||
|
||||
settingsTimer = Script.setInterval(checkSettings, 2000);
|
||||
|
@ -348,6 +381,12 @@ var leapHands = (function () {
|
|||
return;
|
||||
}
|
||||
|
||||
// Hand animation handlers ...
|
||||
if (handAnimationStateHandlers[h] === null) {
|
||||
handAnimationStateHandlers[h] = MyAvatar.addAnimationStateHandler(handAnimationStateFunctions[h],
|
||||
handAnimationStateProperties[h]);
|
||||
}
|
||||
|
||||
// Hand position ...
|
||||
handOffset = hands[h].controller.getAbsTranslation();
|
||||
handRotation = hands[h].controller.getAbsRotation();
|
||||
|
@ -362,37 +401,41 @@ var leapHands = (function () {
|
|||
|
||||
// Hand offset in camera coordinates ...
|
||||
handOffset = {
|
||||
x: hands[h].zeroPosition.x - handOffset.x,
|
||||
y: hands[h].zeroPosition.y - handOffset.z,
|
||||
z: hands[h].zeroPosition.z + handOffset.y
|
||||
x: -handOffset.x,
|
||||
y: -handOffset.z,
|
||||
z: -handOffset.y - hands[h].zeroPosition.z
|
||||
};
|
||||
handOffset.z = -handOffset.z;
|
||||
|
||||
// Hand offset in world coordinates ...
|
||||
cameraOrientation = Camera.getOrientation();
|
||||
handOffset = Vec3.sum(Camera.getPosition(), Vec3.multiplyQbyV(cameraOrientation, handOffset));
|
||||
|
||||
// Hand offset in avatar coordinates ...
|
||||
// Hand offset in avatar coordinates ...
|
||||
inverseAvatarOrientation = Quat.inverse(MyAvatar.orientation);
|
||||
handOffset = Vec3.subtract(handOffset, MyAvatar.position);
|
||||
handOffset = Vec3.multiplyQbyV(inverseAvatarOrientation, handOffset);
|
||||
handOffset.z = -handOffset.z;
|
||||
handOffset.x = -handOffset.x;
|
||||
|
||||
|
||||
// Hand rotation in camera coordinates ...
|
||||
handRotation = {
|
||||
x: -handRotation.x,
|
||||
x: -handRotation.y,
|
||||
y: -handRotation.z,
|
||||
z: -handRotation.y,
|
||||
z: -handRotation.x,
|
||||
w: handRotation.w
|
||||
};
|
||||
|
||||
// Hand rotation in avatar coordinates ...
|
||||
handRotation = Quat.multiply(Quat.angleAxis(180.0, { x: 0, y: 1, z: 0 }), handRotation);
|
||||
cameraOrientation.x = -cameraOrientation.x;
|
||||
cameraOrientation.z = -cameraOrientation.z;
|
||||
handRotation = Quat.multiply(cameraOrientation, handRotation);
|
||||
handRotation = Quat.multiply(inverseAvatarOrientation, handRotation);
|
||||
handRotation = Quat.multiply(HMD_CAMERA_TO_AVATAR_ROTATION[h], handRotation);
|
||||
cameraOrientation = {
|
||||
x: cameraOrientation.z,
|
||||
y: cameraOrientation.y,
|
||||
z: cameraOrientation.x,
|
||||
w: cameraOrientation.w
|
||||
};
|
||||
cameraOrientation = Quat.multiply(cameraOrientation, Quat.inverse(MyAvatar.orientation));
|
||||
handRotation = Quat.multiply(handRotation, cameraOrientation); // Works!!!
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -411,18 +454,19 @@ var leapHands = (function () {
|
|||
|
||||
// Hand rotation in camera coordinates ...
|
||||
handRotation = {
|
||||
x: -handRotation.x,
|
||||
y: -handRotation.z,
|
||||
z: -handRotation.y,
|
||||
x: handRotation.z,
|
||||
y: handRotation.y,
|
||||
z: handRotation.x,
|
||||
w: handRotation.w
|
||||
};
|
||||
|
||||
// Hand rotation in avatar coordinates ...
|
||||
handRotation = Quat.multiply(Quat.angleAxis(90.0, { x: 1, y: 0, z: 0 }), handRotation);
|
||||
handRotation = Quat.multiply(DESKTOP_CAMERA_TO_AVATAR_ROTATION, handRotation);
|
||||
}
|
||||
|
||||
// Set hand position and orientation ...
|
||||
MyAvatar.setJointModelPositionAndOrientation(hands[h].jointName, handOffset, handRotation, true);
|
||||
// Set hand position and orientation for animation state handler ...
|
||||
hands[h].position = handOffset;
|
||||
hands[h].rotation = handRotation;
|
||||
|
||||
// Set finger joints ...
|
||||
for (i = 0; i < NUM_FINGERS; i += 1) {
|
||||
|
@ -436,6 +480,10 @@ var leapHands = (function () {
|
|||
z: side * -locRotation.x,
|
||||
w: locRotation.w
|
||||
};
|
||||
if (j === 0) {
|
||||
// Adjust avatar thumb root joint rotation to make avatar hands look better
|
||||
locRotation = Quat.multiply(LEAP_THUMB_ROOT_ADJUST[h], locRotation);
|
||||
}
|
||||
} else {
|
||||
locRotation = {
|
||||
x: -locRotation.x,
|
||||
|
@ -458,14 +506,9 @@ var leapHands = (function () {
|
|||
hands[h].inactiveCount += 1;
|
||||
|
||||
if (hands[h].inactiveCount === MAX_HAND_INACTIVE_COUNT) {
|
||||
if (h === 0) {
|
||||
MyAvatar.clearJointData("LeftHand");
|
||||
MyAvatar.clearJointData("LeftForeArm");
|
||||
MyAvatar.clearJointData("LeftArm");
|
||||
} else {
|
||||
MyAvatar.clearJointData("RightHand");
|
||||
MyAvatar.clearJointData("RightForeArm");
|
||||
MyAvatar.clearJointData("RightArm");
|
||||
if (handAnimationStateHandlers[h] !== null) {
|
||||
MyAvatar.removeAnimationStateHandler(handAnimationStateHandlers[h]);
|
||||
handAnimationStateHandlers[h] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -483,6 +526,9 @@ var leapHands = (function () {
|
|||
for (h = 0; h < NUM_HANDS; h += 1) {
|
||||
Controller.releaseInputController(hands[h].controller);
|
||||
Controller.releaseInputController(wrists[h].controller);
|
||||
if (handAnimationStateHandlers[h] !== null) {
|
||||
MyAvatar.removeAnimationStateHandler(handAnimationStateHandlers[h]);
|
||||
}
|
||||
for (i = 0; i < NUM_FINGERS; i += 1) {
|
||||
for (j = 0; j < NUM_FINGER_JOINTS; j += 1) {
|
||||
if (fingers[h][i][j].controller !== null) {
|
||||
|
|
226
examples/data_visualization/earthquakes_live.js
Normal file
226
examples/data_visualization/earthquakes_live.js
Normal file
|
@ -0,0 +1,226 @@
|
|||
// earthquakes_live.js
|
||||
//
|
||||
// exploratory implementation in prep for abstract latlong to earth graphing tool for VR
|
||||
// shows all of the quakes in the past 24 hours reported by the USGS
|
||||
//
|
||||
// created by james b. pollack @imgntn on 12/5/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
// working notes: maybe try doing markers as boxes,rotated to the sphere normal, and with the height representing some value
|
||||
|
||||
Script.include('../libraries/promise.js');
|
||||
var Promise = loadPromise();
|
||||
|
||||
Script.include('../libraries/tinyColor.js');
|
||||
var tinyColor = loadTinyColor();
|
||||
|
||||
//you could make it the size of the actual earth.
|
||||
var EARTH_SPHERE_RADIUS = 6371;
|
||||
var EARTH_SPHERE_RADIUS = 2;
|
||||
|
||||
var EARTH_CENTER_POSITION = Vec3.sum(Vec3.sum(MyAvatar.position, {
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
}), Vec3.multiply(EARTH_SPHERE_RADIUS, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var EARTH_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/earthquakes_live/models/earth-noclouds.fbx';
|
||||
|
||||
var SHOULD_SPIN=false;
|
||||
var POLL_FOR_CHANGES = true;
|
||||
//USGS updates the data every five minutes
|
||||
var CHECK_QUAKE_FREQUENCY = 5 * 60 * 1000;
|
||||
|
||||
var QUAKE_MARKER_DIMENSIONS = {
|
||||
x: 0.01,
|
||||
y: 0.01,
|
||||
z: 0.01
|
||||
};
|
||||
|
||||
function createEarth() {
|
||||
var earthProperties = {
|
||||
name: 'Earth',
|
||||
type: 'Model',
|
||||
modelURL: EARTH_MODEL_URL,
|
||||
position: EARTH_CENTER_POSITION,
|
||||
dimensions: {
|
||||
x: EARTH_SPHERE_RADIUS,
|
||||
y: EARTH_SPHERE_RADIUS,
|
||||
z: EARTH_SPHERE_RADIUS
|
||||
},
|
||||
rotation: Quat.fromPitchYawRollDegrees(0, 90, 0),
|
||||
// collisionsWillMove: true,
|
||||
//if you have a shapetype it blocks the smaller markers
|
||||
// shapeType:'sphere'
|
||||
// userData: JSON.stringify({
|
||||
// grabbableKey: {
|
||||
// grabbable: false
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
return Entities.addEntity(earthProperties)
|
||||
}
|
||||
|
||||
function latLongToVector3(lat, lon, radius, height) {
|
||||
var phi = (lat) * Math.PI / 180;
|
||||
var theta = (lon - 180) * Math.PI / 180;
|
||||
|
||||
var x = -(radius + height) * Math.cos(phi) * Math.cos(theta);
|
||||
var y = (radius + height) * Math.sin(phi);
|
||||
var z = (radius + height) * Math.cos(phi) * Math.sin(theta);
|
||||
|
||||
return {
|
||||
x: x,
|
||||
y: y,
|
||||
z: z
|
||||
};
|
||||
}
|
||||
|
||||
function getQuakePosition(earthquake) {
|
||||
var longitude = earthquake.geometry.coordinates[0];
|
||||
var latitude = earthquake.geometry.coordinates[1];
|
||||
var depth = earthquake.geometry.coordinates[2];
|
||||
|
||||
var latlng = latLongToVector3(latitude, longitude, EARTH_SPHERE_RADIUS / 2, 0);
|
||||
|
||||
var position = EARTH_CENTER_POSITION;
|
||||
var finalPosition = Vec3.sum(position, latlng);
|
||||
|
||||
//print('finalpos::' + JSON.stringify(finalPosition))
|
||||
return finalPosition
|
||||
}
|
||||
|
||||
var QUAKE_URL = 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson'
|
||||
|
||||
function get(url) {
|
||||
print('getting' + url)
|
||||
// Return a new promise.
|
||||
return new Promise(function(resolve, reject) {
|
||||
// Do the usual XHR stuff
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', url);
|
||||
req.onreadystatechange = function() {
|
||||
print('req status:: ' + JSON.stringify(req.status))
|
||||
|
||||
if (req.readyState == 4 && req.status == 200) {
|
||||
var myArr = JSON.parse(req.responseText);
|
||||
resolve(myArr);
|
||||
}
|
||||
};
|
||||
|
||||
req.send();
|
||||
});
|
||||
}
|
||||
|
||||
function createQuakeMarker(earthquake) {
|
||||
var markerProperties = {
|
||||
name: earthquake.properties.place,
|
||||
type: 'Sphere',
|
||||
parentID:earth,
|
||||
dimensions: QUAKE_MARKER_DIMENSIONS,
|
||||
position: getQuakePosition(earthquake),
|
||||
ignoreForCollisions:true,
|
||||
lifetime: 6000,
|
||||
color: getQuakeMarkerColor(earthquake)
|
||||
}
|
||||
|
||||
// print('marker properties::' + JSON.stringify(markerProperties))
|
||||
return Entities.addEntity(markerProperties);
|
||||
}
|
||||
|
||||
function getQuakeMarkerColor(earthquake) {
|
||||
var color = {};
|
||||
var magnitude = earthquake.properties.mag;
|
||||
//realistic but will never get full red coloring and will probably be pretty dull for most. must experiment
|
||||
var sValue = scale(magnitude, 0, 10, 0, 100);
|
||||
var HSL_string = "hsl(0, " + sValue + "%, 50%)"
|
||||
var color = tinyColor(HSL_string);
|
||||
var finalColor = {
|
||||
red: color._r,
|
||||
green: color._g,
|
||||
blue: color._b
|
||||
}
|
||||
|
||||
return finalColor
|
||||
}
|
||||
|
||||
function scale(value, min1, max1, min2, max2) {
|
||||
return min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
|
||||
}
|
||||
|
||||
function processQuakes(earthquakes) {
|
||||
print('quakers length' + earthquakes.length)
|
||||
earthquakes.forEach(function(quake) {
|
||||
// print('PROCESSING A QUAKE')
|
||||
var marker = createQuakeMarker(quake);
|
||||
markers.push(marker);
|
||||
})
|
||||
print('markers length:' + markers.length)
|
||||
}
|
||||
|
||||
var quakes;
|
||||
var markers = [];
|
||||
|
||||
var earth = createEarth();
|
||||
|
||||
function getThenProcessQuakes() {
|
||||
get(QUAKE_URL).then(function(response) {
|
||||
print('got it::' + response.features.length)
|
||||
quakes = response.features;
|
||||
processQuakes(quakes);
|
||||
//print("Success!" + JSON.stringify(response));
|
||||
}, function(error) {
|
||||
print('error getting quakes')
|
||||
});
|
||||
}
|
||||
|
||||
function cleanupMarkers() {
|
||||
print('CLEANING UP MARKERS')
|
||||
while (markers.length > 0) {
|
||||
Entities.deleteEntity(markers.pop());
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupEarth() {
|
||||
Entities.deleteEntity(earth);
|
||||
Script.update.disconnect(spinEarth);
|
||||
}
|
||||
|
||||
function cleanupInterval() {
|
||||
if (pollingInterval !== null) {
|
||||
Script.clearInterval(pollingInterval)
|
||||
}
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanupMarkers);
|
||||
Script.scriptEnding.connect(cleanupEarth);
|
||||
Script.scriptEnding.connect(cleanupInterval);
|
||||
|
||||
getThenProcessQuakes();
|
||||
|
||||
var pollingInterval = null;
|
||||
|
||||
if (POLL_FOR_CHANGES === true) {
|
||||
pollingInterval = Script.setInterval(function() {
|
||||
cleanupMarkers();
|
||||
getThenProcessQuakes()
|
||||
}, CHECK_QUAKE_FREQUENCY)
|
||||
}
|
||||
|
||||
|
||||
function spinEarth(){
|
||||
Entities.editEntity(earth,{
|
||||
angularVelocity:{
|
||||
x:0,
|
||||
y:0.25,
|
||||
z:0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if(SHOULD_SPIN===true){
|
||||
Script.update.connect(spinEarth);
|
||||
}
|
|
@ -392,6 +392,11 @@ var toolBar = (function() {
|
|||
url,
|
||||
file;
|
||||
|
||||
if (!event.isLeftButton) {
|
||||
// if another mouse button than left is pressed ignore it
|
||||
return false;
|
||||
}
|
||||
|
||||
clickedOverlay = Overlays.getOverlayAtPoint({
|
||||
x: event.x,
|
||||
y: event.y
|
||||
|
|
|
@ -32,7 +32,7 @@ function randVector(a, b) {
|
|||
|
||||
var startTimeInSeconds = new Date().getTime() / 1000;
|
||||
|
||||
var NATURAL_SIZE_OF_BUTTERFLY = { x: 1.0, y: 0.4, z: 0.2 };
|
||||
var NATURAL_SIZE_OF_BUTTERFLY = { x:0.5, y: 0.2, z: 0.1 };
|
||||
|
||||
var lifeTime = 3600; // One hour lifespan
|
||||
var range = 7.0; // Over what distance in meters do you want the flock to fly around
|
||||
|
@ -65,8 +65,8 @@ function addButterfly() {
|
|||
var color = { red: 100, green: 100, blue: 100 };
|
||||
var size = 0;
|
||||
|
||||
var MINSIZE = 0.06;
|
||||
var RANGESIZE = 0.2;
|
||||
var MINSIZE = 0.01;
|
||||
var RANGESIZE = 0.05;
|
||||
var maxSize = MINSIZE + RANGESIZE;
|
||||
|
||||
size = MINSIZE + Math.random() * RANGESIZE;
|
||||
|
@ -74,7 +74,7 @@ function addButterfly() {
|
|||
var dimensions = Vec3.multiply(NATURAL_SIZE_OF_BUTTERFLY, (size / maxSize));
|
||||
|
||||
var GRAVITY = -0.2;
|
||||
var newFrameRate = 20 + Math.random() * 30;
|
||||
var newFrameRate = 29 + Math.random() * 30;
|
||||
var properties = {
|
||||
type: "Model",
|
||||
lifetime: lifeTime,
|
||||
|
@ -86,17 +86,13 @@ function addButterfly() {
|
|||
dimensions: dimensions,
|
||||
color: color,
|
||||
animation: {
|
||||
url: "http://public.highfidelity.io/models/content/butterfly/butterfly.fbx",
|
||||
firstFrame: 0,
|
||||
url: "http://hifi-content.s3.amazonaws.com/james/butterfly/butterfly.fbx",
|
||||
fps: newFrameRate,
|
||||
currentFrame: 0,
|
||||
hold: false,
|
||||
lastFrame: 10000,
|
||||
loop: true,
|
||||
running: true,
|
||||
startAutomatically:false
|
||||
},
|
||||
modelURL: "http://public.highfidelity.io/models/content/butterfly/butterfly.fbx"
|
||||
modelURL: "http://hifi-content.s3.amazonaws.com/james/butterfly/butterfly.fbx"
|
||||
};
|
||||
butterflies.push(Entities.addEntity(properties));
|
||||
}
|
||||
|
|
|
@ -223,7 +223,10 @@
|
|||
var elDimensionsZ = document.getElementById("property-dim-z");
|
||||
var elResetToNaturalDimensions = document.getElementById("reset-to-natural-dimensions");
|
||||
var elRescaleDimensionsPct = document.getElementById("dimension-rescale-pct");
|
||||
var elRescaleDimensionsButton = document.getElementById("dimension-rescale-button");
|
||||
var elRescaleDimensionsButton = document.getElementById("dimension-rescale-button");
|
||||
|
||||
var elParentID = document.getElementById("property-parent-id");
|
||||
var elParentJointIndex = document.getElementById("property-parent-joint-index");
|
||||
|
||||
var elRegistrationX = document.getElementById("property-reg-x");
|
||||
var elRegistrationY = document.getElementById("property-reg-y");
|
||||
|
@ -453,6 +456,9 @@
|
|||
elDimensionsY.value = properties.dimensions.y.toFixed(2);
|
||||
elDimensionsZ.value = properties.dimensions.z.toFixed(2);
|
||||
|
||||
elParentID.value = properties.parentID;
|
||||
elParentJointIndex.value = properties.parentJointIndex;
|
||||
|
||||
elRegistrationX.value = properties.registrationPoint.x.toFixed(2);
|
||||
elRegistrationY.value = properties.registrationPoint.y.toFixed(2);
|
||||
elRegistrationZ.value = properties.registrationPoint.z.toFixed(2);
|
||||
|
@ -666,6 +672,9 @@
|
|||
elDimensionsY.addEventListener('change', dimensionsChangeFunction);
|
||||
elDimensionsZ.addEventListener('change', dimensionsChangeFunction);
|
||||
|
||||
elParentID.addEventListener('change', createEmitTextPropertyUpdateFunction('parentID'));
|
||||
elParentJointIndex.addEventListener('change', createEmitNumberPropertyUpdateFunction('parentJointIndex'));
|
||||
|
||||
var registrationChangeFunction = createEmitVec3PropertyUpdateFunction(
|
||||
'registrationPoint', elRegistrationX, elRegistrationY, elRegistrationZ);
|
||||
elRegistrationX.addEventListener('change', registrationChangeFunction);
|
||||
|
@ -1055,6 +1064,19 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<span class="label" style="float: left; margin-right: 6px">ParentID</span>
|
||||
<div class="value" style="overflow: hidden;">
|
||||
<input type="text" id="property-parent-id">
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<span class="label" style="float: left; margin-right: 6px">ParentJointIndex</span>
|
||||
<div class="value" style="overflow: hidden;">
|
||||
<input type="text" id="property-parent-joint-index">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Registration</div>
|
||||
<div class="value">
|
||||
|
|
|
@ -2340,6 +2340,11 @@ SelectionDisplay = (function () {
|
|||
|
||||
that.mousePressEvent = function(event) {
|
||||
|
||||
if (!event.isLeftButton) {
|
||||
// if another mouse button than left is pressed ignore it
|
||||
return false;
|
||||
}
|
||||
|
||||
var somethingClicked = false;
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
|
||||
|
|
1155
examples/libraries/tinyColor.js
Normal file
1155
examples/libraries/tinyColor.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -47,7 +47,12 @@ function makeBasketball() {
|
|||
modelURL: basketballURL,
|
||||
restitution: 1.0,
|
||||
damping: 0.00001,
|
||||
shapeType: "sphere"
|
||||
shapeType: "sphere",
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true
|
||||
}
|
||||
})
|
||||
});
|
||||
originalPosition = position;
|
||||
}
|
||||
|
|
|
@ -67,12 +67,17 @@
|
|||
}
|
||||
|
||||
var BOW_SPATIAL_KEY = {
|
||||
relativePosition: {
|
||||
x: 0,
|
||||
leftRelativePosition: {
|
||||
x: 0.05,
|
||||
y: 0.06,
|
||||
z: 0.11
|
||||
z: -0.05
|
||||
},
|
||||
relativeRotation: Quat.fromPitchYawRollDegrees(0, -90, 90)
|
||||
rightRelativePosition: {
|
||||
x: -0.05,
|
||||
y: 0.06,
|
||||
z: -0.05
|
||||
},
|
||||
relativeRotation: Quat.fromPitchYawRollDegrees(0, 90, -90)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -48,12 +48,17 @@ var bow = Entities.addEntity({
|
|||
grabbableKey: {
|
||||
invertSolidWhileHeld: true,
|
||||
spatialKey: {
|
||||
relativePosition: {
|
||||
x: 0,
|
||||
leftRelativePosition: {
|
||||
x: 0.05,
|
||||
y: 0.06,
|
||||
z: 0.11
|
||||
z: -0.05
|
||||
},
|
||||
relativeRotation: Quat.fromPitchYawRollDegrees(0, -90, 90)
|
||||
rightRelativePosition: {
|
||||
x: -0.05,
|
||||
y: 0.06,
|
||||
z: -0.05
|
||||
},
|
||||
relativeRotation: Quat.fromPitchYawRollDegrees(0, 90, -90)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -15,12 +15,20 @@ function createDoll() {
|
|||
|
||||
var scriptURL = Script.resolvePath("doll.js");
|
||||
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 }), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var naturalDimensions = { x: 1.63, y: 1.67, z: 0.26};
|
||||
var naturalDimensions = {
|
||||
x: 1.63,
|
||||
y: 1.67,
|
||||
z: 0.26
|
||||
};
|
||||
|
||||
var desiredDimensions = Vec3.multiply(naturalDimensions, 0.15);
|
||||
|
||||
|
||||
var doll = Entities.addEntity({
|
||||
type: "Model",
|
||||
name: "doll",
|
||||
|
@ -39,7 +47,12 @@ function createDoll() {
|
|||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
collisionsWillMove: true
|
||||
collisionsWillMove: true,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true
|
||||
}
|
||||
})
|
||||
});
|
||||
return doll;
|
||||
}
|
||||
|
|
|
@ -18,14 +18,27 @@ var scriptURL = Script.resolvePath('flashlight.js');
|
|||
|
||||
var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx";
|
||||
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0, y: 0.5, z: 0}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var flashlight = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: modelURL,
|
||||
position: center,
|
||||
dimensions: { x: 0.08, y: 0.30, z: 0.08},
|
||||
dimensions: {
|
||||
x: 0.08,
|
||||
y: 0.30,
|
||||
z: 0.08
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
shapeType: 'box',
|
||||
script: scriptURL
|
||||
});
|
||||
script: scriptURL,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true
|
||||
}
|
||||
})
|
||||
});
|
|
@ -35,7 +35,12 @@ var pingPongGun = Entities.addEntity({
|
|||
z: 0.47
|
||||
},
|
||||
collisionsWillMove: true,
|
||||
collisionSoundURL: COLLISION_SOUND_URL
|
||||
collisionSoundURL: COLLISION_SOUND_URL,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function cleanUp() {
|
||||
|
|
|
@ -19,7 +19,6 @@ var createdStereoInputMenuItem = false;
|
|||
var DEVELOPER_MENU = "Developer";
|
||||
|
||||
var ENTITIES_MENU = DEVELOPER_MENU + " > Entities";
|
||||
var COLLISION_UPDATES_TO_SERVER = "Don't send collision updates to server";
|
||||
|
||||
var RENDER_MENU = DEVELOPER_MENU + " > Render";
|
||||
var ENTITIES_ITEM = "Entities";
|
||||
|
@ -66,7 +65,6 @@ function setupMenus() {
|
|||
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Don't Do Precision Picking", isCheckable: true, isChecked: false });
|
||||
Menu.addMenuItem({ menuName: "Developer > Entities", menuItemName: "Disable Light Entities", isCheckable: true, isChecked: false });
|
||||
*/
|
||||
Menu.addMenuItem({ menuName: ENTITIES_MENU, menuItemName: COLLISION_UPDATES_TO_SERVER, isCheckable: true, isChecked: false });
|
||||
}
|
||||
|
||||
if (!Menu.menuExists(RENDER_MENU)) {
|
||||
|
@ -112,11 +110,7 @@ function setupMenus() {
|
|||
Menu.menuItemEvent.connect(function (menuItem) {
|
||||
print("menuItemEvent() in JS... menuItem=" + menuItem);
|
||||
|
||||
if (menuItem == COLLISION_UPDATES_TO_SERVER) {
|
||||
var dontSendUpdates = Menu.isOptionChecked(COLLISION_UPDATES_TO_SERVER);
|
||||
print(" dontSendUpdates... checked=" + dontSendUpdates);
|
||||
Entities.setSendPhysicsUpdates(!dontSendUpdates);
|
||||
} else if (menuItem == ENTITIES_ITEM) {
|
||||
if (menuItem == ENTITIES_ITEM) {
|
||||
Scene.shouldRenderEntities = Menu.isOptionChecked(ENTITIES_ITEM);
|
||||
} else if (menuItem == AVATARS_ITEM) {
|
||||
Scene.shouldRenderAvatars = Menu.isOptionChecked(AVATARS_ITEM);
|
||||
|
|
69
examples/utilities/tools/reverbTest.js
Normal file
69
examples/utilities/tools/reverbTest.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// reverbTest.js
|
||||
// examples
|
||||
//
|
||||
// Created by Ken Cooke on 11/23/2015.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
Script.include("cookies.js");
|
||||
|
||||
var audioOptions = new AudioEffectOptions({
|
||||
maxRoomSize: 50,
|
||||
roomSize: 50,
|
||||
reverbTime: 4,
|
||||
damping: 0.50,
|
||||
inputBandwidth: 0.8,
|
||||
earlyLevel: 0,
|
||||
tailLevel: 0,
|
||||
dryLevel: -6,
|
||||
wetLevel: -6
|
||||
});
|
||||
|
||||
AudioDevice.setReverbOptions(audioOptions);
|
||||
AudioDevice.setReverb(true);
|
||||
print("Reverb is ON.");
|
||||
|
||||
var panel = new Panel(10, 200);
|
||||
|
||||
var parameters = [
|
||||
{ name: "roomSize", min: 0, max: 100, units: " feet" },
|
||||
{ name: "reverbTime", min: 0, max: 10, units: " sec" },
|
||||
{ name: "damping", min: 0, max: 1, units: " " },
|
||||
{ name: "inputBandwidth", min: 0, max: 1, units: " " },
|
||||
{ name: "earlyLevel", min: -48, max: 0, units: " dB" },
|
||||
{ name: "tailLevel", min: -48, max: 0, units: " dB" },
|
||||
{ name: "wetLevel", min: -48, max: 0, units: " dB" },
|
||||
]
|
||||
|
||||
function setter(name) {
|
||||
return function(value) { audioOptions[name] = value; AudioDevice.setReverbOptions(audioOptions); }
|
||||
}
|
||||
|
||||
function getter(name) {
|
||||
return function() { return audioOptions[name]; }
|
||||
}
|
||||
|
||||
function displayer(units) {
|
||||
return function(value) { return (value).toFixed(1) + units; };
|
||||
}
|
||||
|
||||
// create a slider for each parameter
|
||||
for (var i = 0; i < parameters.length; i++) {
|
||||
var p = parameters[i];
|
||||
panel.newSlider(p.name, p.min, p.max, setter(p.name), getter(p.name), displayer(p.units));
|
||||
}
|
||||
|
||||
Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return panel.mouseMoveEvent(event); });
|
||||
Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return panel.mousePressEvent(event); });
|
||||
Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseReleaseEvent(event); });
|
||||
|
||||
function scriptEnding() {
|
||||
panel.destroy();
|
||||
AudioDevice.setReverb(false);
|
||||
print("Reverb is OFF.");
|
||||
}
|
||||
Script.scriptEnding.connect(scriptEnding);
|
|
@ -2,7 +2,7 @@ set(TARGET_NAME interface)
|
|||
project(${TARGET_NAME})
|
||||
|
||||
# set a default root dir for each of our optional externals if it was not passed
|
||||
set(OPTIONAL_EXTERNALS "LeapMotion" "RtMidi" "RSSDK")
|
||||
set(OPTIONAL_EXTERNALS "LeapMotion")
|
||||
|
||||
if(WIN32)
|
||||
list(APPEND OPTIONAL_EXTERNALS "3DConnexionClient")
|
||||
|
@ -161,13 +161,6 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
|
|||
endif ()
|
||||
endforeach()
|
||||
|
||||
# special OS X modifications for RtMidi library
|
||||
if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI AND APPLE)
|
||||
find_library(CoreMIDI CoreMIDI)
|
||||
add_definitions(-D__MACOSX_CORE__)
|
||||
target_link_libraries(${TARGET_NAME} ${CoreMIDI})
|
||||
endif ()
|
||||
|
||||
# include headers for interface and InterfaceConfig.
|
||||
include_directories("${PROJECT_SOURCE_DIR}/src")
|
||||
|
||||
|
|
9
interface/external/rssdk/readme.txt
vendored
9
interface/external/rssdk/readme.txt
vendored
|
@ -1,9 +0,0 @@
|
|||
|
||||
Instructions for adding the Intel Realsense (RSSDK) to Interface
|
||||
Thijs Wenker, December 19, 2014
|
||||
|
||||
This is Windows only for now.
|
||||
|
||||
1. Download the SDK at https://software.intel.com/en-us/intel-realsense-sdk/download
|
||||
|
||||
2. Copy the `lib` and `include` folder inside this directory
|
43
interface/external/rtmidi/readme.txt
vendored
43
interface/external/rtmidi/readme.txt
vendored
|
@ -1,43 +0,0 @@
|
|||
|
||||
Instructions for adding the RtMidi library to Interface
|
||||
Stephen Birarda, June 30, 2014
|
||||
|
||||
1. Download the RtMidi tarball from High Fidelity S3.
|
||||
http://public.highfidelity.io/dependencies/rtmidi-2.1.0.tar.gz
|
||||
|
||||
2. Copy RtMidi.h to externals/rtmidi/include.
|
||||
|
||||
3. Compile the RtMidi library.
|
||||
|
||||
3. Copy either librtmidi.dylib (dynamic) or librtmidi.a (static) to externals/rtmidi/lib
|
||||
|
||||
4. Delete your build directory, run cmake and build, and you should be all set.
|
||||
|
||||
=========================
|
||||
|
||||
RtMidi: realtime MIDI i/o C++ classes<BR>
|
||||
Copyright (c) 2003-2014 Gary P. Scavone
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
(the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
Any person wishing to distribute modifications to the Software is
|
||||
asked to send the modifications to the original developer so that
|
||||
they can be incorporated into the canonical version. This is,
|
||||
however, not a binding provision of this license.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -13,12 +13,11 @@
|
|||
{ "from": "Hydra.RB", "to": "Standard.RB" },
|
||||
{ "from": "Hydra.RS", "to": "Standard.RS" },
|
||||
|
||||
{ "from": [ "Hydra.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": [ "Hydra.L1", "Hydra.L2" ], "to": "Standard.LeftSecondaryThumb" },
|
||||
|
||||
{ "from": [ "Hydra.R3", "Hydra.R4" ], "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": [ "Hydra.R1", "Hydra.R2" ], "to": "Standard.RightSecondaryThumb" },
|
||||
{ "from": [ "Hydra.L1", "Hydra.L2", "Hydra.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": [ "Hydra.L0" ], "to": "Standard.LeftSecondaryThumb" },
|
||||
|
||||
{ "from": [ "Hydra.R1", "Hydra.R2", "Hydra.R3", "Hydra.R4" ], "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": [ "Hydra.R0" ], "to": "Standard.RightSecondaryThumb" },
|
||||
|
||||
{ "from": "Hydra.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Hydra.RightHand", "to": "Standard.RightHand" }
|
||||
|
|
|
@ -45,7 +45,12 @@ Item {
|
|||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
text: "Framerate: " + root.framerate
|
||||
text: "Render Rate: " + root.renderrate
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
font.pixelSize: root.fontSize
|
||||
text: "Present Rate: " + root.presentrate
|
||||
}
|
||||
Text {
|
||||
color: root.fontColor;
|
||||
|
|
|
@ -44,6 +44,9 @@
|
|||
|
||||
#include <QtNetwork/QNetworkDiskCache>
|
||||
|
||||
#include <gl/Config.h>
|
||||
#include <QOpenGLContextWrapper.h>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <AddressManager.h>
|
||||
#include <ApplicationVersion.h>
|
||||
|
@ -78,7 +81,7 @@
|
|||
#include <ObjectMotionState.h>
|
||||
#include <OctalCode.h>
|
||||
#include <OctreeSceneStats.h>
|
||||
#include <gl/OffscreenGlCanvas.h>
|
||||
#include <gl/OffscreenGLCanvas.h>
|
||||
#include <PathUtils.h>
|
||||
#include <PerfStat.h>
|
||||
#include <PhysicsEngine.h>
|
||||
|
@ -110,8 +113,6 @@
|
|||
#include "devices/EyeTracker.h"
|
||||
#include "devices/Faceshift.h"
|
||||
#include "devices/Leapmotion.h"
|
||||
#include "devices/MIDIManager.h"
|
||||
#include "devices/RealSense.h"
|
||||
#include "DiscoverabilityManager.h"
|
||||
#include "GLCanvas.h"
|
||||
#include "InterfaceActionFactory.h"
|
||||
|
@ -149,6 +150,8 @@
|
|||
#include "ui/Stats.h"
|
||||
#include "ui/UpdateDialog.h"
|
||||
#include "Util.h"
|
||||
#include "InterfaceParentFinder.h"
|
||||
|
||||
|
||||
|
||||
// ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
|
||||
|
@ -297,6 +300,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>();
|
||||
DependencyManager::registerInheritance<EntityActionFactoryInterface, InterfaceActionFactory>();
|
||||
DependencyManager::registerInheritance<SpatialParentFinder, InterfaceParentFinder>();
|
||||
|
||||
Setting::init();
|
||||
|
||||
|
@ -345,6 +349,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
DependencyManager::set<MessagesClient>();
|
||||
DependencyManager::set<UserInputMapper>();
|
||||
DependencyManager::set<controller::ScriptingInterface, ControllerScriptingInterface>();
|
||||
DependencyManager::set<InterfaceParentFinder>();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -615,8 +620,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
|
||||
// enable mouse tracking; otherwise, we only get drag events
|
||||
_glWidget->setMouseTracking(true);
|
||||
_glWidget->makeCurrent();
|
||||
_glWidget->initializeGL();
|
||||
|
||||
_offscreenContext = new OffscreenGlCanvas();
|
||||
_offscreenContext = new OffscreenGLCanvas();
|
||||
_offscreenContext->create(_glWidget->context()->contextHandle());
|
||||
_offscreenContext->makeCurrent();
|
||||
initializeGL();
|
||||
|
@ -719,12 +726,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
// set the local loopback interface for local sounds from audio scripts
|
||||
AudioScriptingInterface::getInstance().setLocalAudioInterface(audioIO.data());
|
||||
|
||||
#ifdef HAVE_RTMIDI
|
||||
// setup the MIDIManager
|
||||
MIDIManager& midiManagerInstance = MIDIManager::getInstance();
|
||||
midiManagerInstance.openDefaultPort();
|
||||
#endif
|
||||
|
||||
this->installEventFilter(this);
|
||||
|
||||
// initialize our face trackers after loading the menu settings
|
||||
|
@ -970,7 +971,6 @@ Application::~Application() {
|
|||
nodeThread->wait();
|
||||
|
||||
Leapmotion::destroy();
|
||||
RealSense::destroy();
|
||||
|
||||
#if 0
|
||||
ConnexionClient::getInstance().destroy();
|
||||
|
@ -1077,6 +1077,11 @@ void Application::initializeUi() {
|
|||
}
|
||||
|
||||
void Application::paintGL() {
|
||||
// paintGL uses a queued connection, so we can get messages from the queue even after we've quit
|
||||
// and the plugins have shutdown
|
||||
if (_aboutToQuit) {
|
||||
return;
|
||||
}
|
||||
_frameCount++;
|
||||
|
||||
// update fps moving average
|
||||
|
@ -1141,7 +1146,7 @@ void Application::paintGL() {
|
|||
_lastInstantaneousFps = instantaneousFps;
|
||||
|
||||
auto displayPlugin = getActiveDisplayPlugin();
|
||||
displayPlugin->preRender();
|
||||
// FIXME not needed anymore?
|
||||
_offscreenContext->makeCurrent();
|
||||
|
||||
// update the avatar with a fresh HMD pose
|
||||
|
@ -1196,6 +1201,9 @@ void Application::paintGL() {
|
|||
QSize size = getDeviceSize();
|
||||
renderArgs._viewport = glm::ivec4(0, 0, size.width(), size.height());
|
||||
_applicationOverlay.renderOverlay(&renderArgs);
|
||||
gpu::FramebufferPointer overlayFramebuffer = _applicationOverlay.getOverlayFramebuffer();
|
||||
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -1250,7 +1258,7 @@ void Application::paintGL() {
|
|||
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)) * hmdRotation);
|
||||
glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix());
|
||||
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
|
||||
+ glm::vec3(0, _raiseMirror * myAvatar->getScale(), 0)
|
||||
+ glm::vec3(0, _raiseMirror * myAvatar->getAvatarScale(), 0)
|
||||
+ (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
|
||||
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror
|
||||
+ (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))) * hmdOffset);
|
||||
|
@ -1258,7 +1266,7 @@ void Application::paintGL() {
|
|||
_myCamera.setRotation(myAvatar->getWorldAlignedOrientation()
|
||||
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
|
||||
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
|
||||
+ glm::vec3(0, _raiseMirror * myAvatar->getScale(), 0)
|
||||
+ glm::vec3(0, _raiseMirror * myAvatar->getAvatarScale(), 0)
|
||||
+ (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
|
||||
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
|
||||
}
|
||||
|
@ -1309,6 +1317,13 @@ void Application::paintGL() {
|
|||
auto baseProjection = renderArgs._viewFrustum->getProjection();
|
||||
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
|
||||
float IPDScale = hmdInterface->getIPDScale();
|
||||
|
||||
// Tell the plugin what pose we're using to render. In this case we're just using the
|
||||
// unmodified head pose because the only plugin that cares (the Oculus plugin) uses it
|
||||
// for rotational timewarp. If we move to support positonal timewarp, we need to
|
||||
// ensure this contains the full pose composed with the eye offsets.
|
||||
mat4 headPose = displayPlugin->getHeadPose(_frameCount);
|
||||
|
||||
// FIXME we probably don't need to set the projection matrix every frame,
|
||||
// only when the display plugin changes (or in non-HMD modes when the user
|
||||
// changes the FOV manually, which right now I don't think they can.
|
||||
|
@ -1324,12 +1339,7 @@ void Application::paintGL() {
|
|||
mat4 eyeOffsetTransform = glm::translate(mat4(), eyeOffset * -1.0f * IPDScale);
|
||||
eyeOffsets[eye] = eyeOffsetTransform;
|
||||
|
||||
// Tell the plugin what pose we're using to render. In this case we're just using the
|
||||
// unmodified head pose because the only plugin that cares (the Oculus plugin) uses it
|
||||
// for rotational timewarp. If we move to support positonal timewarp, we need to
|
||||
// ensure this contains the full pose composed with the eye offsets.
|
||||
mat4 headPose = displayPlugin->getHeadPose();
|
||||
displayPlugin->setEyeRenderPose(eye, headPose);
|
||||
displayPlugin->setEyeRenderPose(_frameCount, eye, headPose);
|
||||
|
||||
eyeProjections[eye] = displayPlugin->getProjection(eye, baseProjection);
|
||||
});
|
||||
|
@ -1344,6 +1354,7 @@ void Application::paintGL() {
|
|||
}
|
||||
|
||||
// Overlay Composition, needs to occur after screen space effects have completed
|
||||
// FIXME migrate composition into the display plugins
|
||||
{
|
||||
PROFILE_RANGE(__FUNCTION__ "/compositor");
|
||||
PerformanceTimer perfTimer("compositor");
|
||||
|
@ -1372,44 +1383,40 @@ void Application::paintGL() {
|
|||
{
|
||||
PROFILE_RANGE(__FUNCTION__ "/pluginOutput");
|
||||
PerformanceTimer perfTimer("pluginOutput");
|
||||
auto primaryFbo = framebufferCache->getPrimaryFramebuffer();
|
||||
GLuint finalTexture = gpu::GLBackend::getTextureID(primaryFbo->getRenderBuffer(0));
|
||||
// Ensure the rendering context commands are completed when rendering
|
||||
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
// Ensure the sync object is flushed to the driver thread before releasing the context
|
||||
// CRITICAL for the mac driver apparently.
|
||||
glFlush();
|
||||
_offscreenContext->doneCurrent();
|
||||
auto primaryFramebuffer = framebufferCache->getPrimaryFramebuffer();
|
||||
auto scratchFramebuffer = framebufferCache->getFramebuffer();
|
||||
gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) {
|
||||
gpu::Vec4i rect;
|
||||
rect.z = size.width();
|
||||
rect.w = size.height();
|
||||
batch.setFramebuffer(scratchFramebuffer);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
batch.blit(primaryFramebuffer, rect, scratchFramebuffer, rect);
|
||||
batch.setFramebuffer(nullptr);
|
||||
});
|
||||
auto finalTexturePointer = scratchFramebuffer->getRenderBuffer(0);
|
||||
GLuint finalTexture = gpu::GLBackend::getTextureID(finalTexturePointer);
|
||||
Q_ASSERT(0 != finalTexture);
|
||||
|
||||
Q_ASSERT(!_lockedFramebufferMap.contains(finalTexture));
|
||||
_lockedFramebufferMap[finalTexture] = scratchFramebuffer;
|
||||
|
||||
// Switches to the display plugin context
|
||||
displayPlugin->preDisplay();
|
||||
// Ensure all operations from the previous context are complete before we try to read the fbo
|
||||
glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync(sync);
|
||||
uint64_t displayStart = usecTimestampNow();
|
||||
|
||||
Q_ASSERT(isCurrentContext(_offscreenContext->getContext()));
|
||||
{
|
||||
PROFILE_RANGE(__FUNCTION__ "/pluginDisplay");
|
||||
PerformanceTimer perfTimer("pluginDisplay");
|
||||
displayPlugin->display(finalTexture, toGlm(size));
|
||||
PROFILE_RANGE(__FUNCTION__ "/pluginSubmitScene");
|
||||
PerformanceTimer perfTimer("pluginSubmitScene");
|
||||
displayPlugin->submitSceneTexture(_frameCount, finalTexture, toGlm(size));
|
||||
}
|
||||
Q_ASSERT(isCurrentContext(_offscreenContext->getContext()));
|
||||
|
||||
{
|
||||
PROFILE_RANGE(__FUNCTION__ "/bufferSwap");
|
||||
PerformanceTimer perfTimer("bufferSwap");
|
||||
displayPlugin->finishFrame();
|
||||
}
|
||||
uint64_t displayEnd = usecTimestampNow();
|
||||
const float displayPeriodUsec = (float)(displayEnd - displayStart); // usecs
|
||||
_lastPaintWait = displayPeriodUsec / (float)USECS_PER_SECOND;
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("makeCurrent");
|
||||
_offscreenContext->makeCurrent();
|
||||
Stats::getInstance()->setRenderDetails(renderArgs._details);
|
||||
|
||||
// Reset the gpu::Context Stages
|
||||
// Back to the default framebuffer;
|
||||
gpu::doInBatch(renderArgs._context, [=](gpu::Batch& batch) {
|
||||
|
@ -2412,6 +2419,9 @@ bool Application::exportEntities(const QString& filename, const QVector<EntityIt
|
|||
exportTree->addEntity(entityItem->getEntityItemID(), properties);
|
||||
}
|
||||
|
||||
// remap IDs on export so that we aren't publishing the IDs of entities in our domain
|
||||
exportTree->remapIDs();
|
||||
|
||||
exportTree->writeToJSONFile(filename.toLocal8Bit().constData());
|
||||
|
||||
// restore the main window's active state
|
||||
|
@ -2434,6 +2444,10 @@ bool Application::exportEntities(const QString& filename, float x, float y, floa
|
|||
properties.setPosition(properties.getPosition() - root);
|
||||
exportTree->addEntity(id, properties);
|
||||
}
|
||||
|
||||
// remap IDs on export so that we aren't publishing the IDs of entities in our domain
|
||||
exportTree->remapIDs();
|
||||
|
||||
exportTree->writeToSVOFile(filename.toLocal8Bit().constData());
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "No models were selected";
|
||||
|
@ -2478,6 +2492,7 @@ bool Application::importEntities(const QString& urlOrFilename) {
|
|||
|
||||
bool success = _entityClipboard->readFromURL(url.toString());
|
||||
if (success) {
|
||||
_entityClipboard->remapIDs();
|
||||
_entityClipboard->reaverageOctreeElements();
|
||||
}
|
||||
return success;
|
||||
|
@ -2519,7 +2534,6 @@ void Application::init() {
|
|||
qCDebug(interfaceapp) << "Loaded settings";
|
||||
|
||||
Leapmotion::init();
|
||||
RealSense::init();
|
||||
|
||||
// fire off an immediate domain-server check in now that settings are loaded
|
||||
DependencyManager::get<NodeList>()->sendDomainServerCheckIn();
|
||||
|
@ -2577,7 +2591,6 @@ void Application::setAvatarUpdateThreading(bool isThreaded) {
|
|||
return;
|
||||
}
|
||||
|
||||
auto myAvatar = getMyAvatar();
|
||||
if (_avatarUpdate) {
|
||||
_avatarUpdate->terminate(); // Must be before we shutdown anim graph.
|
||||
}
|
||||
|
@ -2619,7 +2632,7 @@ void Application::updateMyAvatarLookAtPosition() {
|
|||
lookAtPosition.x = -lookAtPosition.x;
|
||||
}
|
||||
if (isHMD) {
|
||||
glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose();
|
||||
glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(_frameCount);
|
||||
glm::quat hmdRotation = glm::quat_cast(headPose);
|
||||
lookAtSpot = _myCamera.getPosition() + myAvatar->getOrientation() * (hmdRotation * lookAtPosition);
|
||||
} else {
|
||||
|
@ -3073,13 +3086,6 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node
|
|||
//qCDebug(interfaceapp) << ">>> inside... queryOctree()... _viewFrustum.getFieldOfView()=" << _viewFrustum.getFieldOfView();
|
||||
bool wantExtraDebugging = getLogger()->extraDebugging();
|
||||
|
||||
// These will be the same for all servers, so we can set them up once and then reuse for each server we send to.
|
||||
_octreeQuery.setWantLowResMoving(true);
|
||||
_octreeQuery.setWantColor(true);
|
||||
_octreeQuery.setWantDelta(true);
|
||||
_octreeQuery.setWantOcclusionCulling(false);
|
||||
_octreeQuery.setWantCompression(true);
|
||||
|
||||
_octreeQuery.setCameraPosition(_viewFrustum.getPosition());
|
||||
_octreeQuery.setCameraOrientation(_viewFrustum.getOrientation());
|
||||
_octreeQuery.setCameraFov(_viewFrustum.getFieldOfView());
|
||||
|
@ -3323,7 +3329,7 @@ MyAvatar* Application::getMyAvatar() const {
|
|||
return DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
}
|
||||
|
||||
const glm::vec3& Application::getAvatarPosition() const {
|
||||
glm::vec3 Application::getAvatarPosition() const {
|
||||
return getMyAvatar()->getPosition();
|
||||
}
|
||||
|
||||
|
@ -3782,12 +3788,11 @@ void Application::domainChanged(const QString& domainHostname) {
|
|||
_domainConnectionRefusals.clear();
|
||||
}
|
||||
|
||||
void Application::handleDomainConnectionDeniedPacket(QSharedPointer<NLPacket> packet) {
|
||||
void Application::handleDomainConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
// Read deny reason from packet
|
||||
quint16 reasonSize;
|
||||
packet->readPrimitive(&reasonSize);
|
||||
QString reason = QString::fromUtf8(packet->getPayload() + packet->pos(), reasonSize);
|
||||
packet->seek(packet->pos() + reasonSize);
|
||||
message->readPrimitive(&reasonSize);
|
||||
QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize));
|
||||
|
||||
// output to the log so the user knows they got a denied connection request
|
||||
// and check and signal for an access token so that we can make sure they are logged in
|
||||
|
@ -3873,9 +3878,7 @@ void Application::nodeKilled(SharedNodePointer node) {
|
|||
Menu::getInstance()->getActionForOption(MenuOption::UploadAsset)->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::trackIncomingOctreePacket(NLPacket& packet, SharedNodePointer sendingNode, bool wasStatsPacket) {
|
||||
|
||||
void Application::trackIncomingOctreePacket(ReceivedMessage& message, SharedNodePointer sendingNode, bool wasStatsPacket) {
|
||||
// Attempt to identify the sender from its address.
|
||||
if (sendingNode) {
|
||||
const QUuid& nodeUUID = sendingNode->getUUID();
|
||||
|
@ -3884,13 +3887,13 @@ void Application::trackIncomingOctreePacket(NLPacket& packet, SharedNodePointer
|
|||
_octreeServerSceneStats.withWriteLock([&] {
|
||||
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
|
||||
OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID];
|
||||
stats.trackIncomingOctreePacket(packet, wasStatsPacket, sendingNode->getClockSkewUsec());
|
||||
stats.trackIncomingOctreePacket(message, wasStatsPacket, sendingNode->getClockSkewUsec());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingNode) {
|
||||
int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode) {
|
||||
// But, also identify the sender, and keep track of the contained jurisdiction root for this server
|
||||
|
||||
// parse the incoming stats datas stick it in a temporary object for now, while we
|
||||
|
@ -3902,7 +3905,7 @@ int Application::processOctreeStats(NLPacket& packet, SharedNodePointer sendingN
|
|||
// now that we know the node ID, let's add these stats to the stats for that node...
|
||||
_octreeServerSceneStats.withWriteLock([&] {
|
||||
OctreeSceneStats& octreeStats = _octreeServerSceneStats[nodeUUID];
|
||||
statsMessageLength = octreeStats.unpackFromPacket(packet);
|
||||
statsMessageLength = octreeStats.unpackFromPacket(message);
|
||||
|
||||
// see if this is the first we've heard of this node...
|
||||
NodeToJurisdictionMap* jurisdiction = NULL;
|
||||
|
@ -4070,10 +4073,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
scriptEngine->registerGlobalObject("Scene", DependencyManager::get<SceneScriptingInterface>().data());
|
||||
|
||||
scriptEngine->registerGlobalObject("ScriptDiscoveryService", this->getRunningScriptsWidget());
|
||||
|
||||
#ifdef HAVE_RTMIDI
|
||||
scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance());
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Application::canAcceptURL(const QString& urlString) {
|
||||
|
@ -4085,7 +4084,7 @@ bool Application::canAcceptURL(const QString& urlString) {
|
|||
QString lowerPath = url.path().toLower();
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
if (lowerPath.endsWith(i.key())) {
|
||||
if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -4105,7 +4104,7 @@ bool Application::acceptURL(const QString& urlString, bool defaultUpload) {
|
|||
QString lowerPath = url.path().toLower();
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
if (lowerPath.endsWith(i.key())) {
|
||||
if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) {
|
||||
AcceptURLMethod method = i.value();
|
||||
return (this->*method)(urlString);
|
||||
}
|
||||
|
@ -4205,7 +4204,7 @@ bool Application::askToUploadAsset(const QString& filename) {
|
|||
messageBox.setDefaultButton(QMessageBox::Ok);
|
||||
|
||||
// Option to drop model in world for models
|
||||
if (filename.endsWith(FBX_EXTENSION) || filename.endsWith(OBJ_EXTENSION)) {
|
||||
if (filename.endsWith(FBX_EXTENSION, Qt::CaseInsensitive) || filename.endsWith(OBJ_EXTENSION, Qt::CaseInsensitive)) {
|
||||
auto checkBox = new QCheckBox(&messageBox);
|
||||
checkBox->setText("Add to scene");
|
||||
messageBox.setCheckBox(checkBox);
|
||||
|
@ -4240,7 +4239,8 @@ void Application::modelUploadFinished(AssetUpload* upload, const QString& hash)
|
|||
auto filename = QFileInfo(upload->getFilename()).fileName();
|
||||
|
||||
if ((upload->getError() == AssetUpload::NoError) &&
|
||||
(filename.endsWith(FBX_EXTENSION) || filename.endsWith(OBJ_EXTENSION))) {
|
||||
(upload->getExtension().endsWith(FBX_EXTENSION, Qt::CaseInsensitive) ||
|
||||
upload->getExtension().endsWith(OBJ_EXTENSION, Qt::CaseInsensitive))) {
|
||||
|
||||
auto entities = DependencyManager::get<EntityScriptingInterface>();
|
||||
|
||||
|
@ -4521,7 +4521,7 @@ void Application::takeSnapshot() {
|
|||
player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
|
||||
player->play();
|
||||
|
||||
QString fileName = Snapshot::saveSnapshot(_glWidget->grabFrameBuffer());
|
||||
QString fileName = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot());
|
||||
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
if (!accountManager.isLoggedIn()) {
|
||||
|
@ -4532,7 +4532,6 @@ void Application::takeSnapshot() {
|
|||
_snapshotShareDialog = new SnapshotShareDialog(fileName, _glWidget);
|
||||
}
|
||||
_snapshotShareDialog->show();
|
||||
|
||||
}
|
||||
|
||||
float Application::getRenderResolutionScale() const {
|
||||
|
@ -4715,10 +4714,6 @@ const DisplayPlugin* Application::getActiveDisplayPlugin() const {
|
|||
return ((Application*)this)->getActiveDisplayPlugin();
|
||||
}
|
||||
|
||||
bool _activatingDisplayPlugin{ false };
|
||||
QVector<QPair<QString, QString>> _currentDisplayPluginActions;
|
||||
QVector<QPair<QString, QString>> _currentInputPluginActions;
|
||||
|
||||
static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) {
|
||||
auto menu = Menu::getInstance();
|
||||
QString name = displayPlugin->getName();
|
||||
|
@ -4748,9 +4743,10 @@ void Application::updateDisplayMode() {
|
|||
bool first = true;
|
||||
foreach(auto displayPlugin, displayPlugins) {
|
||||
addDisplayPluginToMenu(displayPlugin, first);
|
||||
QObject::connect(displayPlugin.get(), &DisplayPlugin::requestRender, [this] {
|
||||
paintGL();
|
||||
});
|
||||
// This must be a queued connection to avoid a deadlock
|
||||
QObject::connect(displayPlugin.get(), &DisplayPlugin::requestRender,
|
||||
this, &Application::paintGL, Qt::QueuedConnection);
|
||||
|
||||
QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) {
|
||||
resizeGL();
|
||||
});
|
||||
|
@ -4792,19 +4788,18 @@ void Application::updateDisplayMode() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!_currentDisplayPluginActions.isEmpty()) {
|
||||
|
||||
if (!_pluginContainer->currentDisplayActions().isEmpty()) {
|
||||
auto menu = Menu::getInstance();
|
||||
foreach(auto itemInfo, _currentDisplayPluginActions) {
|
||||
foreach(auto itemInfo, _pluginContainer->currentDisplayActions()) {
|
||||
menu->removeMenuItem(itemInfo.first, itemInfo.second);
|
||||
}
|
||||
_currentDisplayPluginActions.clear();
|
||||
_pluginContainer->currentDisplayActions().clear();
|
||||
}
|
||||
|
||||
if (newDisplayPlugin) {
|
||||
_offscreenContext->makeCurrent();
|
||||
_activatingDisplayPlugin = true;
|
||||
newDisplayPlugin->activate();
|
||||
_activatingDisplayPlugin = false;
|
||||
_offscreenContext->makeCurrent();
|
||||
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
|
||||
_offscreenContext->makeCurrent();
|
||||
|
@ -4930,7 +4925,7 @@ mat4 Application::getEyeOffset(int eye) const {
|
|||
|
||||
mat4 Application::getHMDSensorPose() const {
|
||||
if (isHMDMode()) {
|
||||
return getActiveDisplayPlugin()->getHeadPose();
|
||||
return getActiveDisplayPlugin()->getHeadPose(_frameCount);
|
||||
}
|
||||
return mat4();
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
#include "ui/ToolWindow.h"
|
||||
#include "UndoStackScriptingInterface.h"
|
||||
|
||||
class OffscreenGlCanvas;
|
||||
class OffscreenGLCanvas;
|
||||
class GLCanvas;
|
||||
class FaceTracker;
|
||||
class MainWindow;
|
||||
|
@ -158,6 +158,7 @@ public:
|
|||
|
||||
bool isForeground() const { return _isForeground; }
|
||||
|
||||
uint32_t getFrameCount() { return _frameCount; }
|
||||
float getFps() const { return _fps; }
|
||||
float const HMD_TARGET_FRAME_RATE = 75.0f;
|
||||
float const DESKTOP_TARGET_FRAME_RATE = 60.0f;
|
||||
|
@ -185,7 +186,7 @@ public:
|
|||
virtual float getSizeScale() const;
|
||||
virtual int getBoundaryLevelAdjust() const;
|
||||
virtual PickRay computePickRay(float x, float y) const;
|
||||
virtual const glm::vec3& getAvatarPosition() const;
|
||||
virtual glm::vec3 getAvatarPosition() const;
|
||||
virtual void overrideEnvironmentData(const EnvironmentData& newData) { _environment.override(newData); }
|
||||
virtual void endOverrideEnvironmentData() { _environment.endOverride(); }
|
||||
virtual qreal getDevicePixelRatio();
|
||||
|
@ -328,7 +329,7 @@ private slots:
|
|||
void activeChanged(Qt::ApplicationState state);
|
||||
|
||||
void domainSettingsReceived(const QJsonObject& domainSettingsObject);
|
||||
void handleDomainConnectionDeniedPacket(QSharedPointer<NLPacket> packet);
|
||||
void handleDomainConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void notifyPacketVersionMismatch();
|
||||
|
||||
|
@ -394,8 +395,8 @@ private:
|
|||
|
||||
bool importSVOFromURL(const QString& urlString);
|
||||
|
||||
int processOctreeStats(NLPacket& packet, SharedNodePointer sendingNode);
|
||||
void trackIncomingOctreePacket(NLPacket& packet, SharedNodePointer sendingNode, bool wasStatsPacket);
|
||||
int processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode);
|
||||
void trackIncomingOctreePacket(ReceivedMessage& message, SharedNodePointer sendingNode, bool wasStatsPacket);
|
||||
|
||||
void resizeEvent(QResizeEvent* size);
|
||||
|
||||
|
@ -421,10 +422,13 @@ private:
|
|||
|
||||
bool _dependencyManagerIsSetup;
|
||||
|
||||
OffscreenGlCanvas* _offscreenContext { nullptr };
|
||||
OffscreenGLCanvas* _offscreenContext { nullptr };
|
||||
DisplayPluginPointer _displayPlugin;
|
||||
InputPluginList _activeInputPlugins;
|
||||
|
||||
bool _activatingDisplayPlugin { false };
|
||||
QMap<uint32_t, gpu::FramebufferPointer> _lockedFramebufferMap;
|
||||
|
||||
MainWindow* _window;
|
||||
|
||||
ToolWindow* _toolWindow;
|
||||
|
|
|
@ -9,133 +9,13 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
// FIXME ordering of headers
|
||||
#include "Application.h"
|
||||
#include "GLCanvas.h"
|
||||
|
||||
#include <QMimeData>
|
||||
#include <QUrl>
|
||||
#include <QWindow>
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "Menu.h"
|
||||
|
||||
static QGLFormat& getDesiredGLFormat() {
|
||||
// Specify an OpenGL 3.3 format using the Core profile.
|
||||
// That is, no old-school fixed pipeline functionality
|
||||
static QGLFormat glFormat;
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [] {
|
||||
glFormat.setVersion(4, 1);
|
||||
glFormat.setProfile(QGLFormat::CoreProfile); // Requires >=Qt-4.8.0
|
||||
glFormat.setSampleBuffers(false);
|
||||
glFormat.setDepth(false);
|
||||
glFormat.setStencil(false);
|
||||
});
|
||||
return glFormat;
|
||||
}
|
||||
|
||||
GLCanvas::GLCanvas() : QGLWidget(getDesiredGLFormat()) {
|
||||
#ifdef Q_OS_LINUX
|
||||
// Cause GLCanvas::eventFilter to be called.
|
||||
// It wouldn't hurt to do this on Mac and PC too; but apparently it's only needed on linux.
|
||||
qApp->installEventFilter(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
int GLCanvas::getDeviceWidth() const {
|
||||
return width() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f);
|
||||
}
|
||||
|
||||
int GLCanvas::getDeviceHeight() const {
|
||||
return height() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f);
|
||||
}
|
||||
|
||||
void GLCanvas::initializeGL() {
|
||||
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
setAcceptDrops(true);
|
||||
// Note, we *DO NOT* want Qt to automatically swap buffers for us. This results in the "ringing" bug mentioned in WL#19514 when we're throttling the framerate.
|
||||
setAutoBufferSwap(false);
|
||||
}
|
||||
|
||||
void GLCanvas::paintGL() {
|
||||
PROFILE_RANGE(__FUNCTION__);
|
||||
|
||||
// FIXME - I'm not sure why this still remains, it appears as if this GLCanvas gets a single paintGL call near
|
||||
// the beginning of the application starting up. I'm not sure if we really need to call Application::paintGL()
|
||||
// in this case, since the display plugins eventually handle all the painting
|
||||
bool isThrottleFPSEnabled = Menu::getInstance()->isOptionChecked(MenuOption::ThrottleFPSIfNotFocus);
|
||||
if (!qApp->getWindow()->isMinimized() || !isThrottleFPSEnabled) {
|
||||
qApp->paintGL();
|
||||
}
|
||||
}
|
||||
|
||||
void GLCanvas::resizeGL(int width, int height) {
|
||||
qApp->resizeGL();
|
||||
}
|
||||
|
||||
bool GLCanvas::event(QEvent* event) {
|
||||
switch (event->type()) {
|
||||
case QEvent::MouseMove:
|
||||
case QEvent::MouseButtonPress:
|
||||
case QEvent::MouseButtonRelease:
|
||||
case QEvent::MouseButtonDblClick:
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
case QEvent::FocusIn:
|
||||
case QEvent::FocusOut:
|
||||
case QEvent::Resize:
|
||||
case QEvent::TouchBegin:
|
||||
case QEvent::TouchEnd:
|
||||
case QEvent::TouchUpdate:
|
||||
case QEvent::Wheel:
|
||||
case QEvent::DragEnter:
|
||||
case QEvent::Drop:
|
||||
if (QCoreApplication::sendEvent(QCoreApplication::instance(), event)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case QEvent::Paint:
|
||||
// Ignore paint events that occur after we've decided to quit
|
||||
if (qApp->isAboutToQuit()) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
if (QEvent::Paint == event->type() && qApp->isAboutToQuit()) {
|
||||
return true;
|
||||
}
|
||||
return QGLWidget::event(event);
|
||||
}
|
||||
|
||||
|
||||
// Pressing Alt (and Meta) key alone activates the menubar because its style inherits the
|
||||
// SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to
|
||||
// receive keyPress events for the Alt (and Meta) key in a reliable manner.
|
||||
//
|
||||
// This filter catches events before QMenuBar can steal the keyboard focus.
|
||||
// The idea was borrowed from
|
||||
// http://www.archivum.info/qt-interest@trolltech.com/2006-09/00053/Re-(Qt4)-Alt-key-focus-QMenuBar-(solved).html
|
||||
|
||||
bool GLCanvas::eventFilter(QObject*, QEvent* event) {
|
||||
switch (event->type()) {
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
case QEvent::ShortcutOverride:
|
||||
{
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
if (keyEvent->key() == Qt::Key_Alt || keyEvent->key() == Qt::Key_Meta) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
keyPressEvent(keyEvent);
|
||||
} else if (event->type() == QEvent::KeyRelease) {
|
||||
keyReleaseEvent(keyEvent);
|
||||
} else {
|
||||
QGLWidget::event(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
return GLWidget::event(event);
|
||||
}
|
||||
|
|
|
@ -12,31 +12,13 @@
|
|||
#ifndef hifi_GLCanvas_h
|
||||
#define hifi_GLCanvas_h
|
||||
|
||||
#include <QDebug>
|
||||
#include <QGLWidget>
|
||||
#include <QTimer>
|
||||
#include <gl/GLWidget.h>
|
||||
|
||||
/// customized canvas that simply forwards requests/events to the singleton application
|
||||
class GLCanvas : public QGLWidget {
|
||||
class GLCanvas : public GLWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GLCanvas();
|
||||
|
||||
int getDeviceWidth() const;
|
||||
int getDeviceHeight() const;
|
||||
QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); }
|
||||
|
||||
protected:
|
||||
|
||||
virtual void initializeGL();
|
||||
virtual void paintGL();
|
||||
virtual void resizeGL(int width, int height);
|
||||
virtual bool event(QEvent* event);
|
||||
|
||||
private slots:
|
||||
bool eventFilter(QObject*, QEvent* event);
|
||||
|
||||
virtual bool event(QEvent* event) override;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -21,17 +21,17 @@
|
|||
EntityActionPointer interfaceActionFactory(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity) {
|
||||
switch (type) {
|
||||
case ACTION_TYPE_NONE:
|
||||
return nullptr;
|
||||
return EntityActionPointer();
|
||||
case ACTION_TYPE_OFFSET:
|
||||
return (EntityActionPointer) new ObjectActionOffset(id, ownerEntity);
|
||||
return std::make_shared<ObjectActionOffset>(id, ownerEntity);
|
||||
case ACTION_TYPE_SPRING:
|
||||
return (EntityActionPointer) new ObjectActionSpring(id, ownerEntity);
|
||||
return std::make_shared<ObjectActionSpring>(id, ownerEntity);
|
||||
case ACTION_TYPE_HOLD:
|
||||
return (EntityActionPointer) new AvatarActionHold(id, ownerEntity);
|
||||
return std::make_shared<AvatarActionHold>(id, ownerEntity);
|
||||
}
|
||||
|
||||
assert(false);
|
||||
return nullptr;
|
||||
Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity action type");
|
||||
return EntityActionPointer();
|
||||
}
|
||||
|
||||
|
||||
|
|
37
interface/src/InterfaceParentFinder.cpp
Normal file
37
interface/src/InterfaceParentFinder.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// InterfaceParentFinder.cpp
|
||||
// interface/src/
|
||||
//
|
||||
// Created by Seth Alves on 2015-10-21
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <Application.h>
|
||||
#include <EntityTree.h>
|
||||
#include <EntityTreeRenderer.h>
|
||||
#include <avatar/AvatarManager.h>
|
||||
|
||||
#include "InterfaceParentFinder.h"
|
||||
|
||||
SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID) const {
|
||||
SpatiallyNestableWeakPointer parent;
|
||||
|
||||
if (parentID.isNull()) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
// search entities
|
||||
EntityTreeRenderer* treeRenderer = qApp->getEntities();
|
||||
EntityTreePointer tree = treeRenderer->getTree();
|
||||
parent = tree->findEntityByEntityItemID(parentID);
|
||||
if (!parent.expired()) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
// search avatars
|
||||
QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
|
||||
return avatarManager->getAvatarBySessionID(parentID);
|
||||
}
|
27
interface/src/InterfaceParentFinder.h
Normal file
27
interface/src/InterfaceParentFinder.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// InterfaceParentFinder.h
|
||||
// interface/src/
|
||||
//
|
||||
// Created by Seth Alves on 2015-10-21
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_InterfaceParentFinder_h
|
||||
#define hifi_InterfaceParentFinder_h
|
||||
|
||||
#include <memory>
|
||||
#include <QUuid>
|
||||
|
||||
#include <SpatialParentFinder.h>
|
||||
|
||||
class InterfaceParentFinder : public SpatialParentFinder {
|
||||
public:
|
||||
InterfaceParentFinder() { }
|
||||
virtual ~InterfaceParentFinder() { }
|
||||
virtual SpatiallyNestableWeakPointer find(QUuid parentID) const;
|
||||
};
|
||||
|
||||
#endif // hifi_InterfaceParentFinder_h
|
|
@ -29,7 +29,6 @@
|
|||
#include "avatar/AvatarManager.h"
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
#include "devices/Faceshift.h"
|
||||
#include "devices/RealSense.h"
|
||||
#include "input-plugins/SpacemouseManager.h"
|
||||
#include "MainWindow.h"
|
||||
#include "scripting/MenuScriptingInterface.h"
|
||||
|
@ -433,8 +432,6 @@ Menu::Menu() {
|
|||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false,
|
||||
avatarManager.data(), SLOT(setShouldShowReceiveStats(bool)));
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSkeletonCollisionShapes);
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderHeadCollisionShapes);
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderBoundingCollisionShapes);
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtTargets, 0, false);
|
||||
|
@ -462,12 +459,6 @@ Menu::Menu() {
|
|||
MenuWrapper* leapOptionsMenu = handOptionsMenu->addMenu("Leap Motion");
|
||||
addCheckableActionToQMenuAndActionHash(leapOptionsMenu, MenuOption::LeapMotionOnHMD, 0, false);
|
||||
|
||||
#ifdef HAVE_RSSDK
|
||||
MenuWrapper* realSenseOptionsMenu = handOptionsMenu->addMenu("RealSense");
|
||||
addActionToQMenuAndActionHash(realSenseOptionsMenu, MenuOption::LoadRSSDKFile, 0,
|
||||
RealSense::getInstance(), SLOT(loadRSSDKFile()));
|
||||
#endif
|
||||
|
||||
MenuWrapper* networkMenu = developerMenu->addMenu("Network");
|
||||
addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()));
|
||||
addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false,
|
||||
|
|
|
@ -211,7 +211,6 @@ namespace MenuOption {
|
|||
const QString LeapMotionOnHMD = "Leap Motion on HMD";
|
||||
const QString LoadScript = "Open and Run Script File...";
|
||||
const QString LoadScriptURL = "Open and Run Script from URL...";
|
||||
const QString LoadRSSDKFile = "Load .rssdk file";
|
||||
const QString LodTools = "LOD Tools";
|
||||
const QString Login = "Login";
|
||||
const QString Log = "Log";
|
||||
|
@ -239,10 +238,8 @@ namespace MenuOption {
|
|||
const QString ReloadContent = "Reload Content (Clears all caches)";
|
||||
const QString RenderBoundingCollisionShapes = "Show Bounding Collision Shapes";
|
||||
const QString RenderFocusIndicator = "Show Eye Focus";
|
||||
const QString RenderHeadCollisionShapes = "Show Head Collision Shapes";
|
||||
const QString RenderLookAtTargets = "Show Look-at Targets";
|
||||
const QString RenderLookAtVectors = "Show Look-at Vectors";
|
||||
const QString RenderSkeletonCollisionShapes = "Show Skeleton Collision Shapes";
|
||||
const QString RenderResolution = "Scale Resolution";
|
||||
const QString RenderResolutionOne = "1";
|
||||
const QString RenderResolutionTwoThird = "2/3";
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
#include "PluginContainerProxy.h"
|
||||
|
||||
#include <QScreen>
|
||||
#include <QWindow>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
#include <plugins/Plugin.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
#include <display-plugins/DisplayPlugin.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <FramebufferCache.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "MainWindow.h"
|
||||
#include "GLCanvas.h"
|
||||
#include "ui/DialogsManager.h"
|
||||
|
||||
#include <gl/OffscreenGLCanvas.h>
|
||||
#include <QtGui/QOpenGLContext>
|
||||
|
||||
PluginContainerProxy::PluginContainerProxy() {
|
||||
}
|
||||
|
||||
|
@ -30,12 +35,7 @@ void PluginContainerProxy::removeMenu(const QString& menuName) {
|
|||
Menu::getInstance()->removeMenu(menuName);
|
||||
}
|
||||
|
||||
extern bool _activatingDisplayPlugin;
|
||||
extern QVector<QPair<QString, QString>> _currentDisplayPluginActions;
|
||||
extern QVector<QPair<QString, QString>> _currentInputPluginActions;
|
||||
std::map<QString, QActionGroup*> _exclusiveGroups;
|
||||
|
||||
QAction* PluginContainerProxy::addMenuItem(const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable, bool checked, const QString& groupName) {
|
||||
QAction* PluginContainerProxy::addMenuItem(PluginType type, const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable, bool checked, const QString& groupName) {
|
||||
auto menu = Menu::getInstance();
|
||||
MenuWrapper* parentItem = menu->getMenu(path);
|
||||
QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name);
|
||||
|
@ -54,7 +54,7 @@ QAction* PluginContainerProxy::addMenuItem(const QString& path, const QString& n
|
|||
});
|
||||
action->setCheckable(checkable);
|
||||
action->setChecked(checked);
|
||||
if (_activatingDisplayPlugin) {
|
||||
if (type == PluginType::DISPLAY_PLUGIN) {
|
||||
_currentDisplayPluginActions.push_back({ path, name });
|
||||
} else {
|
||||
_currentInputPluginActions.push_back({ path, name });
|
||||
|
@ -150,10 +150,37 @@ void PluginContainerProxy::showDisplayPluginsTools() {
|
|||
DependencyManager::get<DialogsManager>()->hmdTools(true);
|
||||
}
|
||||
|
||||
QGLWidget* PluginContainerProxy::getPrimarySurface() {
|
||||
GLWidget* PluginContainerProxy::getPrimaryWidget() {
|
||||
return qApp->_glWidget;
|
||||
}
|
||||
|
||||
QWindow* PluginContainerProxy::getPrimaryWindow() {
|
||||
return qApp->_glWidget->windowHandle();
|
||||
}
|
||||
|
||||
QOpenGLContext* PluginContainerProxy::getPrimaryContext() {
|
||||
return qApp->_glWidget->context()->contextHandle();
|
||||
}
|
||||
|
||||
const DisplayPlugin* PluginContainerProxy::getActiveDisplayPlugin() const {
|
||||
return qApp->getActiveDisplayPlugin();
|
||||
}
|
||||
|
||||
bool PluginContainerProxy::makeRenderingContextCurrent() {
|
||||
return qApp->_offscreenContext->makeCurrent();
|
||||
}
|
||||
|
||||
void PluginContainerProxy::releaseSceneTexture(uint32_t texture) {
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
auto& framebufferMap = qApp->_lockedFramebufferMap;
|
||||
Q_ASSERT(framebufferMap.contains(texture));
|
||||
auto framebufferPointer = framebufferMap[texture];
|
||||
framebufferMap.remove(texture);
|
||||
auto framebufferCache = DependencyManager::get<FramebufferCache>();
|
||||
framebufferCache->releaseFramebuffer(framebufferPointer);
|
||||
}
|
||||
|
||||
void PluginContainerProxy::releaseOverlayTexture(uint32_t texture) {
|
||||
// FIXME implement present thread compositing
|
||||
}
|
||||
|
||||
|
|
|
@ -2,19 +2,21 @@
|
|||
#ifndef hifi_PluginContainerProxy_h
|
||||
#define hifi_PluginContainerProxy_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QRect>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QRect>
|
||||
|
||||
#include <plugins/Forward.h>
|
||||
#include <plugins/PluginContainer.h>
|
||||
|
||||
class QActionGroup;
|
||||
|
||||
class PluginContainerProxy : public QObject, PluginContainer {
|
||||
Q_OBJECT
|
||||
PluginContainerProxy();
|
||||
virtual ~PluginContainerProxy();
|
||||
virtual void addMenu(const QString& menuName) override;
|
||||
virtual void removeMenu(const QString& menuName) override;
|
||||
virtual QAction* addMenuItem(const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override;
|
||||
virtual QAction* addMenuItem(PluginType type, const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override;
|
||||
virtual void removeMenuItem(const QString& menuName, const QString& menuItem) override;
|
||||
virtual bool isOptionChecked(const QString& name) override;
|
||||
virtual void setIsOptionChecked(const QString& path, bool checked) override;
|
||||
|
@ -22,13 +24,20 @@ class PluginContainerProxy : public QObject, PluginContainer {
|
|||
virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) override;
|
||||
virtual void showDisplayPluginsTools() override;
|
||||
virtual void requestReset() override;
|
||||
virtual QGLWidget* getPrimarySurface() override;
|
||||
virtual bool makeRenderingContextCurrent() override;
|
||||
virtual void releaseSceneTexture(uint32_t texture) override;
|
||||
virtual void releaseOverlayTexture(uint32_t texture) override;
|
||||
virtual GLWidget* getPrimaryWidget() override;
|
||||
virtual QWindow* getPrimaryWindow() override;
|
||||
virtual QOpenGLContext* getPrimaryContext() override;
|
||||
virtual bool isForeground() override;
|
||||
virtual const DisplayPlugin* getActiveDisplayPlugin() const override;
|
||||
|
||||
QRect _savedGeometry{ 10, 120, 800, 600 };
|
||||
std::map<QString, QActionGroup*> _exclusiveGroups;
|
||||
|
||||
friend class Application;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
|
@ -130,9 +130,9 @@ void Stars::render(RenderArgs* renderArgs, float alpha) {
|
|||
|
||||
std::call_once(once, [&] {
|
||||
{
|
||||
auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(standardTransformPNTC_vert)));
|
||||
auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(starsGrid_frag)));
|
||||
auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps));
|
||||
auto vs = gpu::Shader::createVertex(std::string(standardTransformPNTC_vert));
|
||||
auto ps = gpu::Shader::createPixel(std::string(starsGrid_frag));
|
||||
auto program = gpu::Shader::createProgram(vs, ps);
|
||||
gpu::Shader::makeProgram((*program));
|
||||
_timeSlot = program->getBuffers().findLocation(UNIFORM_TIME_NAME);
|
||||
if (_timeSlot == gpu::Shader::INVALID_LOCATION) {
|
||||
|
@ -143,12 +143,12 @@ void Stars::render(RenderArgs* renderArgs, float alpha) {
|
|||
state->setDepthTest(gpu::State::DepthTest(false));
|
||||
state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
|
||||
_gridPipeline.reset(gpu::Pipeline::create(program, state));
|
||||
_gridPipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
{
|
||||
auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(stars_vert)));
|
||||
auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(stars_frag)));
|
||||
auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps));
|
||||
auto vs = gpu::Shader::createVertex(std::string(stars_vert));
|
||||
auto ps = gpu::Shader::createPixel(std::string(stars_frag));
|
||||
auto program = gpu::Shader::createProgram(vs, ps);
|
||||
gpu::Shader::makeProgram((*program));
|
||||
auto state = gpu::StatePointer(new gpu::State());
|
||||
// enable decal blend
|
||||
|
@ -156,7 +156,7 @@ void Stars::render(RenderArgs* renderArgs, float alpha) {
|
|||
state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
state->setAntialiasedLineEnable(true); // line smoothing also smooth points
|
||||
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
|
||||
_starsPipeline.reset(gpu::Pipeline::create(program, state));
|
||||
_starsPipeline = gpu::Pipeline::create(program, state);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
#include "Hand.h"
|
||||
#include "Head.h"
|
||||
#include "Menu.h"
|
||||
#include "ModelReferential.h"
|
||||
#include "Physics.h"
|
||||
#include "Util.h"
|
||||
#include "world.h"
|
||||
|
@ -91,7 +90,6 @@ Avatar::Avatar(RigPointer rig) :
|
|||
_angularAcceleration(0.0f),
|
||||
_lastOrientation(),
|
||||
_leanScale(0.5f),
|
||||
_scale(1.0f),
|
||||
_worldUpDirection(DEFAULT_UP_DIRECTION),
|
||||
_moving(false),
|
||||
_initialized(false),
|
||||
|
@ -101,6 +99,7 @@ Avatar::Avatar(RigPointer rig) :
|
|||
// we may have been created in the network thread, but we live in the main thread
|
||||
moveToThread(qApp->thread());
|
||||
|
||||
setAvatarScale(1.0f);
|
||||
// give the pointer to our head to inherited _headData variable from AvatarData
|
||||
_headData = static_cast<HeadData*>(new Head(this));
|
||||
_handData = static_cast<HandData*>(new Hand(this));
|
||||
|
@ -125,12 +124,12 @@ void Avatar::init() {
|
|||
glm::vec3 Avatar::getChestPosition() const {
|
||||
// for now, let's just assume that the "chest" is halfway between the root and the neck
|
||||
glm::vec3 neckPosition;
|
||||
return _skeletonModel.getNeckPosition(neckPosition) ? (_position + neckPosition) * 0.5f : _position;
|
||||
return _skeletonModel.getNeckPosition(neckPosition) ? (getPosition() + neckPosition) * 0.5f : getPosition();
|
||||
}
|
||||
|
||||
glm::vec3 Avatar::getNeckPosition() const {
|
||||
glm::vec3 neckPosition;
|
||||
return _skeletonModel.getNeckPosition(neckPosition) ? neckPosition : _position;
|
||||
return _skeletonModel.getNeckPosition(neckPosition) ? neckPosition : getPosition();
|
||||
}
|
||||
|
||||
|
||||
|
@ -144,38 +143,14 @@ AABox Avatar::getBounds() const {
|
|||
|
||||
float Avatar::getLODDistance() const {
|
||||
return DependencyManager::get<LODManager>()->getAvatarLODDistanceMultiplier() *
|
||||
glm::distance(qApp->getCamera()->getPosition(), _position) / _scale;
|
||||
glm::distance(qApp->getCamera()->getPosition(), getPosition()) / getAvatarScale();
|
||||
}
|
||||
|
||||
void Avatar::simulate(float deltaTime) {
|
||||
PerformanceTimer perfTimer("simulate");
|
||||
|
||||
// update the avatar's position according to its referential
|
||||
if (_referential) {
|
||||
if (_referential->hasExtraData()) {
|
||||
EntityTreePointer tree = qApp->getEntities()->getTree();
|
||||
switch (_referential->type()) {
|
||||
case Referential::MODEL:
|
||||
_referential = new ModelReferential(_referential,
|
||||
tree,
|
||||
this);
|
||||
break;
|
||||
case Referential::JOINT:
|
||||
_referential = new JointReferential(_referential,
|
||||
tree,
|
||||
this);
|
||||
break;
|
||||
default:
|
||||
qCDebug(interfaceapp) << "[WARNING] Avatar::simulate(): Unknown referential type.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_referential->update();
|
||||
}
|
||||
|
||||
if (_scale != _targetScale) {
|
||||
setScale(_targetScale);
|
||||
if (getAvatarScale() != _targetScale) {
|
||||
setAvatarScale(_targetScale);
|
||||
}
|
||||
|
||||
// update the billboard render flag
|
||||
|
@ -193,7 +168,7 @@ void Avatar::simulate(float deltaTime) {
|
|||
const bool isControllerLogging = DependencyManager::get<AvatarManager>()->getRenderDistanceControllerIsLogging();
|
||||
float renderDistance = DependencyManager::get<AvatarManager>()->getRenderDistance();
|
||||
const float SKIP_HYSTERESIS_PROPORTION = isControllerLogging ? 0.0f : BILLBOARD_HYSTERESIS_PROPORTION;
|
||||
float distance = glm::distance(qApp->getCamera()->getPosition(), _position);
|
||||
float distance = glm::distance(qApp->getCamera()->getPosition(), getPosition());
|
||||
if (_shouldSkipRender) {
|
||||
if (distance < renderDistance * (1.0f - SKIP_HYSTERESIS_PROPORTION)) {
|
||||
_shouldSkipRender = false;
|
||||
|
@ -212,7 +187,7 @@ void Avatar::simulate(float deltaTime) {
|
|||
|
||||
// simple frustum check
|
||||
float boundingRadius = getBillboardSize();
|
||||
bool inViewFrustum = qApp->getViewFrustum()->sphereInFrustum(_position, boundingRadius) !=
|
||||
bool inViewFrustum = qApp->getViewFrustum()->sphereInFrustum(getPosition(), boundingRadius) !=
|
||||
ViewFrustum::OUTSIDE;
|
||||
|
||||
{
|
||||
|
@ -226,16 +201,17 @@ void Avatar::simulate(float deltaTime) {
|
|||
_skeletonModel.getRig()->copyJointsFromJointData(_jointData);
|
||||
_skeletonModel.simulate(deltaTime, _hasNewJointRotations || _hasNewJointTranslations);
|
||||
simulateAttachments(deltaTime);
|
||||
locationChanged(); // joints changed, so if there are any children, update them.
|
||||
_hasNewJointRotations = false;
|
||||
_hasNewJointTranslations = false;
|
||||
}
|
||||
{
|
||||
PerformanceTimer perfTimer("head");
|
||||
glm::vec3 headPosition = _position;
|
||||
glm::vec3 headPosition = getPosition();
|
||||
_skeletonModel.getHeadPosition(headPosition);
|
||||
Head* head = getHead();
|
||||
head->setPosition(headPosition);
|
||||
head->setScale(_scale);
|
||||
head->setScale(getAvatarScale());
|
||||
head->simulate(deltaTime, false, _shouldRenderBillboard);
|
||||
}
|
||||
}
|
||||
|
@ -268,7 +244,7 @@ bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) {
|
|||
glm::vec3 theirLookAt = dynamic_pointer_cast<Avatar>(avatar)->getHead()->getLookAtPosition();
|
||||
glm::vec3 myEyePosition = getHead()->getEyePosition();
|
||||
|
||||
return glm::distance(theirLookAt, myEyePosition) <= (HEAD_SPHERE_RADIUS * getScale());
|
||||
return glm::distance(theirLookAt, myEyePosition) <= (HEAD_SPHERE_RADIUS * getAvatarScale());
|
||||
}
|
||||
|
||||
void Avatar::slamPosition(const glm::vec3& newPosition) {
|
||||
|
@ -279,7 +255,7 @@ void Avatar::slamPosition(const glm::vec3& newPosition) {
|
|||
}
|
||||
|
||||
void Avatar::applyPositionDelta(const glm::vec3& delta) {
|
||||
_position += delta;
|
||||
setPosition(getPosition() + delta);
|
||||
_positionDeltaAccumulator += delta;
|
||||
}
|
||||
|
||||
|
@ -345,15 +321,10 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptr<render::S
|
|||
}
|
||||
|
||||
void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
||||
startRender();
|
||||
if (_referential) {
|
||||
_referential->update();
|
||||
}
|
||||
|
||||
auto& batch = *renderArgs->_batch;
|
||||
PROFILE_RANGE_BATCH(batch, __FUNCTION__);
|
||||
|
||||
if (glm::distance(DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition(), _position) < 10.0f) {
|
||||
if (glm::distance(DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition(), getPosition()) < 10.0f) {
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
auto deferredLighting = DependencyManager::get<DeferredLightingEffect>();
|
||||
|
||||
|
@ -453,7 +424,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
const float BASE_LIGHT_DISTANCE = 2.0f;
|
||||
const float LIGHT_EXPONENT = 1.0f;
|
||||
const float LIGHT_CUTOFF = glm::radians(80.0f);
|
||||
float distance = BASE_LIGHT_DISTANCE * _scale;
|
||||
float distance = BASE_LIGHT_DISTANCE * getAvatarScale();
|
||||
glm::vec3 position = glm::mix(_skeletonModel.getTranslation(), getHead()->getFaceModel().getTranslation(), 0.9f);
|
||||
glm::quat orientation = getOrientation();
|
||||
foreach (const AvatarManager::LocalLight& light, DependencyManager::get<AvatarManager>()->getLocalLights()) {
|
||||
|
@ -463,16 +434,6 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO: re-implement these when we have more detailed avatar collision shapes
|
||||
bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes);
|
||||
if (renderSkeleton) {
|
||||
}
|
||||
bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes);
|
||||
if (renderHead && shouldRenderHead(renderArgs)) {
|
||||
}
|
||||
*/
|
||||
|
||||
bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
|
||||
if (renderBounding && shouldRenderHead(renderArgs) && _skeletonModel.isRenderable()) {
|
||||
PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes");
|
||||
|
@ -484,7 +445,8 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
static const float INDICATOR_OFFSET = 0.22f;
|
||||
static const float INDICATOR_RADIUS = 0.03f;
|
||||
static const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f };
|
||||
glm::vec3 position = glm::vec3(_position.x, getDisplayNamePosition().y + INDICATOR_OFFSET, _position.z);
|
||||
glm::vec3 avatarPosition = getPosition();
|
||||
glm::vec3 position = glm::vec3(avatarPosition.x, getDisplayNamePosition().y + INDICATOR_OFFSET, avatarPosition.z);
|
||||
PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderFocusIndicator");
|
||||
Transform transform;
|
||||
transform.setTranslation(position);
|
||||
|
@ -518,7 +480,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
}
|
||||
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch,
|
||||
Transform(transform).postScale(eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT),
|
||||
Transform(transform).postScale(eyeDiameter * getAvatarScale() / 2.0f + RADIUS_INCREMENT),
|
||||
glm::vec4(LOOKING_AT_ME_COLOR, alpha));
|
||||
|
||||
position = getHead()->getRightEyePosition();
|
||||
|
@ -528,7 +490,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
eyeDiameter = DEFAULT_EYE_DIAMETER;
|
||||
}
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch,
|
||||
Transform(transform).postScale(eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT),
|
||||
Transform(transform).postScale(eyeDiameter * getAvatarScale() / 2.0f + RADIUS_INCREMENT),
|
||||
glm::vec4(LOOKING_AT_ME_COLOR, alpha));
|
||||
|
||||
}
|
||||
|
@ -559,7 +521,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
|
||||
|
||||
Transform transform;
|
||||
transform.setTranslation(_position);
|
||||
transform.setTranslation(getPosition());
|
||||
transform.setScale(height);
|
||||
transform.postScale(sphereRadius);
|
||||
DependencyManager::get<DeferredLightingEffect>()->renderSolidSphereInstance(batch,
|
||||
|
@ -662,9 +624,9 @@ void Avatar::simulateAttachments(float deltaTime) {
|
|||
glm::quat jointRotation;
|
||||
if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) &&
|
||||
_skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) {
|
||||
model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale);
|
||||
model->setTranslation(jointPosition + jointRotation * attachment.translation * getAvatarScale());
|
||||
model->setRotation(jointRotation * attachment.rotation);
|
||||
model->setScaleToFit(true, _scale * attachment.scale, true); // hack to force rescale
|
||||
model->setScaleToFit(true, getAvatarScale() * attachment.scale, true); // hack to force rescale
|
||||
model->setSnapModelToCenter(false); // hack to force resnap
|
||||
model->setSnapModelToCenter(true);
|
||||
model->simulate(deltaTime);
|
||||
|
@ -694,14 +656,14 @@ void Avatar::renderBillboard(RenderArgs* renderArgs) {
|
|||
}
|
||||
// rotate about vertical to face the camera
|
||||
glm::quat rotation = getOrientation();
|
||||
glm::vec3 cameraVector = glm::inverse(rotation) * (qApp->getCamera()->getPosition() - _position);
|
||||
glm::vec3 cameraVector = glm::inverse(rotation) * (qApp->getCamera()->getPosition() - getPosition());
|
||||
rotation = rotation * glm::angleAxis(atan2f(-cameraVector.x, -cameraVector.z), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
|
||||
// compute the size from the billboard camera parameters and scale
|
||||
float size = getBillboardSize();
|
||||
|
||||
Transform transform;
|
||||
transform.setTranslation(_position);
|
||||
transform.setTranslation(getPosition());
|
||||
transform.setRotation(rotation);
|
||||
transform.setScale(size);
|
||||
|
||||
|
@ -719,7 +681,7 @@ void Avatar::renderBillboard(RenderArgs* renderArgs) {
|
|||
}
|
||||
|
||||
float Avatar::getBillboardSize() const {
|
||||
return _scale * BILLBOARD_DISTANCE * glm::tan(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f));
|
||||
return getAvatarScale() * BILLBOARD_DISTANCE * glm::tan(glm::radians(BILLBOARD_FIELD_OF_VIEW / 2.0f));
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
|
@ -754,9 +716,9 @@ glm::vec3 Avatar::getDisplayNamePosition() const {
|
|||
const float HEAD_PROPORTION = 0.75f;
|
||||
float billboardSize = getBillboardSize();
|
||||
|
||||
DEBUG_VALUE("_position =", _position);
|
||||
DEBUG_VALUE("_position =", getPosition());
|
||||
DEBUG_VALUE("billboardSize =", billboardSize);
|
||||
namePosition = _position + bodyUpDirection * (billboardSize * HEAD_PROPORTION);
|
||||
namePosition = getPosition() + bodyUpDirection * (billboardSize * HEAD_PROPORTION);
|
||||
}
|
||||
|
||||
if (glm::any(glm::isnan(namePosition)) || glm::any(glm::isinf(namePosition))) {
|
||||
|
@ -868,7 +830,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co
|
|||
}
|
||||
|
||||
void Avatar::setSkeletonOffset(const glm::vec3& offset) {
|
||||
const float MAX_OFFSET_LENGTH = _scale * 0.5f;
|
||||
const float MAX_OFFSET_LENGTH = getAvatarScale() * 0.5f;
|
||||
float offsetLength = glm::length(offset);
|
||||
if (offsetLength > MAX_OFFSET_LENGTH) {
|
||||
_skeletonOffset = (MAX_OFFSET_LENGTH / offsetLength) * offset;
|
||||
|
@ -881,7 +843,7 @@ glm::vec3 Avatar::getSkeletonPosition() const {
|
|||
// The avatar is rotated PI about the yAxis, so we have to correct for it
|
||||
// to get the skeleton offset contribution in the world-frame.
|
||||
const glm::quat FLIP = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
return _position + getOrientation() * FLIP * _skeletonOffset;
|
||||
return getPosition() + getOrientation() * FLIP * _skeletonOffset;
|
||||
}
|
||||
|
||||
QVector<glm::quat> Avatar::getJointRotations() const {
|
||||
|
@ -896,23 +858,28 @@ QVector<glm::quat> Avatar::getJointRotations() const {
|
|||
}
|
||||
|
||||
glm::quat Avatar::getJointRotation(int index) const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
return AvatarData::getJointRotation(index);
|
||||
}
|
||||
glm::quat rotation;
|
||||
_skeletonModel.getJointRotation(index, rotation);
|
||||
return rotation;
|
||||
}
|
||||
|
||||
glm::vec3 Avatar::getJointTranslation(int index) const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
return AvatarData::getJointTranslation(index);
|
||||
}
|
||||
glm::vec3 translation;
|
||||
_skeletonModel.getJointTranslation(index, translation);
|
||||
return translation;
|
||||
}
|
||||
|
||||
glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
|
||||
glm::quat rotation;
|
||||
_skeletonModel.getAbsoluteJointRotationInRigFrame(index, rotation);
|
||||
return Quaternions::Y_180 * rotation;
|
||||
}
|
||||
|
||||
glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
|
||||
glm::vec3 translation;
|
||||
_skeletonModel.getAbsoluteJointTranslationInRigFrame(index, translation);
|
||||
return Quaternions::Y_180 * translation;
|
||||
}
|
||||
|
||||
int Avatar::getJointIndex(const QString& name) const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
|
@ -960,7 +927,7 @@ glm::vec3 Avatar::getJointPosition(const QString& name) const {
|
|||
|
||||
void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const {
|
||||
//Scale a world space vector as if it was relative to the position
|
||||
positionToScale = _position + _scale * (positionToScale - _position);
|
||||
positionToScale = getPosition() + getAvatarScale() * (positionToScale - getPosition());
|
||||
}
|
||||
|
||||
void Avatar::setFaceModelURL(const QUrl& faceModelURL) {
|
||||
|
@ -1000,7 +967,7 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
|||
for (int i = 0; i < attachmentData.size(); i++) {
|
||||
_attachmentModels[i]->setURL(attachmentData.at(i).modelURL);
|
||||
_attachmentModels[i]->setSnapModelToCenter(true);
|
||||
_attachmentModels[i]->setScaleToFit(true, _scale * _attachmentData.at(i).scale);
|
||||
_attachmentModels[i]->setScaleToFit(true, getAvatarScale() * _attachmentData.at(i).scale);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1019,12 +986,12 @@ int Avatar::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
}
|
||||
|
||||
// change in position implies movement
|
||||
glm::vec3 oldPosition = _position;
|
||||
glm::vec3 oldPosition = getPosition();
|
||||
|
||||
int bytesRead = AvatarData::parseDataFromBuffer(buffer);
|
||||
|
||||
const float MOVE_DISTANCE_THRESHOLD = 0.001f;
|
||||
_moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD;
|
||||
_moving = glm::distance(oldPosition, getPosition()) > MOVE_DISTANCE_THRESHOLD;
|
||||
if (_moving && _motionState) {
|
||||
_motionState->addDirtyFlags(Simulation::DIRTY_POSITION);
|
||||
}
|
||||
|
@ -1088,12 +1055,12 @@ void Avatar::renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, g
|
|||
}
|
||||
}
|
||||
|
||||
void Avatar::setScale(float scale) {
|
||||
_scale = scale;
|
||||
|
||||
if (_targetScale * (1.0f - RESCALING_TOLERANCE) < _scale &&
|
||||
_scale < _targetScale * (1.0f + RESCALING_TOLERANCE)) {
|
||||
_scale = _targetScale;
|
||||
void Avatar::setAvatarScale(float scale) {
|
||||
if (_targetScale * (1.0f - RESCALING_TOLERANCE) < scale &&
|
||||
scale < _targetScale * (1.0f + RESCALING_TOLERANCE)) {
|
||||
setScale(glm::vec3(_targetScale));
|
||||
} else {
|
||||
setScale(glm::vec3(scale));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1108,7 +1075,7 @@ float Avatar::getHeadHeight() const {
|
|||
|
||||
// HACK: We have a really odd case when fading out for some models where this value explodes
|
||||
float result = extents.maximum.y - extents.minimum.y;
|
||||
if (result >= 0.0f && result < 100.0f * _scale ) {
|
||||
if (result >= 0.0f && result < 100.0f * getAvatarScale() ) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -1116,7 +1083,7 @@ float Avatar::getHeadHeight() const {
|
|||
extents = _skeletonModel.getMeshExtents();
|
||||
glm::vec3 neckPosition;
|
||||
if (!extents.isEmpty() && extents.isValid() && _skeletonModel.getNeckPosition(neckPosition)) {
|
||||
return extents.maximum.y / 2.0f - neckPosition.y + _position.y;
|
||||
return extents.maximum.y / 2.0f - neckPosition.y + getPosition().y;
|
||||
}
|
||||
|
||||
const float DEFAULT_HEAD_HEIGHT = 0.25f;
|
||||
|
@ -1189,3 +1156,13 @@ glm::quat Avatar::getRightPalmRotation() {
|
|||
getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation);
|
||||
return rightRotation;
|
||||
}
|
||||
|
||||
void Avatar::setPosition(const glm::vec3& position) {
|
||||
AvatarData::setPosition(position);
|
||||
updateAttitude();
|
||||
}
|
||||
|
||||
void Avatar::setOrientation(const glm::quat& orientation) {
|
||||
AvatarData::setOrientation(orientation);
|
||||
updateAttitude();
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ public:
|
|||
const SkeletonModel& getSkeletonModel() const { return _skeletonModel; }
|
||||
const QVector<Model*>& getAttachmentModels() const { return _attachmentModels; }
|
||||
glm::vec3 getChestPosition() const;
|
||||
float getScale() const { return _scale; }
|
||||
float getAvatarScale() const { return getScale().y; }
|
||||
const Head* getHead() const { return static_cast<const Head*>(_headData); }
|
||||
Head* getHead() { return static_cast<Head*>(_headData); }
|
||||
Hand* getHand() { return static_cast<Hand*>(_handData); }
|
||||
|
@ -108,6 +108,9 @@ public:
|
|||
virtual int getJointIndex(const QString& name) const;
|
||||
virtual QStringList getJointNames() const;
|
||||
|
||||
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
|
||||
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
|
||||
|
||||
virtual void setFaceModelURL(const QUrl& faceModelURL);
|
||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
|
||||
|
@ -155,6 +158,9 @@ public:
|
|||
void setMotionState(AvatarMotionState* motionState) { _motionState = motionState; }
|
||||
AvatarMotionState* getMotionState() { return _motionState; }
|
||||
|
||||
virtual void setPosition(const glm::vec3& position) override;
|
||||
virtual void setOrientation(const glm::quat& orientation) override;
|
||||
|
||||
public slots:
|
||||
|
||||
// FIXME - these should be migrated to use Pose data instead
|
||||
|
@ -186,7 +192,6 @@ protected:
|
|||
glm::quat _lastOrientation;
|
||||
|
||||
float _leanScale;
|
||||
float _scale;
|
||||
glm::vec3 _worldUpDirection;
|
||||
float _stringLength;
|
||||
bool _moving; ///< set when position is changing
|
||||
|
@ -198,7 +203,7 @@ protected:
|
|||
glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; }
|
||||
glm::vec3 getBodyFrontDirection() const { return getOrientation() * IDENTITY_FRONT; }
|
||||
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
|
||||
void setScale(float scale);
|
||||
void setAvatarScale(float scale);
|
||||
void measureMotionDerivatives(float deltaTime);
|
||||
|
||||
float getSkeletonHeight() const;
|
||||
|
|
|
@ -9,63 +9,72 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "QVariantGLM.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
|
||||
#include "AvatarActionHold.h"
|
||||
|
||||
#include <QVariantGLM.h>
|
||||
|
||||
#include "avatar/AvatarManager.h"
|
||||
|
||||
const uint16_t AvatarActionHold::holdVersion = 1;
|
||||
|
||||
AvatarActionHold::AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity) :
|
||||
ObjectActionSpring(id, ownerEntity),
|
||||
_relativePosition(glm::vec3(0.0f)),
|
||||
_relativeRotation(glm::quat()),
|
||||
_hand("right"),
|
||||
_holderID(QUuid()) {
|
||||
ObjectActionSpring(id, ownerEntity)
|
||||
{
|
||||
_type = ACTION_TYPE_HOLD;
|
||||
#if WANT_DEBUG
|
||||
#if WANT_DEBUG
|
||||
qDebug() << "AvatarActionHold::AvatarActionHold";
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
AvatarActionHold::~AvatarActionHold() {
|
||||
#if WANT_DEBUG
|
||||
#if WANT_DEBUG
|
||||
qDebug() << "AvatarActionHold::~AvatarActionHold";
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
std::shared_ptr<Avatar> AvatarActionHold::getTarget(glm::quat& rotation, glm::vec3& position) {
|
||||
std::shared_ptr<Avatar> holdingAvatar = nullptr;
|
||||
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||
auto holdingAvatar = std::static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(_holderID));
|
||||
|
||||
if (!holdingAvatar) {
|
||||
return holdingAvatar;
|
||||
}
|
||||
|
||||
withTryReadLock([&]{
|
||||
QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
|
||||
AvatarSharedPointer holdingAvatarData = avatarManager->getAvatarBySessionID(_holderID);
|
||||
holdingAvatar = std::static_pointer_cast<Avatar>(holdingAvatarData);
|
||||
|
||||
if (holdingAvatar) {
|
||||
glm::vec3 offset;
|
||||
glm::vec3 palmPosition;
|
||||
glm::quat palmRotation;
|
||||
if (_hand == "right") {
|
||||
bool isRightHand = (_hand == "right");
|
||||
glm::vec3 palmPosition { Vectors::ZERO };
|
||||
glm::quat palmRotation { Quaternions::IDENTITY };
|
||||
|
||||
if (_ignoreIK && holdingAvatar->isMyAvatar()) {
|
||||
// We cannot ignore other avatars IK and this is not the point of this option
|
||||
// This is meant to make the grabbing behavior more reactive.
|
||||
if (isRightHand) {
|
||||
palmPosition = holdingAvatar->getHand()->getCopyOfPalmData(HandData::RightHand).getPosition();
|
||||
palmRotation = holdingAvatar->getHand()->getCopyOfPalmData(HandData::RightHand).getRotation();
|
||||
} else {
|
||||
palmPosition = holdingAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand).getPosition();
|
||||
palmRotation = holdingAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand).getRotation();
|
||||
}
|
||||
} else {
|
||||
if (isRightHand) {
|
||||
palmPosition = holdingAvatar->getRightPalmPosition();
|
||||
palmRotation = holdingAvatar->getRightPalmRotation();
|
||||
} else {
|
||||
palmPosition = holdingAvatar->getLeftPalmPosition();
|
||||
palmRotation = holdingAvatar->getLeftPalmRotation();
|
||||
}
|
||||
|
||||
rotation = palmRotation * _relativeRotation;
|
||||
offset = rotation * _relativePosition;
|
||||
position = palmPosition + offset;
|
||||
}
|
||||
|
||||
rotation = palmRotation * _relativeRotation;
|
||||
position = palmPosition + rotation * _relativePosition;
|
||||
});
|
||||
|
||||
return holdingAvatar;
|
||||
}
|
||||
|
||||
void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
|
||||
glm::quat rotation;
|
||||
glm::vec3 position;
|
||||
glm::quat rotation { Quaternions::IDENTITY };
|
||||
glm::vec3 position { Vectors::ZERO };
|
||||
bool valid = false;
|
||||
int holdCount = 0;
|
||||
|
||||
|
@ -168,6 +177,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
QUuid holderID;
|
||||
bool kinematic;
|
||||
bool kinematicSetVelocity;
|
||||
bool ignoreIK;
|
||||
bool needUpdate = false;
|
||||
|
||||
bool somethingChanged = ObjectAction::updateArguments(arguments);
|
||||
|
@ -203,14 +213,20 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
ok = true;
|
||||
kinematic = EntityActionInterface::extractBooleanArgument("hold", arguments, "kinematic", ok, false);
|
||||
if (!ok) {
|
||||
_kinematic = false;
|
||||
kinematic = _kinematic;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
kinematicSetVelocity = EntityActionInterface::extractBooleanArgument("hold", arguments,
|
||||
"kinematicSetVelocity", ok, false);
|
||||
if (!ok) {
|
||||
_kinematicSetVelocity = false;
|
||||
kinematicSetVelocity = _kinematicSetVelocity;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
ignoreIK = EntityActionInterface::extractBooleanArgument("hold", arguments, "ignoreIK", ok, false);
|
||||
if (!ok) {
|
||||
ignoreIK = _ignoreIK;
|
||||
}
|
||||
|
||||
if (somethingChanged ||
|
||||
|
@ -220,7 +236,8 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
hand != _hand ||
|
||||
holderID != _holderID ||
|
||||
kinematic != _kinematic ||
|
||||
kinematicSetVelocity != _kinematicSetVelocity) {
|
||||
kinematicSetVelocity != _kinematicSetVelocity ||
|
||||
ignoreIK != _ignoreIK) {
|
||||
needUpdate = true;
|
||||
}
|
||||
});
|
||||
|
@ -236,6 +253,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
_holderID = holderID;
|
||||
_kinematic = kinematic;
|
||||
_kinematicSetVelocity = kinematicSetVelocity;
|
||||
_ignoreIK = ignoreIK;
|
||||
_active = true;
|
||||
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
|
@ -260,6 +278,7 @@ QVariantMap AvatarActionHold::getArguments() {
|
|||
arguments["hand"] = _hand;
|
||||
arguments["kinematic"] = _kinematic;
|
||||
arguments["kinematicSetVelocity"] = _kinematicSetVelocity;
|
||||
arguments["ignoreIK"] = _ignoreIK;
|
||||
});
|
||||
return arguments;
|
||||
}
|
||||
|
|
|
@ -38,17 +38,19 @@ public:
|
|||
std::shared_ptr<Avatar> getTarget(glm::quat& rotation, glm::vec3& position);
|
||||
|
||||
private:
|
||||
void doKinematicUpdate(float deltaTimeStep);
|
||||
|
||||
static const uint16_t holdVersion;
|
||||
|
||||
glm::vec3 _relativePosition;
|
||||
glm::quat _relativeRotation;
|
||||
QString _hand;
|
||||
glm::vec3 _relativePosition { Vectors::ZERO };
|
||||
glm::quat _relativeRotation { Quaternions::IDENTITY };
|
||||
QString _hand { "right" };
|
||||
QUuid _holderID;
|
||||
|
||||
void doKinematicUpdate(float deltaTimeStep);
|
||||
bool _kinematic { false };
|
||||
bool _kinematicSetVelocity { false };
|
||||
bool _previousSet { false };
|
||||
bool _ignoreIK { false };
|
||||
glm::vec3 _previousPositionalTarget;
|
||||
glm::quat _previousRotationalTarget;
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
|
||||
#include <PerfStat.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <Rig.h>
|
||||
#include <SettingHandle.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include "Application.h"
|
||||
|
@ -35,7 +37,6 @@
|
|||
#include "Menu.h"
|
||||
#include "MyAvatar.h"
|
||||
#include "SceneScriptingInterface.h"
|
||||
#include <Rig.h>
|
||||
|
||||
// 70 times per second - target is 60hz, but this helps account for any small deviations
|
||||
// in the update loop
|
||||
|
@ -75,6 +76,13 @@ AvatarManager::AvatarManager(QObject* parent) :
|
|||
packetReceiver.registerListener(PacketType::AvatarBillboard, this, "processAvatarBillboardPacket");
|
||||
}
|
||||
|
||||
const float SMALLEST_REASONABLE_HORIZON = 5.0f; // meters
|
||||
Setting::Handle<float> avatarRenderDistanceInverseHighLimit("avatarRenderDistanceHighLimit", 1.0f / SMALLEST_REASONABLE_HORIZON);
|
||||
void AvatarManager::setRenderDistanceInverseHighLimit(float newValue) {
|
||||
avatarRenderDistanceInverseHighLimit.set(newValue);
|
||||
_renderDistanceController.setControlledValueHighLimit(newValue);
|
||||
}
|
||||
|
||||
void AvatarManager::init() {
|
||||
_myAvatar->init();
|
||||
{
|
||||
|
@ -93,8 +101,7 @@ void AvatarManager::init() {
|
|||
|
||||
const float target_fps = qApp->getTargetFrameRate();
|
||||
_renderDistanceController.setMeasuredValueSetpoint(target_fps);
|
||||
const float SMALLEST_REASONABLE_HORIZON = 5.0f; // meters
|
||||
_renderDistanceController.setControlledValueHighLimit(1.0f / SMALLEST_REASONABLE_HORIZON);
|
||||
_renderDistanceController.setControlledValueHighLimit(avatarRenderDistanceInverseHighLimit.get());
|
||||
_renderDistanceController.setControlledValueLowLimit(1.0f / (float) TREE_SCALE);
|
||||
// Advice for tuning parameters:
|
||||
// See PIDController.h. There's a section on tuning in the reference.
|
||||
|
@ -191,7 +198,7 @@ void AvatarManager::simulateAvatarFades(float deltaTime) {
|
|||
while (fadingIterator != _avatarFades.end()) {
|
||||
auto avatar = std::static_pointer_cast<Avatar>(*fadingIterator);
|
||||
avatar->startUpdate();
|
||||
avatar->setTargetScale(avatar->getScale() * SHRINK_RATE, true);
|
||||
avatar->setTargetScale(avatar->getAvatarScale() * SHRINK_RATE);
|
||||
if (avatar->getTargetScale() <= MIN_FADE_SCALE) {
|
||||
avatar->removeFromScene(*fadingIterator, scene, pendingChanges);
|
||||
fadingIterator = _avatarFades.erase(fadingIterator);
|
||||
|
@ -402,7 +409,7 @@ void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) {
|
|||
|
||||
AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) {
|
||||
if (sessionID == _myAvatar->getSessionUUID()) {
|
||||
return std::static_pointer_cast<Avatar>(_myAvatar);
|
||||
return _myAvatar;
|
||||
}
|
||||
|
||||
return findAvatar(sessionID);
|
||||
|
|
|
@ -70,14 +70,16 @@ public:
|
|||
|
||||
// Expose results and parameter-tuning operations to other systems, such as stats and javascript.
|
||||
Q_INVOKABLE float getRenderDistance() { return _renderDistance; }
|
||||
Q_INVOKABLE float getRenderDistanceInverseLowLimit() { return _renderDistanceController.getControlledValueLowLimit(); }
|
||||
Q_INVOKABLE float getRenderDistanceInverseHighLimit() { return _renderDistanceController.getControlledValueHighLimit(); }
|
||||
Q_INVOKABLE int getNumberInRenderRange() { return _renderedAvatarCount; }
|
||||
Q_INVOKABLE bool getRenderDistanceControllerIsLogging() { return _renderDistanceController.getIsLogging(); }
|
||||
Q_INVOKABLE void setRenderDistanceControllerHistory(QString label, int size) { return _renderDistanceController.setHistorySize(label, size); }
|
||||
Q_INVOKABLE void setRenderDistanceKP(float newValue) { _renderDistanceController.setKP(newValue); }
|
||||
Q_INVOKABLE void setRenderDistanceKI(float newValue) { _renderDistanceController.setKI(newValue); }
|
||||
Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); }
|
||||
Q_INVOKABLE void setRenderDistanceLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); }
|
||||
Q_INVOKABLE void setRenderDistanceHighLimit(float newValue) { _renderDistanceController.setControlledValueHighLimit(newValue); }
|
||||
Q_INVOKABLE void setRenderDistanceInverseLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); }
|
||||
Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue);
|
||||
|
||||
public slots:
|
||||
void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; }
|
||||
|
|
|
@ -30,7 +30,8 @@ void AvatarUpdate::synchronousProcess() {
|
|||
|
||||
// Keep our own updated value, so that our asynchronous code can consult it.
|
||||
_isHMDMode = qApp->isHMDMode();
|
||||
_headPose = qApp->getActiveDisplayPlugin()->getHeadPose();
|
||||
auto frameCount = qApp->getFrameCount();
|
||||
_headPose = qApp->getActiveDisplayPlugin()->getHeadPose(frameCount);
|
||||
|
||||
if (_updateBillboard) {
|
||||
DependencyManager::get<AvatarManager>()->getMyAvatar()->doUpdateBillboard();
|
||||
|
|
|
@ -37,7 +37,7 @@ void Hand::simulate(float deltaTime, bool isMine) {
|
|||
void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) {
|
||||
float avatarScale = 1.0f;
|
||||
if (_owningAvatar) {
|
||||
avatarScale = _owningAvatar->getScale();
|
||||
avatarScale = _owningAvatar->getAvatarScale();
|
||||
}
|
||||
|
||||
const float alpha = 1.0f;
|
||||
|
|
|
@ -43,9 +43,11 @@ Head::Head(Avatar* owningAvatar) :
|
|||
_longTermAverageLoudness(-1.0f),
|
||||
_audioAttack(0.0f),
|
||||
_audioJawOpen(0.0f),
|
||||
_trailingAudioJawOpen(0.0f),
|
||||
_mouth2(0.0f),
|
||||
_mouth3(0.0f),
|
||||
_mouth4(0.0f),
|
||||
_mouthTime(0.0f),
|
||||
_renderLookatVectors(false),
|
||||
_renderLookatTarget(false),
|
||||
_saccade(0.0f, 0.0f, 0.0f),
|
||||
|
@ -246,6 +248,16 @@ void Head::calculateMouthShapes() {
|
|||
const float JAW_OPEN_SCALE = 0.015f;
|
||||
const float JAW_OPEN_RATE = 0.9f;
|
||||
const float JAW_CLOSE_RATE = 0.90f;
|
||||
const float TIMESTEP_CONSTANT = 0.0032f;
|
||||
const float MMMM_POWER = 0.10f;
|
||||
const float SMILE_POWER = 0.10f;
|
||||
const float FUNNEL_POWER = 0.35f;
|
||||
const float MMMM_SPEED = 2.685f;
|
||||
const float SMILE_SPEED = 1.0f;
|
||||
const float FUNNEL_SPEED = 2.335f;
|
||||
const float STOP_GAIN = 5.0f;
|
||||
|
||||
// From the change in loudness, decide how much to open or close the jaw
|
||||
float audioDelta = sqrtf(glm::max(_averageLoudness - _longTermAverageLoudness, 0.0f)) * JAW_OPEN_SCALE;
|
||||
if (audioDelta > _audioJawOpen) {
|
||||
_audioJawOpen += (audioDelta - _audioJawOpen) * JAW_OPEN_RATE;
|
||||
|
@ -253,21 +265,14 @@ void Head::calculateMouthShapes() {
|
|||
_audioJawOpen *= JAW_CLOSE_RATE;
|
||||
}
|
||||
_audioJawOpen = glm::clamp(_audioJawOpen, 0.0f, 1.0f);
|
||||
_trailingAudioJawOpen = glm::mix(_trailingAudioJawOpen, _audioJawOpen, 0.99f);
|
||||
|
||||
// _mouth2 = "mmmm" shape
|
||||
// _mouth3 = "funnel" shape
|
||||
// _mouth4 = "smile" shape
|
||||
const float FUNNEL_PERIOD = 0.985f;
|
||||
const float FUNNEL_RANDOM_PERIOD = 0.01f;
|
||||
const float MMMM_POWER = 0.25f;
|
||||
const float MMMM_PERIOD = 0.91f;
|
||||
const float MMMM_RANDOM_PERIOD = 0.15f;
|
||||
const float SMILE_PERIOD = 0.925f;
|
||||
const float SMILE_RANDOM_PERIOD = 0.05f;
|
||||
|
||||
_mouth3 = glm::mix(_audioJawOpen, _mouth3, FUNNEL_PERIOD + randFloat() * FUNNEL_RANDOM_PERIOD);
|
||||
_mouth2 = glm::mix(_audioJawOpen * MMMM_POWER, _mouth2, MMMM_PERIOD + randFloat() * MMMM_RANDOM_PERIOD);
|
||||
_mouth4 = glm::mix(_audioJawOpen, _mouth4, SMILE_PERIOD + randFloat() * SMILE_RANDOM_PERIOD);
|
||||
// Advance time at a rate proportional to loudness, and move the mouth shapes through
|
||||
// a cycle at differing speeds to create a continuous random blend of shapes.
|
||||
_mouthTime += sqrtf(_averageLoudness) * TIMESTEP_CONSTANT;
|
||||
_mouth2 = (sinf(_mouthTime * MMMM_SPEED) + 1.0f) * MMMM_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN);
|
||||
_mouth3 = (sinf(_mouthTime * FUNNEL_SPEED) + 1.0f) * FUNNEL_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN);
|
||||
_mouth4 = (sinf(_mouthTime * SMILE_SPEED) + 1.0f) * SMILE_POWER * glm::min(1.0f, _trailingAudioJawOpen * STOP_GAIN);
|
||||
}
|
||||
|
||||
void Head::applyEyelidOffset(glm::quat headOrientation) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue