This commit is contained in:
Leonardo Murillo 2015-12-08 14:07:50 -08:00
commit dda99bfea6
386 changed files with 10959 additions and 5994 deletions

View file

@ -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());

View file

@ -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:

View file

@ -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) {

View file

@ -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();

View file

@ -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);

View file

@ -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();

View file

@ -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;
}

View file

@ -16,7 +16,7 @@
class AssignmentFactory {
public:
static ThreadedAssignment* unpackAssignment(NLPacket& packet);
static ThreadedAssignment* unpackAssignment(ReceivedMessage& message);
};
#endif // hifi_AssignmentFactory_h

View file

@ -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);

View file

@ -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();

View file

@ -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();

View file

@ -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;
};

View file

@ -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);

View file

@ -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;
};

View file

@ -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() &&

View file

@ -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();

View file

@ -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;
}

View file

@ -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();

View file

@ -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() {

View file

@ -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:

View file

@ -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();
}

View file

@ -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;

View 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;
}

View 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

View file

@ -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;
}

View file

@ -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

View file

@ -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());
}

View file

@ -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;

View file

@ -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.

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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...

View file

@ -17,7 +17,6 @@
#include <atomic>
#include <GenericThread.h>
#include <OctreeElementBag.h>
#include "OctreeQueryNode.h"

View file

@ -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) {

View file

@ -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;

View file

@ -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 ()

View file

@ -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 ()

View file

@ -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)

View file

@ -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)

View file

@ -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());
}
}

View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -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) {

View file

@ -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);

View 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;
})();

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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();

View 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/"
}
}
}

View 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
View 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;
};
});

View 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)
});
};
});

View 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
View 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;
};

View 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);
});

View 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;
}

View file

@ -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);

View file

@ -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) {

View 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);
}

View file

@ -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

View file

@ -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));
}

View file

@ -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">

View file

@ -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);

File diff suppressed because it is too large Load diff

View file

@ -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;
}

View file

@ -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)
}

View file

@ -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)
}
}
})

View file

@ -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;
}

View file

@ -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
}
})
});

View file

@ -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() {

View file

@ -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);

View 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);

View file

@ -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")

View file

@ -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

View file

@ -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.

View file

@ -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" }

View file

@ -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;

View file

@ -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();
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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;
};

View file

@ -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();
}

View 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);
}

View 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

View file

@ -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,

View file

@ -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";

View file

@ -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
}

View file

@ -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

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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);

View file

@ -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; }

View file

@ -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();

View file

@ -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;

View file

@ -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