mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-23 13:35:06 +02:00
Merge branch 'master' of https://github.com/worklist/hifi
This commit is contained in:
commit
70a26ff895
15 changed files with 339 additions and 184 deletions
|
@ -11,7 +11,7 @@
|
||||||
// nodes, and broadcasts that data back to them, every BROADCAST_INTERVAL ms.
|
// nodes, and broadcasts that data back to them, every BROADCAST_INTERVAL ms.
|
||||||
|
|
||||||
#include <QtCore/QCoreApplication>
|
#include <QtCore/QCoreApplication>
|
||||||
#include <QtCore/QElapsedTimer>
|
#include <QtCore/QDateTime>
|
||||||
#include <QtCore/QJsonObject>
|
#include <QtCore/QJsonObject>
|
||||||
#include <QtCore/QTimer>
|
#include <QtCore/QTimer>
|
||||||
|
|
||||||
|
@ -31,10 +31,13 @@ const unsigned int AVATAR_DATA_SEND_INTERVAL_USECS = (1 / 60.0) * 1000 * 1000;
|
||||||
|
|
||||||
AvatarMixer::AvatarMixer(const QByteArray& packet) :
|
AvatarMixer::AvatarMixer(const QByteArray& packet) :
|
||||||
ThreadedAssignment(packet),
|
ThreadedAssignment(packet),
|
||||||
|
_lastFrameTimestamp(QDateTime::currentMSecsSinceEpoch()),
|
||||||
_trailingSleepRatio(1.0f),
|
_trailingSleepRatio(1.0f),
|
||||||
_performanceThrottlingRatio(0.0f),
|
_performanceThrottlingRatio(0.0f),
|
||||||
_sumListeners(0),
|
_sumListeners(0),
|
||||||
_numStatFrames(0)
|
_numStatFrames(0),
|
||||||
|
_sumBillboardPackets(0),
|
||||||
|
_sumIdentityPackets(0)
|
||||||
{
|
{
|
||||||
// make sure we hear about node kills so we can tell the other nodes
|
// make sure we hear about node kills so we can tell the other nodes
|
||||||
connect(NodeList::getInstance(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled);
|
connect(NodeList::getInstance(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled);
|
||||||
|
@ -46,6 +49,8 @@ void attachAvatarDataToNode(Node* newNode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 300.0f;
|
||||||
|
|
||||||
// NOTE: some additional optimizations to consider.
|
// NOTE: some additional optimizations to consider.
|
||||||
// 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present
|
// 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present
|
||||||
// if the avatar is not in view or in the keyhole.
|
// if the avatar is not in view or in the keyhole.
|
||||||
|
@ -99,6 +104,41 @@ void AvatarMixer::broadcastAvatarData() {
|
||||||
|
|
||||||
// copy the avatar into the mixedAvatarByteArray packet
|
// copy the avatar into the mixedAvatarByteArray packet
|
||||||
mixedAvatarByteArray.append(avatarByteArray);
|
mixedAvatarByteArray.append(avatarByteArray);
|
||||||
|
|
||||||
|
// if the receiving avatar has just connected make sure we send out the mesh and billboard
|
||||||
|
// for this avatar (assuming they exist)
|
||||||
|
bool forceSend = !myData->checkAndSetHasReceivedFirstPackets();
|
||||||
|
|
||||||
|
// we will also force a send of billboard or identity packet
|
||||||
|
// if either has changed in the last frame
|
||||||
|
|
||||||
|
if (otherNodeData->getBillboardChangeTimestamp() > 0
|
||||||
|
&& (forceSend
|
||||||
|
|| otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp
|
||||||
|
|| randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
|
||||||
|
QByteArray billboardPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard);
|
||||||
|
billboardPacket.append(otherNode->getUUID().toRfc4122());
|
||||||
|
billboardPacket.append(otherNodeData->getAvatar().getBillboard());
|
||||||
|
nodeList->writeDatagram(billboardPacket, node);
|
||||||
|
|
||||||
|
++_sumBillboardPackets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherNodeData->getIdentityChangeTimestamp() > 0
|
||||||
|
&& (forceSend
|
||||||
|
|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
|
||||||
|
|| randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
|
||||||
|
|
||||||
|
QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
|
||||||
|
|
||||||
|
QByteArray individualData = otherNodeData->getAvatar().identityByteArray();
|
||||||
|
individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNode->getUUID().toRfc4122());
|
||||||
|
identityPacket.append(individualData);
|
||||||
|
|
||||||
|
nodeList->writeDatagram(identityPacket, node);
|
||||||
|
|
||||||
|
++_sumIdentityPackets;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,66 +146,8 @@ void AvatarMixer::broadcastAvatarData() {
|
||||||
nodeList->writeDatagram(mixedAvatarByteArray, node);
|
nodeList->writeDatagram(mixedAvatarByteArray, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void broadcastIdentityPacket() {
|
_lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch();
|
||||||
|
|
||||||
NodeList* nodeList = NodeList::getInstance();
|
|
||||||
|
|
||||||
QByteArray avatarIdentityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
|
|
||||||
int numPacketHeaderBytes = avatarIdentityPacket.size();
|
|
||||||
|
|
||||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
|
||||||
if (node->getLinkedData() && node->getType() == NodeType::Agent) {
|
|
||||||
|
|
||||||
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
|
|
||||||
AvatarData& avatar = nodeData->getAvatar();
|
|
||||||
QByteArray individualData = avatar.identityByteArray();
|
|
||||||
individualData.replace(0, NUM_BYTES_RFC4122_UUID, node->getUUID().toRfc4122());
|
|
||||||
|
|
||||||
if (avatarIdentityPacket.size() + individualData.size() > MAX_PACKET_SIZE) {
|
|
||||||
// we've hit MTU, send out the current packet before appending
|
|
||||||
nodeList->broadcastToNodes(avatarIdentityPacket, NodeSet() << NodeType::Agent);
|
|
||||||
avatarIdentityPacket.resize(numPacketHeaderBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// append the individual data to the current the avatarIdentityPacket
|
|
||||||
avatarIdentityPacket.append(individualData);
|
|
||||||
|
|
||||||
// re-set the bool in AvatarMixerClientData so a change between key frames gets sent out
|
|
||||||
nodeData->setHasSentIdentityBetweenKeyFrames(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// send out the final packet
|
|
||||||
if (avatarIdentityPacket.size() > numPacketHeaderBytes) {
|
|
||||||
nodeList->broadcastToNodes(avatarIdentityPacket, NodeSet() << NodeType::Agent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void broadcastBillboardPacket(const SharedNodePointer& sendingNode) {
|
|
||||||
AvatarMixerClientData* nodeData = static_cast<AvatarMixerClientData*>(sendingNode->getLinkedData());
|
|
||||||
AvatarData& avatar = nodeData->getAvatar();
|
|
||||||
QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard);
|
|
||||||
packet.append(sendingNode->getUUID().toRfc4122());
|
|
||||||
packet.append(avatar.getBillboard());
|
|
||||||
|
|
||||||
NodeList* nodeList = NodeList::getInstance();
|
|
||||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
|
||||||
if (node->getType() == NodeType::Agent && node != sendingNode) {
|
|
||||||
nodeList->writeDatagram(packet, node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void broadcastBillboardPackets() {
|
|
||||||
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
|
|
||||||
if (node->getLinkedData() && node->getType() == NodeType::Agent) {
|
|
||||||
AvatarMixerClientData* nodeData = static_cast<AvatarMixerClientData*>(node->getLinkedData());
|
|
||||||
broadcastBillboardPacket(node);
|
|
||||||
nodeData->setHasSentBillboardBetweenKeyFrames(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
||||||
|
@ -202,18 +184,10 @@ void AvatarMixer::readPendingDatagrams() {
|
||||||
if (avatarNode && avatarNode->getLinkedData()) {
|
if (avatarNode && avatarNode->getLinkedData()) {
|
||||||
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(avatarNode->getLinkedData());
|
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(avatarNode->getLinkedData());
|
||||||
AvatarData& avatar = nodeData->getAvatar();
|
AvatarData& avatar = nodeData->getAvatar();
|
||||||
if (avatar.hasIdentityChangedAfterParsing(receivedPacket)
|
|
||||||
&& !nodeData->hasSentIdentityBetweenKeyFrames()) {
|
|
||||||
// this avatar changed their identity in some way and we haven't sent a packet in this keyframe
|
|
||||||
QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity);
|
|
||||||
|
|
||||||
QByteArray individualByteArray = avatar.identityByteArray();
|
// parse the identity packet and update the change timestamp if appropriate
|
||||||
individualByteArray.replace(0, NUM_BYTES_RFC4122_UUID, avatarNode->getUUID().toRfc4122());
|
if (avatar.hasIdentityChangedAfterParsing(receivedPacket)) {
|
||||||
|
nodeData->setIdentityChangeTimestamp(QDateTime::currentMSecsSinceEpoch());
|
||||||
identityPacket.append(individualByteArray);
|
|
||||||
|
|
||||||
nodeData->setHasSentIdentityBetweenKeyFrames(true);
|
|
||||||
nodeList->broadcastToNodes(identityPacket, NodeSet() << NodeType::Agent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -226,12 +200,12 @@ void AvatarMixer::readPendingDatagrams() {
|
||||||
if (avatarNode && avatarNode->getLinkedData()) {
|
if (avatarNode && avatarNode->getLinkedData()) {
|
||||||
AvatarMixerClientData* nodeData = static_cast<AvatarMixerClientData*>(avatarNode->getLinkedData());
|
AvatarMixerClientData* nodeData = static_cast<AvatarMixerClientData*>(avatarNode->getLinkedData());
|
||||||
AvatarData& avatar = nodeData->getAvatar();
|
AvatarData& avatar = nodeData->getAvatar();
|
||||||
if (avatar.hasBillboardChangedAfterParsing(receivedPacket)
|
|
||||||
&& !nodeData->hasSentBillboardBetweenKeyFrames()) {
|
// parse the billboard packet and update the change timestamp if appropriate
|
||||||
// this avatar changed their billboard and we haven't sent a packet in this keyframe
|
if (avatar.hasBillboardChangedAfterParsing(receivedPacket)) {
|
||||||
broadcastBillboardPacket(avatarNode);
|
nodeData->setBillboardChangeTimestamp(QDateTime::currentMSecsSinceEpoch());
|
||||||
nodeData->setHasSentBillboardBetweenKeyFrames(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -252,18 +226,20 @@ void AvatarMixer::sendStatsPacket() {
|
||||||
QJsonObject statsObject;
|
QJsonObject statsObject;
|
||||||
statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames;
|
statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames;
|
||||||
|
|
||||||
|
statsObject["average_billboard_packets_per_frame"] = (float) _sumBillboardPackets / (float) _numStatFrames;
|
||||||
|
statsObject["average_identity_packets_per_frame"] = (float) _sumIdentityPackets / (float) _numStatFrames;
|
||||||
|
|
||||||
statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100;
|
statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100;
|
||||||
statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio;
|
statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio;
|
||||||
|
|
||||||
ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
|
ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject);
|
||||||
|
|
||||||
_sumListeners = 0;
|
_sumListeners = 0;
|
||||||
|
_sumBillboardPackets = 0;
|
||||||
|
_sumIdentityPackets = 0;
|
||||||
_numStatFrames = 0;
|
_numStatFrames = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const qint64 AVATAR_IDENTITY_KEYFRAME_MSECS = 5000;
|
|
||||||
const qint64 AVATAR_BILLBOARD_KEYFRAME_MSECS = 5000;
|
|
||||||
|
|
||||||
void AvatarMixer::run() {
|
void AvatarMixer::run() {
|
||||||
ThreadedAssignment::commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer);
|
ThreadedAssignment::commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer);
|
||||||
|
|
||||||
|
@ -277,12 +253,6 @@ void AvatarMixer::run() {
|
||||||
|
|
||||||
gettimeofday(&startTime, NULL);
|
gettimeofday(&startTime, NULL);
|
||||||
|
|
||||||
QElapsedTimer identityTimer;
|
|
||||||
identityTimer.start();
|
|
||||||
|
|
||||||
QElapsedTimer billboardTimer;
|
|
||||||
billboardTimer.start();
|
|
||||||
|
|
||||||
int usecToSleep = AVATAR_DATA_SEND_INTERVAL_USECS;
|
int usecToSleep = AVATAR_DATA_SEND_INTERVAL_USECS;
|
||||||
|
|
||||||
const int TRAILING_AVERAGE_FRAMES = 100;
|
const int TRAILING_AVERAGE_FRAMES = 100;
|
||||||
|
@ -338,19 +308,6 @@ void AvatarMixer::run() {
|
||||||
|
|
||||||
broadcastAvatarData();
|
broadcastAvatarData();
|
||||||
|
|
||||||
if (identityTimer.elapsed() >= AVATAR_IDENTITY_KEYFRAME_MSECS) {
|
|
||||||
// it's time to broadcast the keyframe identity packets
|
|
||||||
broadcastIdentityPacket();
|
|
||||||
|
|
||||||
// restart the timer so we do it again in AVATAR_IDENTITY_KEYFRAME_MSECS
|
|
||||||
identityTimer.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (billboardTimer.elapsed() >= AVATAR_BILLBOARD_KEYFRAME_MSECS) {
|
|
||||||
broadcastBillboardPackets();
|
|
||||||
billboardTimer.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
QCoreApplication::processEvents();
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
if (_isFinished) {
|
if (_isFinished) {
|
||||||
|
|
|
@ -30,11 +30,15 @@ public slots:
|
||||||
private:
|
private:
|
||||||
void broadcastAvatarData();
|
void broadcastAvatarData();
|
||||||
|
|
||||||
|
quint64 _lastFrameTimestamp;
|
||||||
|
|
||||||
float _trailingSleepRatio;
|
float _trailingSleepRatio;
|
||||||
float _performanceThrottlingRatio;
|
float _performanceThrottlingRatio;
|
||||||
|
|
||||||
int _sumListeners;
|
int _sumListeners;
|
||||||
int _numStatFrames;
|
int _numStatFrames;
|
||||||
|
int _sumBillboardPackets;
|
||||||
|
int _sumIdentityPackets;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__hifi__AvatarMixer__) */
|
#endif /* defined(__hifi__AvatarMixer__) */
|
||||||
|
|
|
@ -10,8 +10,9 @@
|
||||||
|
|
||||||
AvatarMixerClientData::AvatarMixerClientData() :
|
AvatarMixerClientData::AvatarMixerClientData() :
|
||||||
NodeData(),
|
NodeData(),
|
||||||
_hasSentIdentityBetweenKeyFrames(false),
|
_hasReceivedFirstPackets(false),
|
||||||
_hasSentBillboardBetweenKeyFrames(false)
|
_billboardChangeTimestamp(0),
|
||||||
|
_identityChangeTimestamp(0)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,3 +22,9 @@ int AvatarMixerClientData::parseData(const QByteArray& packet) {
|
||||||
int offset = numBytesForPacketHeader(packet);
|
int offset = numBytesForPacketHeader(packet);
|
||||||
return _avatar.parseDataAtOffset(packet, offset);
|
return _avatar.parseDataAtOffset(packet, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AvatarMixerClientData::checkAndSetHasReceivedFirstPackets() {
|
||||||
|
bool oldValue = _hasReceivedFirstPackets;
|
||||||
|
_hasReceivedFirstPackets = true;
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
|
|
@ -20,22 +20,21 @@ public:
|
||||||
AvatarMixerClientData();
|
AvatarMixerClientData();
|
||||||
|
|
||||||
int parseData(const QByteArray& packet);
|
int parseData(const QByteArray& packet);
|
||||||
|
|
||||||
bool hasSentIdentityBetweenKeyFrames() const { return _hasSentIdentityBetweenKeyFrames; }
|
|
||||||
void setHasSentIdentityBetweenKeyFrames(bool hasSentIdentityBetweenKeyFrames)
|
|
||||||
{ _hasSentIdentityBetweenKeyFrames = hasSentIdentityBetweenKeyFrames; }
|
|
||||||
|
|
||||||
bool hasSentBillboardBetweenKeyFrames() const { return _hasSentBillboardBetweenKeyFrames; }
|
|
||||||
void setHasSentBillboardBetweenKeyFrames(bool hasSentBillboardBetweenKeyFrames)
|
|
||||||
{ _hasSentBillboardBetweenKeyFrames = hasSentBillboardBetweenKeyFrames; }
|
|
||||||
|
|
||||||
AvatarData& getAvatar() { return _avatar; }
|
AvatarData& getAvatar() { return _avatar; }
|
||||||
|
|
||||||
private:
|
bool checkAndSetHasReceivedFirstPackets();
|
||||||
|
|
||||||
bool _hasSentIdentityBetweenKeyFrames;
|
quint64 getBillboardChangeTimestamp() const { return _billboardChangeTimestamp; }
|
||||||
bool _hasSentBillboardBetweenKeyFrames;
|
void setBillboardChangeTimestamp(quint64 billboardChangeTimestamp) { _billboardChangeTimestamp = billboardChangeTimestamp; }
|
||||||
|
|
||||||
|
quint64 getIdentityChangeTimestamp() const { return _identityChangeTimestamp; }
|
||||||
|
void setIdentityChangeTimestamp(quint64 identityChangeTimestamp) { _identityChangeTimestamp = identityChangeTimestamp; }
|
||||||
|
|
||||||
|
private:
|
||||||
AvatarData _avatar;
|
AvatarData _avatar;
|
||||||
|
bool _hasReceivedFirstPackets;
|
||||||
|
quint64 _billboardChangeTimestamp;
|
||||||
|
quint64 _identityChangeTimestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* defined(__hifi__AvatarMixerClientData__) */
|
#endif /* defined(__hifi__AvatarMixerClientData__) */
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
Script.include("lookWithTouch.js");
|
Script.include("lookWithTouch.js");
|
||||||
Script.include("editVoxels.js");
|
Script.include("editVoxels.js");
|
||||||
Script.include("selectAudioDevice.js");
|
Script.include("selectAudioDevice.js");
|
||||||
|
Script.include("hydraMove.js");
|
|
@ -400,9 +400,9 @@ function calcScaleFromThumb(newThumbX) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setAudioPosition() {
|
function setAudioPosition() {
|
||||||
var camera = Camera.getPosition();
|
var position = MyAvatar.position;
|
||||||
var forwardVector = Quat.getFront(MyAvatar.orientation);
|
var forwardVector = Quat.getFront(MyAvatar.orientation);
|
||||||
audioOptions.position = Vec3.sum(camera, forwardVector);
|
audioOptions.position = Vec3.sum(position, forwardVector);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNewPasteVoxel(pickRay) {
|
function getNewPasteVoxel(pickRay) {
|
||||||
|
@ -735,6 +735,7 @@ function trackKeyReleaseEvent(event) {
|
||||||
if (event.text == "TAB") {
|
if (event.text == "TAB") {
|
||||||
editToolsOn = !editToolsOn;
|
editToolsOn = !editToolsOn;
|
||||||
moveTools();
|
moveTools();
|
||||||
|
setAudioPosition(); // make sure we set the audio position before playing sounds
|
||||||
showPreviewGuides();
|
showPreviewGuides();
|
||||||
Audio.playSound(clickSound, audioOptions);
|
Audio.playSound(clickSound, audioOptions);
|
||||||
}
|
}
|
||||||
|
|
16
examples/includeExample.js
Normal file
16
examples/includeExample.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//
|
||||||
|
// includeExample.js
|
||||||
|
// hifi
|
||||||
|
//
|
||||||
|
// Created by Brad Hefta-Gaub on 3/24/14
|
||||||
|
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// This is an example script that demonstrates use of the Script.include() feature
|
||||||
|
//
|
||||||
|
|
||||||
|
// You can include scripts from URLs
|
||||||
|
Script.include("http://public.highfidelity.io/scripts/lookWithTouch.js");
|
||||||
|
|
||||||
|
// You can also include scripts that are relative to the current script
|
||||||
|
Script.include("editVoxels.js");
|
||||||
|
Script.include("../examples/selectAudioDevice.js");
|
|
@ -28,6 +28,7 @@
|
||||||
#include <QDesktopWidget>
|
#include <QDesktopWidget>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
#include <QInputDialog>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QMenuBar>
|
#include <QMenuBar>
|
||||||
|
@ -330,8 +331,19 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
|
||||||
LocalVoxelsList::getInstance()->addPersistantTree(DOMAIN_TREE_NAME, _voxels.getTree());
|
LocalVoxelsList::getInstance()->addPersistantTree(DOMAIN_TREE_NAME, _voxels.getTree());
|
||||||
LocalVoxelsList::getInstance()->addPersistantTree(CLIPBOARD_TREE_NAME, &_clipboard);
|
LocalVoxelsList::getInstance()->addPersistantTree(CLIPBOARD_TREE_NAME, &_clipboard);
|
||||||
|
|
||||||
// do this as late as possible so that all required subsystems are inialized
|
// check first run...
|
||||||
loadScripts();
|
QVariant firstRunValue = _settings->value("firstRun",QVariant(true));
|
||||||
|
if (firstRunValue.isValid() && firstRunValue.toBool()) {
|
||||||
|
qDebug() << "This is a first run...";
|
||||||
|
// clear the scripts, and set out script to our default scripts
|
||||||
|
clearScriptsBeforeRunning();
|
||||||
|
loadScript("http://public.highfidelity.io/scripts/defaultScripts.js");
|
||||||
|
|
||||||
|
_settings->setValue("firstRun",QVariant(false));
|
||||||
|
} else {
|
||||||
|
// do this as late as possible so that all required subsystems are inialized
|
||||||
|
loadScripts();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Application::~Application() {
|
Application::~Application() {
|
||||||
|
@ -3453,6 +3465,13 @@ void Application::loadScripts() {
|
||||||
settings->endArray();
|
settings->endArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::clearScriptsBeforeRunning() {
|
||||||
|
// clears all scripts from the settings
|
||||||
|
QSettings* settings = new QSettings(this);
|
||||||
|
settings->beginWriteArray("Settings");
|
||||||
|
settings->endArray();
|
||||||
|
}
|
||||||
|
|
||||||
void Application::saveScripts() {
|
void Application::saveScripts() {
|
||||||
// saves all current running scripts
|
// saves all current running scripts
|
||||||
QSettings* settings = new QSettings(this);
|
QSettings* settings = new QSettings(this);
|
||||||
|
@ -3508,35 +3527,17 @@ void Application::cleanupScriptMenuItem(const QString& scriptMenuName) {
|
||||||
Menu::getInstance()->removeAction(Menu::getInstance()->getActiveScriptsMenu(), scriptMenuName);
|
Menu::getInstance()->removeAction(Menu::getInstance()->getActiveScriptsMenu(), scriptMenuName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::loadScript(const QString& fileNameString) {
|
void Application::loadScript(const QString& scriptName) {
|
||||||
QByteArray fileNameAscii = fileNameString.toLocal8Bit();
|
|
||||||
const char* fileName = fileNameAscii.data();
|
|
||||||
|
|
||||||
std::ifstream file(fileName, std::ios::in|std::ios::binary|std::ios::ate);
|
|
||||||
if(!file.is_open()) {
|
|
||||||
qDebug("Error loading file %s", fileName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
qDebug("Loading file %s...", fileName);
|
|
||||||
_activeScripts.append(fileNameString);
|
|
||||||
|
|
||||||
// get file length....
|
|
||||||
unsigned long fileLength = file.tellg();
|
|
||||||
file.seekg( 0, std::ios::beg );
|
|
||||||
|
|
||||||
// read the entire file into a buffer, WHAT!? Why not.
|
|
||||||
char* entireFile = new char[fileLength+1];
|
|
||||||
file.read((char*)entireFile, fileLength);
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
entireFile[fileLength] = 0;// null terminate
|
|
||||||
QString script(entireFile);
|
|
||||||
delete[] entireFile;
|
|
||||||
|
|
||||||
// start the script on a new thread...
|
// start the script on a new thread...
|
||||||
bool wantMenuItems = true; // tells the ScriptEngine object to add menu items for itself
|
bool wantMenuItems = true; // tells the ScriptEngine object to add menu items for itself
|
||||||
|
ScriptEngine* scriptEngine = new ScriptEngine(QUrl(scriptName), wantMenuItems, &_controllerScriptingInterface);
|
||||||
|
|
||||||
ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, &_controllerScriptingInterface);
|
if (!scriptEngine->hasScript()) {
|
||||||
|
qDebug() << "Application::loadScript(), script failed to load...";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_activeScripts.append(scriptName);
|
||||||
|
|
||||||
// add a stop menu item
|
// add a stop menu item
|
||||||
Menu::getInstance()->addActionToQMenuAndActionHash(Menu::getInstance()->getActiveScriptsMenu(),
|
Menu::getInstance()->addActionToQMenuAndActionHash(Menu::getInstance()->getActiveScriptsMenu(),
|
||||||
|
@ -3599,6 +3600,31 @@ void Application::loadDialog() {
|
||||||
loadScript(fileNameString);
|
loadScript(fileNameString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::loadScriptURLDialog() {
|
||||||
|
|
||||||
|
QInputDialog scriptURLDialog(Application::getInstance()->getWindow());
|
||||||
|
scriptURLDialog.setWindowTitle("Open and Run Script URL");
|
||||||
|
scriptURLDialog.setLabelText("Script:");
|
||||||
|
scriptURLDialog.setWindowFlags(Qt::Sheet);
|
||||||
|
const float DIALOG_RATIO_OF_WINDOW = 0.30f;
|
||||||
|
scriptURLDialog.resize(scriptURLDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW,
|
||||||
|
scriptURLDialog.size().height());
|
||||||
|
|
||||||
|
int dialogReturn = scriptURLDialog.exec();
|
||||||
|
QString newScript;
|
||||||
|
if (dialogReturn == QDialog::Accepted) {
|
||||||
|
if (scriptURLDialog.textValue().size() > 0) {
|
||||||
|
// the user input a new hostname, use that
|
||||||
|
newScript = scriptURLDialog.textValue();
|
||||||
|
}
|
||||||
|
loadScript(newScript);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFakeEnterEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Application::toggleLogDialog() {
|
void Application::toggleLogDialog() {
|
||||||
if (! _logDialog) {
|
if (! _logDialog) {
|
||||||
_logDialog = new LogDialog(_glWidget, getLogger());
|
_logDialog = new LogDialog(_glWidget, getLogger());
|
||||||
|
|
|
@ -115,6 +115,7 @@ public:
|
||||||
void loadScript(const QString& fileNameString);
|
void loadScript(const QString& fileNameString);
|
||||||
void loadScripts();
|
void loadScripts();
|
||||||
void storeSizeAndPosition();
|
void storeSizeAndPosition();
|
||||||
|
void clearScriptsBeforeRunning();
|
||||||
void saveScripts();
|
void saveScripts();
|
||||||
void initializeGL();
|
void initializeGL();
|
||||||
void paintGL();
|
void paintGL();
|
||||||
|
@ -254,6 +255,7 @@ public slots:
|
||||||
void setRenderVoxels(bool renderVoxels);
|
void setRenderVoxels(bool renderVoxels);
|
||||||
void doKillLocalVoxels();
|
void doKillLocalVoxels();
|
||||||
void loadDialog();
|
void loadDialog();
|
||||||
|
void loadScriptURLDialog();
|
||||||
void toggleLogDialog();
|
void toggleLogDialog();
|
||||||
void initAvatarAndViewFrustum();
|
void initAvatarAndViewFrustum();
|
||||||
void stopAllScripts();
|
void stopAllScripts();
|
||||||
|
|
|
@ -110,6 +110,8 @@ Menu::Menu() :
|
||||||
|
|
||||||
addDisabledActionAndSeparator(fileMenu, "Scripts");
|
addDisabledActionAndSeparator(fileMenu, "Scripts");
|
||||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadDialog()));
|
addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadDialog()));
|
||||||
|
addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScriptURL,
|
||||||
|
Qt::CTRL | Qt::SHIFT | Qt::Key_O, appInstance, SLOT(loadScriptURLDialog()));
|
||||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::StopAllScripts, 0, appInstance, SLOT(stopAllScripts()));
|
addActionToQMenuAndActionHash(fileMenu, MenuOption::StopAllScripts, 0, appInstance, SLOT(stopAllScripts()));
|
||||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::ReloadAllScripts, 0, appInstance, SLOT(reloadAllScripts()));
|
addActionToQMenuAndActionHash(fileMenu, MenuOption::ReloadAllScripts, 0, appInstance, SLOT(reloadAllScripts()));
|
||||||
_activeScriptsMenu = fileMenu->addMenu("Running Scripts");
|
_activeScriptsMenu = fileMenu->addMenu("Running Scripts");
|
||||||
|
|
|
@ -274,7 +274,8 @@ namespace MenuOption {
|
||||||
const QString OffAxisProjection = "Off-Axis Projection";
|
const QString OffAxisProjection = "Off-Axis Projection";
|
||||||
const QString OldVoxelCullingMode = "Old Voxel Culling Mode";
|
const QString OldVoxelCullingMode = "Old Voxel Culling Mode";
|
||||||
const QString TurnWithHead = "Turn using Head";
|
const QString TurnWithHead = "Turn using Head";
|
||||||
const QString LoadScript = "Open and Run Script...";
|
const QString LoadScript = "Open and Run Script File...";
|
||||||
|
const QString LoadScriptURL = "Open and Run Script from URL...";
|
||||||
const QString Oscilloscope = "Audio Oscilloscope";
|
const QString Oscilloscope = "Audio Oscilloscope";
|
||||||
const QString Pair = "Pair";
|
const QString Pair = "Pair";
|
||||||
const QString Particles = "Particles";
|
const QString Particles = "Particles";
|
||||||
|
@ -306,4 +307,6 @@ namespace MenuOption {
|
||||||
const QString VoxelTextures = "Voxel Textures";
|
const QString VoxelTextures = "Voxel Textures";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sendFakeEnterEvent();
|
||||||
|
|
||||||
#endif /* defined(__hifi__Menu__) */
|
#endif /* defined(__hifi__Menu__) */
|
||||||
|
|
|
@ -48,15 +48,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
||||||
AvatarHash::iterator avatarIterator = _avatarHash.begin();
|
AvatarHash::iterator avatarIterator = _avatarHash.begin();
|
||||||
while (avatarIterator != _avatarHash.end()) {
|
while (avatarIterator != _avatarHash.end()) {
|
||||||
Avatar* avatar = static_cast<Avatar*>(avatarIterator.value().data());
|
Avatar* avatar = static_cast<Avatar*>(avatarIterator.value().data());
|
||||||
if (avatar == static_cast<Avatar*>(_myAvatar.data())) {
|
if (avatar == static_cast<Avatar*>(_myAvatar.data()) || !avatar->isInitialized()) {
|
||||||
// DO NOT update _myAvatar! Its update has already been done earlier in the main loop.
|
// DO NOT update _myAvatar! Its update has already been done earlier in the main loop.
|
||||||
//updateMyAvatar(deltaTime);
|
// DO NOT update uninitialized Avatars
|
||||||
++avatarIterator;
|
++avatarIterator;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!avatar->isInitialized()) {
|
|
||||||
avatar->init();
|
|
||||||
}
|
|
||||||
if (avatar->getOwningAvatarMixer()) {
|
if (avatar->getOwningAvatarMixer()) {
|
||||||
// this avatar's mixer is still around, go ahead and simulate it
|
// this avatar's mixer is still around, go ahead and simulate it
|
||||||
avatar->simulate(deltaTime);
|
avatar->simulate(deltaTime);
|
||||||
|
@ -120,22 +117,40 @@ void AvatarManager::renderAvatarFades(const glm::vec3& cameraPosition, Avatar::R
|
||||||
|
|
||||||
foreach(const AvatarSharedPointer& fadingAvatar, _avatarFades) {
|
foreach(const AvatarSharedPointer& fadingAvatar, _avatarFades) {
|
||||||
Avatar* avatar = static_cast<Avatar*>(fadingAvatar.data());
|
Avatar* avatar = static_cast<Avatar*>(fadingAvatar.data());
|
||||||
if (avatar != static_cast<Avatar*>(_myAvatar.data())) {
|
if (avatar != static_cast<Avatar*>(_myAvatar.data()) && avatar->isInitialized()) {
|
||||||
avatar->render(cameraPosition, renderMode);
|
avatar->render(cameraPosition, renderMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AvatarSharedPointer AvatarManager::matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer<Node>& mixerWeakPointer) {
|
||||||
|
AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID);
|
||||||
|
|
||||||
|
if (!matchingAvatar) {
|
||||||
|
// construct a new Avatar for this node
|
||||||
|
Avatar* avatar = new Avatar();
|
||||||
|
avatar->setOwningAvatarMixer(mixerWeakPointer);
|
||||||
|
|
||||||
|
// insert the new avatar into our hash
|
||||||
|
matchingAvatar = AvatarSharedPointer(avatar);
|
||||||
|
_avatarHash.insert(nodeUUID, matchingAvatar);
|
||||||
|
|
||||||
|
qDebug() << "Adding avatar with UUID" << nodeUUID << "to AvatarManager hash.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchingAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer<Node>& mixerWeakPointer) {
|
void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer<Node>& mixerWeakPointer) {
|
||||||
switch (packetTypeForPacket(datagram)) {
|
switch (packetTypeForPacket(datagram)) {
|
||||||
case PacketTypeBulkAvatarData:
|
case PacketTypeBulkAvatarData:
|
||||||
processAvatarDataPacket(datagram, mixerWeakPointer);
|
processAvatarDataPacket(datagram, mixerWeakPointer);
|
||||||
break;
|
break;
|
||||||
case PacketTypeAvatarIdentity:
|
case PacketTypeAvatarIdentity:
|
||||||
processAvatarIdentityPacket(datagram);
|
processAvatarIdentityPacket(datagram, mixerWeakPointer);
|
||||||
break;
|
break;
|
||||||
case PacketTypeAvatarBillboard:
|
case PacketTypeAvatarBillboard:
|
||||||
processAvatarBillboardPacket(datagram);
|
processAvatarBillboardPacket(datagram, mixerWeakPointer);
|
||||||
break;
|
break;
|
||||||
case PacketTypeKillAvatar:
|
case PacketTypeKillAvatar:
|
||||||
processKillAvatar(datagram);
|
processKillAvatar(datagram);
|
||||||
|
@ -154,26 +169,21 @@ void AvatarManager::processAvatarDataPacket(const QByteArray &datagram, const QW
|
||||||
QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(bytesRead, NUM_BYTES_RFC4122_UUID));
|
QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(bytesRead, NUM_BYTES_RFC4122_UUID));
|
||||||
bytesRead += NUM_BYTES_RFC4122_UUID;
|
bytesRead += NUM_BYTES_RFC4122_UUID;
|
||||||
|
|
||||||
AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID);
|
AvatarSharedPointer matchingAvatarData = matchingOrNewAvatar(nodeUUID, mixerWeakPointer);
|
||||||
|
|
||||||
if (!matchingAvatar) {
|
|
||||||
// construct a new Avatar for this node
|
|
||||||
Avatar* avatar = new Avatar();
|
|
||||||
avatar->setOwningAvatarMixer(mixerWeakPointer);
|
|
||||||
|
|
||||||
// insert the new avatar into our hash
|
|
||||||
matchingAvatar = AvatarSharedPointer(avatar);
|
|
||||||
_avatarHash.insert(nodeUUID, matchingAvatar);
|
|
||||||
|
|
||||||
qDebug() << "Adding avatar with UUID" << nodeUUID << "to AvatarManager hash.";
|
|
||||||
}
|
|
||||||
|
|
||||||
// have the matching (or new) avatar parse the data from the packet
|
// have the matching (or new) avatar parse the data from the packet
|
||||||
bytesRead += matchingAvatar->parseDataAtOffset(datagram, bytesRead);
|
bytesRead += matchingAvatarData->parseDataAtOffset(datagram, bytesRead);
|
||||||
|
|
||||||
|
Avatar* matchingAvatar = reinterpret_cast<Avatar*>(matchingAvatarData.data());
|
||||||
|
|
||||||
|
if (!matchingAvatar->isInitialized()) {
|
||||||
|
// now that we have AvatarData for this Avatar we are go for init
|
||||||
|
matchingAvatar->init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) {
|
void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet, const QWeakPointer<Node>& mixerWeakPointer) {
|
||||||
// setup a data stream to parse the packet
|
// setup a data stream to parse the packet
|
||||||
QDataStream identityStream(packet);
|
QDataStream identityStream(packet);
|
||||||
identityStream.skipRawData(numBytesForPacketHeader(packet));
|
identityStream.skipRawData(numBytesForPacketHeader(packet));
|
||||||
|
@ -187,7 +197,7 @@ void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) {
|
||||||
identityStream >> nodeUUID >> faceMeshURL >> skeletonURL >> displayName;
|
identityStream >> nodeUUID >> faceMeshURL >> skeletonURL >> displayName;
|
||||||
|
|
||||||
// mesh URL for a UUID, find avatar in our list
|
// mesh URL for a UUID, find avatar in our list
|
||||||
AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID);
|
AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(nodeUUID, mixerWeakPointer);
|
||||||
if (matchingAvatar) {
|
if (matchingAvatar) {
|
||||||
Avatar* avatar = static_cast<Avatar*>(matchingAvatar.data());
|
Avatar* avatar = static_cast<Avatar*>(matchingAvatar.data());
|
||||||
|
|
||||||
|
@ -206,11 +216,11 @@ void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarManager::processAvatarBillboardPacket(const QByteArray& packet) {
|
void AvatarManager::processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer) {
|
||||||
int headerSize = numBytesForPacketHeader(packet);
|
int headerSize = numBytesForPacketHeader(packet);
|
||||||
QUuid nodeUUID = QUuid::fromRfc4122(QByteArray::fromRawData(packet.constData() + headerSize, NUM_BYTES_RFC4122_UUID));
|
QUuid nodeUUID = QUuid::fromRfc4122(QByteArray::fromRawData(packet.constData() + headerSize, NUM_BYTES_RFC4122_UUID));
|
||||||
|
|
||||||
AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID);
|
AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(nodeUUID, mixerWeakPointer);
|
||||||
if (matchingAvatar) {
|
if (matchingAvatar) {
|
||||||
Avatar* avatar = static_cast<Avatar*>(matchingAvatar.data());
|
Avatar* avatar = static_cast<Avatar*>(matchingAvatar.data());
|
||||||
QByteArray billboard = packet.mid(headerSize + NUM_BYTES_RFC4122_UUID);
|
QByteArray billboard = packet.mid(headerSize + NUM_BYTES_RFC4122_UUID);
|
||||||
|
@ -234,7 +244,9 @@ void AvatarManager::processKillAvatar(const QByteArray& datagram) {
|
||||||
AvatarHash::iterator AvatarManager::erase(const AvatarHash::iterator& iterator) {
|
AvatarHash::iterator AvatarManager::erase(const AvatarHash::iterator& iterator) {
|
||||||
if (iterator.key() != MY_AVATAR_KEY) {
|
if (iterator.key() != MY_AVATAR_KEY) {
|
||||||
qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash.";
|
qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash.";
|
||||||
_avatarFades.push_back(iterator.value());
|
if (reinterpret_cast<Avatar*>(iterator.value().data())->isInitialized()) {
|
||||||
|
_avatarFades.push_back(iterator.value());
|
||||||
|
}
|
||||||
return AvatarHashMap::erase(iterator);
|
return AvatarHashMap::erase(iterator);
|
||||||
} else {
|
} else {
|
||||||
// never remove _myAvatar from the list
|
// never remove _myAvatar from the list
|
||||||
|
|
|
@ -39,9 +39,11 @@ public slots:
|
||||||
private:
|
private:
|
||||||
AvatarManager(const AvatarManager& other);
|
AvatarManager(const AvatarManager& other);
|
||||||
|
|
||||||
|
AvatarSharedPointer matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer<Node>& mixerWeakPointer);
|
||||||
|
|
||||||
void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
|
void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
|
||||||
void processAvatarIdentityPacket(const QByteArray& packet);
|
void processAvatarIdentityPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
|
||||||
void processAvatarBillboardPacket(const QByteArray& packet);
|
void processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
|
||||||
void processKillAvatar(const QByteArray& datagram);
|
void processKillAvatar(const QByteArray& datagram);
|
||||||
|
|
||||||
void simulateAvatarFades(float deltaTime);
|
void simulateAvatarFades(float deltaTime);
|
||||||
|
|
|
@ -64,13 +64,10 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co
|
||||||
_quatLibrary(),
|
_quatLibrary(),
|
||||||
_vec3Library()
|
_vec3Library()
|
||||||
{
|
{
|
||||||
QByteArray fileNameAscii = fileNameString.toLocal8Bit();
|
|
||||||
const char* scriptMenuName = fileNameAscii.data();
|
|
||||||
|
|
||||||
// some clients will use these menu features
|
// some clients will use these menu features
|
||||||
if (!fileNameString.isEmpty()) {
|
if (!fileNameString.isEmpty()) {
|
||||||
_scriptMenuName = "Stop ";
|
_scriptMenuName = "Stop ";
|
||||||
_scriptMenuName.append(scriptMenuName);
|
_scriptMenuName.append(qPrintable(fileNameString));
|
||||||
_scriptMenuName.append(QString(" [%1]").arg(_scriptNumber));
|
_scriptMenuName.append(QString(" [%1]").arg(_scriptNumber));
|
||||||
} else {
|
} else {
|
||||||
_scriptMenuName = "Stop Script ";
|
_scriptMenuName = "Stop Script ";
|
||||||
|
@ -79,6 +76,72 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co
|
||||||
_scriptNumber++;
|
_scriptNumber++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems,
|
||||||
|
AbstractControllerScriptingInterface* controllerScriptingInterface) :
|
||||||
|
_scriptContents(),
|
||||||
|
_isFinished(false),
|
||||||
|
_isRunning(false),
|
||||||
|
_isInitialized(false),
|
||||||
|
_engine(),
|
||||||
|
_isAvatar(false),
|
||||||
|
_avatarIdentityTimer(NULL),
|
||||||
|
_avatarBillboardTimer(NULL),
|
||||||
|
_timerFunctionMap(),
|
||||||
|
_isListeningToAudioStream(false),
|
||||||
|
_avatarSound(NULL),
|
||||||
|
_numAvatarSoundSentBytes(0),
|
||||||
|
_controllerScriptingInterface(controllerScriptingInterface),
|
||||||
|
_avatarData(NULL),
|
||||||
|
_wantMenuItems(wantMenuItems),
|
||||||
|
_scriptMenuName(),
|
||||||
|
_fileNameString(),
|
||||||
|
_quatLibrary(),
|
||||||
|
_vec3Library()
|
||||||
|
{
|
||||||
|
QString scriptURLString = scriptURL.toString();
|
||||||
|
_fileNameString = scriptURLString;
|
||||||
|
// some clients will use these menu features
|
||||||
|
if (!scriptURLString.isEmpty()) {
|
||||||
|
_scriptMenuName = "Stop ";
|
||||||
|
_scriptMenuName.append(qPrintable(scriptURLString));
|
||||||
|
_scriptMenuName.append(QString(" [%1]").arg(_scriptNumber));
|
||||||
|
} else {
|
||||||
|
_scriptMenuName = "Stop Script ";
|
||||||
|
_scriptMenuName.append(_scriptNumber);
|
||||||
|
}
|
||||||
|
_scriptNumber++;
|
||||||
|
|
||||||
|
QUrl url(scriptURL);
|
||||||
|
|
||||||
|
// if the scheme is empty, maybe they typed in a file, let's try
|
||||||
|
if (url.scheme().isEmpty()) {
|
||||||
|
url = QUrl::fromLocalFile(scriptURLString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok, let's see if it's valid... and if so, load it
|
||||||
|
if (url.isValid()) {
|
||||||
|
if (url.scheme() == "file") {
|
||||||
|
QString fileName = url.toLocalFile();
|
||||||
|
QFile scriptFile(fileName);
|
||||||
|
if (scriptFile.open(QFile::ReadOnly | QFile::Text)) {
|
||||||
|
qDebug() << "Loading file:" << fileName;
|
||||||
|
QTextStream in(&scriptFile);
|
||||||
|
_scriptContents = in.readAll();
|
||||||
|
} else {
|
||||||
|
qDebug() << "ERROR Loading file:" << fileName;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QNetworkAccessManager* networkManager = new QNetworkAccessManager(this);
|
||||||
|
QNetworkReply* reply = networkManager->get(QNetworkRequest(url));
|
||||||
|
qDebug() << "Downloading included script at" << url;
|
||||||
|
QEventLoop loop;
|
||||||
|
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||||
|
loop.exec();
|
||||||
|
_scriptContents = reply->readAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ScriptEngine::setIsAvatar(bool isAvatar) {
|
void ScriptEngine::setIsAvatar(bool isAvatar) {
|
||||||
_isAvatar = isAvatar;
|
_isAvatar = isAvatar;
|
||||||
|
|
||||||
|
@ -113,11 +176,12 @@ void ScriptEngine::cleanupMenuItems() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScriptEngine::setScriptContents(const QString& scriptContents) {
|
bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) {
|
||||||
if (_isRunning) {
|
if (_isRunning) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_scriptContents = scriptContents;
|
_scriptContents = scriptContents;
|
||||||
|
_fileNameString = fileNameString;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,3 +500,55 @@ void ScriptEngine::stopTimer(QTimer *timer) {
|
||||||
delete timer;
|
delete timer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUrl ScriptEngine::resolveInclude(const QString& include) const {
|
||||||
|
// first lets check to see if it's already a full URL
|
||||||
|
QUrl url(include);
|
||||||
|
if (!url.scheme().isEmpty()) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we apparently weren't a fully qualified url, so, let's assume we're relative
|
||||||
|
// to the original URL of our script
|
||||||
|
QUrl parentURL(_fileNameString);
|
||||||
|
|
||||||
|
// if the parent URL's scheme is empty, then this is probably a local file...
|
||||||
|
if (parentURL.scheme().isEmpty()) {
|
||||||
|
parentURL = QUrl::fromLocalFile(_fileNameString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// at this point we should have a legitimate fully qualified URL for our parent
|
||||||
|
url = parentURL.resolved(url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::include(const QString& includeFile) {
|
||||||
|
QUrl url = resolveInclude(includeFile);
|
||||||
|
QString includeContents;
|
||||||
|
|
||||||
|
if (url.scheme() == "file") {
|
||||||
|
QString fileName = url.toLocalFile();
|
||||||
|
QFile scriptFile(fileName);
|
||||||
|
if (scriptFile.open(QFile::ReadOnly | QFile::Text)) {
|
||||||
|
qDebug() << "Loading file:" << fileName;
|
||||||
|
QTextStream in(&scriptFile);
|
||||||
|
includeContents = in.readAll();
|
||||||
|
} else {
|
||||||
|
qDebug() << "ERROR Loading file:" << fileName;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QNetworkAccessManager* networkManager = new QNetworkAccessManager(this);
|
||||||
|
QNetworkReply* reply = networkManager->get(QNetworkRequest(url));
|
||||||
|
qDebug() << "Downloading included script at" << includeFile;
|
||||||
|
QEventLoop loop;
|
||||||
|
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||||
|
loop.exec();
|
||||||
|
includeContents = reply->readAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
QScriptValue result = _engine.evaluate(includeContents);
|
||||||
|
if (_engine.hasUncaughtException()) {
|
||||||
|
int line = _engine.uncaughtExceptionLineNumber();
|
||||||
|
qDebug() << "Uncaught exception at (" << includeFile << ") line" << line << ":" << result.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -33,8 +33,11 @@ const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 10
|
||||||
class ScriptEngine : public QObject {
|
class ScriptEngine : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
ScriptEngine(const QUrl& scriptURL, bool wantMenuItems = false,
|
||||||
|
AbstractControllerScriptingInterface* controllerScriptingInterface = NULL);
|
||||||
|
|
||||||
ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false,
|
ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false,
|
||||||
const QString& scriptMenuName = QString(""),
|
const QString& fileNameString = QString(""),
|
||||||
AbstractControllerScriptingInterface* controllerScriptingInterface = NULL);
|
AbstractControllerScriptingInterface* controllerScriptingInterface = NULL);
|
||||||
|
|
||||||
/// Access the VoxelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener
|
/// Access the VoxelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener
|
||||||
|
@ -44,7 +47,7 @@ public:
|
||||||
static ParticlesScriptingInterface* getParticlesScriptingInterface() { return &_particlesScriptingInterface; }
|
static ParticlesScriptingInterface* getParticlesScriptingInterface() { return &_particlesScriptingInterface; }
|
||||||
|
|
||||||
/// sets the script contents, will return false if failed, will fail if script is already running
|
/// sets the script contents, will return false if failed, will fail if script is already running
|
||||||
bool setScriptContents(const QString& scriptContents);
|
bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString(""));
|
||||||
|
|
||||||
const QString& getScriptMenuName() const { return _scriptMenuName; }
|
const QString& getScriptMenuName() const { return _scriptMenuName; }
|
||||||
void cleanupMenuItems();
|
void cleanupMenuItems();
|
||||||
|
@ -68,6 +71,8 @@ public:
|
||||||
|
|
||||||
void timerFired();
|
void timerFired();
|
||||||
|
|
||||||
|
bool hasScript() const { return !_scriptContents.isEmpty(); }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
|
@ -75,6 +80,7 @@ public slots:
|
||||||
QObject* setTimeout(const QScriptValue& function, int timeoutMS);
|
QObject* setTimeout(const QScriptValue& function, int timeoutMS);
|
||||||
void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||||
void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||||
|
void include(const QString& includeFile);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void update(float deltaTime);
|
void update(float deltaTime);
|
||||||
|
@ -97,6 +103,7 @@ protected:
|
||||||
int _numAvatarSoundSentBytes;
|
int _numAvatarSoundSentBytes;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QUrl resolveInclude(const QString& include) const;
|
||||||
void sendAvatarIdentityPacket();
|
void sendAvatarIdentityPacket();
|
||||||
void sendAvatarBillboardPacket();
|
void sendAvatarBillboardPacket();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue