mirror of
https://github.com/lubosz/overte.git
synced 2025-08-27 08:45:52 +02:00
Merge upstream/master into andrew/inertia
Conflicts: interface/src/avatar/MyAvatar.h
This commit is contained in:
commit
5ffd307926
159 changed files with 8158 additions and 1342 deletions
|
@ -39,7 +39,7 @@ you to run the full stack of the virtual world.
|
|||
In order to set up your own virtual world, you need to set up and run your own
|
||||
local "domain".
|
||||
|
||||
The domain-server gives a number different types of assignments to the assignment-client for different features: audio, avatars, voxels, particles, and meta-voxels.
|
||||
The domain-server gives a number different types of assignments to the assignment-client for different features: audio, avatars, voxels, particles, meta-voxels and models.
|
||||
|
||||
Follow the instructions in the [build guide](BUILD.md) to build the various components.
|
||||
|
||||
|
@ -57,7 +57,7 @@ Any target can be terminated with Ctrl-C (SIGINT) in the associated Terminal win
|
|||
|
||||
This assignment-client will grab one assignment from the domain-server. You can tell the assignment-client what type you want it to be with the `-t` option. You can also run an assignment-client that forks off *n* assignment-clients with the `-n` option.
|
||||
|
||||
./assignment-client -n 5
|
||||
./assignment-client -n 6
|
||||
|
||||
To test things out you'll want to run the Interface client.
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
|||
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
|
|
@ -25,7 +25,9 @@
|
|||
#include <ResourceCache.h>
|
||||
#include <UUID.h>
|
||||
#include <VoxelConstants.h>
|
||||
#include <ParticlesScriptingInterface.h>
|
||||
|
||||
#include <ParticlesScriptingInterface.h> // TODO: consider moving to scriptengine.h
|
||||
#include <ModelsScriptingInterface.h> // TODO: consider moving to scriptengine.h
|
||||
|
||||
#include "Agent.h"
|
||||
|
||||
|
@ -33,6 +35,7 @@ Agent::Agent(const QByteArray& packet) :
|
|||
ThreadedAssignment(packet),
|
||||
_voxelEditSender(),
|
||||
_particleEditSender(),
|
||||
_modelEditSender(),
|
||||
_receivedAudioBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO),
|
||||
_avatarHashMap()
|
||||
{
|
||||
|
@ -41,6 +44,7 @@ Agent::Agent(const QByteArray& packet) :
|
|||
|
||||
_scriptEngine.getVoxelsScriptingInterface()->setPacketSender(&_voxelEditSender);
|
||||
_scriptEngine.getParticlesScriptingInterface()->setPacketSender(&_particleEditSender);
|
||||
_scriptEngine.getModelsScriptingInterface()->setPacketSender(&_modelEditSender);
|
||||
}
|
||||
|
||||
void Agent::readPendingDatagrams() {
|
||||
|
@ -68,6 +72,10 @@ void Agent::readPendingDatagrams() {
|
|||
_scriptEngine.getParticlesScriptingInterface()->getJurisdictionListener()->
|
||||
queueReceivedPacket(matchedNode, receivedPacket);
|
||||
break;
|
||||
case NodeType::ModelServer:
|
||||
_scriptEngine.getModelsScriptingInterface()->getJurisdictionListener()->
|
||||
queueReceivedPacket(matchedNode, receivedPacket);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,10 +90,23 @@ void Agent::readPendingDatagrams() {
|
|||
SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||
sourceNode->setLastHeardMicrostamp(usecTimestampNow());
|
||||
|
||||
} else if (datagramPacketType == PacketTypeModelAddResponse) {
|
||||
// this will keep creatorTokenIDs to IDs mapped correctly
|
||||
ModelItem::handleAddModelResponse(receivedPacket);
|
||||
|
||||
// also give our local particle tree a chance to remap any internal locally created particles
|
||||
_modelViewer.getTree()->handleAddModelResponse(receivedPacket);
|
||||
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||
sourceNode->setLastHeardMicrostamp(usecTimestampNow());
|
||||
|
||||
} else if (datagramPacketType == PacketTypeParticleData
|
||||
|| datagramPacketType == PacketTypeParticleErase
|
||||
|| datagramPacketType == PacketTypeOctreeStats
|
||||
|| datagramPacketType == PacketTypeVoxelData
|
||||
|| datagramPacketType == PacketTypeModelData
|
||||
|| datagramPacketType == PacketTypeModelErase
|
||||
) {
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||
|
@ -117,6 +138,10 @@ void Agent::readPendingDatagrams() {
|
|||
_particleViewer.processDatagram(mutablePacket, sourceNode);
|
||||
}
|
||||
|
||||
if (datagramPacketType == PacketTypeModelData || datagramPacketType == PacketTypeModelErase) {
|
||||
_modelViewer.processDatagram(mutablePacket, sourceNode);
|
||||
}
|
||||
|
||||
if (datagramPacketType == PacketTypeVoxelData) {
|
||||
_voxelViewer.processDatagram(mutablePacket, sourceNode);
|
||||
}
|
||||
|
@ -159,14 +184,17 @@ void Agent::run() {
|
|||
<< NodeType::AudioMixer
|
||||
<< NodeType::AvatarMixer
|
||||
<< NodeType::VoxelServer
|
||||
<< NodeType::ParticleServer);
|
||||
<< NodeType::ParticleServer
|
||||
<< NodeType::ModelServer
|
||||
);
|
||||
|
||||
// figure out the URL for the script for this agent assignment
|
||||
QUrl scriptURL;
|
||||
if (_payload.isEmpty()) {
|
||||
scriptURL = QUrl(QString("http://%1:8080/assignment/%2")
|
||||
.arg(NodeList::getInstance()->getDomainHandler().getIP().toString(),
|
||||
uuidStringWithoutCurlyBraces(_uuid)));
|
||||
scriptURL = QUrl(QString("http://%1:%2/assignment/%3")
|
||||
.arg(NodeList::getInstance()->getDomainHandler().getIP().toString())
|
||||
.arg(DOMAIN_SERVER_HTTP_PORT)
|
||||
.arg(uuidStringWithoutCurlyBraces(_uuid)));
|
||||
} else {
|
||||
scriptURL = QUrl(_payload);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
|
||||
#include <AvatarHashMap.h>
|
||||
#include <MixedAudioRingBuffer.h>
|
||||
#include <ModelEditPacketSender.h>
|
||||
#include <ModelTree.h>
|
||||
#include <ModelTreeHeadlessViewer.h>
|
||||
#include <ParticleEditPacketSender.h>
|
||||
#include <ParticleTree.h>
|
||||
#include <ParticleTreeHeadlessViewer.h>
|
||||
|
@ -61,9 +64,11 @@ private:
|
|||
ScriptEngine _scriptEngine;
|
||||
VoxelEditPacketSender _voxelEditSender;
|
||||
ParticleEditPacketSender _particleEditSender;
|
||||
ModelEditPacketSender _modelEditSender;
|
||||
|
||||
ParticleTreeHeadlessViewer _particleViewer;
|
||||
VoxelTreeHeadlessViewer _voxelViewer;
|
||||
ModelTreeHeadlessViewer _modelViewer;
|
||||
|
||||
MixedAudioRingBuffer _receivedAudioBuffer;
|
||||
AvatarHashMap _avatarHashMap;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "audio/AudioMixer.h"
|
||||
#include "avatars/AvatarMixer.h"
|
||||
#include "metavoxels/MetavoxelServer.h"
|
||||
#include "models/ModelServer.h"
|
||||
#include "particles/ParticleServer.h"
|
||||
#include "voxels/VoxelServer.h"
|
||||
|
||||
|
@ -41,6 +42,8 @@ ThreadedAssignment* AssignmentFactory::unpackAssignment(const QByteArray& packet
|
|||
return new ParticleServer(packet);
|
||||
case Assignment::MetavoxelServerType:
|
||||
return new MetavoxelServer(packet);
|
||||
case Assignment::ModelServerType:
|
||||
return new ModelServer(packet);
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
|
34
assignment-client/src/models/ModelNodeData.h
Normal file
34
assignment-client/src/models/ModelNodeData.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// ModelNodeData.h
|
||||
// assignment-client/src/models
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 4/29/14
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_ModelNodeData_h
|
||||
#define hifi_ModelNodeData_h
|
||||
|
||||
#include <PacketHeaders.h>
|
||||
|
||||
#include "../octree/OctreeQueryNode.h"
|
||||
|
||||
class ModelNodeData : public OctreeQueryNode {
|
||||
public:
|
||||
ModelNodeData() :
|
||||
OctreeQueryNode(),
|
||||
_lastDeletedModelsSentAt(0) { };
|
||||
|
||||
virtual PacketType getMyPacketType() const { return PacketTypeModelData; }
|
||||
|
||||
quint64 getLastDeletedModelsSentAt() const { return _lastDeletedModelsSentAt; }
|
||||
void setLastDeletedModelsSentAt(quint64 sentAt) { _lastDeletedModelsSentAt = sentAt; }
|
||||
|
||||
private:
|
||||
quint64 _lastDeletedModelsSentAt;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelNodeData_h
|
137
assignment-client/src/models/ModelServer.cpp
Normal file
137
assignment-client/src/models/ModelServer.cpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
//
|
||||
// ModelServer.cpp
|
||||
// assignment-client/src/models
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 4/29/14
|
||||
// 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 <QTimer>
|
||||
#include <ModelTree.h>
|
||||
|
||||
#include "ModelServer.h"
|
||||
#include "ModelServerConsts.h"
|
||||
#include "ModelNodeData.h"
|
||||
|
||||
const char* MODEL_SERVER_NAME = "Model";
|
||||
const char* MODEL_SERVER_LOGGING_TARGET_NAME = "model-server";
|
||||
const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo";
|
||||
|
||||
ModelServer::ModelServer(const QByteArray& packet) : OctreeServer(packet) {
|
||||
// nothing special to do here...
|
||||
}
|
||||
|
||||
ModelServer::~ModelServer() {
|
||||
ModelTree* tree = (ModelTree*)_tree;
|
||||
tree->removeNewlyCreatedHook(this);
|
||||
}
|
||||
|
||||
OctreeQueryNode* ModelServer::createOctreeQueryNode() {
|
||||
return new ModelNodeData();
|
||||
}
|
||||
|
||||
Octree* ModelServer::createTree() {
|
||||
ModelTree* tree = new ModelTree(true);
|
||||
tree->addNewlyCreatedHook(this);
|
||||
return tree;
|
||||
}
|
||||
|
||||
void ModelServer::beforeRun() {
|
||||
QTimer* pruneDeletedModelsTimer = new QTimer(this);
|
||||
connect(pruneDeletedModelsTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedModels()));
|
||||
const int PRUNE_DELETED_MODELS_INTERVAL_MSECS = 1 * 1000; // once every second
|
||||
pruneDeletedModelsTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS);
|
||||
}
|
||||
|
||||
void ModelServer::modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode) {
|
||||
unsigned char outputBuffer[MAX_PACKET_SIZE];
|
||||
unsigned char* copyAt = outputBuffer;
|
||||
|
||||
int numBytesPacketHeader = populatePacketHeader(reinterpret_cast<char*>(outputBuffer), PacketTypeModelAddResponse);
|
||||
int packetLength = numBytesPacketHeader;
|
||||
copyAt += numBytesPacketHeader;
|
||||
|
||||
// encode the creatorTokenID
|
||||
uint32_t creatorTokenID = newModel.getCreatorTokenID();
|
||||
memcpy(copyAt, &creatorTokenID, sizeof(creatorTokenID));
|
||||
copyAt += sizeof(creatorTokenID);
|
||||
packetLength += sizeof(creatorTokenID);
|
||||
|
||||
// encode the model ID
|
||||
uint32_t modelID = newModel.getID();
|
||||
memcpy(copyAt, &modelID, sizeof(modelID));
|
||||
copyAt += sizeof(modelID);
|
||||
packetLength += sizeof(modelID);
|
||||
|
||||
NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, senderNode);
|
||||
}
|
||||
|
||||
|
||||
// ModelServer will use the "special packets" to send list of recently deleted models
|
||||
bool ModelServer::hasSpecialPacketToSend(const SharedNodePointer& node) {
|
||||
bool shouldSendDeletedModels = false;
|
||||
|
||||
// check to see if any new models have been added since we last sent to this node...
|
||||
ModelNodeData* nodeData = static_cast<ModelNodeData*>(node->getLinkedData());
|
||||
if (nodeData) {
|
||||
quint64 deletedModelsSentAt = nodeData->getLastDeletedModelsSentAt();
|
||||
|
||||
ModelTree* tree = static_cast<ModelTree*>(_tree);
|
||||
shouldSendDeletedModels = tree->hasModelsDeletedSince(deletedModelsSentAt);
|
||||
}
|
||||
|
||||
return shouldSendDeletedModels;
|
||||
}
|
||||
|
||||
int ModelServer::sendSpecialPacket(const SharedNodePointer& node) {
|
||||
unsigned char outputBuffer[MAX_PACKET_SIZE];
|
||||
size_t packetLength = 0;
|
||||
|
||||
ModelNodeData* nodeData = static_cast<ModelNodeData*>(node->getLinkedData());
|
||||
if (nodeData) {
|
||||
quint64 deletedModelsSentAt = nodeData->getLastDeletedModelsSentAt();
|
||||
quint64 deletePacketSentAt = usecTimestampNow();
|
||||
|
||||
ModelTree* tree = static_cast<ModelTree*>(_tree);
|
||||
bool hasMoreToSend = true;
|
||||
|
||||
// TODO: is it possible to send too many of these packets? what if you deleted 1,000,000 models?
|
||||
while (hasMoreToSend) {
|
||||
hasMoreToSend = tree->encodeModelsDeletedSince(deletedModelsSentAt,
|
||||
outputBuffer, MAX_PACKET_SIZE, packetLength);
|
||||
|
||||
//qDebug() << "sending PacketType_MODEL_ERASE packetLength:" << packetLength;
|
||||
|
||||
NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, SharedNodePointer(node));
|
||||
}
|
||||
|
||||
nodeData->setLastDeletedModelsSentAt(deletePacketSentAt);
|
||||
}
|
||||
|
||||
// TODO: caller is expecting a packetLength, what if we send more than one packet??
|
||||
return packetLength;
|
||||
}
|
||||
|
||||
void ModelServer::pruneDeletedModels() {
|
||||
ModelTree* tree = static_cast<ModelTree*>(_tree);
|
||||
if (tree->hasAnyDeletedModels()) {
|
||||
|
||||
//qDebug() << "there are some deleted models to consider...";
|
||||
quint64 earliestLastDeletedModelsSent = usecTimestampNow() + 1; // in the future
|
||||
foreach (const SharedNodePointer& otherNode, NodeList::getInstance()->getNodeHash()) {
|
||||
if (otherNode->getLinkedData()) {
|
||||
ModelNodeData* nodeData = static_cast<ModelNodeData*>(otherNode->getLinkedData());
|
||||
quint64 nodeLastDeletedModelsSentAt = nodeData->getLastDeletedModelsSentAt();
|
||||
if (nodeLastDeletedModelsSentAt < earliestLastDeletedModelsSent) {
|
||||
earliestLastDeletedModelsSent = nodeLastDeletedModelsSentAt;
|
||||
}
|
||||
}
|
||||
}
|
||||
//qDebug() << "earliestLastDeletedModelsSent=" << earliestLastDeletedModelsSent;
|
||||
tree->forgetModelsDeletedBefore(earliestLastDeletedModelsSent);
|
||||
}
|
||||
}
|
||||
|
50
assignment-client/src/models/ModelServer.h
Normal file
50
assignment-client/src/models/ModelServer.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// ModelServer.h
|
||||
// assignment-client/src/models
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 4/29/14
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_ModelServer_h
|
||||
#define hifi_ModelServer_h
|
||||
|
||||
#include "../octree/OctreeServer.h"
|
||||
|
||||
#include "ModelItem.h"
|
||||
#include "ModelServerConsts.h"
|
||||
#include "ModelTree.h"
|
||||
|
||||
/// Handles assignments of type ModelServer - sending models to various clients.
|
||||
class ModelServer : public OctreeServer, public NewlyCreatedModelHook {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ModelServer(const QByteArray& packet);
|
||||
~ModelServer();
|
||||
|
||||
// Subclasses must implement these methods
|
||||
virtual OctreeQueryNode* createOctreeQueryNode();
|
||||
virtual Octree* createTree();
|
||||
virtual char getMyNodeType() const { return NodeType::ModelServer; }
|
||||
virtual PacketType getMyQueryMessageType() const { return PacketTypeModelQuery; }
|
||||
virtual const char* getMyServerName() const { return MODEL_SERVER_NAME; }
|
||||
virtual const char* getMyLoggingServerTargetName() const { return MODEL_SERVER_LOGGING_TARGET_NAME; }
|
||||
virtual const char* getMyDefaultPersistFilename() const { return LOCAL_MODELS_PERSIST_FILE; }
|
||||
|
||||
// subclass may implement these method
|
||||
virtual void beforeRun();
|
||||
virtual bool hasSpecialPacketToSend(const SharedNodePointer& node);
|
||||
virtual int sendSpecialPacket(const SharedNodePointer& node);
|
||||
|
||||
virtual void modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode);
|
||||
|
||||
public slots:
|
||||
void pruneDeletedModels();
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
#endif // hifi_ModelServer_h
|
19
assignment-client/src/models/ModelServerConsts.h
Normal file
19
assignment-client/src/models/ModelServerConsts.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// ModelServerConsts.h
|
||||
// assignment-client/src/models
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 4/29/14
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_ModelServerConsts_h
|
||||
#define hifi_ModelServerConsts_h
|
||||
|
||||
extern const char* MODEL_SERVER_NAME;
|
||||
extern const char* MODEL_SERVER_LOGGING_TARGET_NAME;
|
||||
extern const char* LOCAL_MODELS_PERSIST_FILE;
|
||||
|
||||
#endif // hifi_ModelServerConsts_h
|
|
@ -833,7 +833,7 @@ void OctreeServer::readPendingDatagrams() {
|
|||
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
|
||||
if (packetType == getMyQueryMessageType()) {
|
||||
|
||||
// If we got a PacketType_VOXEL_QUERY, then we're talking to an NodeType_t_AVATAR, and we
|
||||
// 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.
|
||||
if (matchingNode) {
|
||||
nodeList->updateNodeWithDataFromPacket(matchingNode, receivedPacket);
|
||||
|
|
|
@ -63,7 +63,7 @@ public:
|
|||
// Subclasses must implement these methods
|
||||
virtual OctreeQueryNode* createOctreeQueryNode() = 0;
|
||||
virtual Octree* createTree() = 0;
|
||||
virtual unsigned char getMyNodeType() const = 0;
|
||||
virtual char getMyNodeType() const = 0;
|
||||
virtual PacketType getMyQueryMessageType() const = 0;
|
||||
virtual const char* getMyServerName() const = 0;
|
||||
virtual const char* getMyLoggingServerTargetName() const = 0;
|
||||
|
|
|
@ -28,7 +28,7 @@ public:
|
|||
// Subclasses must implement these methods
|
||||
virtual OctreeQueryNode* createOctreeQueryNode();
|
||||
virtual Octree* createTree();
|
||||
virtual unsigned char getMyNodeType() const { return NodeType::ParticleServer; }
|
||||
virtual char getMyNodeType() const { return NodeType::ParticleServer; }
|
||||
virtual PacketType getMyQueryMessageType() const { return PacketTypeParticleQuery; }
|
||||
virtual const char* getMyServerName() const { return PARTICLE_SERVER_NAME; }
|
||||
virtual const char* getMyLoggingServerTargetName() const { return PARTICLE_SERVER_LOGGING_TARGET_NAME; }
|
||||
|
|
|
@ -37,7 +37,7 @@ public:
|
|||
// Subclasses must implement these methods
|
||||
virtual OctreeQueryNode* createOctreeQueryNode();
|
||||
virtual Octree* createTree();
|
||||
virtual unsigned char getMyNodeType() const { return NodeType::VoxelServer; }
|
||||
virtual char getMyNodeType() const { return NodeType::VoxelServer; }
|
||||
virtual PacketType getMyQueryMessageType() const { return PacketTypeVoxelQuery; }
|
||||
virtual const char* getMyServerName() const { return VOXEL_SERVER_NAME; }
|
||||
virtual const char* getMyLoggingServerTargetName() const { return VOXEL_SERVER_LOGGING_TARGET_NAME; }
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QtCore/QProcess>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QUrlQuery>
|
||||
|
||||
#include <gnutls/dtls.h>
|
||||
|
||||
|
@ -31,18 +32,22 @@
|
|||
|
||||
#include "DomainServer.h"
|
||||
|
||||
const quint16 DOMAIN_SERVER_HTTP_PORT = 8080;
|
||||
|
||||
DomainServer::DomainServer(int argc, char* argv[]) :
|
||||
QCoreApplication(argc, argv),
|
||||
_HTTPManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
|
||||
_staticAssignmentHash(),
|
||||
_assignmentQueue(),
|
||||
_httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
|
||||
_httpsManager(NULL),
|
||||
_allAssignments(),
|
||||
_unfulfilledAssignments(),
|
||||
_isUsingDTLS(false),
|
||||
_x509Credentials(NULL),
|
||||
_dhParams(NULL),
|
||||
_priorityCache(NULL),
|
||||
_dtlsSessions()
|
||||
_dtlsSessions(),
|
||||
_oauthProviderURL(),
|
||||
_oauthClientID(),
|
||||
_hostname(),
|
||||
_networkReplyUUIDMap(),
|
||||
_sessionAuthenticationHash()
|
||||
{
|
||||
gnutls_global_init();
|
||||
|
||||
|
@ -53,22 +58,20 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
_argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
|
||||
|
||||
if (optionallySetupDTLS()) {
|
||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
|
||||
// we either read a certificate and private key or were not passed one, good to load assignments
|
||||
// and set up the node list
|
||||
qDebug() << "Setting up LimitedNodeList and assignments.";
|
||||
setupNodeListAndAssignments();
|
||||
|
||||
if (_isUsingDTLS) {
|
||||
// we're using DTLS and our NodeList socket is good to go, so make the required DTLS changes
|
||||
// DTLS requires that IP_DONTFRAG be set
|
||||
// This is not accessible on some platforms (OS X) so we need to make sure DTLS still works without it
|
||||
|
||||
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
|
||||
|
||||
// connect our socket to read datagrams received on the DTLS socket
|
||||
connect(&nodeList->getDTLSSocket(), &QUdpSocket::readyRead, this, &DomainServer::readAvailableDTLSDatagrams);
|
||||
}
|
||||
|
||||
_networkAccessManager = new QNetworkAccessManager(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,44 +89,7 @@ DomainServer::~DomainServer() {
|
|||
gnutls_global_deinit();
|
||||
}
|
||||
|
||||
bool DomainServer::optionallySetupDTLS() {
|
||||
if (readX509KeyAndCertificate()) {
|
||||
if (_x509Credentials) {
|
||||
qDebug() << "Generating Diffie-Hellman parameters.";
|
||||
|
||||
// generate Diffie-Hellman parameters
|
||||
// When short bit length is used, it might be wise to regenerate parameters often.
|
||||
int dhBits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY);
|
||||
|
||||
_dhParams = new gnutls_dh_params_t;
|
||||
gnutls_dh_params_init(_dhParams);
|
||||
gnutls_dh_params_generate2(*_dhParams, dhBits);
|
||||
|
||||
qDebug() << "Successfully generated Diffie-Hellman parameters.";
|
||||
|
||||
// set the D-H paramters on the X509 credentials
|
||||
gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams);
|
||||
|
||||
// setup the key used for cookie verification
|
||||
_cookieKey = new gnutls_datum_t;
|
||||
gnutls_key_generate(_cookieKey, GNUTLS_COOKIE_KEY_SIZE);
|
||||
|
||||
_priorityCache = new gnutls_priority_t;
|
||||
const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE";
|
||||
gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL);
|
||||
|
||||
_isUsingDTLS = true;
|
||||
|
||||
qDebug() << "Initial DTLS setup complete.";
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool DomainServer::readX509KeyAndCertificate() {
|
||||
bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
||||
const QString X509_CERTIFICATE_OPTION = "cert";
|
||||
const QString X509_PRIVATE_KEY_OPTION = "key";
|
||||
const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE";
|
||||
|
@ -157,6 +123,22 @@ bool DomainServer::readX509KeyAndCertificate() {
|
|||
|
||||
qDebug() << "Successfully read certificate and private key.";
|
||||
|
||||
// we need to also pass this certificate and private key to the HTTPS manager
|
||||
// this is used for Oauth callbacks when authorizing users against a data server
|
||||
|
||||
QFile certFile(certPath);
|
||||
certFile.open(QIODevice::ReadOnly);
|
||||
|
||||
QFile keyFile(keyPath);
|
||||
keyFile.open(QIODevice::ReadOnly);
|
||||
|
||||
QSslCertificate sslCertificate(&certFile);
|
||||
QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8());
|
||||
|
||||
_httpsManager = new HTTPSManager(DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this, this);
|
||||
|
||||
qDebug() << "TCP server listening for HTTPS connections on" << DOMAIN_SERVER_HTTPS_PORT;
|
||||
|
||||
} else if (!certPath.isEmpty() || !keyPath.isEmpty()) {
|
||||
qDebug() << "Missing certificate or private key. domain-server will now quit.";
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
|
@ -166,6 +148,67 @@ bool DomainServer::readX509KeyAndCertificate() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DomainServer::optionallySetupOAuth() {
|
||||
const QString OAUTH_PROVIDER_URL_OPTION = "oauth-provider";
|
||||
const QString OAUTH_CLIENT_ID_OPTION = "oauth-client-id";
|
||||
const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET";
|
||||
const QString REDIRECT_HOSTNAME_OPTION = "hostname";
|
||||
|
||||
_oauthProviderURL = QUrl(_argumentVariantMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
|
||||
_oauthClientID = _argumentVariantMap.value(OAUTH_CLIENT_ID_OPTION).toString();
|
||||
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
|
||||
_hostname = _argumentVariantMap.value(REDIRECT_HOSTNAME_OPTION).toString();
|
||||
|
||||
if (!_oauthProviderURL.isEmpty() || !_hostname.isEmpty() || !_oauthClientID.isEmpty()) {
|
||||
if (_oauthProviderURL.isEmpty()
|
||||
|| _hostname.isEmpty()
|
||||
|| _oauthClientID.isEmpty()
|
||||
|| _oauthClientSecret.isEmpty()) {
|
||||
qDebug() << "Missing OAuth provider URL, hostname, client ID, or client secret. domain-server will now quit.";
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << "OAuth will be used to identify clients using provider at" << _oauthProviderURL.toString();
|
||||
qDebug() << "OAuth Client ID is" << _oauthClientID;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DomainServer::optionallySetupDTLS() {
|
||||
if (_x509Credentials) {
|
||||
qDebug() << "Generating Diffie-Hellman parameters.";
|
||||
|
||||
// generate Diffie-Hellman parameters
|
||||
// When short bit length is used, it might be wise to regenerate parameters often.
|
||||
int dhBits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY);
|
||||
|
||||
_dhParams = new gnutls_dh_params_t;
|
||||
gnutls_dh_params_init(_dhParams);
|
||||
gnutls_dh_params_generate2(*_dhParams, dhBits);
|
||||
|
||||
qDebug() << "Successfully generated Diffie-Hellman parameters.";
|
||||
|
||||
// set the D-H paramters on the X509 credentials
|
||||
gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams);
|
||||
|
||||
// setup the key used for cookie verification
|
||||
_cookieKey = new gnutls_datum_t;
|
||||
gnutls_key_generate(_cookieKey, GNUTLS_COOKIE_KEY_SIZE);
|
||||
|
||||
_priorityCache = new gnutls_priority_t;
|
||||
const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE";
|
||||
gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL);
|
||||
|
||||
_isUsingDTLS = true;
|
||||
|
||||
qDebug() << "Initial DTLS setup complete.";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
||||
|
||||
const QString CUSTOM_PORT_OPTION = "port";
|
||||
|
@ -246,7 +289,8 @@ void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes)
|
|||
|
||||
void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment) {
|
||||
qDebug() << "Inserting assignment" << *newAssignment << "to static assignment hash.";
|
||||
_staticAssignmentHash.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment));
|
||||
newAssignment->setIsStatic(true);
|
||||
_allAssignments.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment));
|
||||
}
|
||||
|
||||
void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configArray) {
|
||||
|
@ -279,7 +323,9 @@ void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configAr
|
|||
qDebug() << "URL for script is" << assignmentURL;
|
||||
|
||||
// scripts passed on CL or via JSON are static - so they are added back to the queue if the node dies
|
||||
_assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment));
|
||||
SharedAssignmentPointer sharedScriptAssignment(scriptAssignment);
|
||||
_unfulfilledAssignments.enqueue(sharedScriptAssignment);
|
||||
_allAssignments.insert(sharedScriptAssignment->getUUID(), sharedScriptAssignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -340,47 +386,71 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
|
|||
}
|
||||
}
|
||||
|
||||
const QString ALLOWED_ROLES_CONFIG_KEY = "allowed-roles";
|
||||
|
||||
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
|
||||
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer
|
||||
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer
|
||||
<< NodeType::MetavoxelServer;
|
||||
|
||||
void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
|
||||
void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
|
||||
|
||||
NodeType_t nodeType;
|
||||
HifiSockAddr publicSockAddr, localSockAddr;
|
||||
|
||||
int numPreInterestBytes = parseNodeDataFromByteArray(nodeType, publicSockAddr, localSockAddr, packet, senderSockAddr);
|
||||
|
||||
QUuid assignmentUUID = uuidFromPacketHeader(packet);
|
||||
bool isStaticAssignment = _staticAssignmentHash.contains(assignmentUUID);
|
||||
SharedAssignmentPointer matchingAssignment = SharedAssignmentPointer();
|
||||
|
||||
if (isStaticAssignment) {
|
||||
// this is a static assignment, make sure the UUID sent is for an assignment we're actually trying to give out
|
||||
matchingAssignment = matchingQueuedAssignmentForCheckIn(assignmentUUID, nodeType);
|
||||
|
||||
if (matchingAssignment) {
|
||||
// remove the matching assignment from the assignment queue so we don't take the next check in
|
||||
// (if it exists)
|
||||
removeMatchingAssignmentFromQueue(matchingAssignment);
|
||||
}
|
||||
} else {
|
||||
assignmentUUID = QUuid();
|
||||
QUuid packetUUID = uuidFromPacketHeader(packet);
|
||||
|
||||
// check if this connect request matches an assignment in the queue
|
||||
bool isFulfilledOrUnfulfilledAssignment = _allAssignments.contains(packetUUID);
|
||||
SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
|
||||
if (isFulfilledOrUnfulfilledAssignment) {
|
||||
matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(packetUUID, nodeType);
|
||||
}
|
||||
|
||||
// make sure this was either not a static assignment or it was and we had a matching one in teh queue
|
||||
if ((!isStaticAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType)) || (isStaticAssignment && matchingAssignment)) {
|
||||
if (!matchingQueuedAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) {
|
||||
// this is an Agent, and we require authentication so we can compare the user's roles to our list of allowed ones
|
||||
if (_sessionAuthenticationHash.contains(packetUUID)) {
|
||||
if (!_sessionAuthenticationHash.value(packetUUID)) {
|
||||
// we've decided this is a user that isn't allowed in, return out
|
||||
// TODO: provide information to the user so they know why they can't connect
|
||||
return;
|
||||
} else {
|
||||
// we're letting this user in, don't return and remove their UUID from the hash
|
||||
_sessionAuthenticationHash.remove(packetUUID);
|
||||
}
|
||||
} else {
|
||||
// we don't know anything about this client
|
||||
// we have an OAuth provider, ask this interface client to auth against it
|
||||
QByteArray oauthRequestByteArray = byteArrayWithPopulatedHeader(PacketTypeDomainOAuthRequest);
|
||||
QDataStream oauthRequestStream(&oauthRequestByteArray, QIODevice::Append);
|
||||
QUrl authorizationURL = packetUUID.isNull() ? oauthAuthorizationURL() : oauthAuthorizationURL(packetUUID);
|
||||
oauthRequestStream << authorizationURL;
|
||||
|
||||
// send this oauth request datagram back to the client
|
||||
LimitedNodeList::getInstance()->writeUnverifiedDatagram(oauthRequestByteArray, senderSockAddr);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((!isFulfilledOrUnfulfilledAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
|
||||
|| (isFulfilledOrUnfulfilledAssignment && matchingQueuedAssignment)) {
|
||||
// this was either not a static assignment or it was and we had a matching one in the queue
|
||||
|
||||
// create a new session UUID for this node
|
||||
QUuid nodeUUID = QUuid::createUuid();
|
||||
|
||||
SharedNodePointer newNode = LimitedNodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType,
|
||||
publicSockAddr, localSockAddr);
|
||||
|
||||
// when the newNode is created the linked data is also created
|
||||
// if this was a static assignment set the UUID, set the sendingSockAddr
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
|
||||
nodeData->setStaticAssignmentUUID(assignmentUUID);
|
||||
if (isFulfilledOrUnfulfilledAssignment) {
|
||||
nodeData->setAssignmentUUID(packetUUID);
|
||||
}
|
||||
|
||||
nodeData->setSendingSockAddr(senderSockAddr);
|
||||
|
||||
// reply back to the user with a PacketTypeDomainList
|
||||
|
@ -388,6 +458,40 @@ void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packe
|
|||
}
|
||||
}
|
||||
|
||||
QUrl DomainServer::oauthRedirectURL() {
|
||||
return QString("https://%1:%2/oauth").arg(_hostname).arg(_httpsManager->serverPort());
|
||||
}
|
||||
|
||||
const QString OAUTH_CLIENT_ID_QUERY_KEY = "client_id";
|
||||
const QString OAUTH_REDIRECT_URI_QUERY_KEY = "redirect_uri";
|
||||
|
||||
QUrl DomainServer::oauthAuthorizationURL(const QUuid& stateUUID) {
|
||||
// for now these are all interface clients that have a GUI
|
||||
// so just send them back the full authorization URL
|
||||
QUrl authorizationURL = _oauthProviderURL;
|
||||
|
||||
const QString OAUTH_AUTHORIZATION_PATH = "/oauth/authorize";
|
||||
authorizationURL.setPath(OAUTH_AUTHORIZATION_PATH);
|
||||
|
||||
QUrlQuery authorizationQuery;
|
||||
|
||||
authorizationQuery.addQueryItem(OAUTH_CLIENT_ID_QUERY_KEY, _oauthClientID);
|
||||
|
||||
const QString OAUTH_RESPONSE_TYPE_QUERY_KEY = "response_type";
|
||||
const QString OAUTH_REPSONSE_TYPE_QUERY_VALUE = "code";
|
||||
authorizationQuery.addQueryItem(OAUTH_RESPONSE_TYPE_QUERY_KEY, OAUTH_REPSONSE_TYPE_QUERY_VALUE);
|
||||
|
||||
const QString OAUTH_STATE_QUERY_KEY = "state";
|
||||
// create a new UUID that will be the state parameter for oauth authorization AND the new session UUID for that node
|
||||
authorizationQuery.addQueryItem(OAUTH_STATE_QUERY_KEY, uuidStringWithoutCurlyBraces(stateUUID));
|
||||
|
||||
authorizationQuery.addQueryItem(OAUTH_REDIRECT_URI_QUERY_KEY, oauthRedirectURL().toString());
|
||||
|
||||
authorizationURL.setQuery(authorizationQuery);
|
||||
|
||||
return authorizationURL;
|
||||
}
|
||||
|
||||
int DomainServer::parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
|
||||
HifiSockAddr& localSockAddr, const QByteArray& packet,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
|
@ -451,52 +555,54 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
|||
DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL;
|
||||
int dataMTU = dtlsSession ? (int)gnutls_dtls_get_data_mtu(*dtlsSession->getGnuTLSSession()) : MAX_PACKET_SIZE;
|
||||
|
||||
// if the node has any interest types, send back those nodes as well
|
||||
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
|
||||
|
||||
// reset our nodeByteArray and nodeDataStream
|
||||
QByteArray nodeByteArray;
|
||||
QDataStream nodeDataStream(&nodeByteArray, QIODevice::Append);
|
||||
|
||||
if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) {
|
||||
if (nodeData->isAuthenticated()) {
|
||||
// if this authenticated node has any interest types, send back those nodes as well
|
||||
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
|
||||
|
||||
// don't send avatar nodes to other avatars, that will come from avatar mixer
|
||||
nodeDataStream << *otherNode.data();
|
||||
// reset our nodeByteArray and nodeDataStream
|
||||
QByteArray nodeByteArray;
|
||||
QDataStream nodeDataStream(&nodeByteArray, QIODevice::Append);
|
||||
|
||||
// pack the secret that these two nodes will use to communicate with each other
|
||||
QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID());
|
||||
if (secretUUID.isNull()) {
|
||||
// generate a new secret UUID these two nodes can use
|
||||
secretUUID = QUuid::createUuid();
|
||||
if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) {
|
||||
|
||||
// set that on the current Node's sessionSecretHash
|
||||
nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID);
|
||||
// don't send avatar nodes to other avatars, that will come from avatar mixer
|
||||
nodeDataStream << *otherNode.data();
|
||||
|
||||
// set it on the other Node's sessionSecretHash
|
||||
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())
|
||||
// pack the secret that these two nodes will use to communicate with each other
|
||||
QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID());
|
||||
if (secretUUID.isNull()) {
|
||||
// generate a new secret UUID these two nodes can use
|
||||
secretUUID = QUuid::createUuid();
|
||||
|
||||
// set that on the current Node's sessionSecretHash
|
||||
nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID);
|
||||
|
||||
// set it on the other Node's sessionSecretHash
|
||||
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())
|
||||
->getSessionSecretHash().insert(node->getUUID(), secretUUID);
|
||||
|
||||
}
|
||||
|
||||
nodeDataStream << secretUUID;
|
||||
|
||||
if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) {
|
||||
// we need to break here and start a new packet
|
||||
// so send the current one
|
||||
|
||||
if (!dtlsSession) {
|
||||
nodeList->writeDatagram(broadcastPacket, node, senderSockAddr);
|
||||
} else {
|
||||
dtlsSession->writeDatagram(broadcastPacket);
|
||||
|
||||
}
|
||||
|
||||
// reset the broadcastPacket structure
|
||||
broadcastPacket.resize(numBroadcastPacketLeadBytes);
|
||||
broadcastDataStream.device()->seek(numBroadcastPacketLeadBytes);
|
||||
nodeDataStream << secretUUID;
|
||||
|
||||
if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) {
|
||||
// we need to break here and start a new packet
|
||||
// so send the current one
|
||||
|
||||
if (!dtlsSession) {
|
||||
nodeList->writeDatagram(broadcastPacket, node, senderSockAddr);
|
||||
} else {
|
||||
dtlsSession->writeDatagram(broadcastPacket);
|
||||
}
|
||||
|
||||
// reset the broadcastPacket structure
|
||||
broadcastPacket.resize(numBroadcastPacketLeadBytes);
|
||||
broadcastDataStream.device()->seek(numBroadcastPacketLeadBytes);
|
||||
}
|
||||
|
||||
// append the nodeByteArray to the current state of broadcastDataStream
|
||||
broadcastPacket.append(nodeByteArray);
|
||||
}
|
||||
|
||||
// append the nodeByteArray to the current state of broadcastDataStream
|
||||
broadcastPacket.append(nodeByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -670,9 +776,7 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
|
|||
PacketType requestType = packetTypeForPacket(receivedPacket);
|
||||
|
||||
if (requestType == PacketTypeDomainConnectRequest) {
|
||||
// add this node to our NodeList
|
||||
// and send back session UUID right away
|
||||
addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr);
|
||||
handleConnectRequest(receivedPacket, senderSockAddr);
|
||||
} else if (requestType == PacketTypeDomainListRequest) {
|
||||
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket);
|
||||
|
||||
|
@ -738,7 +842,8 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
|
|||
nodeJson[JSON_KEY_WAKE_TIMESTAMP] = QString::number(node->getWakeTimestamp());
|
||||
|
||||
// if the node has pool information, add it
|
||||
SharedAssignmentPointer matchingAssignment = _staticAssignmentHash.value(node->getUUID());
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
|
||||
if (matchingAssignment) {
|
||||
nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool();
|
||||
}
|
||||
|
@ -774,9 +879,11 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
|
||||
// enumerate the NodeList to find the assigned nodes
|
||||
foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) {
|
||||
if (_staticAssignmentHash.value(node->getUUID())) {
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
if (!nodeData->getAssignmentUUID().isNull()) {
|
||||
// add the node using the UUID as the key
|
||||
QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID());
|
||||
QString uuidString = uuidStringWithoutCurlyBraces(nodeData->getAssignmentUUID());
|
||||
assignedNodesJSON[uuidString] = jsonObjectForNode(node);
|
||||
}
|
||||
}
|
||||
|
@ -786,7 +893,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
QJsonObject queuedAssignmentsJSON;
|
||||
|
||||
// add the queued but unfilled assignments to the json
|
||||
foreach(const SharedAssignmentPointer& assignment, _assignmentQueue) {
|
||||
foreach(const SharedAssignmentPointer& assignment, _unfulfilledAssignments) {
|
||||
QJsonObject queuedAssignmentJSON;
|
||||
|
||||
QString uuidString = uuidStringWithoutCurlyBraces(assignment->getUUID());
|
||||
|
@ -903,7 +1010,9 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
.arg(newPath).arg(assignmentPool == emptyPool ? "" : " - pool is " + assignmentPool));
|
||||
|
||||
// add the script assigment to the assignment queue
|
||||
_assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment));
|
||||
SharedAssignmentPointer sharedScriptedAssignment(scriptAssignment);
|
||||
_unfulfilledAssignments.enqueue(sharedScriptedAssignment);
|
||||
_allAssignments.insert(sharedScriptedAssignment->getUUID(), sharedScriptedAssignment);
|
||||
}
|
||||
|
||||
// respond with a 200 code for successful upload
|
||||
|
@ -950,6 +1059,114 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
return false;
|
||||
}
|
||||
|
||||
bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &url) {
|
||||
const QString URI_OAUTH = "/oauth";
|
||||
if (url.path() == URI_OAUTH) {
|
||||
|
||||
QUrlQuery codeURLQuery(url);
|
||||
|
||||
const QString CODE_QUERY_KEY = "code";
|
||||
QString authorizationCode = codeURLQuery.queryItemValue(CODE_QUERY_KEY);
|
||||
|
||||
const QString STATE_QUERY_KEY = "state";
|
||||
QUuid stateUUID = QUuid(codeURLQuery.queryItemValue(STATE_QUERY_KEY));
|
||||
|
||||
|
||||
if (!authorizationCode.isEmpty() && !stateUUID.isNull()) {
|
||||
// fire off a request with this code and state to get an access token for the user
|
||||
|
||||
const QString OAUTH_TOKEN_REQUEST_PATH = "/oauth/token";
|
||||
QUrl tokenRequestUrl = _oauthProviderURL;
|
||||
tokenRequestUrl.setPath(OAUTH_TOKEN_REQUEST_PATH);
|
||||
|
||||
const QString OAUTH_GRANT_TYPE_POST_STRING = "grant_type=authorization_code";
|
||||
QString tokenPostBody = OAUTH_GRANT_TYPE_POST_STRING;
|
||||
tokenPostBody += QString("&code=%1&redirect_uri=%2&client_id=%3&client_secret=%4")
|
||||
.arg(authorizationCode, oauthRedirectURL().toString(), _oauthClientID, _oauthClientSecret);
|
||||
|
||||
QNetworkRequest tokenRequest(tokenRequestUrl);
|
||||
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
QNetworkReply* tokenReply = _networkAccessManager->post(tokenRequest, tokenPostBody.toLocal8Bit());
|
||||
|
||||
qDebug() << "Requesting a token for user with session UUID" << uuidStringWithoutCurlyBraces(stateUUID);
|
||||
|
||||
// insert this to our pending token replies so we can associate the returned access token with the right UUID
|
||||
_networkReplyUUIDMap.insert(tokenReply, stateUUID);
|
||||
|
||||
connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::handleTokenRequestFinished);
|
||||
}
|
||||
|
||||
// respond with a 200 code indicating that login is complete
|
||||
connection->respond(HTTPConnection::StatusCode200);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const QString OAUTH_JSON_ACCESS_TOKEN_KEY = "access_token";
|
||||
|
||||
void DomainServer::handleTokenRequestFinished() {
|
||||
QNetworkReply* networkReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply);
|
||||
|
||||
if (!matchingSessionUUID.isNull() && networkReply->error() == QNetworkReply::NoError) {
|
||||
// pull the access token from the returned JSON and store it with the matching session UUID
|
||||
QJsonDocument returnedJSON = QJsonDocument::fromJson(networkReply->readAll());
|
||||
QString accessToken = returnedJSON.object()[OAUTH_JSON_ACCESS_TOKEN_KEY].toString();
|
||||
|
||||
qDebug() << "Received access token for user with UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID);
|
||||
|
||||
// fire off a request to get this user's identity so we can see if we will let them in
|
||||
QUrl profileURL = _oauthProviderURL;
|
||||
profileURL.setPath("/api/v1/users/profile");
|
||||
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
|
||||
|
||||
QNetworkReply* profileReply = _networkAccessManager->get(QNetworkRequest(profileURL));
|
||||
|
||||
qDebug() << "Requesting access token for user with session UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID);
|
||||
|
||||
connect(profileReply, &QNetworkReply::finished, this, &DomainServer::handleProfileRequestFinished);
|
||||
|
||||
_networkReplyUUIDMap.insert(profileReply, matchingSessionUUID);
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::handleProfileRequestFinished() {
|
||||
QNetworkReply* networkReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply);
|
||||
|
||||
if (!matchingSessionUUID.isNull() && networkReply->error() == QNetworkReply::NoError) {
|
||||
QJsonDocument profileJSON = QJsonDocument::fromJson(networkReply->readAll());
|
||||
|
||||
if (profileJSON.object()["status"].toString() == "success") {
|
||||
// pull the user roles from the response
|
||||
QJsonArray userRolesArray = profileJSON.object()["data"].toObject()["user"].toObject()["roles"].toArray();
|
||||
|
||||
QJsonArray allowedRolesArray = _argumentVariantMap.value(ALLOWED_ROLES_CONFIG_KEY).toJsonValue().toArray();
|
||||
|
||||
bool shouldAllowUserToConnect = false;
|
||||
|
||||
foreach(const QJsonValue& roleValue, userRolesArray) {
|
||||
if (allowedRolesArray.contains(roleValue)) {
|
||||
// the user has a role that lets them in
|
||||
// set the bool to true and break
|
||||
shouldAllowUserToConnect = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Confirmed authentication state for user" << uuidStringWithoutCurlyBraces(matchingSessionUUID)
|
||||
<< "-" << shouldAllowUserToConnect;
|
||||
|
||||
// insert this UUID and a flag that indicates if they are allowed to connect
|
||||
_sessionAuthenticationHash.insert(matchingSessionUUID, shouldAllowUserToConnect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment) {
|
||||
QUuid oldUUID = assignment->getUUID();
|
||||
assignment->resetUUID();
|
||||
|
@ -963,13 +1180,8 @@ void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer&
|
|||
}
|
||||
|
||||
// add the static assignment back under the right UUID, and to the queue
|
||||
_staticAssignmentHash.insert(assignment->getUUID(), assignment);
|
||||
|
||||
_assignmentQueue.enqueue(assignment);
|
||||
|
||||
// remove the old assignment from the _staticAssignmentHash
|
||||
// this must be done last so copies are created before the assignment passed by reference is killed
|
||||
_staticAssignmentHash.remove(oldUUID);
|
||||
_allAssignments.insert(assignment->getUUID(), assignment);
|
||||
_unfulfilledAssignments.enqueue(assignment);
|
||||
}
|
||||
|
||||
void DomainServer::nodeAdded(SharedNodePointer node) {
|
||||
|
@ -983,10 +1195,10 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
|
|||
|
||||
if (nodeData) {
|
||||
// if this node's UUID matches a static assignment we need to throw it back in the assignment queue
|
||||
if (!nodeData->getStaticAssignmentUUID().isNull()) {
|
||||
SharedAssignmentPointer matchedAssignment = _staticAssignmentHash.value(nodeData->getStaticAssignmentUUID());
|
||||
if (!nodeData->getAssignmentUUID().isNull()) {
|
||||
SharedAssignmentPointer matchedAssignment = _allAssignments.take(nodeData->getAssignmentUUID());
|
||||
|
||||
if (matchedAssignment) {
|
||||
if (matchedAssignment && matchedAssignment->isStatic()) {
|
||||
refreshStaticAssignmentAndAddToQueue(matchedAssignment);
|
||||
}
|
||||
}
|
||||
|
@ -1010,11 +1222,11 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
|
|||
}
|
||||
|
||||
SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) {
|
||||
QQueue<SharedAssignmentPointer>::iterator i = _assignmentQueue.begin();
|
||||
QQueue<SharedAssignmentPointer>::iterator i = _unfulfilledAssignments.begin();
|
||||
|
||||
while (i != _assignmentQueue.end()) {
|
||||
while (i != _unfulfilledAssignments.end()) {
|
||||
if (i->data()->getType() == Assignment::typeForNodeType(nodeType) && i->data()->getUUID() == checkInUUID) {
|
||||
return _assignmentQueue.takeAt(i - _assignmentQueue.begin());
|
||||
return _unfulfilledAssignments.takeAt(i - _unfulfilledAssignments.begin());
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
|
@ -1026,9 +1238,9 @@ SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const Q
|
|||
SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assignment& requestAssignment) {
|
||||
// this is an unassigned client talking to us directly for an assignment
|
||||
// go through our queue and see if there are any assignments to give out
|
||||
QQueue<SharedAssignmentPointer>::iterator sharedAssignment = _assignmentQueue.begin();
|
||||
QQueue<SharedAssignmentPointer>::iterator sharedAssignment = _unfulfilledAssignments.begin();
|
||||
|
||||
while (sharedAssignment != _assignmentQueue.end()) {
|
||||
while (sharedAssignment != _unfulfilledAssignments.end()) {
|
||||
Assignment* assignment = sharedAssignment->data();
|
||||
bool requestIsAllTypes = requestAssignment.getType() == Assignment::AllTypes;
|
||||
bool assignmentTypesMatch = assignment->getType() == requestAssignment.getType();
|
||||
|
@ -1038,16 +1250,12 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig
|
|||
if ((requestIsAllTypes || assignmentTypesMatch) && (nietherHasPool || assignmentPoolsMatch)) {
|
||||
|
||||
// remove the assignment from the queue
|
||||
SharedAssignmentPointer deployableAssignment = _assignmentQueue.takeAt(sharedAssignment
|
||||
- _assignmentQueue.begin());
|
||||
SharedAssignmentPointer deployableAssignment = _unfulfilledAssignments.takeAt(sharedAssignment
|
||||
- _unfulfilledAssignments.begin());
|
||||
|
||||
if (deployableAssignment->getType() != Assignment::AgentType
|
||||
|| _staticAssignmentHash.contains(deployableAssignment->getUUID())) {
|
||||
// this is a static assignment
|
||||
// until we get a check-in from that GUID
|
||||
// put assignment back in queue but stick it at the back so the others have a chance to go out
|
||||
_assignmentQueue.enqueue(deployableAssignment);
|
||||
}
|
||||
// until we get a connection for this assignment
|
||||
// put assignment back in queue but stick it at the back so the others have a chance to go out
|
||||
_unfulfilledAssignments.enqueue(deployableAssignment);
|
||||
|
||||
// stop looping, we've handed out an assignment
|
||||
return deployableAssignment;
|
||||
|
@ -1061,10 +1269,10 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig
|
|||
}
|
||||
|
||||
void DomainServer::removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment) {
|
||||
QQueue<SharedAssignmentPointer>::iterator potentialMatchingAssignment = _assignmentQueue.begin();
|
||||
while (potentialMatchingAssignment != _assignmentQueue.end()) {
|
||||
QQueue<SharedAssignmentPointer>::iterator potentialMatchingAssignment = _unfulfilledAssignments.begin();
|
||||
while (potentialMatchingAssignment != _unfulfilledAssignments.end()) {
|
||||
if (potentialMatchingAssignment->data()->getUUID() == removableAssignment->getUUID()) {
|
||||
_assignmentQueue.erase(potentialMatchingAssignment);
|
||||
_unfulfilledAssignments.erase(potentialMatchingAssignment);
|
||||
|
||||
// we matched and removed an assignment, bail out
|
||||
break;
|
||||
|
@ -1078,7 +1286,7 @@ void DomainServer::addStaticAssignmentsToQueue() {
|
|||
|
||||
// if the domain-server has just restarted,
|
||||
// check if there are static assignments that we need to throw into the assignment queue
|
||||
QHash<QUuid, SharedAssignmentPointer> staticHashCopy = _staticAssignmentHash;
|
||||
QHash<QUuid, SharedAssignmentPointer> staticHashCopy = _allAssignments;
|
||||
QHash<QUuid, SharedAssignmentPointer>::iterator staticAssignment = staticHashCopy.begin();
|
||||
while (staticAssignment != staticHashCopy.end()) {
|
||||
// add any of the un-matched static assignments to the queue
|
||||
|
|
|
@ -23,20 +23,21 @@
|
|||
#include <gnutls/gnutls.h>
|
||||
|
||||
#include <Assignment.h>
|
||||
#include <HTTPManager.h>
|
||||
#include <HTTPSConnection.h>
|
||||
#include <LimitedNodeList.h>
|
||||
|
||||
#include "DTLSServerSession.h"
|
||||
|
||||
typedef QSharedPointer<Assignment> SharedAssignmentPointer;
|
||||
|
||||
class DomainServer : public QCoreApplication, public HTTPRequestHandler {
|
||||
class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DomainServer(int argc, char* argv[]);
|
||||
~DomainServer();
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url);
|
||||
|
||||
void exit(int retCode = 0);
|
||||
|
||||
|
@ -52,12 +53,13 @@ private slots:
|
|||
void readAvailableDTLSDatagrams();
|
||||
private:
|
||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||
bool optionallySetupOAuth();
|
||||
bool optionallySetupDTLS();
|
||||
bool readX509KeyAndCertificate();
|
||||
bool optionallyReadX509KeyAndCertificate();
|
||||
|
||||
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
void addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
int parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
|
||||
HifiSockAddr& localSockAddr, const QByteArray& packet, const HifiSockAddr& senderSockAddr);
|
||||
NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes);
|
||||
|
@ -76,13 +78,20 @@ private:
|
|||
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
|
||||
void addStaticAssignmentsToQueue();
|
||||
|
||||
QUrl oauthRedirectURL();
|
||||
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
|
||||
|
||||
void handleTokenRequestFinished();
|
||||
void handleProfileRequestFinished();
|
||||
|
||||
QJsonObject jsonForSocket(const HifiSockAddr& socket);
|
||||
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
|
||||
|
||||
HTTPManager _HTTPManager;
|
||||
HTTPManager _httpManager;
|
||||
HTTPSManager* _httpsManager;
|
||||
|
||||
QHash<QUuid, SharedAssignmentPointer> _staticAssignmentHash;
|
||||
QQueue<SharedAssignmentPointer> _assignmentQueue;
|
||||
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
|
||||
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
|
||||
|
||||
QVariantMap _argumentVariantMap;
|
||||
|
||||
|
@ -93,6 +102,15 @@ private:
|
|||
gnutls_priority_t* _priorityCache;
|
||||
|
||||
QHash<HifiSockAddr, DTLSServerSession*> _dtlsSessions;
|
||||
|
||||
QNetworkAccessManager* _networkAccessManager;
|
||||
|
||||
QUrl _oauthProviderURL;
|
||||
QString _oauthClientID;
|
||||
QString _oauthClientSecret;
|
||||
QString _hostname;
|
||||
QMap<QNetworkReply*, QUuid> _networkReplyUUIDMap;
|
||||
QHash<QUuid, bool> _sessionAuthenticationHash;
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServer_h
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
|
||||
DomainServerNodeData::DomainServerNodeData() :
|
||||
_sessionSecretHash(),
|
||||
_staticAssignmentUUID(),
|
||||
_assignmentUUID(),
|
||||
_statsJSONObject(),
|
||||
_sendingSockAddr()
|
||||
_sendingSockAddr(),
|
||||
_isAuthenticated(true)
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -27,20 +27,24 @@ public:
|
|||
|
||||
void parseJSONStatsPacket(const QByteArray& statsPacket);
|
||||
|
||||
void setStaticAssignmentUUID(const QUuid& staticAssignmentUUID) { _staticAssignmentUUID = staticAssignmentUUID; }
|
||||
const QUuid& getStaticAssignmentUUID() const { return _staticAssignmentUUID; }
|
||||
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
||||
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
||||
|
||||
void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; }
|
||||
const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; }
|
||||
|
||||
void setIsAuthenticated(bool isAuthenticated) { _isAuthenticated = isAuthenticated; }
|
||||
bool isAuthenticated() const { return _isAuthenticated; }
|
||||
|
||||
QHash<QUuid, QUuid>& getSessionSecretHash() { return _sessionSecretHash; }
|
||||
private:
|
||||
QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject);
|
||||
|
||||
QHash<QUuid, QUuid> _sessionSecretHash;
|
||||
QUuid _staticAssignmentUUID;
|
||||
QUuid _assignmentUUID;
|
||||
QJsonObject _statsJSONObject;
|
||||
HifiSockAddr _sendingSockAddr;
|
||||
bool _isAuthenticated;
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServerNodeData_h
|
||||
|
|
7
examples/dialogExample.js
Normal file
7
examples/dialogExample.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
Window.alert("This is an alert box");
|
||||
|
||||
var confirmed = Window.confirm("This is a confirmation dialog")
|
||||
Window.alert("Your response was: " + confirmed);
|
||||
|
||||
var prompt = Window.prompt("This is a prompt dialog", "This is the default text");
|
||||
Window.alert("Your response was: " + prompt);
|
93
examples/editModelExample.js
Normal file
93
examples/editModelExample.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// editModelExample.js
|
||||
// examples
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/31/13.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// This is an example script that demonstrates creating and editing a model
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var count = 0;
|
||||
var moveUntil = 2000;
|
||||
var stopAfter = moveUntil + 100;
|
||||
|
||||
var pitch = 90.0;
|
||||
var yaw = 0.0;
|
||||
var roll = 180.0;
|
||||
var rotation = Quat.fromPitchYawRollDegrees(pitch, yaw, roll)
|
||||
|
||||
var originalProperties = {
|
||||
position: { x: 10,
|
||||
y: 0,
|
||||
z: 0 },
|
||||
|
||||
radius : 0.1,
|
||||
|
||||
color: { red: 0,
|
||||
green: 255,
|
||||
blue: 0 },
|
||||
|
||||
//modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX",
|
||||
//modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx",
|
||||
//modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx",
|
||||
//modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo",
|
||||
//modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx",
|
||||
modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Combat_tank_V01.FBX",
|
||||
|
||||
modelRotation: rotation
|
||||
};
|
||||
|
||||
var positionDelta = { x: 0, y: 0, z: 0 };
|
||||
|
||||
|
||||
var modelID = Models.addModel(originalProperties);
|
||||
|
||||
function moveModel(deltaTime) {
|
||||
if (count >= moveUntil) {
|
||||
|
||||
// delete it...
|
||||
if (count == moveUntil) {
|
||||
print("calling Models.deleteModel()");
|
||||
Models.deleteModel(modelID);
|
||||
}
|
||||
|
||||
// stop it...
|
||||
if (count >= stopAfter) {
|
||||
print("calling Script.stop()");
|
||||
Script.stop();
|
||||
}
|
||||
|
||||
count++;
|
||||
return; // break early
|
||||
}
|
||||
|
||||
//print("count =" + count);
|
||||
count++;
|
||||
|
||||
//print("modelID.creatorTokenID = " + modelID.creatorTokenID);
|
||||
|
||||
var newProperties = {
|
||||
position: {
|
||||
x: originalProperties.position.x + (count * positionDelta.x),
|
||||
y: originalProperties.position.y + (count * positionDelta.y),
|
||||
z: originalProperties.position.z + (count * positionDelta.z)
|
||||
},
|
||||
radius : 0.25,
|
||||
|
||||
};
|
||||
|
||||
|
||||
//print("modelID = " + modelID);
|
||||
//print("newProperties.position = " + newProperties.position.x + "," + newProperties.position.y+ "," + newProperties.position.z);
|
||||
|
||||
Models.editModel(modelID, newProperties);
|
||||
}
|
||||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.update.connect(moveModel);
|
||||
|
342
examples/editModels.js
Normal file
342
examples/editModels.js
Normal file
|
@ -0,0 +1,342 @@
|
|||
//
|
||||
// editModels.js
|
||||
// examples
|
||||
//
|
||||
// Created by Clément Brisset on 4/24/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
var LASER_WIDTH = 4;
|
||||
var LASER_COLOR = { red: 255, green: 0, blue: 0 };
|
||||
var LASER_LENGTH_FACTOR = 1.5;
|
||||
|
||||
var LEFT = 0;
|
||||
var RIGHT = 1;
|
||||
|
||||
function controller(wichSide) {
|
||||
this.side = wichSide;
|
||||
this.palm = 2 * wichSide;
|
||||
this.tip = 2 * wichSide + 1;
|
||||
this.trigger = wichSide;
|
||||
|
||||
this.oldPalmPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
this.palmPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
|
||||
this.oldTipPosition = Controller.getSpatialControlPosition(this.tip);
|
||||
this.tipPosition = Controller.getSpatialControlPosition(this.tip);
|
||||
|
||||
this.oldUp = Controller.getSpatialControlNormal(this.palm);
|
||||
this.up = this.oldUp;
|
||||
|
||||
this.oldFront = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition));
|
||||
this.front = this.oldFront;
|
||||
|
||||
this.oldRight = Vec3.cross(this.front, this.up);
|
||||
this.right = this.oldRight;
|
||||
|
||||
this.oldRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
|
||||
this.rotation = this.oldRotation;
|
||||
|
||||
this.triggerValue = Controller.getTriggerValue(this.trigger);
|
||||
|
||||
this.pressed = false; // is trigger pressed
|
||||
this.pressing = false; // is trigger being pressed (is pressed now but wasn't previously)
|
||||
|
||||
this.grabbing = false;
|
||||
this.modelID;
|
||||
|
||||
this.laser = Overlays.addOverlay("line3d", {
|
||||
position: this.palmPosition,
|
||||
end: this.tipPosition,
|
||||
color: LASER_COLOR,
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
lineWidth: LASER_WIDTH
|
||||
});
|
||||
|
||||
this.guideScale = 0.02;
|
||||
this.ball = Overlays.addOverlay("sphere", {
|
||||
position: this.palmPosition,
|
||||
size: this.guideScale,
|
||||
solid: true,
|
||||
color: { red: 0, green: 255, blue: 0 },
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
});
|
||||
this.leftRight = Overlays.addOverlay("line3d", {
|
||||
position: this.palmPosition,
|
||||
end: this.tipPosition,
|
||||
color: { red: 0, green: 0, blue: 255 },
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
lineWidth: LASER_WIDTH
|
||||
});
|
||||
this.topDown = Overlays.addOverlay("line3d", {
|
||||
position: this.palmPosition,
|
||||
end: this.tipPosition,
|
||||
color: { red: 0, green: 0, blue: 255 },
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
lineWidth: LASER_WIDTH
|
||||
});
|
||||
|
||||
|
||||
|
||||
this.grab = function (modelID) {
|
||||
if (!modelID.isKnownID) {
|
||||
var identify = Models.identifyModel(modelID);
|
||||
if (!identify.isKnownID) {
|
||||
print("Unknown ID " + identify.id + "(grab)");
|
||||
return;
|
||||
}
|
||||
modelID = identify;
|
||||
}
|
||||
print("Grabbing " + modelID.id);
|
||||
this.grabbing = true;
|
||||
this.modelID = modelID;
|
||||
}
|
||||
|
||||
this.release = function () {
|
||||
this.grabbing = false;
|
||||
this.modelID = 0;
|
||||
}
|
||||
|
||||
this.checkTrigger = function () {
|
||||
if (this.triggerValue > 0.9) {
|
||||
if (this.pressed) {
|
||||
this.pressing = false;
|
||||
} else {
|
||||
this.pressing = true;
|
||||
}
|
||||
this.pressed = true;
|
||||
} else {
|
||||
this.pressing = false;
|
||||
this.pressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.moveLaser = function () {
|
||||
var endPosition = Vec3.sum(this.palmPosition, Vec3.multiply(this.front, LASER_LENGTH_FACTOR));
|
||||
|
||||
Overlays.editOverlay(this.laser, {
|
||||
position: this.palmPosition,
|
||||
end: endPosition,
|
||||
visible: true
|
||||
});
|
||||
|
||||
|
||||
Overlays.editOverlay(this.ball, {
|
||||
position: endPosition,
|
||||
visible: true
|
||||
});
|
||||
Overlays.editOverlay(this.leftRight, {
|
||||
position: Vec3.sum(endPosition, Vec3.multiply(this.right, 2 * this.guideScale)),
|
||||
end: Vec3.sum(endPosition, Vec3.multiply(this.right, -2 * this.guideScale)),
|
||||
visible: true
|
||||
});
|
||||
Overlays.editOverlay(this.topDown, {position: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)),
|
||||
end: Vec3.sum(endPosition, Vec3.multiply(this.up, -2 * this.guideScale)),
|
||||
visible: true
|
||||
});
|
||||
}
|
||||
|
||||
this.checkModel = function (modelID) {
|
||||
if (!modelID.isKnownID) {
|
||||
var identify = Models.identifyModel(modelID);
|
||||
if (!identify.isKnownID) {
|
||||
print("Unknown ID " + identify.id + "(checkModel)");
|
||||
return;
|
||||
}
|
||||
modelID = identify;
|
||||
}
|
||||
// P P - Model
|
||||
// /| A - Palm
|
||||
// / | d B - unit vector toward tip
|
||||
// / | X - base of the perpendicular line
|
||||
// A---X----->B d - distance fom axis
|
||||
// x x - distance from A
|
||||
//
|
||||
// |X-A| = (P-A).B
|
||||
// X == A + ((P-A).B)B
|
||||
// d = |P-X|
|
||||
|
||||
var A = this.palmPosition;
|
||||
var B = this.front;
|
||||
var P = Models.getModelProperties(modelID).position;
|
||||
|
||||
this.x = Vec3.dot(Vec3.subtract(P, A), B);
|
||||
this.y = Vec3.dot(Vec3.subtract(P, A), this.up);
|
||||
this.z = Vec3.dot(Vec3.subtract(P, A), this.right);
|
||||
var X = Vec3.sum(A, Vec3.multiply(B, this.x));
|
||||
var d = Vec3.length(Vec3.subtract(P, X));
|
||||
|
||||
// Vec3.print("A: ", A);
|
||||
// Vec3.print("B: ", B);
|
||||
// Vec3.print("Particle pos: ", P);
|
||||
// print("d: " + d + ", x: " + this.x);
|
||||
if (d < Models.getModelProperties(modelID).radius && 0 < this.x && this.x < LASER_LENGTH_FACTOR) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.update = function () {
|
||||
this.oldPalmPosition = this.palmPosition;
|
||||
this.oldTipPosition = this.tipPosition;
|
||||
this.palmPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
this.tipPosition = Controller.getSpatialControlPosition(this.tip);
|
||||
|
||||
this.oldUp = this.up;
|
||||
this.up = Vec3.normalize(Controller.getSpatialControlNormal(this.palm));
|
||||
|
||||
this.oldFront = this.front;
|
||||
this.front = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition));
|
||||
|
||||
this.oldRight = this.right;
|
||||
this.right = Vec3.normalize(Vec3.cross(this.front, this.up));
|
||||
|
||||
this.oldRotation = this.rotation;
|
||||
this.rotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
|
||||
|
||||
this.triggerValue = Controller.getTriggerValue(this.trigger);
|
||||
|
||||
this.checkTrigger();
|
||||
|
||||
if (this.pressing) {
|
||||
Vec3.print("Looking at: ", this.palmPosition);
|
||||
var foundModels = Models.findModels(this.palmPosition, LASER_LENGTH_FACTOR);
|
||||
for (var i = 0; i < foundModels.length; i++) {
|
||||
print("Model found ID (" + foundModels[i].id + ")");
|
||||
if (this.checkModel(foundModels[i])) {
|
||||
if (this.grab(foundModels[i])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.pressed && this.grabbing) {
|
||||
// release if trigger not pressed anymore.
|
||||
this.release();
|
||||
}
|
||||
|
||||
this.moveLaser();
|
||||
}
|
||||
|
||||
this.cleanup = function () {
|
||||
Overlays.deleteOverlay(this.laser);
|
||||
Overlays.deleteOverlay(this.ball);
|
||||
Overlays.deleteOverlay(this.leftRight);
|
||||
Overlays.deleteOverlay(this.topDown);
|
||||
}
|
||||
}
|
||||
|
||||
var leftController = new controller(LEFT);
|
||||
var rightController = new controller(RIGHT);
|
||||
|
||||
function moveModels() {
|
||||
if (leftController.grabbing) {
|
||||
if (rightController.grabbing) {
|
||||
var properties = Models.getModelProperties(leftController.modelID);
|
||||
|
||||
var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x));
|
||||
var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x));
|
||||
|
||||
var oldMiddle = Vec3.multiply(Vec3.sum(oldLeftPoint, oldRightPoint), 0.5);
|
||||
var oldLength = Vec3.length(Vec3.subtract(oldLeftPoint, oldRightPoint));
|
||||
|
||||
|
||||
var leftPoint = Vec3.sum(leftController.palmPosition, Vec3.multiply(leftController.front, leftController.x));
|
||||
var rightPoint = Vec3.sum(rightController.palmPosition, Vec3.multiply(rightController.front, rightController.x));
|
||||
|
||||
var middle = Vec3.multiply(Vec3.sum(leftPoint, rightPoint), 0.5);
|
||||
var length = Vec3.length(Vec3.subtract(leftPoint, rightPoint));
|
||||
|
||||
var ratio = length / oldLength;
|
||||
|
||||
var newPosition = Vec3.sum(middle,
|
||||
Vec3.multiply(Vec3.subtract(properties.position, oldMiddle), ratio));
|
||||
Vec3.print("Ratio : " + ratio + " New position: ", newPosition);
|
||||
var rotation = Quat.multiply(leftController.rotation,
|
||||
Quat.inverse(leftController.oldRotation));
|
||||
rotation = Quat.multiply(rotation, properties.modelRotation);
|
||||
|
||||
Models.editModel(leftController.modelID, {
|
||||
position: newPosition,
|
||||
//modelRotation: rotation,
|
||||
radius: properties.radius * ratio
|
||||
});
|
||||
|
||||
return;
|
||||
} else {
|
||||
var newPosition = Vec3.sum(leftController.palmPosition,
|
||||
Vec3.multiply(leftController.front, leftController.x));
|
||||
newPosition = Vec3.sum(newPosition,
|
||||
Vec3.multiply(leftController.up, leftController.y));
|
||||
newPosition = Vec3.sum(newPosition,
|
||||
Vec3.multiply(leftController.right, leftController.z));
|
||||
|
||||
var rotation = Quat.multiply(leftController.rotation,
|
||||
Quat.inverse(leftController.oldRotation));
|
||||
rotation = Quat.multiply(rotation,
|
||||
Models.getModelProperties(leftController.modelID).modelRotation);
|
||||
|
||||
Models.editModel(leftController.modelID, {
|
||||
position: newPosition,
|
||||
modelRotation: rotation
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (rightController.grabbing) {
|
||||
var newPosition = Vec3.sum(rightController.palmPosition,
|
||||
Vec3.multiply(rightController.front, rightController.x));
|
||||
newPosition = Vec3.sum(newPosition,
|
||||
Vec3.multiply(rightController.up, rightController.y));
|
||||
newPosition = Vec3.sum(newPosition,
|
||||
Vec3.multiply(rightController.right, rightController.z));
|
||||
|
||||
var rotation = Quat.multiply(rightController.rotation,
|
||||
Quat.inverse(rightController.oldRotation));
|
||||
rotation = Quat.multiply(rotation,
|
||||
Models.getModelProperties(rightController.modelID).modelRotation);
|
||||
|
||||
Models.editModel(rightController.modelID, {
|
||||
position: newPosition,
|
||||
modelRotation: rotation
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function checkController(deltaTime) {
|
||||
var numberOfButtons = Controller.getNumberOfButtons();
|
||||
var numberOfTriggers = Controller.getNumberOfTriggers();
|
||||
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
|
||||
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
|
||||
|
||||
// this is expected for hydras
|
||||
if (!(numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2)) {
|
||||
//print("no hydra connected?");
|
||||
return; // bail if no hydra
|
||||
}
|
||||
|
||||
leftController.update();
|
||||
rightController.update();
|
||||
moveModels();
|
||||
}
|
||||
|
||||
function scriptEnding() {
|
||||
leftController.cleanup();
|
||||
rightController.cleanup();
|
||||
}
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.update.connect(checkController);
|
||||
|
||||
|
||||
|
|
@ -331,8 +331,9 @@ function ScaleSelector() {
|
|||
});
|
||||
this.setScale = function(scale) {
|
||||
this.scale = scale;
|
||||
this.power = Math.floor(Math.log(scale));
|
||||
this.power = Math.floor(Math.log(scale) / Math.log(2));
|
||||
rescaleImport();
|
||||
this.update();
|
||||
}
|
||||
|
||||
this.show = function(doShow) {
|
||||
|
@ -835,7 +836,6 @@ function showPreviewLines() {
|
|||
|
||||
if (copyScale) {
|
||||
scaleSelector.setScale(intersection.voxel.s);
|
||||
scaleSelector.update();
|
||||
}
|
||||
moveTools();
|
||||
} else {
|
||||
|
|
13
examples/fileBrowserExample.js
Normal file
13
examples/fileBrowserExample.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
var file = Window.browse("File Browser Example", "/");
|
||||
if (file === null) {
|
||||
Window.alert("No file was selected");
|
||||
} else {
|
||||
Window.alert("Selected file: " + file);
|
||||
}
|
||||
|
||||
file = Window.browse("Relative Directory Example", "./images", "PNG or JPG files(*.png *.jpg);;SVG files (*.svg)");
|
||||
if (file === null) {
|
||||
Window.alert("No file was selected");
|
||||
} else {
|
||||
Window.alert("Selected file: " + file);
|
||||
}
|
11
examples/locationExample.js
Normal file
11
examples/locationExample.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
var goto = Window.prompt("Where would you like to go? (ex. @username, #location, 1.0,500.3,100.2)");
|
||||
var url = "hifi://" + goto;
|
||||
print("Going to: " + url);
|
||||
location = url;
|
||||
|
||||
// If the destination location is a user or named location the new location may not be set by the time execution reaches here
|
||||
// because it requires an asynchronous lookup. Coordinate changes should be be reflected immediately, though. (ex: hifi://0,0,0)
|
||||
print("URL: " + location.href);
|
||||
print("Protocol: " + location.protocol);
|
||||
print("Hostname: " + location.hostname);
|
||||
print("Pathname: " + location.pathname);
|
301
examples/placeModelsWithHands.js
Normal file
301
examples/placeModelsWithHands.js
Normal file
|
@ -0,0 +1,301 @@
|
|||
//
|
||||
// placeModelsWithHands.js
|
||||
// examples
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 1/20/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
// maybe we should make these constants...
|
||||
var LEFT_PALM = 0;
|
||||
var LEFT_TIP = 1;
|
||||
var LEFT_BUTTON_FWD = 5;
|
||||
var LEFT_BUTTON_3 = 3;
|
||||
|
||||
var RIGHT_PALM = 2;
|
||||
var RIGHT_TIP = 3;
|
||||
var RIGHT_BUTTON_FWD = 11;
|
||||
var RIGHT_BUTTON_3 = 9;
|
||||
|
||||
var leftModelAlreadyInHand = false;
|
||||
var rightModelAlreadyInHand = false;
|
||||
var leftRecentlyDeleted = false;
|
||||
var rightRecentlyDeleted = false;
|
||||
var leftHandModel;
|
||||
var rightHandModel;
|
||||
|
||||
//var throwSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw");
|
||||
//var catchSound = new Sound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw");
|
||||
var targetRadius = 0.5;
|
||||
|
||||
var radiusDefault = 0.25;
|
||||
var modelRadius = radiusDefault;
|
||||
var radiusMinimum = 0.05;
|
||||
var radiusMaximum = 0.5;
|
||||
|
||||
var modelURLs = [
|
||||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX",
|
||||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx",
|
||||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx",
|
||||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo",
|
||||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx",
|
||||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Combat_tank_V01.FBX",
|
||||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/orc.fbx",
|
||||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx",
|
||||
];
|
||||
|
||||
var currentModelURL = 1;
|
||||
var numModels = modelURLs.length;
|
||||
|
||||
|
||||
|
||||
function keyPressEvent(event) {
|
||||
//print("event.text=" + event.text);
|
||||
if (event.text == "ESC") {
|
||||
if (leftRecentlyDeleted) {
|
||||
leftRecentlyDeleted = false;
|
||||
leftModelAlreadyInHand = false;
|
||||
}
|
||||
if (rightRecentlyDeleted) {
|
||||
rightRecentlyDeleted = false;
|
||||
rightModelAlreadyInHand = false;
|
||||
}
|
||||
} else if (event.text == "DELETE") {
|
||||
if (leftModelAlreadyInHand) {
|
||||
print("want to delete leftHandModel=" + leftHandModel);
|
||||
Models.deleteModel(leftHandModel);
|
||||
leftHandModel = "";
|
||||
//leftModelAlreadyInHand = false;
|
||||
leftRecentlyDeleted = true;
|
||||
}
|
||||
if (rightModelAlreadyInHand) {
|
||||
print("want to delete rightHandModel=" + rightHandModel);
|
||||
Models.deleteModel(rightHandModel);
|
||||
rightHandModel = "";
|
||||
//rightModelAlreadyInHand = false;
|
||||
rightRecentlyDeleted = true;
|
||||
}
|
||||
} else {
|
||||
var nVal = parseInt(event.text);
|
||||
if ((nVal >= 0) && (nVal < numModels)) {
|
||||
currentModelURL = nVal;
|
||||
print("Model = " + currentModelURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var wantDebugging = false;
|
||||
function debugPrint(message) {
|
||||
if (wantDebugging) {
|
||||
print(message);
|
||||
}
|
||||
}
|
||||
|
||||
function getModelHoldPosition(whichSide) {
|
||||
var normal;
|
||||
var tipPosition;
|
||||
if (whichSide == LEFT_PALM) {
|
||||
normal = Controller.getSpatialControlNormal(LEFT_PALM);
|
||||
tipPosition = Controller.getSpatialControlPosition(LEFT_TIP);
|
||||
} else {
|
||||
normal = Controller.getSpatialControlNormal(RIGHT_PALM);
|
||||
tipPosition = Controller.getSpatialControlPosition(RIGHT_TIP);
|
||||
}
|
||||
|
||||
var MODEL_FORWARD_OFFSET = 0.08; // put the model a bit forward of fingers
|
||||
position = { x: MODEL_FORWARD_OFFSET * normal.x,
|
||||
y: MODEL_FORWARD_OFFSET * normal.y,
|
||||
z: MODEL_FORWARD_OFFSET * normal.z };
|
||||
|
||||
position.x += tipPosition.x;
|
||||
position.y += tipPosition.y;
|
||||
position.z += tipPosition.z;
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
function checkControllerSide(whichSide) {
|
||||
|
||||
var BUTTON_FWD;
|
||||
var BUTTON_3;
|
||||
var palmPosition;
|
||||
var modelAlreadyInHand;
|
||||
var handMessage;
|
||||
|
||||
if (whichSide == LEFT_PALM) {
|
||||
BUTTON_FWD = LEFT_BUTTON_FWD;
|
||||
BUTTON_3 = LEFT_BUTTON_3;
|
||||
palmPosition = Controller.getSpatialControlPosition(LEFT_PALM);
|
||||
modelAlreadyInHand = leftModelAlreadyInHand;
|
||||
handMessage = "LEFT";
|
||||
} else {
|
||||
BUTTON_FWD = RIGHT_BUTTON_FWD;
|
||||
BUTTON_3 = RIGHT_BUTTON_3;
|
||||
palmPosition = Controller.getSpatialControlPosition(RIGHT_PALM);
|
||||
modelAlreadyInHand = rightModelAlreadyInHand;
|
||||
handMessage = "RIGHT";
|
||||
}
|
||||
|
||||
//print("checkControllerSide..." + handMessage);
|
||||
|
||||
var grabButtonPressed = (Controller.isButtonPressed(BUTTON_FWD) || Controller.isButtonPressed(BUTTON_3));
|
||||
|
||||
// If I don't currently have a model in my hand, then try to grab closest one
|
||||
if (!modelAlreadyInHand && grabButtonPressed) {
|
||||
var closestModel = Models.findClosestModel(palmPosition, targetRadius);
|
||||
|
||||
if (closestModel.isKnownID) {
|
||||
|
||||
//debugPrint
|
||||
print(handMessage + " HAND- CAUGHT SOMETHING!!");
|
||||
|
||||
if (whichSide == LEFT_PALM) {
|
||||
leftModelAlreadyInHand = true;
|
||||
leftHandModel = closestModel;
|
||||
} else {
|
||||
rightModelAlreadyInHand = true;
|
||||
rightHandModel = closestModel;
|
||||
}
|
||||
var modelPosition = getModelHoldPosition(whichSide);
|
||||
|
||||
var properties = { position: { x: modelPosition.x,
|
||||
y: modelPosition.y,
|
||||
z: modelPosition.z },
|
||||
radius: modelRadius,
|
||||
};
|
||||
|
||||
print(">>>>>>>>>>>> EDIT MODEL.... modelRadius=" +modelRadius);
|
||||
|
||||
Models.editModel(closestModel, properties);
|
||||
|
||||
/*
|
||||
var options = new AudioInjectionOptions();
|
||||
options.position = modelPosition;
|
||||
options.volume = 1.0;
|
||||
Audio.playSound(catchSound, options);
|
||||
*/
|
||||
|
||||
return; // exit early
|
||||
}
|
||||
}
|
||||
|
||||
// If '3' is pressed, and not holding a model, make a new one
|
||||
if (Controller.isButtonPressed(BUTTON_3) && !modelAlreadyInHand) {
|
||||
var modelPosition = getModelHoldPosition(whichSide);
|
||||
var properties = { position: { x: modelPosition.x,
|
||||
y: modelPosition.y,
|
||||
z: modelPosition.z },
|
||||
radius: modelRadius,
|
||||
modelURL: modelURLs[currentModelURL]
|
||||
};
|
||||
|
||||
print("modelRadius=" +modelRadius);
|
||||
|
||||
newModel = Models.addModel(properties);
|
||||
if (whichSide == LEFT_PALM) {
|
||||
leftModelAlreadyInHand = true;
|
||||
leftHandModel = newModel;
|
||||
} else {
|
||||
rightModelAlreadyInHand = true;
|
||||
rightHandModel = newModel;
|
||||
}
|
||||
|
||||
// Play a new model sound
|
||||
/*
|
||||
var options = new AudioInjectionOptions();
|
||||
options.position = modelPosition;
|
||||
options.volume = 1.0;
|
||||
Audio.playSound(catchSound, options);
|
||||
*/
|
||||
|
||||
return; // exit early
|
||||
}
|
||||
|
||||
if (modelAlreadyInHand) {
|
||||
if (whichSide == LEFT_PALM) {
|
||||
handModel = leftHandModel;
|
||||
whichTip = LEFT_TIP;
|
||||
} else {
|
||||
handModel = rightHandModel;
|
||||
whichTip = RIGHT_TIP;
|
||||
}
|
||||
|
||||
// If holding the model keep it in the palm
|
||||
if (grabButtonPressed) {
|
||||
//debugPrint
|
||||
print(">>>>> " + handMessage + "-MODEL IN HAND, grabbing, hold and move");
|
||||
var modelPosition = getModelHoldPosition(whichSide);
|
||||
var properties = { position: { x: modelPosition.x,
|
||||
y: modelPosition.y,
|
||||
z: modelPosition.z },
|
||||
radius: modelRadius,
|
||||
};
|
||||
|
||||
print(">>>>>>>>>>>> EDIT MODEL.... modelRadius=" +modelRadius);
|
||||
|
||||
Models.editModel(handModel, properties);
|
||||
} else {
|
||||
debugPrint(">>>>> " + handMessage + "-MODEL IN HAND, not grabbing, RELEASE!!!");
|
||||
if (whichSide == LEFT_PALM) {
|
||||
leftModelAlreadyInHand = false;
|
||||
leftHandModel = false;
|
||||
} else {
|
||||
rightModelAlreadyInHand = false;
|
||||
rightHandModel = false;
|
||||
}
|
||||
|
||||
/*
|
||||
var options = new AudioInjectionOptions();
|
||||
options.position = modelPosition;
|
||||
options.volume = 1.0;
|
||||
Audio.playSound(throwSound, options);
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function checkController(deltaTime) {
|
||||
var numberOfButtons = Controller.getNumberOfButtons();
|
||||
var numberOfTriggers = Controller.getNumberOfTriggers();
|
||||
var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
|
||||
var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
|
||||
|
||||
// this is expected for hydras
|
||||
if (!(numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2)) {
|
||||
debugPrint("no hydra connected?");
|
||||
return; // bail if no hydra
|
||||
}
|
||||
|
||||
checkControllerSide(LEFT_PALM);
|
||||
checkControllerSide(RIGHT_PALM);
|
||||
|
||||
if (rightModelAlreadyInHand) {
|
||||
var rightTriggerValue = Controller.getTriggerValue(1);
|
||||
if (rightTriggerValue > 0.0) {
|
||||
var possibleRadius = ((1.0 - rightTriggerValue) * (radiusMaximum - radiusMinimum)) + radiusMinimum;
|
||||
modelRadius = possibleRadius;
|
||||
} else {
|
||||
modelRadius = radiusDefault;
|
||||
}
|
||||
}
|
||||
|
||||
if (leftModelAlreadyInHand) {
|
||||
var leftTriggerValue = Controller.getTriggerValue(0);
|
||||
if (leftTriggerValue > 0.0) {
|
||||
var possibleRadius = ((1.0 - leftTriggerValue) * (radiusMaximum - radiusMinimum)) + radiusMinimum;
|
||||
modelRadius = possibleRadius;
|
||||
} else {
|
||||
modelRadius = radiusDefault;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.update.connect(checkController);
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
|
|
@ -19,6 +19,12 @@ function mouseMoveEvent(event) {
|
|||
print("computePickRay direction=" + pickRay.direction.x + ", " + pickRay.direction.y + ", " + pickRay.direction.z);
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var intersection = Voxels.findRayIntersection(pickRay);
|
||||
if (!intersection.accurate) {
|
||||
print(">>> NOTE: intersection not accurate. will try calling Voxels.findRayIntersectionBlocking()");
|
||||
intersection = Voxels.findRayIntersectionBlocking(pickRay);
|
||||
print(">>> AFTER BLOCKING CALL intersection.accurate=" + intersection.accurate);
|
||||
}
|
||||
|
||||
if (intersection.intersects) {
|
||||
print("intersection voxel.red/green/blue=" + intersection.voxel.red + ", "
|
||||
+ intersection.voxel.green + ", " + intersection.voxel.blue);
|
||||
|
|
12
examples/windowExample.js
Normal file
12
examples/windowExample.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
var width = 0,
|
||||
height = 0;
|
||||
|
||||
function onUpdate(dt) {
|
||||
if (width != Window.innerWidth || height != Window.innerHeight) {
|
||||
width = Window.innerWidth;
|
||||
height = Window.innerHeight;
|
||||
print("New window dimensions: " + width + ", " + height);
|
||||
}
|
||||
}
|
||||
|
||||
Script.update.connect(onUpdate);
|
|
@ -49,7 +49,7 @@ configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVe
|
|||
|
||||
# grab the implementation and header files from src dirs
|
||||
file(GLOB INTERFACE_SRCS src/*.cpp src/*.h)
|
||||
foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels)
|
||||
foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles models)
|
||||
file(GLOB_RECURSE SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h)
|
||||
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${SUBDIR_SRCS}")
|
||||
endforeach(SUBDIR)
|
||||
|
@ -124,6 +124,7 @@ link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
|
|||
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(audio ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
|
|
@ -4,22 +4,22 @@
|
|||
<context>
|
||||
<name>Application</name>
|
||||
<message>
|
||||
<location filename="src/Application.cpp" line="1380"/>
|
||||
<location filename="src/Application.cpp" line="1481"/>
|
||||
<source>Export Voxels</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/Application.cpp" line="1381"/>
|
||||
<location filename="src/Application.cpp" line="1482"/>
|
||||
<source>Sparse Voxel Octree Files (*.svo)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/Application.cpp" line="3608"/>
|
||||
<location filename="src/Application.cpp" line="3465"/>
|
||||
<source>Open Script</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/Application.cpp" line="3609"/>
|
||||
<location filename="src/Application.cpp" line="3466"/>
|
||||
<source>JavaScript Files (*.js)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -113,18 +113,18 @@
|
|||
<context>
|
||||
<name>Menu</name>
|
||||
<message>
|
||||
<location filename="src/Menu.cpp" line="460"/>
|
||||
<location filename="src/Menu.cpp" line="554"/>
|
||||
<source>Open .ini config file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/Menu.cpp" line="462"/>
|
||||
<location filename="src/Menu.cpp" line="474"/>
|
||||
<location filename="src/Menu.cpp" line="556"/>
|
||||
<location filename="src/Menu.cpp" line="568"/>
|
||||
<source>Text files (*.ini)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="src/Menu.cpp" line="472"/>
|
||||
<location filename="src/Menu.cpp" line="566"/>
|
||||
<source>Save .ini config file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -271,4 +271,57 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RunningScriptsWidget</name>
|
||||
<message>
|
||||
<location filename="ui/runningScriptsWidget.ui" line="14"/>
|
||||
<location filename="../build/interface/ui_runningScriptsWidget.h" line="140"/>
|
||||
<source>Form</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/runningScriptsWidget.ui" line="39"/>
|
||||
<location filename="../build/interface/ui_runningScriptsWidget.h" line="141"/>
|
||||
<source><html><head/><body><p><span style=" font-size:18pt;">Running Scripts</span></p></body></html></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/runningScriptsWidget.ui" line="63"/>
|
||||
<location filename="../build/interface/ui_runningScriptsWidget.h" line="142"/>
|
||||
<source><html><head/><body><p><span style=" font-weight:600;">Currently running</span></p></body></html></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/runningScriptsWidget.ui" line="89"/>
|
||||
<location filename="../build/interface/ui_runningScriptsWidget.h" line="143"/>
|
||||
<source>Reload all</source>
|
||||
<oldsource>Reload All</oldsource>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/runningScriptsWidget.ui" line="116"/>
|
||||
<location filename="../build/interface/ui_runningScriptsWidget.h" line="144"/>
|
||||
<source>Stop all</source>
|
||||
<oldsource>Stop All</oldsource>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/runningScriptsWidget.ui" line="137"/>
|
||||
<location filename="../build/interface/ui_runningScriptsWidget.h" line="145"/>
|
||||
<source><html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/runningScriptsWidget.ui" line="154"/>
|
||||
<location filename="../build/interface/ui_runningScriptsWidget.h" line="146"/>
|
||||
<source>(click a script or use the 1-9 keys to load and run it)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="ui/runningScriptsWidget.ui" line="202"/>
|
||||
<location filename="../build/interface/ui_runningScriptsWidget.h" line="148"/>
|
||||
<source>There are no scripts currently running.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
58
interface/resources/images/hifi-logo.svg
Normal file
58
interface/resources/images/hifi-logo.svg
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 55.5 55.5" enable-background="new 0 0 55.5 55.5" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#0E7077" d="M47.4,8.1C42.1,2.9,35.2,0,27.8,0C20.3,0,13.4,2.9,8.1,8.1C2.9,13.4,0,20.3,0,27.8
|
||||
c0,7.4,2.9,14.4,8.1,19.6c5.2,5.2,12.2,8.1,19.6,8.1c7.4,0,14.4-2.9,19.6-8.1c5.2-5.2,8.1-12.2,8.1-19.6
|
||||
C55.5,20.3,52.6,13.4,47.4,8.1z M45.4,45.4c-4.7,4.7-11,7.3-17.7,7.3c-6.7,0-12.9-2.6-17.7-7.3c-4.7-4.7-7.3-11-7.3-17.7
|
||||
c0-6.7,2.6-12.9,7.3-17.7c4.7-4.7,11-7.3,17.7-7.3c6.7,0,12.9,2.6,17.7,7.3c4.7,4.7,7.3,11,7.3,17.7
|
||||
C52.7,34.4,50.1,40.7,45.4,45.4z"/>
|
||||
<g>
|
||||
<path fill="#0E7077" d="M20.3,37.6c-0.6,0-1.2-0.5-1.2-1.2V15c0-0.6,0.5-1.2,1.2-1.2c0.6,0,1.2,0.5,1.2,1.2v21.5
|
||||
C21.5,37.1,21,37.6,20.3,37.6z"/>
|
||||
<path fill="#0E7077" d="M21.7,14.4c-0.8,0.8-2,0.8-2.7,0c-0.8-0.8-0.8-2,0-2.7c0.8-0.8,2-0.8,2.7,0
|
||||
C22.5,12.5,22.5,13.7,21.7,14.4"/>
|
||||
<g>
|
||||
<path fill="#0E7077" d="M20.3,16.2c-0.8,0-1.6-0.3-2.2-0.9c-1.2-1.2-1.2-3.2,0-4.4c0.6-0.6,1.4-0.9,2.2-0.9
|
||||
c0.8,0,1.6,0.3,2.2,0.9c0.6,0.6,0.9,1.4,0.9,2.2c0,0.8-0.3,1.6-0.9,2.2C21.9,15.8,21.2,16.2,20.3,16.2z M20.3,12.3
|
||||
c-0.2,0-0.4,0.1-0.6,0.2c-0.3,0.3-0.3,0.8,0,1.1c0.3,0.3,0.8,0.3,1.1,0c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6
|
||||
C20.7,12.4,20.5,12.3,20.3,12.3z"/>
|
||||
</g>
|
||||
<path fill="#0E7077" d="M19,37c0.8-0.8,2-0.8,2.7,0c0.8,0.8,0.8,2,0,2.7c-0.8,0.8-2,0.8-2.7,0C18.2,39,18.2,37.8,19,37"/>
|
||||
<g>
|
||||
<path fill="#0E7077" d="M20.3,41.5c-0.8,0-1.6-0.3-2.2-0.9c-0.6-0.6-0.9-1.4-0.9-2.2c0-0.8,0.3-1.6,0.9-2.2
|
||||
c0.6-0.6,1.4-0.9,2.2-0.9c0.8,0,1.6,0.3,2.2,0.9c0.6,0.6,0.9,1.4,0.9,2.2c0,0.8-0.3,1.6-0.9,2.2C21.9,41.2,21.2,41.5,20.3,41.5z
|
||||
M20.3,37.6c-0.2,0-0.4,0.1-0.6,0.2c-0.1,0.1-0.2,0.3-0.2,0.6c0,0.2,0.1,0.4,0.2,0.6c0.3,0.3,0.8,0.3,1.1,0
|
||||
c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6C20.7,37.7,20.5,37.6,20.3,37.6z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#0E7077" d="M35.2,41.7c-0.6,0-1.2-0.5-1.2-1.2V19c0-0.6,0.5-1.2,1.2-1.2c0.6,0,1.2,0.5,1.2,1.2v21.5
|
||||
C36.4,41.2,35.8,41.7,35.2,41.7z"/>
|
||||
</g>
|
||||
<path fill="#0E7077" d="M36.6,18.5c-0.8,0.8-2,0.8-2.7,0c-0.8-0.8-0.8-2,0-2.7c0.8-0.8,2-0.8,2.7,0
|
||||
C37.3,16.5,37.3,17.7,36.6,18.5"/>
|
||||
<g>
|
||||
<path fill="#0E7077" d="M35.2,20.2c-0.8,0-1.6-0.3-2.2-0.9c-1.2-1.2-1.2-3.2,0-4.4c0.6-0.6,1.4-0.9,2.2-0.9
|
||||
c0.8,0,1.6,0.3,2.2,0.9c0.6,0.6,0.9,1.4,0.9,2.2c0,0.8-0.3,1.6-0.9,2.2C36.8,19.9,36,20.2,35.2,20.2z M35.2,16.3
|
||||
c-0.2,0-0.4,0.1-0.6,0.2c-0.3,0.3-0.3,0.8,0,1.1c0.3,0.3,0.8,0.3,1.1,0c0.1-0.1,0.2-0.3,0.2-0.6c0-0.2-0.1-0.4-0.2-0.6
|
||||
C35.6,16.4,35.4,16.3,35.2,16.3z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#0E7077" d="M35.2,45.6c-0.8,0-1.6-0.3-2.2-0.9c-1.2-1.2-1.2-3.2,0-4.4c0.6-0.6,1.4-0.9,2.2-0.9
|
||||
c0.8,0,1.6,0.3,2.2,0.9c0.6,0.6,0.9,1.4,0.9,2.2c0,0.8-0.3,1.6-0.9,2.2C36.8,45.2,36,45.6,35.2,45.6z M35.2,41.7
|
||||
c-0.2,0-0.4,0.1-0.6,0.2c-0.3,0.3-0.3,0.8,0,1.1c0.3,0.3,0.8,0.3,1.1,0c0.1-0.1,0.2-0.3,0.2-0.6s-0.1-0.4-0.2-0.6
|
||||
C35.6,41.8,35.4,41.7,35.2,41.7z"/>
|
||||
</g>
|
||||
<path fill="#0E7077" d="M33.8,41.1c0.8-0.8,2-0.8,2.7,0c0.8,0.8,0.8,2,0,2.7c-0.8,0.8-2,0.8-2.7,0C33.1,43.1,33.1,41.8,33.8,41.1
|
||||
"/>
|
||||
<g>
|
||||
|
||||
<rect x="19.6" y="26.2" transform="matrix(0.9064 0.4224 -0.4224 0.9064 14.1626 -9.1647)" fill="#0E7077" width="16.4" height="2.3"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
17
interface/resources/images/login.svg
Normal file
17
interface/resources/images/login.svg
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 23.6 22.3" enable-background="new 0 0 23.6 22.3" xml:space="preserve">
|
||||
<g>
|
||||
<g id="Your_Icon_8_">
|
||||
<g>
|
||||
<polygon fill="#E7EEEE" points="6.8,7.8 0,7.8 0,14.1 6.8,14.1 6.8,19 14.4,11 6.8,3.1 "/>
|
||||
<polygon fill="#E7EEEE" points="12.9,3.1 14.3,3.1 14.3,1.4 12.9,1.4 12.9,1.4 11.2,1.4 11.2,6.5 12.9,8.2 "/>
|
||||
<polygon fill="#E7EEEE" points="12.9,13.7 11.2,15.5 11.2,20.7 14.3,20.7 14.3,19.1 12.9,19.1 "/>
|
||||
<path fill="#E7EEEE" d="M15,0v22.3l8.5-2.4V2.4L15,0z M17.8,12.2h-0.3c-0.4,0-0.7-0.4-0.8-0.9h-0.3v-0.7h0.3
|
||||
c0.1-0.5,0.4-0.9,0.8-0.9h0.3c0.5,0,0.8,0.5,0.8,1.2C18.7,11.6,18.3,12.2,17.8,12.2z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
7
interface/resources/images/plus-white.svg
Normal file
7
interface/resources/images/plus-white.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Your_Icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="23 23 54 54" enable-background="new 23 23 54 54" xml:space="preserve">
|
||||
<polygon fill="#FFFFFF" points="77,41 59,41 59,23 41,23 41,41 23,41 23,59 41,59 41,77 59,77 59,59 77,59 "/>
|
||||
</svg>
|
After Width: | Height: | Size: 565 B |
|
@ -33,6 +33,7 @@ QPushButton#searchButton {
|
|||
}
|
||||
|
||||
QPushButton#revealLogButton {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
background: url(styles/txt-file.svg);
|
||||
background-repeat: none;
|
||||
background-position: left center;
|
||||
|
@ -50,9 +51,9 @@ QCheckBox {
|
|||
}
|
||||
|
||||
QCheckBox::indicator:unchecked {
|
||||
image: url(:/styles/unchecked.svg);
|
||||
image: url(styles/unchecked.svg);
|
||||
}
|
||||
|
||||
QCheckBox::indicator:checked {
|
||||
image: url(:/styles/checked.svg);
|
||||
image: url(styles/checked.svg);
|
||||
}
|
|
@ -30,7 +30,6 @@
|
|||
#include <QImage>
|
||||
#include <QInputDialog>
|
||||
#include <QKeyEvent>
|
||||
#include <QMainWindow>
|
||||
#include <QMenuBar>
|
||||
#include <QMouseEvent>
|
||||
#include <QNetworkAccessManager>
|
||||
|
@ -55,6 +54,7 @@
|
|||
#include <AccountManager.h>
|
||||
#include <AudioInjector.h>
|
||||
#include <Logging.h>
|
||||
#include <ModelsScriptingInterface.h>
|
||||
#include <OctalCode.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <ParticlesScriptingInterface.h>
|
||||
|
@ -77,11 +77,14 @@
|
|||
#include "scripting/ClipboardScriptingInterface.h"
|
||||
#include "scripting/MenuScriptingInterface.h"
|
||||
#include "scripting/SettingsScriptingInterface.h"
|
||||
#include "scripting/WindowScriptingInterface.h"
|
||||
#include "scripting/LocationScriptingInterface.h"
|
||||
|
||||
#include "ui/InfoView.h"
|
||||
#include "ui/OAuthWebViewHandler.h"
|
||||
#include "ui/Snapshot.h"
|
||||
#include "ui/TextRenderer.h"
|
||||
#include "ui/Stats.h"
|
||||
#include "ui/TextRenderer.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -132,7 +135,7 @@ QString& Application::resourcesPath() {
|
|||
|
||||
Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
||||
QApplication(argc, argv),
|
||||
_window(new QMainWindow(desktop())),
|
||||
_window(new MainWindow(desktop())),
|
||||
_glWidget(new GLCanvas()),
|
||||
_nodeThread(new QThread(this)),
|
||||
_datagramProcessor(),
|
||||
|
@ -164,7 +167,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
_packetsPerSecond(0),
|
||||
_bytesPerSecond(0),
|
||||
_previousScriptLocation(),
|
||||
_logger(new FileLogger(this))
|
||||
_logger(new FileLogger(this)),
|
||||
_runningScriptsWidget(new RunningScriptsWidget(_window)),
|
||||
_runningScriptsWidgetWasVisible(false)
|
||||
{
|
||||
// init GnuTLS for DTLS with domain-servers
|
||||
DTLSClientSession::globalInit();
|
||||
|
@ -263,12 +268,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
|
||||
// tell the NodeList instance who to tell the domain server we care about
|
||||
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
|
||||
<< NodeType::VoxelServer << NodeType::ParticleServer
|
||||
<< NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer
|
||||
<< NodeType::MetavoxelServer);
|
||||
|
||||
// connect to the packet sent signal of the _voxelEditSender and the _particleEditSender
|
||||
connect(&_voxelEditSender, &VoxelEditPacketSender::packetSent, this, &Application::packetSent);
|
||||
connect(&_particleEditSender, &ParticleEditPacketSender::packetSent, this, &Application::packetSent);
|
||||
connect(&_modelEditSender, &ModelEditPacketSender::packetSent, this, &Application::packetSent);
|
||||
|
||||
// move the silentNodeTimer to the _nodeThread
|
||||
QTimer* silentNodeTimer = new QTimer();
|
||||
|
@ -312,14 +318,19 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
// Tell our voxel edit sender about our known jurisdictions
|
||||
_voxelEditSender.setVoxelServerJurisdictions(&_voxelServerJurisdictions);
|
||||
_particleEditSender.setServerJurisdictions(&_particleServerJurisdictions);
|
||||
_modelEditSender.setServerJurisdictions(&_modelServerJurisdictions);
|
||||
|
||||
Particle::setVoxelEditPacketSender(&_voxelEditSender);
|
||||
Particle::setParticleEditPacketSender(&_particleEditSender);
|
||||
|
||||
// when -url in command line, teleport to location
|
||||
urlGoTo(argc, constArgv);
|
||||
|
||||
// For now we're going to set the PPS for outbound packets to be super high, this is
|
||||
// probably not the right long term solution. But for now, we're going to do this to
|
||||
// allow you to move a particle around in your hand
|
||||
_particleEditSender.setPacketsPerSecond(3000); // super high!!
|
||||
_modelEditSender.setPacketsPerSecond(3000); // super high!!
|
||||
|
||||
// Set the sixense filtering
|
||||
_sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense));
|
||||
|
@ -331,7 +342,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
LocalVoxelsList::getInstance()->addPersistantTree(DOMAIN_TREE_NAME, _voxels.getTree());
|
||||
LocalVoxelsList::getInstance()->addPersistantTree(CLIPBOARD_TREE_NAME, &_clipboard);
|
||||
|
||||
_window->addDockWidget(Qt::NoDockWidgetArea, _runningScriptsWidget = new RunningScriptsWidget());
|
||||
_runningScriptsWidget->setRunningScripts(getRunningScripts());
|
||||
connect(_runningScriptsWidget, &RunningScriptsWidget::stopScriptName, this, &Application::stopScript);
|
||||
|
||||
|
@ -352,8 +362,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
QMutexLocker locker(&_settingsMutex);
|
||||
_previousScriptLocation = _settings->value("LastScriptLocation", QVariant("")).toString();
|
||||
}
|
||||
|
||||
connect(_window, &MainWindow::windowGeometryChanged,
|
||||
_runningScriptsWidget, &RunningScriptsWidget::setBoundary);
|
||||
|
||||
//When -url in command line, teleport to location
|
||||
urlGoTo(argc, constArgv);
|
||||
|
||||
// call the OAuthWebviewHandler static getter so that its instance lives in our thread
|
||||
OAuthWebViewHandler::getInstance();
|
||||
// make sure the High Fidelity root CA is in our list of trusted certs
|
||||
OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig();
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
|
@ -387,6 +406,7 @@ Application::~Application() {
|
|||
_voxelHideShowThread.terminate();
|
||||
_voxelEditSender.terminate();
|
||||
_particleEditSender.terminate();
|
||||
_modelEditSender.terminate();
|
||||
|
||||
storeSizeAndPosition();
|
||||
saveScripts();
|
||||
|
@ -487,6 +507,8 @@ void Application::initializeGL() {
|
|||
_voxelEditSender.initialize(_enableProcessVoxelsThread);
|
||||
_voxelHideShowThread.initialize(_enableProcessVoxelsThread);
|
||||
_particleEditSender.initialize(_enableProcessVoxelsThread);
|
||||
_modelEditSender.initialize(_enableProcessVoxelsThread);
|
||||
|
||||
if (_enableProcessVoxelsThread) {
|
||||
qDebug("Voxel parsing thread created.");
|
||||
}
|
||||
|
@ -539,41 +561,6 @@ void Application::paintGL() {
|
|||
_myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
|
||||
_myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
|
||||
|
||||
glm::vec3 planeNormal = _myCamera.getTargetRotation() * IDENTITY_FRONT;
|
||||
const float BASE_PUSHBACK_RADIUS = 0.25f;
|
||||
float pushbackRadius = _myCamera.getNearClip() + _myAvatar->getScale() * BASE_PUSHBACK_RADIUS;
|
||||
glm::vec4 plane(planeNormal, -glm::dot(planeNormal, _myCamera.getTargetPosition()) - pushbackRadius);
|
||||
|
||||
// push camera out of any intersecting avatars
|
||||
foreach (const AvatarSharedPointer& avatarData, _avatarManager.getAvatarHash()) {
|
||||
Avatar* avatar = static_cast<Avatar*>(avatarData.data());
|
||||
if (avatar->isMyAvatar()) {
|
||||
continue;
|
||||
}
|
||||
if (glm::distance(avatar->getPosition(), _myCamera.getTargetPosition()) >
|
||||
avatar->getBoundingRadius() + pushbackRadius) {
|
||||
continue;
|
||||
}
|
||||
float angle = angleBetween(avatar->getPosition() - _myCamera.getTargetPosition(), planeNormal);
|
||||
if (angle > PI_OVER_TWO) {
|
||||
continue;
|
||||
}
|
||||
float scale = 1.0f - angle / PI_OVER_TWO;
|
||||
scale = qMin(1.0f, scale * 2.5f);
|
||||
static CollisionList collisions(64);
|
||||
collisions.clear();
|
||||
if (!avatar->findPlaneCollisions(plane, collisions)) {
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < collisions.size(); i++) {
|
||||
pushback = qMax(pushback, glm::length(collisions.getCollision(i)->_penetration) * scale);
|
||||
}
|
||||
}
|
||||
const float MAX_PUSHBACK = 0.35f;
|
||||
pushback = qMin(pushback, MAX_PUSHBACK * _myAvatar->getScale());
|
||||
const float BASE_PUSHBACK_FOCAL_LENGTH = 0.5f;
|
||||
pushbackFocalLength = BASE_PUSHBACK_FOCAL_LENGTH * _myAvatar->getScale();
|
||||
|
||||
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
|
||||
_myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing
|
||||
_myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition());
|
||||
|
@ -757,6 +744,8 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod
|
|||
channel = BandwidthMeter::AVATARS;
|
||||
break;
|
||||
case NodeType::VoxelServer:
|
||||
case NodeType::ParticleServer:
|
||||
case NodeType::ModelServer:
|
||||
channel = BandwidthMeter::VOXELS;
|
||||
break;
|
||||
default:
|
||||
|
@ -1263,8 +1252,8 @@ void Application::dropEvent(QDropEvent *event) {
|
|||
|
||||
void Application::sendPingPackets() {
|
||||
QByteArray pingPacket = NodeList::getInstance()->constructPingPacket();
|
||||
controlledBroadcastToNodes(pingPacket, NodeSet() << NodeType::VoxelServer
|
||||
<< NodeType::ParticleServer
|
||||
controlledBroadcastToNodes(pingPacket, NodeSet()
|
||||
<< NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer
|
||||
<< NodeType::AudioMixer << NodeType::AvatarMixer
|
||||
<< NodeType::MetavoxelServer);
|
||||
}
|
||||
|
@ -1656,6 +1645,9 @@ void Application::init() {
|
|||
_particles.init();
|
||||
_particles.setViewFrustum(getViewFrustum());
|
||||
|
||||
_models.init();
|
||||
_models.setViewFrustum(getViewFrustum());
|
||||
|
||||
_metavoxels.init();
|
||||
|
||||
_particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager);
|
||||
|
@ -1798,35 +1790,63 @@ void Application::updateMyAvatarLookAtPosition() {
|
|||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()");
|
||||
|
||||
FaceTracker* tracker = getActiveFaceTracker();
|
||||
|
||||
bool isLookingAtSomeone = false;
|
||||
glm::vec3 lookAtSpot;
|
||||
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||
// When I am in mirror mode, just look right at the camera (myself)
|
||||
lookAtSpot = _myCamera.getPosition();
|
||||
|
||||
} else {
|
||||
// look in direction of the mouse ray, but use distance from intersection, if any
|
||||
float distance = TREE_SCALE;
|
||||
if (_myAvatar->getLookAtTargetAvatar() && _myAvatar != _myAvatar->getLookAtTargetAvatar()) {
|
||||
distance = glm::distance(_mouseRayOrigin,
|
||||
static_cast<Avatar*>(_myAvatar->getLookAtTargetAvatar())->getHead()->calculateAverageEyePosition());
|
||||
isLookingAtSomeone = true;
|
||||
// If I am looking at someone else, look directly at one of their eyes
|
||||
if (tracker) {
|
||||
// If tracker active, look at the eye for the side my gaze is biased toward
|
||||
if (tracker->getEstimatedEyeYaw() > _myAvatar->getHead()->getFinalYaw()) {
|
||||
// Look at their right eye
|
||||
lookAtSpot = static_cast<Avatar*>(_myAvatar->getLookAtTargetAvatar())->getHead()->getRightEyePosition();
|
||||
} else {
|
||||
// Look at their left eye
|
||||
lookAtSpot = static_cast<Avatar*>(_myAvatar->getLookAtTargetAvatar())->getHead()->getLeftEyePosition();
|
||||
}
|
||||
} else {
|
||||
// Need to add randomly looking back and forth between left and right eye for case with no tracker
|
||||
lookAtSpot = static_cast<Avatar*>(_myAvatar->getLookAtTargetAvatar())->getHead()->getEyePosition();
|
||||
}
|
||||
} else {
|
||||
// I am not looking at anyone else, so just look forward
|
||||
lookAtSpot = _myAvatar->getHead()->calculateAverageEyePosition() + (_myAvatar->getHead()->getFinalOrientation() * glm::vec3(0.f, 0.f, -TREE_SCALE));
|
||||
}
|
||||
// TODO: Add saccade to mouse pointer when stable, IF not looking at someone (since we know we are looking at it)
|
||||
/*
|
||||
const float FIXED_MIN_EYE_DISTANCE = 0.3f;
|
||||
float minEyeDistance = FIXED_MIN_EYE_DISTANCE + (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON ? 0.0f :
|
||||
glm::distance(_mouseRayOrigin, _myAvatar->getHead()->calculateAverageEyePosition()));
|
||||
lookAtSpot = _mouseRayOrigin + _mouseRayDirection * qMax(minEyeDistance, distance);
|
||||
*/
|
||||
|
||||
}
|
||||
FaceTracker* tracker = getActiveFaceTracker();
|
||||
//
|
||||
// Deflect the eyes a bit to match the detected Gaze from 3D camera if active
|
||||
//
|
||||
if (tracker) {
|
||||
float eyePitch = tracker->getEstimatedEyePitch();
|
||||
float eyeYaw = tracker->getEstimatedEyeYaw();
|
||||
|
||||
const float GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT = 0.1f;
|
||||
// deflect using Faceshift gaze data
|
||||
glm::vec3 origin = _myAvatar->getHead()->calculateAverageEyePosition();
|
||||
float pitchSign = (_myCamera.getMode() == CAMERA_MODE_MIRROR) ? -1.0f : 1.0f;
|
||||
float deflection = Menu::getInstance()->getFaceshiftEyeDeflection();
|
||||
if (isLookingAtSomeone) {
|
||||
deflection *= GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT;
|
||||
}
|
||||
lookAtSpot = origin + _myCamera.getRotation() * glm::quat(glm::radians(glm::vec3(
|
||||
eyePitch * pitchSign * deflection, eyeYaw * deflection, 0.0f))) *
|
||||
glm::inverse(_myCamera.getRotation()) * (lookAtSpot - origin);
|
||||
}
|
||||
|
||||
_myAvatar->getHead()->setLookAtPosition(lookAtSpot);
|
||||
}
|
||||
|
||||
|
@ -1868,6 +1888,7 @@ void Application::updateThreads(float deltaTime) {
|
|||
_voxelHideShowThread.threadRoutine();
|
||||
_voxelEditSender.threadRoutine();
|
||||
_particleEditSender.threadRoutine();
|
||||
_modelEditSender.threadRoutine();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1986,6 +2007,8 @@ void Application::update(float deltaTime) {
|
|||
_particles.update(); // update the particles...
|
||||
_particleCollisionSystem.update(); // collide the particles...
|
||||
|
||||
_models.update(); // update the models...
|
||||
|
||||
_overlays.update(deltaTime);
|
||||
|
||||
// let external parties know we're updating
|
||||
|
@ -2024,6 +2047,7 @@ void Application::updateMyAvatar(float deltaTime) {
|
|||
_lastQueriedTime = now;
|
||||
queryOctree(NodeType::VoxelServer, PacketTypeVoxelQuery, _voxelServerJurisdictions);
|
||||
queryOctree(NodeType::ParticleServer, PacketTypeParticleQuery, _particleServerJurisdictions);
|
||||
queryOctree(NodeType::ModelServer, PacketTypeModelQuery, _modelServerJurisdictions);
|
||||
_lastQueriedViewFrustum = _viewFrustum;
|
||||
}
|
||||
}
|
||||
|
@ -2325,6 +2349,7 @@ void Application::updateShadowMap() {
|
|||
|
||||
_avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE);
|
||||
_particles.render();
|
||||
_models.render();
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
|
@ -2446,7 +2471,6 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
"Application::displaySide() ... atmosphere...");
|
||||
_environment.renderAtmospheres(whichCamera);
|
||||
}
|
||||
|
||||
glEnable(GL_LIGHTING);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
|
@ -2491,6 +2515,13 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
_particles.render();
|
||||
}
|
||||
|
||||
// render models...
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) {
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::displaySide() ... models...");
|
||||
_models.render();
|
||||
}
|
||||
|
||||
// render the ambient occlusion effect if enabled
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AmbientOcclusion)) {
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
|
@ -2630,7 +2661,6 @@ void Application::displayOverlay() {
|
|||
if (audioLevel > AUDIO_METER_SCALE_WIDTH) {
|
||||
audioLevel = AUDIO_METER_SCALE_WIDTH;
|
||||
}
|
||||
|
||||
bool isClipping = ((_audio.getTimeSinceLastClip() > 0.f) && (_audio.getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME));
|
||||
|
||||
if ((_audio.getTimeSinceLastClip() > 0.f) && (_audio.getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME)) {
|
||||
|
@ -2703,7 +2733,7 @@ void Application::displayOverlay() {
|
|||
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::HeadMouse)) {
|
||||
_myAvatar->renderHeadMouse();
|
||||
_myAvatar->renderHeadMouse(_glWidget->width(), _glWidget->height());
|
||||
}
|
||||
|
||||
// Display stats and log text onscreen
|
||||
|
@ -3065,9 +3095,9 @@ void Application::updateWindowTitle(){
|
|||
QString buildVersion = " (build " + applicationVersion() + ")";
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
QString username = AccountManager::getInstance().getUsername();
|
||||
QString title = QString() + (!username.isEmpty() ? username + " " : QString()) + nodeList->getSessionUUID().toString()
|
||||
+ " @ " + nodeList->getDomainHandler().getHostname() + buildVersion;
|
||||
QString username = AccountManager::getInstance().getAccountInfo().getUsername();
|
||||
QString title = QString() + (!username.isEmpty() ? username + " @ " : QString())
|
||||
+ nodeList->getDomainHandler().getHostname() + buildVersion;
|
||||
qDebug("Application title set to: %s", title.toStdString().c_str());
|
||||
_window->setWindowTitle(title);
|
||||
}
|
||||
|
@ -3086,8 +3116,14 @@ void Application::domainChanged(const QString& domainHostname) {
|
|||
// reset the particle renderer
|
||||
_particles.clear();
|
||||
|
||||
// reset the model renderer
|
||||
_models.clear();
|
||||
|
||||
// reset the voxels renderer
|
||||
_voxels.killLocalVoxels();
|
||||
|
||||
// reset the auth URL for OAuth web view handler
|
||||
OAuthWebViewHandler::getInstance().clearLastAuthorizationURL();
|
||||
}
|
||||
|
||||
void Application::connectedToDomain(const QString& hostname) {
|
||||
|
@ -3162,7 +3198,7 @@ void Application::nodeKilled(SharedNodePointer node) {
|
|||
_voxelFades.push_back(fade);
|
||||
}
|
||||
|
||||
// If the voxel server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server
|
||||
// If the particle server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server
|
||||
_particleServerJurisdictions.erase(_particleServerJurisdictions.find(nodeUUID));
|
||||
}
|
||||
|
||||
|
@ -3173,6 +3209,38 @@ void Application::nodeKilled(SharedNodePointer node) {
|
|||
}
|
||||
_octreeSceneStatsLock.unlock();
|
||||
|
||||
} else if (node->getType() == NodeType::ModelServer) {
|
||||
|
||||
QUuid nodeUUID = node->getUUID();
|
||||
// see if this is the first we've heard of this node...
|
||||
if (_modelServerJurisdictions.find(nodeUUID) != _modelServerJurisdictions.end()) {
|
||||
unsigned char* rootCode = _modelServerJurisdictions[nodeUUID].getRootOctalCode();
|
||||
VoxelPositionSize rootDetails;
|
||||
voxelDetailsForCode(rootCode, rootDetails);
|
||||
|
||||
qDebug("model server going away...... v[%f, %f, %f, %f]",
|
||||
rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s);
|
||||
|
||||
// Add the jurisditionDetails object to the list of "fade outs"
|
||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontFadeOnVoxelServerChanges)) {
|
||||
VoxelFade fade(VoxelFade::FADE_OUT, NODE_KILLED_RED, NODE_KILLED_GREEN, NODE_KILLED_BLUE);
|
||||
fade.voxelDetails = rootDetails;
|
||||
const float slightly_smaller = 0.99f;
|
||||
fade.voxelDetails.s = fade.voxelDetails.s * slightly_smaller;
|
||||
_voxelFades.push_back(fade);
|
||||
}
|
||||
|
||||
// If the model server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server
|
||||
_modelServerJurisdictions.erase(_modelServerJurisdictions.find(nodeUUID));
|
||||
}
|
||||
|
||||
// also clean up scene stats for that server
|
||||
_octreeSceneStatsLock.lockForWrite();
|
||||
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
|
||||
_octreeServerSceneStats.erase(nodeUUID);
|
||||
}
|
||||
_octreeSceneStatsLock.unlock();
|
||||
|
||||
} else if (node->getType() == NodeType::AvatarMixer) {
|
||||
// our avatar mixer has gone away - clear the hash of avatars
|
||||
_avatarManager.clearOtherAvatars();
|
||||
|
@ -3196,7 +3264,6 @@ void Application::trackIncomingVoxelPacket(const QByteArray& packet, const Share
|
|||
}
|
||||
|
||||
int Application::parseOctreeStats(const QByteArray& packet, const 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
|
||||
|
@ -3223,16 +3290,21 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin
|
|||
|
||||
// see if this is the first we've heard of this node...
|
||||
NodeToJurisdictionMap* jurisdiction = NULL;
|
||||
QString serverType;
|
||||
if (sendingNode->getType() == NodeType::VoxelServer) {
|
||||
jurisdiction = &_voxelServerJurisdictions;
|
||||
} else {
|
||||
serverType = "Voxel";
|
||||
} else if (sendingNode->getType() == NodeType::ParticleServer) {
|
||||
jurisdiction = &_particleServerJurisdictions;
|
||||
serverType = "Particle";
|
||||
} else {
|
||||
jurisdiction = &_modelServerJurisdictions;
|
||||
serverType = "Model";
|
||||
}
|
||||
|
||||
|
||||
if (jurisdiction->find(nodeUUID) == jurisdiction->end()) {
|
||||
qDebug("stats from new server... v[%f, %f, %f, %f]",
|
||||
rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s);
|
||||
qDebug("stats from new %s server... [%f, %f, %f, %f]",
|
||||
qPrintable(serverType), rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s);
|
||||
|
||||
// Add the jurisditionDetails object to the list of "fade outs"
|
||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::DontFadeOnVoxelServerChanges)) {
|
||||
|
@ -3325,31 +3397,23 @@ void Application::reloadAllScripts() {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::toggleRunningScriptsWidget()
|
||||
{
|
||||
if (!_runningScriptsWidget->toggleViewAction()->isChecked()) {
|
||||
_runningScriptsWidget->move(_window->geometry().topLeft().x(), _window->geometry().topLeft().y());
|
||||
_runningScriptsWidget->resize(0, _window->height());
|
||||
_runningScriptsWidget->toggleViewAction()->trigger();
|
||||
_runningScriptsWidget->grabKeyboard();
|
||||
void Application::manageRunningScriptsWidgetVisibility(bool shown) {
|
||||
if (_runningScriptsWidgetWasVisible && shown) {
|
||||
_runningScriptsWidget->show();
|
||||
} else if (_runningScriptsWidgetWasVisible && !shown) {
|
||||
_runningScriptsWidget->hide();
|
||||
}
|
||||
}
|
||||
|
||||
QPropertyAnimation* slideAnimation = new QPropertyAnimation(_runningScriptsWidget, "geometry", _runningScriptsWidget);
|
||||
slideAnimation->setStartValue(_runningScriptsWidget->geometry());
|
||||
slideAnimation->setEndValue(QRect(_window->geometry().topLeft().x(), _window->geometry().topLeft().y(),
|
||||
310, _runningScriptsWidget->height()));
|
||||
slideAnimation->setDuration(250);
|
||||
slideAnimation->start(QAbstractAnimation::DeleteWhenStopped);
|
||||
void Application::toggleRunningScriptsWidget() {
|
||||
if (_runningScriptsWidgetWasVisible) {
|
||||
_runningScriptsWidget->hide();
|
||||
_runningScriptsWidgetWasVisible = false;
|
||||
} else {
|
||||
_runningScriptsWidget->releaseKeyboard();
|
||||
|
||||
QPropertyAnimation* slideAnimation = new QPropertyAnimation(_runningScriptsWidget, "geometry", _runningScriptsWidget);
|
||||
slideAnimation->setStartValue(_runningScriptsWidget->geometry());
|
||||
slideAnimation->setEndValue(QRect(_window->geometry().topLeft().x(), _window->geometry().topLeft().y(),
|
||||
0, _runningScriptsWidget->height()));
|
||||
slideAnimation->setDuration(250);
|
||||
slideAnimation->start(QAbstractAnimation::DeleteWhenStopped);
|
||||
|
||||
QTimer::singleShot(260, _runningScriptsWidget->toggleViewAction(), SLOT(trigger()));
|
||||
_runningScriptsWidget->setBoundary(QRect(_window->geometry().topLeft(),
|
||||
_window->size()));
|
||||
_runningScriptsWidget->show();
|
||||
_runningScriptsWidgetWasVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3394,6 +3458,9 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
|
|||
scriptEngine->getParticlesScriptingInterface()->setPacketSender(&_particleEditSender);
|
||||
scriptEngine->getParticlesScriptingInterface()->setParticleTree(_particles.getTree());
|
||||
|
||||
scriptEngine->getModelsScriptingInterface()->setPacketSender(&_modelEditSender);
|
||||
scriptEngine->getModelsScriptingInterface()->setModelTree(_models.getTree());
|
||||
|
||||
// hook our avatar object into this script engine
|
||||
scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features
|
||||
|
||||
|
@ -3406,6 +3473,15 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
|
|||
connect(scriptEngine, SIGNAL(finished(const QString&)), clipboardScriptable, SLOT(deleteLater()));
|
||||
|
||||
scriptEngine->registerGlobalObject("Overlays", &_overlays);
|
||||
|
||||
QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance());
|
||||
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||
LocationScriptingInterface::locationSetter, windowValue);
|
||||
|
||||
// register `location` on the global object.
|
||||
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||
LocationScriptingInterface::locationSetter);
|
||||
|
||||
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
|
||||
scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
|
||||
scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance());
|
||||
|
@ -3438,9 +3514,8 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
|
|||
return scriptEngine;
|
||||
}
|
||||
|
||||
void Application::loadDialog() {
|
||||
QString Application::getPreviousScriptLocation() {
|
||||
QString suggestedName;
|
||||
|
||||
if (_previousScriptLocation.isEmpty()) {
|
||||
QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
// Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475
|
||||
|
@ -3450,14 +3525,22 @@ void Application::loadDialog() {
|
|||
} else {
|
||||
suggestedName = _previousScriptLocation;
|
||||
}
|
||||
return suggestedName;
|
||||
}
|
||||
|
||||
QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName,
|
||||
void Application::setPreviousScriptLocation(const QString& previousScriptLocation) {
|
||||
_previousScriptLocation = previousScriptLocation;
|
||||
QMutexLocker locker(&_settingsMutex);
|
||||
_settings->setValue("LastScriptLocation", _previousScriptLocation);
|
||||
}
|
||||
|
||||
void Application::loadDialog() {
|
||||
|
||||
QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"),
|
||||
getPreviousScriptLocation(),
|
||||
tr("JavaScript Files (*.js)"));
|
||||
if (!fileNameString.isEmpty()) {
|
||||
_previousScriptLocation = fileNameString;
|
||||
QMutexLocker locker(&_settingsMutex);
|
||||
_settings->setValue("LastScriptLocation", _previousScriptLocation);
|
||||
|
||||
setPreviousScriptLocation(fileNameString);
|
||||
loadScript(fileNameString);
|
||||
}
|
||||
}
|
||||
|
@ -3576,34 +3659,33 @@ void Application::takeSnapshot() {
|
|||
void Application::urlGoTo(int argc, const char * constArgv[]) {
|
||||
//Gets the url (hifi://domain/destination/orientation)
|
||||
QString customUrl = getCmdOption(argc, constArgv, "-url");
|
||||
|
||||
if (customUrl.startsWith("hifi://")) {
|
||||
if(customUrl.startsWith(CUSTOM_URL_SCHEME + "//")) {
|
||||
QStringList urlParts = customUrl.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts);
|
||||
if (urlParts.count() > 1) {
|
||||
if (urlParts.count() == 1) {
|
||||
// location coordinates or place name
|
||||
QString domain = urlParts[0];
|
||||
Menu::goToDomain(domain);
|
||||
} else if (urlParts.count() > 1) {
|
||||
// if url has 2 or more parts, the first one is domain name
|
||||
QString domain = urlParts[0];
|
||||
|
||||
|
||||
// second part is either a destination coordinate or
|
||||
// a place name
|
||||
QString destination = urlParts[1];
|
||||
|
||||
|
||||
// any third part is an avatar orientation.
|
||||
QString orientation = urlParts.count() > 2 ? urlParts[2] : QString();
|
||||
|
||||
|
||||
Menu::goToDomain(domain);
|
||||
|
||||
|
||||
// goto either @user, #place, or x-xx,y-yy,z-zz
|
||||
// style co-ordinate.
|
||||
Menu::goTo(destination);
|
||||
|
||||
|
||||
if (!orientation.isEmpty()) {
|
||||
// location orientation
|
||||
Menu::goToOrientation(orientation);
|
||||
}
|
||||
} else if (urlParts.count() == 1) {
|
||||
// location coordinates or place name
|
||||
QString destination = urlParts[0];
|
||||
Menu::goTo(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,9 +25,11 @@
|
|||
#include <QSet>
|
||||
#include <QSettings>
|
||||
#include <QStringList>
|
||||
#include <QHash>
|
||||
#include <QTouchEvent>
|
||||
#include <QUndoStack>
|
||||
|
||||
#include <ModelEditPacketSender.h>
|
||||
#include <NetworkPacket.h>
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
|
@ -38,6 +40,7 @@
|
|||
#include <ViewFrustum.h>
|
||||
#include <VoxelEditPacketSender.h>
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "Audio.h"
|
||||
#include "AudioReflector.h"
|
||||
#include "BuckyBalls.h"
|
||||
|
@ -49,7 +52,6 @@
|
|||
#include "Menu.h"
|
||||
#include "MetavoxelSystem.h"
|
||||
#include "PacketHeaders.h"
|
||||
#include "ParticleTreeRenderer.h"
|
||||
#include "Stars.h"
|
||||
#include "avatar/Avatar.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
|
@ -58,6 +60,8 @@
|
|||
#include "devices/Faceshift.h"
|
||||
#include "devices/SixenseManager.h"
|
||||
#include "devices/Visage.h"
|
||||
#include "models/ModelTreeRenderer.h"
|
||||
#include "particles/ParticleTreeRenderer.h"
|
||||
#include "renderer/AmbientOcclusionEffect.h"
|
||||
#include "renderer/GeometryCache.h"
|
||||
#include "renderer/GlowEffect.h"
|
||||
|
@ -85,7 +89,6 @@ class QAction;
|
|||
class QActionGroup;
|
||||
class QGLWidget;
|
||||
class QKeyEvent;
|
||||
class QMainWindow;
|
||||
class QMouseEvent;
|
||||
class QNetworkAccessManager;
|
||||
class QSettings;
|
||||
|
@ -124,6 +127,8 @@ public:
|
|||
void restoreSizeAndPosition();
|
||||
ScriptEngine* loadScript(const QString& fileNameString, bool loadScriptFromEditor = false);
|
||||
void loadScripts();
|
||||
QString getPreviousScriptLocation();
|
||||
void setPreviousScriptLocation(const QString& previousScriptLocation);
|
||||
void storeSizeAndPosition();
|
||||
void clearScriptsBeforeRunning();
|
||||
void saveScripts();
|
||||
|
@ -136,6 +141,7 @@ public:
|
|||
void keyReleaseEvent(QKeyEvent* event);
|
||||
|
||||
void focusOutEvent(QFocusEvent* event);
|
||||
void focusInEvent(QFocusEvent* event);
|
||||
|
||||
void mouseMoveEvent(QMouseEvent* event);
|
||||
void mousePressEvent(QMouseEvent* event);
|
||||
|
@ -173,6 +179,7 @@ public:
|
|||
VoxelTree* getVoxelTree() { return _voxels.getTree(); }
|
||||
ParticleTreeRenderer* getParticles() { return &_particles; }
|
||||
MetavoxelSystem* getMetavoxels() { return &_metavoxels; }
|
||||
ModelTreeRenderer* getModels() { return &_models; }
|
||||
bool getImportSucceded() { return _importSucceded; }
|
||||
VoxelSystem* getSharedVoxelSystem() { return &_sharedVoxelSystem; }
|
||||
VoxelTree* getClipboard() { return &_clipboard; }
|
||||
|
@ -192,10 +199,10 @@ public:
|
|||
/// if you need to access the application settings, use lockSettings()/unlockSettings()
|
||||
QSettings* lockSettings() { _settingsMutex.lock(); return _settings; }
|
||||
void unlockSettings() { _settingsMutex.unlock(); }
|
||||
|
||||
|
||||
void saveSettings();
|
||||
|
||||
QMainWindow* getWindow() { return _window; }
|
||||
MainWindow* getWindow() { return _window; }
|
||||
NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; }
|
||||
void lockOctreeSceneStats() { _octreeSceneStatsLock.lockForRead(); }
|
||||
void unlockOctreeSceneStats() { _octreeSceneStatsLock.unlock(); }
|
||||
|
@ -243,6 +250,7 @@ public:
|
|||
glm::vec2 getViewportDimensions() const{ return glm::vec2(_glWidget->width(),_glWidget->height()); }
|
||||
NodeToJurisdictionMap& getVoxelServerJurisdictions() { return _voxelServerJurisdictions; }
|
||||
NodeToJurisdictionMap& getParticleServerJurisdictions() { return _particleServerJurisdictions; }
|
||||
NodeToJurisdictionMap& getModelServerJurisdictions() { return _modelServerJurisdictions; }
|
||||
void pasteVoxelsToOctalCode(const unsigned char* octalCodeDestination);
|
||||
|
||||
void skipVersion(QString latestVersion);
|
||||
|
@ -312,6 +320,8 @@ private slots:
|
|||
|
||||
void parseVersionXml();
|
||||
|
||||
void manageRunningScriptsWidgetVisibility(bool shown);
|
||||
|
||||
private:
|
||||
void resetCamerasOnResizeGL(Camera& camera, int width, int height);
|
||||
void updateProjectionMatrix();
|
||||
|
@ -372,7 +382,7 @@ private:
|
|||
|
||||
void displayRearMirrorTools();
|
||||
|
||||
QMainWindow* _window;
|
||||
MainWindow* _window;
|
||||
GLCanvas* _glWidget; // our GLCanvas has a couple extra features
|
||||
|
||||
BandwidthMeter _bandwidthMeter;
|
||||
|
@ -386,7 +396,7 @@ private:
|
|||
int _numChangedSettings;
|
||||
|
||||
QUndoStack _undoStack;
|
||||
|
||||
|
||||
glm::vec3 _gravity;
|
||||
|
||||
// Frame Rate Measurement
|
||||
|
@ -411,6 +421,8 @@ private:
|
|||
ParticleTreeRenderer _particles;
|
||||
ParticleCollisionSystem _particleCollisionSystem;
|
||||
|
||||
ModelTreeRenderer _models;
|
||||
|
||||
QByteArray _voxelsFilename;
|
||||
bool _wantToKillLocalVoxels;
|
||||
|
||||
|
@ -431,7 +443,7 @@ private:
|
|||
Faceplus _faceplus;
|
||||
Faceshift _faceshift;
|
||||
Visage _visage;
|
||||
|
||||
|
||||
SixenseManager _sixenseManager;
|
||||
|
||||
Camera _myCamera; // My view onto the world
|
||||
|
@ -488,6 +500,7 @@ private:
|
|||
VoxelHideShowThread _voxelHideShowThread;
|
||||
VoxelEditPacketSender _voxelEditSender;
|
||||
ParticleEditPacketSender _particleEditSender;
|
||||
ModelEditPacketSender _modelEditSender;
|
||||
|
||||
int _packetsPerSecond;
|
||||
int _bytesPerSecond;
|
||||
|
@ -500,6 +513,7 @@ private:
|
|||
|
||||
NodeToJurisdictionMap _voxelServerJurisdictions;
|
||||
NodeToJurisdictionMap _particleServerJurisdictions;
|
||||
NodeToJurisdictionMap _modelServerJurisdictions;
|
||||
NodeToOctreeSceneStats _octreeServerSceneStats;
|
||||
QReadWriteLock _octreeSceneStatsLock;
|
||||
|
||||
|
@ -519,9 +533,11 @@ private:
|
|||
TouchEvent _lastTouchEvent;
|
||||
|
||||
Overlays _overlays;
|
||||
|
||||
AudioReflector _audioReflector;
|
||||
RunningScriptsWidget* _runningScriptsWidget;
|
||||
QHash<QString, ScriptEngine*> _scriptEnginesHash;
|
||||
bool _runningScriptsWidgetWasVisible;
|
||||
};
|
||||
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include "Application.h"
|
||||
#include "Menu.h"
|
||||
#include "ui/OAuthWebViewHandler.h"
|
||||
|
||||
#include "DatagramProcessor.h"
|
||||
|
||||
|
@ -56,9 +57,15 @@ void DatagramProcessor::processDatagrams() {
|
|||
Particle::handleAddParticleResponse(incomingPacket);
|
||||
application->getParticles()->getTree()->handleAddParticleResponse(incomingPacket);
|
||||
break;
|
||||
|
||||
case PacketTypeModelAddResponse:
|
||||
// this will keep creatorTokenIDs to IDs mapped correctly
|
||||
ModelItem::handleAddModelResponse(incomingPacket);
|
||||
application->getModels()->getTree()->handleAddModelResponse(incomingPacket);
|
||||
break;
|
||||
case PacketTypeParticleData:
|
||||
case PacketTypeParticleErase:
|
||||
case PacketTypeModelData:
|
||||
case PacketTypeModelErase:
|
||||
case PacketTypeVoxelData:
|
||||
case PacketTypeVoxelErase:
|
||||
case PacketTypeOctreeStats:
|
||||
|
@ -112,6 +119,18 @@ void DatagramProcessor::processDatagrams() {
|
|||
application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size());
|
||||
break;
|
||||
}
|
||||
case PacketTypeDomainOAuthRequest: {
|
||||
QDataStream readStream(incomingPacket);
|
||||
readStream.skipRawData(numBytesForPacketHeader(incomingPacket));
|
||||
|
||||
QUrl authorizationURL;
|
||||
readStream >> authorizationURL;
|
||||
|
||||
QMetaObject::invokeMethod(&OAuthWebViewHandler::getInstance(), "displayWebviewForAuthorizationURL",
|
||||
Q_ARG(const QUrl&, authorizationURL));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
nodeList->processNodeData(senderSockAddr, incomingPacket);
|
||||
break;
|
||||
|
|
|
@ -24,8 +24,8 @@ GLCanvas::GLCanvas() : QGLWidget(QGLFormat(QGL::NoDepthBuffer)),
|
|||
{
|
||||
}
|
||||
|
||||
bool GLCanvas::isThrottleRendering() const {
|
||||
return _throttleRendering || Application::getInstance()->getWindow()->isMinimized();
|
||||
bool GLCanvas::isThrottleRendering() const {
|
||||
return _throttleRendering || Application::getInstance()->getWindow()->isMinimized();
|
||||
}
|
||||
|
||||
void GLCanvas::initializeGL() {
|
||||
|
@ -34,7 +34,7 @@ void GLCanvas::initializeGL() {
|
|||
setAcceptDrops(true);
|
||||
connect(Application::getInstance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(activeChanged(Qt::ApplicationState)));
|
||||
connect(&_frameTimer, SIGNAL(timeout()), this, SLOT(throttleRender()));
|
||||
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
@ -81,14 +81,14 @@ void GLCanvas::activeChanged(Qt::ApplicationState state) {
|
|||
_frameTimer.stop();
|
||||
_throttleRendering = false;
|
||||
break;
|
||||
|
||||
|
||||
case Qt::ApplicationSuspended:
|
||||
case Qt::ApplicationHidden:
|
||||
// If we're hidden or are about to suspend, don't render anything.
|
||||
_throttleRendering = false;
|
||||
_frameTimer.stop();
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
// Otherwise, throttle.
|
||||
if (!_throttleRendering) {
|
||||
|
|
|
@ -22,31 +22,31 @@ public:
|
|||
GLCanvas();
|
||||
bool isThrottleRendering() const;
|
||||
protected:
|
||||
|
||||
|
||||
QTimer _frameTimer;
|
||||
bool _throttleRendering;
|
||||
int _idleRenderInterval;
|
||||
|
||||
|
||||
virtual void initializeGL();
|
||||
virtual void paintGL();
|
||||
virtual void resizeGL(int width, int height);
|
||||
|
||||
|
||||
virtual void keyPressEvent(QKeyEvent* event);
|
||||
virtual void keyReleaseEvent(QKeyEvent* event);
|
||||
|
||||
|
||||
virtual void focusOutEvent(QFocusEvent* event);
|
||||
|
||||
|
||||
virtual void mouseMoveEvent(QMouseEvent* event);
|
||||
virtual void mousePressEvent(QMouseEvent* event);
|
||||
virtual void mouseReleaseEvent(QMouseEvent* event);
|
||||
|
||||
|
||||
virtual bool event(QEvent* event);
|
||||
|
||||
|
||||
virtual void wheelEvent(QWheelEvent* event);
|
||||
|
||||
virtual void dragEnterEvent(QDragEnterEvent *event);
|
||||
virtual void dropEvent(QDropEvent* event);
|
||||
|
||||
|
||||
private slots:
|
||||
void activeChanged(Qt::ApplicationState state);
|
||||
void throttleRender();
|
||||
|
|
72
interface/src/MainWindow.cpp
Normal file
72
interface/src/MainWindow.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// MainWindow.cpp
|
||||
// interface
|
||||
//
|
||||
// Created by Mohammed Nafees on 04/06/2014.
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "Menu.h"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QMoveEvent>
|
||||
#include <QResizeEvent>
|
||||
#include <QShowEvent>
|
||||
#include <QHideEvent>
|
||||
#include <QWindowStateChangeEvent>
|
||||
|
||||
MainWindow::MainWindow(QWidget* parent) :
|
||||
QMainWindow(parent) {
|
||||
}
|
||||
|
||||
void MainWindow::moveEvent(QMoveEvent* event) {
|
||||
emit windowGeometryChanged(QRect(event->pos(), size()));
|
||||
QMainWindow::moveEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::resizeEvent(QResizeEvent* event) {
|
||||
emit windowGeometryChanged(QRect(QPoint(x(), y()), event->size()));
|
||||
QMainWindow::resizeEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::showEvent(QShowEvent* event) {
|
||||
if (event->spontaneous()) {
|
||||
emit windowShown(true);
|
||||
}
|
||||
QMainWindow::showEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::hideEvent(QHideEvent* event) {
|
||||
if (event->spontaneous()) {
|
||||
emit windowShown(false);
|
||||
}
|
||||
QMainWindow::hideEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::WindowStateChange) {
|
||||
QWindowStateChangeEvent* stateChangeEvent = static_cast<QWindowStateChangeEvent*>(event);
|
||||
if ((stateChangeEvent->oldState() == Qt::WindowNoState ||
|
||||
stateChangeEvent->oldState() == Qt::WindowMaximized) &&
|
||||
windowState() == Qt::WindowMinimized) {
|
||||
emit windowShown(false);
|
||||
} else {
|
||||
emit windowShown(true);
|
||||
}
|
||||
|
||||
if (isFullScreen() != Menu::getInstance()->isOptionChecked(MenuOption::Fullscreen)) {
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::Fullscreen, isFullScreen());
|
||||
}
|
||||
} else if (event->type() == QEvent::ActivationChange) {
|
||||
if (isActiveWindow()) {
|
||||
emit windowShown(true);
|
||||
} else {
|
||||
emit windowShown(false);
|
||||
}
|
||||
}
|
||||
QMainWindow::changeEvent(event);
|
||||
}
|
35
interface/src/MainWindow.h
Normal file
35
interface/src/MainWindow.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// MainWindow.h
|
||||
// interface
|
||||
//
|
||||
// Created by Mohammed Nafees on 04/06/2014.
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef __hifi__MainWindow__
|
||||
#define __hifi__MainWindow__
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MainWindow(QWidget* parent = NULL);
|
||||
|
||||
signals:
|
||||
void windowGeometryChanged(QRect geometry);
|
||||
void windowShown(bool shown);
|
||||
|
||||
protected:
|
||||
virtual void moveEvent(QMoveEvent* event);
|
||||
virtual void resizeEvent(QResizeEvent* event);
|
||||
virtual void showEvent(QShowEvent* event);
|
||||
virtual void hideEvent(QHideEvent* event);
|
||||
virtual void changeEvent(QEvent* event);
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__MainWindow__) */
|
|
@ -40,6 +40,7 @@
|
|||
#include "ui/InfoView.h"
|
||||
#include "ui/MetavoxelEditor.h"
|
||||
#include "ui/ModelsBrowser.h"
|
||||
#include "ui/LoginDialog.h"
|
||||
|
||||
|
||||
Menu* Menu::_instance = NULL;
|
||||
|
@ -169,12 +170,12 @@ Menu::Menu() :
|
|||
|
||||
|
||||
QMenu* editMenu = addMenu("Edit");
|
||||
|
||||
|
||||
QUndoStack* undoStack = Application::getInstance()->getUndoStack();
|
||||
QAction* undoAction = undoStack->createUndoAction(editMenu);
|
||||
undoAction->setShortcut(Qt::CTRL | Qt::Key_Z);
|
||||
addActionToQMenuAndActionHash(editMenu, undoAction);
|
||||
|
||||
|
||||
QAction* redoAction = undoStack->createRedoAction(editMenu);
|
||||
redoAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Z);
|
||||
addActionToQMenuAndActionHash(editMenu, redoAction);
|
||||
|
@ -279,8 +280,9 @@ Menu::Menu() :
|
|||
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Models, 0, true);
|
||||
addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
|
||||
|
||||
QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxel Options");
|
||||
|
@ -320,7 +322,7 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Visage, 0, true,
|
||||
appInstance->getVisage(), SLOT(updateEnabled()));
|
||||
#endif
|
||||
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::GlowWhenSpeaking, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false);
|
||||
|
||||
|
@ -334,7 +336,6 @@ Menu::Menu() :
|
|||
SLOT(setFilter(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::PlaySlaps, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandsCollideWithSelf, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, true);
|
||||
|
@ -723,31 +724,31 @@ QAction* Menu::addActionToQMenuAndActionHash(QMenu* destinationMenu,
|
|||
QAction::MenuRole role,
|
||||
int menuItemLocation) {
|
||||
QAction* actionBefore = NULL;
|
||||
|
||||
|
||||
if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) {
|
||||
actionBefore = destinationMenu->actions()[menuItemLocation];
|
||||
}
|
||||
|
||||
|
||||
if (!actionName.isEmpty()) {
|
||||
action->setText(actionName);
|
||||
}
|
||||
|
||||
|
||||
if (shortcut != 0) {
|
||||
action->setShortcut(shortcut);
|
||||
}
|
||||
|
||||
|
||||
if (role != QAction::NoRole) {
|
||||
action->setMenuRole(role);
|
||||
}
|
||||
|
||||
|
||||
if (!actionBefore) {
|
||||
destinationMenu->addAction(action);
|
||||
} else {
|
||||
destinationMenu->insertAction(actionBefore, action);
|
||||
}
|
||||
|
||||
|
||||
_actionHash.insert(action->text(), action);
|
||||
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
|
@ -813,42 +814,12 @@ void sendFakeEnterEvent() {
|
|||
QCoreApplication::sendEvent(glWidget, &enterEvent);
|
||||
}
|
||||
|
||||
const int QLINE_MINIMUM_WIDTH = 400;
|
||||
const float DIALOG_RATIO_OF_WINDOW = 0.30f;
|
||||
|
||||
void Menu::loginForCurrentDomain() {
|
||||
QDialog loginDialog(Application::getInstance()->getWindow());
|
||||
loginDialog.setWindowTitle("Login");
|
||||
|
||||
QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom);
|
||||
loginDialog.setLayout(layout);
|
||||
loginDialog.setWindowFlags(Qt::Sheet);
|
||||
|
||||
QFormLayout* form = new QFormLayout();
|
||||
layout->addLayout(form, 1);
|
||||
|
||||
QLineEdit* loginLineEdit = new QLineEdit();
|
||||
loginLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
|
||||
form->addRow("Login:", loginLineEdit);
|
||||
|
||||
QLineEdit* passwordLineEdit = new QLineEdit();
|
||||
passwordLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
|
||||
passwordLineEdit->setEchoMode(QLineEdit::Password);
|
||||
form->addRow("Password:", passwordLineEdit);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
loginDialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
|
||||
loginDialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
|
||||
layout->addWidget(buttons);
|
||||
|
||||
int dialogReturn = loginDialog.exec();
|
||||
|
||||
if (dialogReturn == QDialog::Accepted && !loginLineEdit->text().isEmpty() && !passwordLineEdit->text().isEmpty()) {
|
||||
// attempt to get an access token given this username and password
|
||||
AccountManager::getInstance().requestAccessToken(loginLineEdit->text(), passwordLineEdit->text());
|
||||
}
|
||||
|
||||
sendFakeEnterEvent();
|
||||
LoginDialog* loginDialog = new LoginDialog(Application::getInstance()->getWindow());
|
||||
loginDialog->show();
|
||||
loginDialog->resizeAndPosition(false);
|
||||
}
|
||||
|
||||
void Menu::editPreferences() {
|
||||
|
@ -927,44 +898,53 @@ void Menu::goTo() {
|
|||
int dialogReturn = gotoDialog.exec();
|
||||
if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) {
|
||||
QString desiredDestination = gotoDialog.textValue();
|
||||
|
||||
if (desiredDestination.startsWith(CUSTOM_URL_SCHEME + "//")) {
|
||||
QStringList urlParts = desiredDestination.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts);
|
||||
|
||||
if (urlParts.count() > 1) {
|
||||
// if url has 2 or more parts, the first one is domain name
|
||||
QString domain = urlParts[0];
|
||||
|
||||
// second part is either a destination coordinate or
|
||||
// a place name
|
||||
QString destination = urlParts[1];
|
||||
|
||||
// any third part is an avatar orientation.
|
||||
QString orientation = urlParts.count() > 2 ? urlParts[2] : QString();
|
||||
|
||||
goToDomain(domain);
|
||||
|
||||
// goto either @user, #place, or x-xx,y-yy,z-zz
|
||||
// style co-ordinate.
|
||||
goTo(destination);
|
||||
|
||||
if (!orientation.isEmpty()) {
|
||||
// location orientation
|
||||
goToOrientation(orientation);
|
||||
}
|
||||
} else if (urlParts.count() == 1) {
|
||||
// location coordinates or place name
|
||||
QString destination = urlParts[0];
|
||||
goTo(destination);
|
||||
}
|
||||
|
||||
} else {
|
||||
goToUser(gotoDialog.textValue());
|
||||
if (!goToURL(desiredDestination)) {;
|
||||
goTo(desiredDestination);
|
||||
}
|
||||
}
|
||||
sendFakeEnterEvent();
|
||||
}
|
||||
|
||||
bool Menu::goToURL(QString location) {
|
||||
if (location.startsWith(CUSTOM_URL_SCHEME + "//")) {
|
||||
QStringList urlParts = location.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts);
|
||||
|
||||
if (urlParts.count() > 1) {
|
||||
// if url has 2 or more parts, the first one is domain name
|
||||
QString domain = urlParts[0];
|
||||
|
||||
// second part is either a destination coordinate or
|
||||
// a place name
|
||||
QString destination = urlParts[1];
|
||||
|
||||
// any third part is an avatar orientation.
|
||||
QString orientation = urlParts.count() > 2 ? urlParts[2] : QString();
|
||||
|
||||
goToDomain(domain);
|
||||
|
||||
// goto either @user, #place, or x-xx,y-yy,z-zz
|
||||
// style co-ordinate.
|
||||
goTo(destination);
|
||||
|
||||
if (!orientation.isEmpty()) {
|
||||
// location orientation
|
||||
goToOrientation(orientation);
|
||||
}
|
||||
} else if (urlParts.count() == 1) {
|
||||
QString destination = urlParts[0];
|
||||
|
||||
// If this starts with # or @, treat it as a user/location, otherwise treat it as a domain
|
||||
if (destination[0] == '#' || destination[0] == '@') {
|
||||
goTo(destination);
|
||||
} else {
|
||||
goToDomain(destination);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Menu::goToUser(const QString& user) {
|
||||
LocationManager* manager = &LocationManager::getInstance();
|
||||
manager->goTo(user);
|
||||
|
@ -1119,7 +1099,7 @@ void Menu::toggleLoginMenuItem() {
|
|||
|
||||
if (accountManager.isLoggedIn()) {
|
||||
// change the menu item to logout
|
||||
_loginAction->setText("Logout " + accountManager.getUsername());
|
||||
_loginAction->setText("Logout " + accountManager.getAccountInfo().getUsername());
|
||||
connect(_loginAction, &QAction::triggered, &accountManager, &AccountManager::logout);
|
||||
} else {
|
||||
// change the menu item to login
|
||||
|
@ -1274,7 +1254,7 @@ void Menu::autoAdjustLOD(float currentFPS) {
|
|||
_avatarLODDistanceMultiplier - DISTANCE_DECREASE_RATE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool changed = false;
|
||||
quint64 elapsed = now - _lastAdjust;
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
const float ADJUST_LOD_DOWN_FPS = 40.0;
|
||||
const float ADJUST_LOD_UP_FPS = 55.0;
|
||||
const float DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS = 30.0f;
|
||||
const float DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS = 30.0f;
|
||||
|
||||
const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 5;
|
||||
const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2;
|
||||
|
@ -79,7 +79,7 @@ public:
|
|||
|
||||
void triggerOption(const QString& menuOption);
|
||||
QAction* getActionForOption(const QString& menuOption);
|
||||
|
||||
|
||||
float getAudioJitterBufferSamples() const { return _audioJitterBufferSamples; }
|
||||
void setAudioJitterBufferSamples(float audioJitterBufferSamples) { _audioJitterBufferSamples = audioJitterBufferSamples; }
|
||||
float getFieldOfView() const { return _fieldOfView; }
|
||||
|
@ -133,7 +133,7 @@ public:
|
|||
const QKeySequence& shortcut = 0,
|
||||
QAction::MenuRole role = QAction::NoRole,
|
||||
int menuItemLocation = UNSPECIFIED_POSITION);
|
||||
|
||||
|
||||
void removeAction(QMenu* menu, const QString& actionName);
|
||||
|
||||
bool static goToDestination(QString destination);
|
||||
|
@ -152,6 +152,7 @@ public slots:
|
|||
void importSettings();
|
||||
void exportSettings();
|
||||
void goTo();
|
||||
bool goToURL(QString location);
|
||||
void goToUser(const QString& user);
|
||||
void pasteToVoxel();
|
||||
void openUrl(const QUrl& url);
|
||||
|
@ -276,9 +277,6 @@ namespace MenuOption {
|
|||
const QString AudioSpatialProcessingWithDiffusions = "With Diffusions";
|
||||
const QString AudioSpatialProcessingDontDistanceAttenuate = "Don't calculate distance attenuation";
|
||||
const QString AudioSpatialProcessingAlternateDistanceAttenuate = "Alternate distance attenuation";
|
||||
|
||||
|
||||
|
||||
const QString Avatars = "Avatars";
|
||||
const QString Bandwidth = "Bandwidth Display";
|
||||
const QString BandwidthDetails = "Bandwidth Details";
|
||||
|
@ -330,6 +328,7 @@ namespace MenuOption {
|
|||
const QString MetavoxelEditor = "Metavoxel Editor...";
|
||||
const QString Metavoxels = "Metavoxels";
|
||||
const QString Mirror = "Mirror";
|
||||
const QString Models = "Models";
|
||||
const QString MoveWithLean = "Move with Lean";
|
||||
const QString MuteAudio = "Mute Microphone";
|
||||
const QString NameLocation = "Name this location";
|
||||
|
@ -341,7 +340,6 @@ namespace MenuOption {
|
|||
const QString Particles = "Particles";
|
||||
const QString PasteToVoxel = "Paste to Voxel...";
|
||||
const QString PipelineWarnings = "Show Render Pipeline Warnings";
|
||||
const QString PlaySlaps = "Play Slaps";
|
||||
const QString Preferences = "Preferences...";
|
||||
const QString Quit = "Quit";
|
||||
const QString ReloadAllScripts = "Reload All Scripts";
|
||||
|
|
|
@ -9,21 +9,29 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDebug>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHttpMultiPart>
|
||||
#include <QImage>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QStandardPaths>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTextStream>
|
||||
#include <QVBoxLayout>
|
||||
#include <QVariant>
|
||||
|
||||
#include <AccountManager.h>
|
||||
|
||||
#include <FBXReader.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "ModelUploader.h"
|
||||
|
||||
|
@ -32,6 +40,10 @@ static const QString NAME_FIELD = "name";
|
|||
static const QString FILENAME_FIELD = "filename";
|
||||
static const QString TEXDIR_FIELD = "texdir";
|
||||
static const QString LOD_FIELD = "lod";
|
||||
static const QString JOINT_INDEX_FIELD = "jointIndex";
|
||||
static const QString SCALE_FIELD = "scale";
|
||||
static const QString JOINT_FIELD = "joint";
|
||||
static const QString FREE_JOINT_FIELD = "freeJoint";
|
||||
|
||||
static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
|
||||
static const QString DATA_SERVER_URL = "https://data-web.highfidelity.io";
|
||||
|
@ -40,6 +52,7 @@ static const QString MODEL_URL = "/api/v1/models";
|
|||
static const QString SETTING_NAME = "LastModelUploadLocation";
|
||||
|
||||
static const int MAX_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||
static const int MAX_TEXTURE_SIZE = 1024;
|
||||
static const int TIMEOUT = 1000;
|
||||
static const int MAX_CHECK = 30;
|
||||
|
||||
|
@ -76,7 +89,8 @@ bool ModelUploader::zip() {
|
|||
}
|
||||
|
||||
|
||||
QString filename = QFileDialog::getOpenFileName(NULL, "Select your .fst file ...", lastLocation, "*.fst");
|
||||
QString filename = QFileDialog::getOpenFileName(NULL, "Select your model file ...",
|
||||
lastLocation, "Model files (*.fst *.fbx)");
|
||||
if (filename == "") {
|
||||
// If the user canceled we return.
|
||||
Application::getInstance()->unlockSettings();
|
||||
|
@ -85,89 +99,161 @@ bool ModelUploader::zip() {
|
|||
settings->setValue(SETTING_NAME, filename);
|
||||
Application::getInstance()->unlockSettings();
|
||||
|
||||
bool _nameIsPresent = false;
|
||||
QString texDir;
|
||||
// First we check the FST file (if any)
|
||||
QFile* fst;
|
||||
QVariantHash mapping;
|
||||
QString basePath;
|
||||
QString fbxFile;
|
||||
if (filename.toLower().endsWith(".fst")) {
|
||||
fst = new QFile(filename, this);
|
||||
if (!fst->open(QFile::ReadOnly | QFile::Text)) {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("Could not open FST file."),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("Could not open FST file.");
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Reading FST file : " << QFileInfo(*fst).filePath();
|
||||
mapping = readMapping(fst->readAll());
|
||||
basePath = QFileInfo(*fst).path();
|
||||
fbxFile = basePath + "/" + mapping.value(FILENAME_FIELD).toString();
|
||||
QFileInfo fbxInfo(fbxFile);
|
||||
if (!fbxInfo.exists() || !fbxInfo.isFile()) { // Check existence
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(fbxInfo.fileName());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
fst = new QTemporaryFile(this);
|
||||
fst->open(QFile::WriteOnly);
|
||||
fbxFile = filename;
|
||||
basePath = QFileInfo(filename).path();
|
||||
mapping.insert(FILENAME_FIELD, QFileInfo(filename).fileName());
|
||||
}
|
||||
|
||||
// open the fbx file
|
||||
QFile fbx(fbxFile);
|
||||
if (!fbx.open(QIODevice::ReadOnly)) {
|
||||
return false;
|
||||
}
|
||||
QByteArray fbxContents = fbx.readAll();
|
||||
FBXGeometry geometry = readFBX(fbxContents, QVariantHash());
|
||||
|
||||
// First we check the FST file
|
||||
QFile fst(filename);
|
||||
if (!fst.open(QFile::ReadOnly | QFile::Text)) {
|
||||
// make sure we have some basic mappings
|
||||
if (!mapping.contains(NAME_FIELD)) {
|
||||
mapping.insert(NAME_FIELD, QFileInfo(filename).baseName());
|
||||
}
|
||||
if (!mapping.contains(TEXDIR_FIELD)) {
|
||||
mapping.insert(TEXDIR_FIELD, ".");
|
||||
}
|
||||
|
||||
// mixamo/autodesk defaults
|
||||
if (!mapping.contains(SCALE_FIELD)) {
|
||||
mapping.insert(SCALE_FIELD, geometry.author == "www.makehuman.org" ? 150.0 : 15.0);
|
||||
}
|
||||
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
|
||||
if (!joints.contains("jointEyeLeft")) {
|
||||
joints.insert("jointEyeLeft", geometry.jointIndices.contains("EyeLeft") ? "EyeLeft" : "LeftEye");
|
||||
}
|
||||
if (!joints.contains("jointEyeRight")) {
|
||||
joints.insert("jointEyeRight", geometry.jointIndices.contains("EyeRight") ? "EyeRight" : "RightEye");
|
||||
}
|
||||
if (!joints.contains("jointNeck")) {
|
||||
joints.insert("jointNeck", "Neck");
|
||||
}
|
||||
if (!joints.contains("jointRoot")) {
|
||||
joints.insert("jointRoot", "Hips");
|
||||
}
|
||||
if (!joints.contains("jointLean")) {
|
||||
joints.insert("jointLean", "Spine");
|
||||
}
|
||||
if (!joints.contains("jointHead")) {
|
||||
const char* topName = (geometry.applicationName == "mixamo.com") ? "HeadTop_End" : "HeadEnd";
|
||||
joints.insert("jointHead", geometry.jointIndices.contains(topName) ? topName : "Head");
|
||||
}
|
||||
if (!joints.contains("jointLeftHand")) {
|
||||
joints.insert("jointLeftHand", "LeftHand");
|
||||
}
|
||||
if (!joints.contains("jointRightHand")) {
|
||||
joints.insert("jointRightHand", "RightHand");
|
||||
}
|
||||
mapping.insert(JOINT_FIELD, joints);
|
||||
if (!mapping.contains(FREE_JOINT_FIELD)) {
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightArm");
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm");
|
||||
}
|
||||
|
||||
// open the dialog to configure the rest
|
||||
ModelPropertiesDialog properties(_isHead, mapping, basePath, geometry);
|
||||
if (properties.exec() == QDialog::Rejected) {
|
||||
return false;
|
||||
}
|
||||
mapping = properties.getMapping();
|
||||
|
||||
QByteArray nameField = mapping.value(NAME_FIELD).toByteArray();
|
||||
if (!nameField.isEmpty()) {
|
||||
QHttpPart textPart;
|
||||
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"model_name\"");
|
||||
textPart.setBody(nameField);
|
||||
_dataMultiPart->append(textPart);
|
||||
_url = S3_URL + ((_isHead)? "/models/heads/" : "/models/skeletons/") + nameField + ".fst";
|
||||
} else {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("Could not open FST file."),
|
||||
QString("Model name is missing in the .fst file."),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("Could not open FST file.");
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Reading FST file : " << QFileInfo(fst).filePath();
|
||||
|
||||
// Compress and copy the fst
|
||||
if (!addPart(QFileInfo(fst).filePath(), QString("fst"))) {
|
||||
qDebug() << "[Warning] " << QString("Model name is missing in the .fst file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let's read through the FST file
|
||||
QTextStream stream(&fst);
|
||||
QList<QString> line;
|
||||
while (!stream.atEnd()) {
|
||||
line = stream.readLine().split(QRegExp("[ =]"), QString::SkipEmptyParts);
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// according to what is read, we modify the command
|
||||
if (line[0] == NAME_FIELD) {
|
||||
QHttpPart textPart;
|
||||
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"
|
||||
" name=\"model_name\"");
|
||||
textPart.setBody(line[1].toUtf8());
|
||||
_dataMultiPart->append(textPart);
|
||||
_url = S3_URL + ((_isHead)? "/models/heads/" : "/models/skeletons/") + line[1].toUtf8() + ".fst";
|
||||
_nameIsPresent = true;
|
||||
} else if (line[0] == FILENAME_FIELD) {
|
||||
fbxFile = QFileInfo(fst).path() + "/" + line[1];
|
||||
QFileInfo fbxInfo(fbxFile);
|
||||
if (!fbxInfo.exists() || !fbxInfo.isFile()) { // Check existence
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(fbxInfo.fileName());
|
||||
return false;
|
||||
}
|
||||
// Compress and copy
|
||||
if (!addPart(fbxInfo.filePath(), "fbx")) {
|
||||
return false;
|
||||
}
|
||||
} else if (line[0] == TEXDIR_FIELD) { // Check existence
|
||||
texDir = QFileInfo(fst).path() + "/" + line[1];
|
||||
QFileInfo texInfo(texDir);
|
||||
if (!texInfo.exists() || !texInfo.isDir()) {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("Texture directory could not be found."),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("Texture directory could not be found.");
|
||||
return false;
|
||||
}
|
||||
} else if (line[0] == LOD_FIELD) {
|
||||
QFileInfo lod(QFileInfo(fst).path() + "/" + line[1]);
|
||||
if (!lod.exists() || !lod.isFile()) { // Check existence
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("LOD file %1 could not be found.").arg(lod.fileName()),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(lod.fileName());
|
||||
}
|
||||
// Compress and copy
|
||||
if (!addPart(lod.filePath(), QString("lod%1").arg(++_lodCount))) {
|
||||
return false;
|
||||
}
|
||||
QByteArray texdirField = mapping.value(TEXDIR_FIELD).toByteArray();
|
||||
QString texDir;
|
||||
if (!texdirField.isEmpty()) {
|
||||
texDir = basePath + "/" + texdirField;
|
||||
QFileInfo texInfo(texDir);
|
||||
if (!texInfo.exists() || !texInfo.isDir()) {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("Texture directory could not be found."),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("Texture directory could not be found.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!addTextures(texDir, fbxFile)) {
|
||||
QVariantHash lodField = mapping.value(LOD_FIELD).toHash();
|
||||
for (QVariantHash::const_iterator it = lodField.constBegin(); it != lodField.constEnd(); it++) {
|
||||
QFileInfo lod(basePath + "/" + it.key());
|
||||
if (!lod.exists() || !lod.isFile()) { // Check existence
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("LOD file %1 could not be found.").arg(lod.fileName()),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(lod.fileName());
|
||||
}
|
||||
// Compress and copy
|
||||
if (!addPart(lod.filePath(), QString("lod%1").arg(++_lodCount))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Write out, compress and copy the fst
|
||||
if (!addPart(*fst, writeMapping(mapping), QString("fst"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compress and copy the fbx
|
||||
if (!addPart(fbx, fbxContents, "fbx")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!addTextures(texDir, geometry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -181,15 +267,6 @@ bool ModelUploader::zip() {
|
|||
}
|
||||
_dataMultiPart->append(textPart);
|
||||
|
||||
if (!_nameIsPresent) {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("Model name is missing in the .fst file."),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("Model name is missing in the .fst file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
_readyToSend = true;
|
||||
return true;
|
||||
}
|
||||
|
@ -350,27 +427,18 @@ void ModelUploader::processCheck() {
|
|||
delete reply;
|
||||
}
|
||||
|
||||
bool ModelUploader::addTextures(const QString& texdir, const QString fbxFile) {
|
||||
QFile fbx(fbxFile);
|
||||
if (!fbx.open(QIODevice::ReadOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray buffer = fbx.readAll();
|
||||
QVariantHash variantHash = readMapping(buffer);
|
||||
FBXGeometry geometry = readFBX(buffer, variantHash);
|
||||
|
||||
bool ModelUploader::addTextures(const QString& texdir, const FBXGeometry& geometry) {
|
||||
foreach (FBXMesh mesh, geometry.meshes) {
|
||||
foreach (FBXMeshPart part, mesh.parts) {
|
||||
if (!part.diffuseTexture.filename.isEmpty()) {
|
||||
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty()) {
|
||||
if (!addPart(texdir + "/" + part.diffuseTexture.filename,
|
||||
QString("texture%1").arg(++_texturesCount))) {
|
||||
QString("texture%1").arg(++_texturesCount), true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!part.normalTexture.filename.isEmpty()) {
|
||||
if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty()) {
|
||||
if (!addPart(texdir + "/" + part.normalTexture.filename,
|
||||
QString("texture%1").arg(++_texturesCount))) {
|
||||
QString("texture%1").arg(++_texturesCount), true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -380,7 +448,7 @@ bool ModelUploader::addTextures(const QString& texdir, const QString fbxFile) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ModelUploader::addPart(const QString &path, const QString& name) {
|
||||
bool ModelUploader::addPart(const QString &path, const QString& name, bool isTexture) {
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
QMessageBox::warning(NULL,
|
||||
|
@ -390,7 +458,29 @@ bool ModelUploader::addPart(const QString &path, const QString& name) {
|
|||
qDebug() << "[Warning] " << QString("Could not open %1").arg(path);
|
||||
return false;
|
||||
}
|
||||
QByteArray buffer = qCompress(file.readAll());
|
||||
return addPart(file, file.readAll(), name, isTexture);
|
||||
}
|
||||
|
||||
bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const QString& name, bool isTexture) {
|
||||
QFileInfo fileInfo(file);
|
||||
QByteArray recodedContents = contents;
|
||||
if (isTexture) {
|
||||
QString extension = fileInfo.suffix().toLower();
|
||||
bool isJpeg = (extension == "jpg");
|
||||
bool mustRecode = !(isJpeg || extension == "png");
|
||||
QImage image = QImage::fromData(contents);
|
||||
if (image.width() > MAX_TEXTURE_SIZE || image.height() > MAX_TEXTURE_SIZE) {
|
||||
image = image.scaled(MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, Qt::KeepAspectRatio);
|
||||
mustRecode = true;
|
||||
}
|
||||
if (mustRecode) {
|
||||
QBuffer buffer;
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
image.save(&buffer, isJpeg ? "JPG" : "PNG");
|
||||
recodedContents = buffer.data();
|
||||
}
|
||||
}
|
||||
QByteArray buffer = qCompress(recodedContents);
|
||||
|
||||
// Qt's qCompress() default compression level (-1) is the standard zLib compression.
|
||||
// Here remove Qt's custom header that prevent the data server from uncompressing the files with zLib.
|
||||
|
@ -406,7 +496,7 @@ bool ModelUploader::addPart(const QString &path, const QString& name) {
|
|||
|
||||
|
||||
qDebug() << "File " << QFileInfo(file).fileName() << " added to model.";
|
||||
_totalSize += file.size();
|
||||
_totalSize += recodedContents.size();
|
||||
if (_totalSize > MAX_SIZE) {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
|
@ -420,8 +510,165 @@ bool ModelUploader::addPart(const QString &path, const QString& name) {
|
|||
return true;
|
||||
}
|
||||
|
||||
ModelPropertiesDialog::ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping,
|
||||
const QString& basePath, const FBXGeometry& geometry) :
|
||||
_isHead(isHead),
|
||||
_originalMapping(originalMapping),
|
||||
_basePath(basePath),
|
||||
_geometry(geometry) {
|
||||
|
||||
setWindowTitle("Set Model Properties");
|
||||
|
||||
QFormLayout* form = new QFormLayout();
|
||||
setLayout(form);
|
||||
|
||||
form->addRow("Name:", _name = new QLineEdit());
|
||||
|
||||
form->addRow("Texture Directory:", _textureDirectory = new QPushButton());
|
||||
connect(_textureDirectory, SIGNAL(clicked(bool)), SLOT(chooseTextureDirectory()));
|
||||
|
||||
form->addRow("Scale:", _scale = new QDoubleSpinBox());
|
||||
_scale->setMaximum(FLT_MAX);
|
||||
_scale->setSingleStep(0.01);
|
||||
|
||||
form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox());
|
||||
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
|
||||
form->addRow("Neck Joint:", _neckJoint = createJointBox());
|
||||
if (!isHead) {
|
||||
form->addRow("Root Joint:", _rootJoint = createJointBox());
|
||||
form->addRow("Lean Joint:", _leanJoint = createJointBox());
|
||||
form->addRow("Head Joint:", _headJoint = createJointBox());
|
||||
form->addRow("Left Hand Joint:", _leftHandJoint = createJointBox());
|
||||
form->addRow("Right Hand Joint:", _rightHandJoint = createJointBox());
|
||||
|
||||
form->addRow("Free Joints:", _freeJoints = new QVBoxLayout());
|
||||
QPushButton* newFreeJoint = new QPushButton("New Free Joint");
|
||||
_freeJoints->addWidget(newFreeJoint);
|
||||
connect(newFreeJoint, SIGNAL(clicked(bool)), SLOT(createNewFreeJoint()));
|
||||
}
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok |
|
||||
QDialogButtonBox::Cancel | QDialogButtonBox::Reset);
|
||||
connect(buttons, SIGNAL(accepted()), SLOT(accept()));
|
||||
connect(buttons, SIGNAL(rejected()), SLOT(reject()));
|
||||
connect(buttons->button(QDialogButtonBox::Reset), SIGNAL(clicked(bool)), SLOT(reset()));
|
||||
|
||||
form->addRow(buttons);
|
||||
|
||||
// reset to initialize the fields
|
||||
reset();
|
||||
}
|
||||
|
||||
QVariantHash ModelPropertiesDialog::getMapping() const {
|
||||
QVariantHash mapping = _originalMapping;
|
||||
mapping.insert(NAME_FIELD, _name->text());
|
||||
mapping.insert(TEXDIR_FIELD, _textureDirectory->text());
|
||||
mapping.insert(SCALE_FIELD, QString::number(_scale->value()));
|
||||
|
||||
// update the joint indices
|
||||
QVariantHash jointIndices;
|
||||
for (int i = 0; i < _geometry.joints.size(); i++) {
|
||||
jointIndices.insert(_geometry.joints.at(i).name, QString::number(i));
|
||||
}
|
||||
mapping.insert(JOINT_INDEX_FIELD, jointIndices);
|
||||
|
||||
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
|
||||
insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
|
||||
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
|
||||
if (!_isHead) {
|
||||
insertJointMapping(joints, "jointRoot", _rootJoint->currentText());
|
||||
insertJointMapping(joints, "jointLean", _leanJoint->currentText());
|
||||
insertJointMapping(joints, "jointHead", _headJoint->currentText());
|
||||
insertJointMapping(joints, "jointLeftHand", _leftHandJoint->currentText());
|
||||
insertJointMapping(joints, "jointRightHand", _rightHandJoint->currentText());
|
||||
|
||||
mapping.remove(FREE_JOINT_FIELD);
|
||||
for (int i = 0; i < _freeJoints->count() - 1; i++) {
|
||||
QComboBox* box = static_cast<QComboBox*>(_freeJoints->itemAt(i)->widget()->layout()->itemAt(0)->widget());
|
||||
mapping.insertMulti(FREE_JOINT_FIELD, box->currentText());
|
||||
}
|
||||
}
|
||||
mapping.insert(JOINT_FIELD, joints);
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
static void setJointText(QComboBox* box, const QString& text) {
|
||||
box->setCurrentIndex(qMax(box->findText(text), 0));
|
||||
}
|
||||
|
||||
void ModelPropertiesDialog::reset() {
|
||||
_name->setText(_originalMapping.value(NAME_FIELD).toString());
|
||||
_textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString());
|
||||
_scale->setValue(_originalMapping.value(SCALE_FIELD).toDouble());
|
||||
|
||||
QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash();
|
||||
setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString());
|
||||
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
|
||||
setJointText(_neckJoint, jointHash.value("jointNeck").toString());
|
||||
if (!_isHead) {
|
||||
setJointText(_rootJoint, jointHash.value("jointRoot").toString());
|
||||
setJointText(_leanJoint, jointHash.value("jointLean").toString());
|
||||
setJointText(_headJoint, jointHash.value("jointHead").toString());
|
||||
setJointText(_leftHandJoint, jointHash.value("jointLeftHand").toString());
|
||||
setJointText(_rightHandJoint, jointHash.value("jointRightHand").toString());
|
||||
|
||||
while (_freeJoints->count() > 1) {
|
||||
delete _freeJoints->itemAt(0)->widget();
|
||||
}
|
||||
foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) {
|
||||
QString jointName = joint.toString();
|
||||
if (_geometry.jointIndices.contains(jointName)) {
|
||||
createNewFreeJoint(jointName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelPropertiesDialog::chooseTextureDirectory() {
|
||||
QString directory = QFileDialog::getExistingDirectory(this, "Choose Texture Directory",
|
||||
_basePath + "/" + _textureDirectory->text());
|
||||
if (directory.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!directory.startsWith(_basePath)) {
|
||||
QMessageBox::warning(NULL, "Invalid texture directory", "Texture directory must be child of base path.");
|
||||
return;
|
||||
}
|
||||
_textureDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1));
|
||||
}
|
||||
|
||||
void ModelPropertiesDialog::createNewFreeJoint(const QString& joint) {
|
||||
QWidget* freeJoint = new QWidget();
|
||||
QHBoxLayout* freeJointLayout = new QHBoxLayout();
|
||||
freeJointLayout->setContentsMargins(QMargins());
|
||||
freeJoint->setLayout(freeJointLayout);
|
||||
QComboBox* jointBox = createJointBox(false);
|
||||
jointBox->setCurrentText(joint);
|
||||
freeJointLayout->addWidget(jointBox, 1);
|
||||
QPushButton* deleteJoint = new QPushButton("Delete");
|
||||
freeJointLayout->addWidget(deleteJoint);
|
||||
freeJoint->connect(deleteJoint, SIGNAL(clicked(bool)), SLOT(deleteLater()));
|
||||
_freeJoints->insertWidget(_freeJoints->count() - 1, freeJoint);
|
||||
}
|
||||
|
||||
QComboBox* ModelPropertiesDialog::createJointBox(bool withNone) const {
|
||||
QComboBox* box = new QComboBox();
|
||||
if (withNone) {
|
||||
box->addItem("(none)");
|
||||
}
|
||||
foreach (const FBXJoint& joint, _geometry.joints) {
|
||||
box->addItem(joint.name);
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
void ModelPropertiesDialog::insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const {
|
||||
if (_geometry.jointIndices.contains(name)) {
|
||||
joints.insert(joint, name);
|
||||
} else {
|
||||
joints.remove(joint);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,12 +12,19 @@
|
|||
#ifndef hifi_ModelUploader_h
|
||||
#define hifi_ModelUploader_h
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTimer>
|
||||
|
||||
class QDialog;
|
||||
#include <FBXReader.h>
|
||||
|
||||
class QComboBox;
|
||||
class QDoubleSpinBox;
|
||||
class QFileInfo;
|
||||
class QHttpMultiPart;
|
||||
class QLineEdit;
|
||||
class QProgressBar;
|
||||
class QPushButton;
|
||||
class QVBoxLayout;
|
||||
|
||||
class ModelUploader : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -56,8 +63,46 @@ private:
|
|||
|
||||
|
||||
bool zip();
|
||||
bool addTextures(const QString& texdir, const QString fbxFile);
|
||||
bool addPart(const QString& path, const QString& name);
|
||||
bool addTextures(const QString& texdir, const FBXGeometry& geometry);
|
||||
bool addPart(const QString& path, const QString& name, bool isTexture = false);
|
||||
bool addPart(const QFile& file, const QByteArray& contents, const QString& name, bool isTexture = false);
|
||||
};
|
||||
|
||||
/// A dialog that allows customization of various model properties.
|
||||
class ModelPropertiesDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping,
|
||||
const QString& basePath, const FBXGeometry& geometry);
|
||||
|
||||
QVariantHash getMapping() const;
|
||||
|
||||
private slots:
|
||||
void reset();
|
||||
void chooseTextureDirectory();
|
||||
void createNewFreeJoint(const QString& joint = QString());
|
||||
|
||||
private:
|
||||
QComboBox* createJointBox(bool withNone = true) const;
|
||||
void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const;
|
||||
|
||||
bool _isHead;
|
||||
QVariantHash _originalMapping;
|
||||
QString _basePath;
|
||||
FBXGeometry _geometry;
|
||||
QLineEdit* _name;
|
||||
QPushButton* _textureDirectory;
|
||||
QDoubleSpinBox* _scale;
|
||||
QComboBox* _leftEyeJoint;
|
||||
QComboBox* _rightEyeJoint;
|
||||
QComboBox* _neckJoint;
|
||||
QComboBox* _rootJoint;
|
||||
QComboBox* _leanJoint;
|
||||
QComboBox* _headJoint;
|
||||
QComboBox* _leftHandJoint;
|
||||
QComboBox* _rightHandJoint;
|
||||
QVBoxLayout* _freeJoints;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelUploader_h
|
||||
|
|
|
@ -16,12 +16,13 @@ ScriptHighlighting::ScriptHighlighting(QTextDocument* parent) :
|
|||
QSyntaxHighlighter(parent)
|
||||
{
|
||||
_keywordRegex = QRegExp("\\b(break|case|catch|continue|debugger|default|delete|do|else|finally|for|function|if|in|instanceof|new|return|switch|this|throw|try|typeof|var|void|while|with)\\b");
|
||||
_qoutedTextRegex = QRegExp("\".*\"");
|
||||
_qoutedTextRegex = QRegExp("\"[^\"]*(\"){0,1}");
|
||||
_multiLineCommentBegin = QRegExp("/\\*");
|
||||
_multiLineCommentEnd = QRegExp("\\*/");
|
||||
_numberRegex = QRegExp("[0-9]+(\\.[0-9]+){0,1}");
|
||||
_singleLineComment = QRegExp("//[^\n]*");
|
||||
_truefalseRegex = QRegExp("\\b(true|false)\\b");
|
||||
_alphacharRegex = QRegExp("[A-Za-z]");
|
||||
}
|
||||
|
||||
void ScriptHighlighting::highlightBlock(const QString& text) {
|
||||
|
@ -60,7 +61,19 @@ void ScriptHighlighting::formatComments(const QString& text) {
|
|||
int index = _singleLineComment.indexIn(text);
|
||||
while (index >= 0) {
|
||||
int length = _singleLineComment.matchedLength();
|
||||
setFormat(index, length, Qt::lightGray);
|
||||
int quoted_index = _qoutedTextRegex.indexIn(text);
|
||||
bool valid = true;
|
||||
while (quoted_index >= 0 && valid) {
|
||||
int quoted_length = _qoutedTextRegex.matchedLength();
|
||||
if (quoted_index <= index && index <= (quoted_index + quoted_length)) {
|
||||
valid = false;
|
||||
}
|
||||
quoted_index = _qoutedTextRegex.indexIn(text, quoted_index + quoted_length);
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
setFormat(index, length, Qt::lightGray);
|
||||
}
|
||||
index = _singleLineComment.indexIn(text, index + length);
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +91,9 @@ void ScriptHighlighting::formatNumbers(const QString& text){
|
|||
int index = _numberRegex.indexIn(text);
|
||||
while (index >= 0) {
|
||||
int length = _numberRegex.matchedLength();
|
||||
setFormat(index, length, Qt::green);
|
||||
if (index == 0 || _alphacharRegex.indexIn(text, index - 1) != (index - 1)) {
|
||||
setFormat(index, length, Qt::green);
|
||||
}
|
||||
index = _numberRegex.indexIn(text, index + length);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ protected:
|
|||
void formatTrueFalse(const QString& text);
|
||||
|
||||
private:
|
||||
QRegExp _alphacharRegex;
|
||||
QRegExp _keywordRegex;
|
||||
QRegExp _qoutedTextRegex;
|
||||
QRegExp _multiLineCommentBegin;
|
||||
|
|
|
@ -34,12 +34,13 @@ XmppClient& XmppClient::getInstance() {
|
|||
|
||||
void XmppClient::xmppConnected() {
|
||||
_publicChatRoom = _xmppMUCManager.addRoom(DEFAULT_CHAT_ROOM);
|
||||
_publicChatRoom->setNickName(AccountManager::getInstance().getUsername());
|
||||
_publicChatRoom->setNickName(AccountManager::getInstance().getAccountInfo().getUsername());
|
||||
_publicChatRoom->join();
|
||||
}
|
||||
|
||||
void XmppClient::xmppError(QXmppClient::Error error) {
|
||||
qDebug() << "Error connnecting to XMPP for user " << AccountManager::getInstance().getUsername() << ": " << error;
|
||||
qDebug() << "Error connnecting to XMPP for user "
|
||||
<< AccountManager::getInstance().getAccountInfo().getUsername() << ": " << error;
|
||||
}
|
||||
|
||||
void XmppClient::connectToServer() {
|
||||
|
@ -50,8 +51,8 @@ void XmppClient::connectToServer() {
|
|||
connect(&_xmppClient, SIGNAL(error(QXmppClient::Error)), this, SLOT(xmppError(QXmppClient::Error)));
|
||||
}
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
QString user = accountManager.getUsername();
|
||||
const QString& password = accountManager.getXMPPPassword();
|
||||
QString user = accountManager.getAccountInfo().getUsername();
|
||||
const QString& password = accountManager.getAccountInfo().getXMPPPassword();
|
||||
_xmppClient.connectToServer(user + "@" + DEFAULT_XMPP_SERVER, password);
|
||||
}
|
||||
|
||||
|
|
|
@ -206,13 +206,13 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
|
|||
GLOW_FROM_AVERAGE_LOUDNESS = 0.0f;
|
||||
}
|
||||
|
||||
Glower glower(_moving && distanceToTarget > GLOW_DISTANCE && renderMode == NORMAL_RENDER_MODE
|
||||
float glowLevel = _moving && distanceToTarget > GLOW_DISTANCE && renderMode == NORMAL_RENDER_MODE
|
||||
? 1.0f
|
||||
: GLOW_FROM_AVERAGE_LOUDNESS);
|
||||
: GLOW_FROM_AVERAGE_LOUDNESS;
|
||||
|
||||
// render body
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
|
||||
renderBody(renderMode);
|
||||
renderBody(renderMode, glowLevel);
|
||||
}
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) {
|
||||
_skeletonModel.updateShapePositions();
|
||||
|
@ -326,17 +326,21 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
|
|||
return glm::angleAxis(angle * proportion, axis);
|
||||
}
|
||||
|
||||
void Avatar::renderBody(RenderMode renderMode) {
|
||||
if (_shouldRenderBillboard || !(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) {
|
||||
// render the billboard until both models are loaded
|
||||
renderBillboard();
|
||||
return;
|
||||
}
|
||||
void Avatar::renderBody(RenderMode renderMode, float glowLevel) {
|
||||
Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ?
|
||||
Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
|
||||
_skeletonModel.render(1.0f, modelRenderMode);
|
||||
Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE;
|
||||
{
|
||||
Glower glower(glowLevel);
|
||||
|
||||
if (_shouldRenderBillboard || !(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) {
|
||||
// render the billboard until both models are loaded
|
||||
renderBillboard();
|
||||
return;
|
||||
}
|
||||
_skeletonModel.render(1.0f, modelRenderMode);
|
||||
getHand()->render(false);
|
||||
}
|
||||
getHead()->render(1.0f, modelRenderMode);
|
||||
getHand()->render(false);
|
||||
}
|
||||
|
||||
bool Avatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const {
|
||||
|
@ -451,7 +455,7 @@ void Avatar::renderDisplayName() {
|
|||
// The up vector must be relative to the rotation current rotation matrix:
|
||||
// we set the identity
|
||||
glm::dvec3 testPoint0 = glm::dvec3(textPosition);
|
||||
glm::dvec3 testPoint1 = glm::dvec3(textPosition) + glm::dvec3(IDENTITY_UP);
|
||||
glm::dvec3 testPoint1 = glm::dvec3(textPosition) + glm::dvec3(Application::getInstance()->getCamera()->getRotation() * IDENTITY_UP);
|
||||
|
||||
bool success;
|
||||
success = gluProject(testPoint0.x, testPoint0.y, testPoint0.z,
|
||||
|
@ -466,7 +470,7 @@ void Avatar::renderDisplayName() {
|
|||
double textWindowHeight = abs(result1[1] - result0[1]);
|
||||
float scaleFactor = (textWindowHeight > EPSILON) ? 1.0f / textWindowHeight : 1.0f;
|
||||
glScalef(scaleFactor, scaleFactor, 1.0);
|
||||
|
||||
|
||||
glScalef(1.0f, -1.0f, 1.0f); // TextRenderer::draw paints the text upside down in y axis
|
||||
|
||||
int text_x = -_displayNameBoundingRect.width() / 2;
|
||||
|
@ -772,7 +776,16 @@ float Avatar::getSkeletonHeight() const {
|
|||
|
||||
float Avatar::getHeadHeight() const {
|
||||
Extents extents = getHead()->getFaceModel().getBindExtents();
|
||||
return extents.maximum.y - extents.minimum.y;
|
||||
if (!extents.isEmpty()) {
|
||||
return extents.maximum.y - extents.minimum.y;
|
||||
}
|
||||
glm::vec3 neckPosition;
|
||||
glm::vec3 headPosition;
|
||||
if (_skeletonModel.getNeckPosition(neckPosition) && _skeletonModel.getHeadPosition(headPosition)) {
|
||||
return glm::distance(neckPosition, headPosition);
|
||||
}
|
||||
const float DEFAULT_HEAD_HEIGHT = 0.1f;
|
||||
return DEFAULT_HEAD_HEIGHT;
|
||||
}
|
||||
|
||||
bool Avatar::collisionWouldMoveAvatar(CollisionInfo& collision) const {
|
||||
|
|
|
@ -185,7 +185,7 @@ protected:
|
|||
float getPelvisToHeadLength() const;
|
||||
|
||||
void renderDisplayName();
|
||||
virtual void renderBody(RenderMode renderMode);
|
||||
virtual void renderBody(RenderMode renderMode, float glowLevel = 0.0f);
|
||||
virtual bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const;
|
||||
|
||||
virtual void updateJointMappings();
|
||||
|
|
|
@ -29,11 +29,11 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) {
|
|||
neckPosition = owningAvatar->getPosition();
|
||||
}
|
||||
setTranslation(neckPosition);
|
||||
glm::quat neckRotation;
|
||||
if (!owningAvatar->getSkeletonModel().getNeckRotation(neckRotation)) {
|
||||
neckRotation = owningAvatar->getOrientation();
|
||||
glm::quat neckParentRotation;
|
||||
if (!owningAvatar->getSkeletonModel().getNeckParentRotation(neckParentRotation)) {
|
||||
neckParentRotation = owningAvatar->getOrientation();
|
||||
}
|
||||
setRotation(neckRotation);
|
||||
setRotation(neckParentRotation);
|
||||
const float MODEL_SCALE = 0.0006f;
|
||||
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale() * MODEL_SCALE);
|
||||
|
||||
|
|
|
@ -26,8 +26,6 @@ public:
|
|||
|
||||
virtual void simulate(float deltaTime, bool fullUpdate = true);
|
||||
|
||||
protected:
|
||||
|
||||
virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||
virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||
|
||||
|
|
|
@ -54,76 +54,10 @@ void Hand::simulate(float deltaTime, bool isMine) {
|
|||
}
|
||||
}
|
||||
|
||||
void Hand::playSlaps(PalmData& palm, Avatar* avatar) {
|
||||
// Check for palm collisions
|
||||
glm::vec3 myPalmPosition = palm.getPosition();
|
||||
float palmCollisionDistance = 0.1f;
|
||||
bool wasColliding = palm.getIsCollidingWithPalm();
|
||||
palm.setIsCollidingWithPalm(false);
|
||||
// If 'Play Slaps' is enabled, look for palm-to-palm collisions and make sound
|
||||
for (size_t j = 0; j < avatar->getHand()->getNumPalms(); j++) {
|
||||
PalmData& otherPalm = avatar->getHand()->getPalms()[j];
|
||||
if (!otherPalm.isActive()) {
|
||||
continue;
|
||||
}
|
||||
glm::vec3 otherPalmPosition = otherPalm.getPosition();
|
||||
if (glm::length(otherPalmPosition - myPalmPosition) < palmCollisionDistance) {
|
||||
palm.setIsCollidingWithPalm(true);
|
||||
if (!wasColliding) {
|
||||
const float PALM_COLLIDE_VOLUME = 1.f;
|
||||
const float PALM_COLLIDE_FREQUENCY = 1000.f;
|
||||
const float PALM_COLLIDE_DURATION_MAX = 0.75f;
|
||||
const float PALM_COLLIDE_DECAY_PER_SAMPLE = 0.01f;
|
||||
Application::getInstance()->getAudio()->startDrumSound(PALM_COLLIDE_VOLUME,
|
||||
PALM_COLLIDE_FREQUENCY,
|
||||
PALM_COLLIDE_DURATION_MAX,
|
||||
PALM_COLLIDE_DECAY_PER_SAMPLE);
|
||||
// If the other person's palm is in motion, move mine downward to show I was hit
|
||||
const float MIN_VELOCITY_FOR_SLAP = 0.05f;
|
||||
if (glm::length(otherPalm.getVelocity()) > MIN_VELOCITY_FOR_SLAP) {
|
||||
// add slapback here
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We create a static CollisionList that is recycled for each collision test.
|
||||
const float MAX_COLLISIONS_PER_AVATAR = 32;
|
||||
static CollisionList handCollisions(MAX_COLLISIONS_PER_AVATAR);
|
||||
|
||||
void Hand::collideAgainstAvatarOld(Avatar* avatar, bool isMyHand) {
|
||||
if (!avatar || avatar == _owningAvatar) {
|
||||
// don't collide with our own hands (that is done elsewhere)
|
||||
return;
|
||||
}
|
||||
float scaledPalmRadius = PALM_COLLISION_RADIUS * _owningAvatar->getScale();
|
||||
for (size_t i = 0; i < getNumPalms(); i++) {
|
||||
PalmData& palm = getPalms()[i];
|
||||
if (!palm.isActive()) {
|
||||
continue;
|
||||
}
|
||||
if (isMyHand && Menu::getInstance()->isOptionChecked(MenuOption::PlaySlaps)) {
|
||||
playSlaps(palm, avatar);
|
||||
}
|
||||
|
||||
glm::vec3 totalPenetration;
|
||||
handCollisions.clear();
|
||||
if (avatar->findSphereCollisions(palm.getPosition(), scaledPalmRadius, handCollisions)) {
|
||||
for (int j = 0; j < handCollisions.size(); ++j) {
|
||||
CollisionInfo* collision = handCollisions.getCollision(j);
|
||||
if (isMyHand) {
|
||||
totalPenetration = addPenetrations(totalPenetration, collision->_penetration);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isMyHand) {
|
||||
// resolve penetration
|
||||
palm.addToPosition(-totalPenetration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) {
|
||||
if (!avatar || avatar == _owningAvatar) {
|
||||
// don't collide hands against ourself (that is done elsewhere)
|
||||
|
|
|
@ -58,7 +58,6 @@ public:
|
|||
const glm::vec3& getLeapFingerTipBallPosition (int ball) const { return _leapFingerTipBalls [ball].position;}
|
||||
const glm::vec3& getLeapFingerRootBallPosition(int ball) const { return _leapFingerRootBalls[ball].position;}
|
||||
|
||||
void collideAgainstAvatarOld(Avatar* avatar, bool isMyHand);
|
||||
void collideAgainstAvatar(Avatar* avatar, bool isMyHand);
|
||||
void collideAgainstOurself();
|
||||
|
||||
|
@ -80,8 +79,6 @@ private:
|
|||
void renderLeapFingerTrails();
|
||||
|
||||
void calculateGeometry();
|
||||
|
||||
void playSlaps(PalmData& palm, Avatar* avatar);
|
||||
};
|
||||
|
||||
#endif // hifi_Hand_h
|
||||
|
|
|
@ -161,7 +161,9 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
|
|||
_leftEyePosition = _rightEyePosition = getPosition();
|
||||
if (!billboard) {
|
||||
_faceModel.simulate(deltaTime);
|
||||
_faceModel.getEyePositions(_leftEyePosition, _rightEyePosition);
|
||||
if (!_faceModel.getEyePositions(_leftEyePosition, _rightEyePosition)) {
|
||||
static_cast<Avatar*>(_owningAvatar)->getSkeletonModel().getEyePositions(_leftEyePosition, _rightEyePosition);
|
||||
}
|
||||
}
|
||||
_eyePosition = calculateAverageEyePosition();
|
||||
}
|
||||
|
|
|
@ -90,11 +90,8 @@ MyAvatar::~MyAvatar() {
|
|||
}
|
||||
|
||||
void MyAvatar::reset() {
|
||||
// TODO? resurrect headMouse stuff?
|
||||
//_headMouseX = _glWidget->width() / 2;
|
||||
//_headMouseY = _glWidget->height() / 2;
|
||||
_skeletonModel.reset();
|
||||
getHead()->reset();
|
||||
getHead()->reset();
|
||||
getHand()->reset();
|
||||
_oculusYawOffset = 0.0f;
|
||||
|
||||
|
@ -111,23 +108,7 @@ void MyAvatar::update(float deltaTime) {
|
|||
// Faceshift drive is enabled, set the avatar drive based on the head position
|
||||
moveWithLean();
|
||||
}
|
||||
|
||||
// Update head mouse from faceshift if active
|
||||
Faceshift* faceshift = Application::getInstance()->getFaceshift();
|
||||
if (faceshift->isActive()) {
|
||||
// TODO? resurrect headMouse stuff?
|
||||
//glm::vec3 headVelocity = faceshift->getHeadAngularVelocity();
|
||||
//// sets how quickly head angular rotation moves the head mouse
|
||||
//const float HEADMOUSE_FACESHIFT_YAW_SCALE = 40.0f;
|
||||
//const float HEADMOUSE_FACESHIFT_PITCH_SCALE = 30.0f;
|
||||
//_headMouseX -= headVelocity.y * HEADMOUSE_FACESHIFT_YAW_SCALE;
|
||||
//_headMouseY -= headVelocity.x * HEADMOUSE_FACESHIFT_PITCH_SCALE;
|
||||
//
|
||||
//// Constrain head-driven mouse to edges of screen
|
||||
//_headMouseX = glm::clamp(_headMouseX, 0, _glWidget->width());
|
||||
//_headMouseY = glm::clamp(_headMouseY, 0, _glWidget->height());
|
||||
}
|
||||
|
||||
|
||||
// Get audio loudness data from audio input device
|
||||
Audio* audio = Application::getInstance()->getAudio();
|
||||
head->setAudioLoudness(audio->getLastInputLoudness());
|
||||
|
@ -353,32 +334,41 @@ void MyAvatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
|
|||
}
|
||||
}
|
||||
|
||||
void MyAvatar::renderHeadMouse() const {
|
||||
// TODO? resurrect headMouse stuff?
|
||||
/*
|
||||
void MyAvatar::renderHeadMouse(int screenWidth, int screenHeight) const {
|
||||
|
||||
Faceshift* faceshift = Application::getInstance()->getFaceshift();
|
||||
|
||||
// Display small target box at center or head mouse target that can also be used to measure LOD
|
||||
float headPitch = getHead()->getFinalPitch();
|
||||
float headYaw = getHead()->getFinalYaw();
|
||||
//
|
||||
// It should be noted that the following constant is a function
|
||||
// how far the viewer's head is away from both the screen and the size of the screen,
|
||||
// which are both things we cannot know without adding a calibration phase.
|
||||
//
|
||||
const float PIXELS_PER_VERTICAL_DEGREE = 20.0f;
|
||||
float aspectRatio = (float) screenWidth / (float) screenHeight;
|
||||
int headMouseX = screenWidth / 2.f - headYaw * aspectRatio * PIXELS_PER_VERTICAL_DEGREE;
|
||||
int headMouseY = screenHeight / 2.f - headPitch * PIXELS_PER_VERTICAL_DEGREE;
|
||||
|
||||
glColor3f(1.0f, 1.0f, 1.0f);
|
||||
glDisable(GL_LINE_SMOOTH);
|
||||
const int PIXEL_BOX = 16;
|
||||
glBegin(GL_LINES);
|
||||
glVertex2f(_headMouseX - PIXEL_BOX/2, _headMouseY);
|
||||
glVertex2f(_headMouseX + PIXEL_BOX/2, _headMouseY);
|
||||
glVertex2f(_headMouseX, _headMouseY - PIXEL_BOX/2);
|
||||
glVertex2f(_headMouseX, _headMouseY + PIXEL_BOX/2);
|
||||
glVertex2f(headMouseX - PIXEL_BOX/2, headMouseY);
|
||||
glVertex2f(headMouseX + PIXEL_BOX/2, headMouseY);
|
||||
glVertex2f(headMouseX, headMouseY - PIXEL_BOX/2);
|
||||
glVertex2f(headMouseX, headMouseY + PIXEL_BOX/2);
|
||||
glEnd();
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
glColor3f(1.0f, 0.0f, 0.0f);
|
||||
glPointSize(3.0f);
|
||||
glDisable(GL_POINT_SMOOTH);
|
||||
glBegin(GL_POINTS);
|
||||
glVertex2f(_headMouseX - 1, _headMouseY + 1);
|
||||
glEnd();
|
||||
// If Faceshift is active, show eye pitch and yaw as separate pointer
|
||||
if (_faceshift.isActive()) {
|
||||
const float EYE_TARGET_PIXELS_PER_DEGREE = 40.0;
|
||||
int eyeTargetX = (_glWidget->width() / 2) - _faceshift.getEstimatedEyeYaw() * EYE_TARGET_PIXELS_PER_DEGREE;
|
||||
int eyeTargetY = (_glWidget->height() / 2) - _faceshift.getEstimatedEyePitch() * EYE_TARGET_PIXELS_PER_DEGREE;
|
||||
if (faceshift->isActive()) {
|
||||
|
||||
float avgEyePitch = faceshift->getEstimatedEyePitch();
|
||||
float avgEyeYaw = faceshift->getEstimatedEyeYaw();
|
||||
int eyeTargetX = (screenWidth / 2) - avgEyeYaw * aspectRatio * PIXELS_PER_VERTICAL_DEGREE;
|
||||
int eyeTargetY = (screenHeight / 2) - avgEyePitch * PIXELS_PER_VERTICAL_DEGREE;
|
||||
|
||||
glColor3f(0.0f, 1.0f, 1.0f);
|
||||
glDisable(GL_LINE_SMOOTH);
|
||||
glBegin(GL_LINES);
|
||||
|
@ -389,7 +379,6 @@ void MyAvatar::renderHeadMouse() const {
|
|||
glEnd();
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void MyAvatar::setLocalGravity(glm::vec3 gravity) {
|
||||
|
@ -459,7 +448,7 @@ void MyAvatar::loadData(QSettings* settings) {
|
|||
setScale(_scale);
|
||||
Application::getInstance()->getCamera()->setScale(_scale);
|
||||
|
||||
setFaceModelURL(settings->value("faceModelURL").toUrl());
|
||||
setFaceModelURL(settings->value("faceModelURL", DEFAULT_HEAD_MODEL_URL).toUrl());
|
||||
setSkeletonModelURL(settings->value("skeletonModelURL").toUrl());
|
||||
setDisplayName(settings->value("displayName").toString());
|
||||
|
||||
|
@ -497,23 +486,24 @@ void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) {
|
|||
}
|
||||
|
||||
void MyAvatar::updateLookAtTargetAvatar() {
|
||||
Application* applicationInstance = Application::getInstance();
|
||||
|
||||
if (!applicationInstance->isMousePressed()) {
|
||||
glm::vec3 mouseOrigin = applicationInstance->getMouseRayOrigin();
|
||||
glm::vec3 mouseDirection = applicationInstance->getMouseRayDirection();
|
||||
|
||||
foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
|
||||
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
|
||||
float distance;
|
||||
if (avatar->findRayIntersection(mouseOrigin, mouseDirection, distance)) {
|
||||
//
|
||||
// Look at the avatar whose eyes are closest to the ray in direction of my avatar's head
|
||||
//
|
||||
_lookAtTargetAvatar.clear();
|
||||
_targetAvatarPosition = glm::vec3(0, 0, 0);
|
||||
const float MIN_LOOKAT_ANGLE = PI / 4.0f; // Smallest angle between face and person where we will look at someone
|
||||
float smallestAngleTo = MIN_LOOKAT_ANGLE;
|
||||
foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) {
|
||||
Avatar* avatar = static_cast<Avatar*>(avatarPointer.data());
|
||||
if (!avatar->isMyAvatar()) {
|
||||
float angleTo = glm::angle(getHead()->getFinalOrientation() * glm::vec3(0.0f, 0.0f, -1.0f),
|
||||
glm::normalize(avatar->getHead()->getEyePosition() - getHead()->getEyePosition()));
|
||||
if (angleTo < smallestAngleTo) {
|
||||
_lookAtTargetAvatar = avatarPointer;
|
||||
_targetAvatarPosition = avatarPointer->getPosition();
|
||||
return;
|
||||
smallestAngleTo = angleTo;
|
||||
}
|
||||
}
|
||||
_lookAtTargetAvatar.clear();
|
||||
_targetAvatarPosition = glm::vec3(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -549,7 +539,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
_billboardValid = false;
|
||||
}
|
||||
|
||||
void MyAvatar::renderBody(RenderMode renderMode) {
|
||||
void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) {
|
||||
if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) {
|
||||
return; // wait until both models are loaded
|
||||
}
|
||||
|
|
|
@ -42,10 +42,10 @@ public:
|
|||
void moveWithLean();
|
||||
|
||||
void render(const glm::vec3& cameraPosition, RenderMode renderMode = NORMAL_RENDER_MODE);
|
||||
void renderBody(RenderMode renderMode);
|
||||
void renderBody(RenderMode renderMode, float glowLevel = 0.0f);
|
||||
bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const;
|
||||
void renderDebugBodyPoints();
|
||||
void renderHeadMouse() const;
|
||||
void renderHeadMouse(int screenWidth, int screenHeight) const;
|
||||
|
||||
// setters
|
||||
void setMousePressed(bool mousePressed) { _mousePressed = mousePressed; }
|
||||
|
|
|
@ -221,6 +221,14 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const
|
|||
glm::normalize(inverse * axes[0])) * joint.rotation;
|
||||
}
|
||||
|
||||
void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
||||
_owningAvatar->getHead()->getFaceModel().maybeUpdateNeckRotation(parentState, joint, state);
|
||||
}
|
||||
|
||||
void SkeletonModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
||||
_owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(parentState, joint, state);
|
||||
}
|
||||
|
||||
void SkeletonModel::renderJointConstraints(int jointIndex) {
|
||||
if (jointIndex == -1) {
|
||||
return;
|
||||
|
|
|
@ -46,6 +46,8 @@ protected:
|
|||
virtual void updateJointState(int index);
|
||||
|
||||
virtual void maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||
virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||
virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||
|
||||
private:
|
||||
|
||||
|
|
123
interface/src/models/ModelTreeRenderer.cpp
Normal file
123
interface/src/models/ModelTreeRenderer.cpp
Normal file
|
@ -0,0 +1,123 @@
|
|||
//
|
||||
// ModelTreeRenderer.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/6/13.
|
||||
// Copyright 2013 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 <glm/gtx/quaternion.hpp>
|
||||
|
||||
#include "InterfaceConfig.h"
|
||||
|
||||
#include "ModelTreeRenderer.h"
|
||||
|
||||
ModelTreeRenderer::ModelTreeRenderer() :
|
||||
OctreeRenderer() {
|
||||
}
|
||||
|
||||
ModelTreeRenderer::~ModelTreeRenderer() {
|
||||
// delete the models in _modelsItemModels
|
||||
foreach(Model* model, _modelsItemModels) {
|
||||
delete model;
|
||||
}
|
||||
_modelsItemModels.clear();
|
||||
}
|
||||
|
||||
void ModelTreeRenderer::init() {
|
||||
OctreeRenderer::init();
|
||||
}
|
||||
|
||||
|
||||
void ModelTreeRenderer::update() {
|
||||
if (_tree) {
|
||||
ModelTree* tree = static_cast<ModelTree*>(_tree);
|
||||
tree->update();
|
||||
}
|
||||
}
|
||||
|
||||
void ModelTreeRenderer::render() {
|
||||
OctreeRenderer::render();
|
||||
}
|
||||
|
||||
Model* ModelTreeRenderer::getModel(const QString& url) {
|
||||
Model* model = NULL;
|
||||
|
||||
// if we don't already have this model then create it and initialize it
|
||||
if (_modelsItemModels.find(url) == _modelsItemModels.end()) {
|
||||
model = new Model();
|
||||
model->init();
|
||||
model->setURL(QUrl(url));
|
||||
_modelsItemModels[url] = model;
|
||||
} else {
|
||||
model = _modelsItemModels[url];
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) {
|
||||
// actually render it here...
|
||||
// we need to iterate the actual modelItems of the element
|
||||
ModelTreeElement* modelTreeElement = (ModelTreeElement*)element;
|
||||
|
||||
const QList<ModelItem>& modelItems = modelTreeElement->getModels();
|
||||
|
||||
uint16_t numberOfModels = modelItems.size();
|
||||
|
||||
for (uint16_t i = 0; i < numberOfModels; i++) {
|
||||
const ModelItem& modelItem = modelItems[i];
|
||||
// render modelItem aspoints
|
||||
glm::vec3 position = modelItem.getPosition() * (float)TREE_SCALE;
|
||||
glColor3ub(modelItem.getColor()[RED_INDEX],modelItem.getColor()[GREEN_INDEX],modelItem.getColor()[BLUE_INDEX]);
|
||||
float radius = modelItem.getRadius() * (float)TREE_SCALE;
|
||||
//glm::vec3 center = position + glm::vec3(radius, radius, radius); // center it around the position
|
||||
|
||||
bool drawAsModel = modelItem.hasModel();
|
||||
|
||||
args->_renderedItems++;
|
||||
|
||||
if (drawAsModel) {
|
||||
glPushMatrix();
|
||||
const float alpha = 1.0f;
|
||||
|
||||
Model* model = getModel(modelItem.getModelURL());
|
||||
|
||||
model->setScaleToFit(true, radius * 2.0f);
|
||||
model->setSnapModelToCenter(true);
|
||||
|
||||
// set the rotation
|
||||
glm::quat rotation = modelItem.getModelRotation();
|
||||
model->setRotation(rotation);
|
||||
|
||||
// set the position
|
||||
model->setTranslation(position);
|
||||
|
||||
model->simulate(0.0f);
|
||||
|
||||
|
||||
model->render(alpha); // TODO: should we allow modelItems to have alpha on their models?
|
||||
|
||||
const bool wantDebugSphere = false;
|
||||
if (wantDebugSphere) {
|
||||
glPushMatrix();
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
glutWireSphere(radius, 15, 15);
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
glPopMatrix();
|
||||
} else {
|
||||
glPushMatrix();
|
||||
glTranslatef(position.x, position.y, position.z);
|
||||
glutSolidSphere(radius, 15, 15);
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) {
|
||||
static_cast<ModelTree*>(_tree)->processEraseMessage(dataByteArray, sourceNode);
|
||||
}
|
55
interface/src/models/ModelTreeRenderer.h
Normal file
55
interface/src/models/ModelTreeRenderer.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// ModelTreeRenderer.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/6/13.
|
||||
// Copyright 2013 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_ModelTreeRenderer_h
|
||||
#define hifi_ModelTreeRenderer_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <ModelTree.h>
|
||||
#include <Octree.h>
|
||||
#include <OctreePacketData.h>
|
||||
#include <OctreeRenderer.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
#include "renderer/Model.h"
|
||||
|
||||
// Generic client side Octree renderer class.
|
||||
class ModelTreeRenderer : public OctreeRenderer {
|
||||
public:
|
||||
ModelTreeRenderer();
|
||||
virtual ~ModelTreeRenderer();
|
||||
|
||||
virtual Octree* createTree() { return new ModelTree(true); }
|
||||
virtual char getMyNodeType() const { return NodeType::ModelServer; }
|
||||
virtual PacketType getMyQueryMessageType() const { return PacketTypeModelQuery; }
|
||||
virtual PacketType getExpectedPacketType() const { return PacketTypeModelData; }
|
||||
virtual void renderElement(OctreeElement* element, RenderArgs* args);
|
||||
|
||||
void update();
|
||||
|
||||
ModelTree* getTree() { return (ModelTree*)_tree; }
|
||||
|
||||
void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode);
|
||||
|
||||
virtual void init();
|
||||
virtual void render();
|
||||
|
||||
protected:
|
||||
Model* getModel(const QString& url);
|
||||
|
||||
QMap<QString, Model*> _modelsItemModels;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelTreeRenderer_h
|
|
@ -31,7 +31,7 @@ public:
|
|||
virtual ~ParticleTreeRenderer();
|
||||
|
||||
virtual Octree* createTree() { return new ParticleTree(true); }
|
||||
virtual NodeType_t getMyNodeType() const { return NodeType::ParticleServer; }
|
||||
virtual char getMyNodeType() const { return NodeType::ParticleServer; }
|
||||
virtual PacketType getMyQueryMessageType() const { return PacketTypeParticleQuery; }
|
||||
virtual PacketType getExpectedPacketType() const { return PacketTypeParticleData; }
|
||||
virtual void renderElement(OctreeElement* element, RenderArgs* args);
|
|
@ -305,7 +305,6 @@ void GeometryCache::setBlendedVertices(const QPointer<Model>& model, const QWeak
|
|||
|
||||
QSharedPointer<Resource> GeometryCache::createResource(const QUrl& url,
|
||||
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
|
||||
|
||||
QSharedPointer<NetworkGeometry> geometry(new NetworkGeometry(url, fallback.staticCast<NetworkGeometry>(), delayLoad),
|
||||
&Resource::allReferencesCleared);
|
||||
geometry->setLODParent(geometry);
|
||||
|
@ -320,6 +319,20 @@ NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGe
|
|||
_mapping(mapping),
|
||||
_textureBase(textureBase.isValid() ? textureBase : url),
|
||||
_fallback(fallback) {
|
||||
|
||||
if (url.isEmpty()) {
|
||||
// make the minimal amount of dummy geometry to satisfy Model
|
||||
FBXJoint joint = { false, QVector<int>(), -1 };
|
||||
_geometry.joints.append(joint);
|
||||
_geometry.leftEyeJointIndex = -1;
|
||||
_geometry.rightEyeJointIndex = -1;
|
||||
_geometry.neckJointIndex = -1;
|
||||
_geometry.rootJointIndex = -1;
|
||||
_geometry.leanJointIndex = -1;
|
||||
_geometry.headJointIndex = -1;
|
||||
_geometry.leftHandJointIndex = -1;
|
||||
_geometry.rightHandJointIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetworkGeometry::isLoadedWithTextures() const {
|
||||
|
|
|
@ -34,12 +34,18 @@ static int vec3VectorTypeId = qRegisterMetaType<QVector<glm::vec3> >();
|
|||
Model::Model(QObject* parent) :
|
||||
QObject(parent),
|
||||
_scale(1.0f, 1.0f, 1.0f),
|
||||
_scaleToFit(false),
|
||||
_scaleToFitLargestDimension(0.0f),
|
||||
_scaledToFit(false),
|
||||
_snapModelToCenter(false),
|
||||
_snappedToCenter(false),
|
||||
_shapesAreDirty(true),
|
||||
_boundingRadius(0.f),
|
||||
_boundingShape(),
|
||||
_boundingShapeLocalOffset(0.f),
|
||||
_lodDistance(0.0f),
|
||||
_pupilDilation(0.0f) {
|
||||
_pupilDilation(0.0f),
|
||||
_url("http://invalid.com") {
|
||||
// we may have been created in the network thread, but we live in the main thread
|
||||
moveToThread(Application::getInstance()->thread());
|
||||
}
|
||||
|
@ -60,6 +66,13 @@ Model::SkinLocations Model::_skinNormalMapLocations;
|
|||
Model::SkinLocations Model::_skinShadowLocations;
|
||||
|
||||
void Model::setScale(const glm::vec3& scale) {
|
||||
setScaleInternal(scale);
|
||||
// if anyone sets scale manually, then we are no longer scaled to fit
|
||||
_scaleToFit = false;
|
||||
_scaledToFit = false;
|
||||
}
|
||||
|
||||
void Model::setScaleInternal(const glm::vec3& scale) {
|
||||
float scaleLength = glm::length(_scale);
|
||||
float relativeDeltaScale = glm::length(_scale - scale) / scaleLength;
|
||||
|
||||
|
@ -70,6 +83,15 @@ void Model::setScale(const glm::vec3& scale) {
|
|||
}
|
||||
}
|
||||
|
||||
void Model::setOffset(const glm::vec3& offset) {
|
||||
_offset = offset;
|
||||
|
||||
// if someone manually sets our offset, then we are no longer snapped to center
|
||||
_snapModelToCenter = false;
|
||||
_snappedToCenter = false;
|
||||
}
|
||||
|
||||
|
||||
void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations) {
|
||||
program.bind();
|
||||
locations.clusterMatrices = program.uniformLocation("clusterMatrices");
|
||||
|
@ -357,6 +379,23 @@ Extents Model::getBindExtents() const {
|
|||
return scaledExtents;
|
||||
}
|
||||
|
||||
Extents Model::getMeshExtents() const {
|
||||
if (!isActive()) {
|
||||
return Extents();
|
||||
}
|
||||
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
|
||||
Extents scaledExtents = { extents.minimum * _scale, extents.maximum * _scale };
|
||||
return scaledExtents;
|
||||
}
|
||||
|
||||
Extents Model::getUnscaledMeshExtents() const {
|
||||
if (!isActive()) {
|
||||
return Extents();
|
||||
}
|
||||
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
|
||||
return extents;
|
||||
}
|
||||
|
||||
bool Model::getJointState(int index, glm::quat& rotation) const {
|
||||
if (index == -1 || index >= _jointStates.size()) {
|
||||
return false;
|
||||
|
@ -395,6 +434,17 @@ bool Model::getNeckRotation(glm::quat& neckRotation) const {
|
|||
return isActive() && getJointRotation(_geometry->getFBXGeometry().neckJointIndex, neckRotation);
|
||||
}
|
||||
|
||||
bool Model::getNeckParentRotation(glm::quat& neckParentRotation) const {
|
||||
if (!isActive()) {
|
||||
return false;
|
||||
}
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
if (geometry.neckJointIndex == -1) {
|
||||
return false;
|
||||
}
|
||||
return getJointRotation(geometry.joints.at(geometry.neckJointIndex).parentIndex, neckParentRotation);
|
||||
}
|
||||
|
||||
bool Model::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const {
|
||||
if (!isActive()) {
|
||||
return false;
|
||||
|
@ -762,9 +812,52 @@ void Blender::run() {
|
|||
Q_ARG(const QVector<glm::vec3>&, vertices), Q_ARG(const QVector<glm::vec3>&, normals));
|
||||
}
|
||||
|
||||
void Model::setScaleToFit(bool scaleToFit, float largestDimension) {
|
||||
if (_scaleToFit != scaleToFit || _scaleToFitLargestDimension != largestDimension) {
|
||||
_scaleToFit = scaleToFit;
|
||||
_scaleToFitLargestDimension = largestDimension;
|
||||
_scaledToFit = false; // force rescaling
|
||||
}
|
||||
}
|
||||
|
||||
void Model::scaleToFit() {
|
||||
Extents modelMeshExtents = getUnscaledMeshExtents();
|
||||
|
||||
// size is our "target size in world space"
|
||||
// we need to set our model scale so that the extents of the mesh, fit in a cube that size...
|
||||
glm::vec3 dimensions = modelMeshExtents.maximum - modelMeshExtents.minimum;
|
||||
float maxDimension = glm::max(glm::max(dimensions.x, dimensions.y), dimensions.z);
|
||||
float maxScale = _scaleToFitLargestDimension / maxDimension;
|
||||
glm::vec3 scale(maxScale, maxScale, maxScale);
|
||||
setScaleInternal(scale);
|
||||
_scaledToFit = true;
|
||||
}
|
||||
|
||||
void Model::setSnapModelToCenter(bool snapModelToCenter) {
|
||||
if (_snapModelToCenter != snapModelToCenter) {
|
||||
_snapModelToCenter = snapModelToCenter;
|
||||
_snappedToCenter = false; // force re-centering
|
||||
}
|
||||
}
|
||||
|
||||
void Model::snapToCenter() {
|
||||
Extents modelMeshExtents = getUnscaledMeshExtents();
|
||||
glm::vec3 halfDimensions = (modelMeshExtents.maximum - modelMeshExtents.minimum) * 0.5f;
|
||||
glm::vec3 offset = -modelMeshExtents.minimum - halfDimensions;
|
||||
_offset = offset;
|
||||
_snappedToCenter = true;
|
||||
}
|
||||
|
||||
void Model::simulate(float deltaTime, bool fullUpdate) {
|
||||
fullUpdate = updateGeometry() || fullUpdate;
|
||||
fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit) || (_snapModelToCenter && !_snappedToCenter);
|
||||
if (isActive() && fullUpdate) {
|
||||
// check for scale to fit
|
||||
if (_scaleToFit && !_scaledToFit) {
|
||||
scaleToFit();
|
||||
}
|
||||
if (_snapModelToCenter && !_snappedToCenter) {
|
||||
snapToCenter();
|
||||
}
|
||||
simulateInternal(deltaTime);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,10 +39,19 @@ public:
|
|||
void setRotation(const glm::quat& rotation) { _rotation = rotation; }
|
||||
const glm::quat& getRotation() const { return _rotation; }
|
||||
|
||||
/// enables/disables scale to fit behavior, the model will be automatically scaled to the specified largest dimension
|
||||
void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f);
|
||||
bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled
|
||||
bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit
|
||||
bool getScaleToFitDimension() const { return _scaleToFitLargestDimension; } /// the dimension model is scaled to
|
||||
|
||||
void setSnapModelToCenter(bool snapModelToCenter);
|
||||
bool getSnapModelToCenter() { return _snapModelToCenter; }
|
||||
|
||||
void setScale(const glm::vec3& scale);
|
||||
const glm::vec3& getScale() const { return _scale; }
|
||||
|
||||
void setOffset(const glm::vec3& offset) { _offset = offset; }
|
||||
void setOffset(const glm::vec3& offset);
|
||||
const glm::vec3& getOffset() const { return _offset; }
|
||||
|
||||
void setPupilDilation(float dilation) { _pupilDilation = dilation; }
|
||||
|
@ -53,7 +62,7 @@ public:
|
|||
|
||||
bool isActive() const { return _geometry && _geometry->isLoaded(); }
|
||||
|
||||
bool isRenderable() const { return !_meshStates.isEmpty(); }
|
||||
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _geometry->getMeshes().isEmpty()); }
|
||||
|
||||
bool isLoadedWithTextures() const { return _geometry && _geometry->isLoadedWithTextures(); }
|
||||
|
||||
|
@ -80,6 +89,12 @@ public:
|
|||
/// Returns the extents of the model in its bind pose.
|
||||
Extents getBindExtents() const;
|
||||
|
||||
/// Returns the extents of the model's mesh
|
||||
Extents getMeshExtents() const;
|
||||
|
||||
/// Returns the unscaled extents of the model's mesh
|
||||
Extents getUnscaledMeshExtents() const;
|
||||
|
||||
/// Returns a reference to the shared geometry.
|
||||
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
|
||||
|
||||
|
@ -117,6 +132,10 @@ public:
|
|||
/// \return whether or not the neck was found
|
||||
bool getNeckRotation(glm::quat& neckRotation) const;
|
||||
|
||||
/// Returns the rotation of the neck joint's parent.
|
||||
/// \return whether or not the neck was found
|
||||
bool getNeckParentRotation(glm::quat& neckRotation) const;
|
||||
|
||||
/// Retrieve the positions of up to two eye meshes.
|
||||
/// \return whether or not both eye meshes were found
|
||||
bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const;
|
||||
|
@ -203,6 +222,13 @@ protected:
|
|||
glm::quat _rotation;
|
||||
glm::vec3 _scale;
|
||||
glm::vec3 _offset;
|
||||
|
||||
bool _scaleToFit; /// If you set scaleToFit, we will calculate scale based on MeshExtents
|
||||
float _scaleToFitLargestDimension; /// this is the dimension that scale to fit will use
|
||||
bool _scaledToFit; /// have we scaled to fit
|
||||
|
||||
bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space
|
||||
bool _snappedToCenter; /// are we currently snapped to center
|
||||
|
||||
class JointState {
|
||||
public:
|
||||
|
@ -229,6 +255,10 @@ protected:
|
|||
|
||||
// returns 'true' if needs fullUpdate after geometry change
|
||||
bool updateGeometry();
|
||||
|
||||
void setScaleInternal(const glm::vec3& scale);
|
||||
void scaleToFit();
|
||||
void snapToCenter();
|
||||
|
||||
void simulateInternal(float deltaTime);
|
||||
|
||||
|
|
49
interface/src/scripting/LocationScriptingInterface.cpp
Normal file
49
interface/src/scripting/LocationScriptingInterface.cpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// LocationScriptingInterface.cpp
|
||||
// interface/src/scripting
|
||||
//
|
||||
// Created by Ryan Huffman on 4/29/14.
|
||||
// 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 <glm/glm.hpp>
|
||||
|
||||
#include "NodeList.h"
|
||||
|
||||
#include "LocationScriptingInterface.h"
|
||||
|
||||
LocationScriptingInterface* LocationScriptingInterface::getInstance() {
|
||||
static LocationScriptingInterface sharedInstance;
|
||||
return &sharedInstance;
|
||||
}
|
||||
|
||||
QString LocationScriptingInterface::getHref() {
|
||||
return getProtocol() + "//" + getHostname() + getPathname();
|
||||
}
|
||||
|
||||
QString LocationScriptingInterface::getPathname() {
|
||||
const glm::vec3& position = Application::getInstance()->getAvatar()->getPosition();
|
||||
QString path;
|
||||
path.sprintf("/%.4f,%.4f,%.4f", position.x, position.y, position.z);
|
||||
return path;
|
||||
}
|
||||
|
||||
QString LocationScriptingInterface::getHostname() {
|
||||
return NodeList::getInstance()->getDomainHandler().getHostname();
|
||||
}
|
||||
|
||||
void LocationScriptingInterface::assign(const QString& url) {
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "goToURL", Q_ARG(const QString&, url));
|
||||
}
|
||||
|
||||
QScriptValue LocationScriptingInterface::locationGetter(QScriptContext* context, QScriptEngine* engine) {
|
||||
return engine->newQObject(getInstance());
|
||||
}
|
||||
|
||||
QScriptValue LocationScriptingInterface::locationSetter(QScriptContext* context, QScriptEngine* engine) {
|
||||
LocationScriptingInterface::getInstance()->assign(context->argument(0).toString());
|
||||
return QScriptValue::UndefinedValue;
|
||||
}
|
46
interface/src/scripting/LocationScriptingInterface.h
Normal file
46
interface/src/scripting/LocationScriptingInterface.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// LocationScriptingInterface.h
|
||||
// interface/src/scripting
|
||||
//
|
||||
// Created by Ryan Huffman on 4/29/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_LocationScriptingInterface_h
|
||||
#define hifi_LocationScriptingInterface_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QScriptContext>
|
||||
#include <QScriptEngine>
|
||||
#include <QScriptValue>
|
||||
#include <QString>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
class LocationScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString href READ getHref)
|
||||
Q_PROPERTY(QString protocol READ getProtocol)
|
||||
Q_PROPERTY(QString hostname READ getHostname)
|
||||
Q_PROPERTY(QString pathname READ getPathname)
|
||||
LocationScriptingInterface() { };
|
||||
public:
|
||||
static LocationScriptingInterface* getInstance();
|
||||
|
||||
QString getHref();
|
||||
QString getProtocol() { return CUSTOM_URL_SCHEME; };
|
||||
QString getPathname();
|
||||
QString getHostname();
|
||||
|
||||
static QScriptValue locationGetter(QScriptContext* context, QScriptEngine* engine);
|
||||
static QScriptValue locationSetter(QScriptContext* context, QScriptEngine* engine);
|
||||
|
||||
public slots:
|
||||
void assign(const QString& url);
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_LocationScriptingInterface_h
|
119
interface/src/scripting/WindowScriptingInterface.cpp
Normal file
119
interface/src/scripting/WindowScriptingInterface.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
//
|
||||
// WindowScriptingInterface.cpp
|
||||
// interface/src/scripting
|
||||
//
|
||||
// Created by Ryan Huffman on 4/29/14.
|
||||
// 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 <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Menu.h"
|
||||
|
||||
#include "WindowScriptingInterface.h"
|
||||
|
||||
WindowScriptingInterface* WindowScriptingInterface::getInstance() {
|
||||
static WindowScriptingInterface sharedInstance;
|
||||
return &sharedInstance;
|
||||
}
|
||||
|
||||
QScriptValue WindowScriptingInterface::alert(const QString& message) {
|
||||
QScriptValue retVal;
|
||||
QMetaObject::invokeMethod(this, "showAlert", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QScriptValue, retVal), Q_ARG(const QString&, message));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
QScriptValue WindowScriptingInterface::confirm(const QString& message) {
|
||||
QScriptValue retVal;
|
||||
QMetaObject::invokeMethod(this, "showConfirm", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QScriptValue, retVal), Q_ARG(const QString&, message));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
QScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) {
|
||||
QScriptValue retVal;
|
||||
QMetaObject::invokeMethod(this, "showPrompt", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QScriptValue, retVal),
|
||||
Q_ARG(const QString&, message), Q_ARG(const QString&, defaultText));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
QScriptValue WindowScriptingInterface::browse(const QString& title, const QString& directory, const QString& nameFilter) {
|
||||
QScriptValue retVal;
|
||||
QMetaObject::invokeMethod(this, "showBrowse", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QScriptValue, retVal),
|
||||
Q_ARG(const QString&, title), Q_ARG(const QString&, directory), Q_ARG(const QString&, nameFilter));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/// Display an alert box
|
||||
/// \param const QString& message message to display
|
||||
/// \return QScriptValue::UndefinedValue
|
||||
QScriptValue WindowScriptingInterface::showAlert(const QString& message) {
|
||||
QMessageBox::warning(Application::getInstance()->getWindow(), "", message);
|
||||
return QScriptValue::UndefinedValue;
|
||||
}
|
||||
|
||||
/// Display a confirmation box with the options 'Yes' and 'No'
|
||||
/// \param const QString& message message to display
|
||||
/// \return QScriptValue `true` if 'Yes' was clicked, `false` otherwise
|
||||
QScriptValue WindowScriptingInterface::showConfirm(const QString& message) {
|
||||
QMessageBox::StandardButton response = QMessageBox::question(Application::getInstance()->getWindow(), "", message);
|
||||
return QScriptValue(response == QMessageBox::Yes);
|
||||
}
|
||||
|
||||
/// Display a prompt with a text box
|
||||
/// \param const QString& message message to display
|
||||
/// \param const QString& defaultText default text in the text box
|
||||
/// \return QScriptValue string text value in text box if the dialog was accepted, `null` otherwise.
|
||||
QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const QString& defaultText) {
|
||||
QInputDialog promptDialog(Application::getInstance()->getWindow());
|
||||
promptDialog.setWindowTitle("");
|
||||
promptDialog.setLabelText(message);
|
||||
promptDialog.setTextValue(defaultText);
|
||||
|
||||
if (promptDialog.exec() == QDialog::Accepted) {
|
||||
return QScriptValue(promptDialog.textValue());
|
||||
}
|
||||
|
||||
return QScriptValue::NullValue;
|
||||
}
|
||||
|
||||
/// Display a file dialog. If `directory` is an invalid file or directory the browser will start at the current
|
||||
/// working directory.
|
||||
/// \param const QString& title title of the window
|
||||
/// \param const QString& directory directory to start the file browser at
|
||||
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
|
||||
/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
|
||||
QScriptValue WindowScriptingInterface::showBrowse(const QString& title, const QString& directory, const QString& nameFilter) {
|
||||
// On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus
|
||||
// filename if the directory is valid.
|
||||
QString path = "";
|
||||
QFileInfo fileInfo = QFileInfo(directory);
|
||||
if (fileInfo.isDir()) {
|
||||
fileInfo.setFile(directory, "__HIFI_INVALID_FILE__");
|
||||
path = fileInfo.filePath();
|
||||
}
|
||||
|
||||
QFileDialog fileDialog(Application::getInstance()->getWindow(), title, path, nameFilter);
|
||||
fileDialog.setFileMode(QFileDialog::ExistingFile);
|
||||
if (fileDialog.exec()) {
|
||||
return QScriptValue(fileDialog.selectedFiles().first());
|
||||
}
|
||||
return QScriptValue::NullValue;
|
||||
}
|
||||
|
||||
int WindowScriptingInterface::getInnerWidth() {
|
||||
return Application::getInstance()->getWindow()->geometry().width();
|
||||
}
|
||||
|
||||
int WindowScriptingInterface::getInnerHeight() {
|
||||
return Application::getInstance()->getWindow()->geometry().height();
|
||||
}
|
42
interface/src/scripting/WindowScriptingInterface.h
Normal file
42
interface/src/scripting/WindowScriptingInterface.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// WindowScriptingInterface.cpp
|
||||
// interface/src/scripting
|
||||
//
|
||||
// Created by Ryan Huffman on 4/29/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_WindowScriptingInterface_h
|
||||
#define hifi_WindowScriptingInterface_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QScriptValue>
|
||||
#include <QString>
|
||||
|
||||
class WindowScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int innerWidth READ getInnerWidth)
|
||||
Q_PROPERTY(int innerHeight READ getInnerHeight)
|
||||
WindowScriptingInterface() { };
|
||||
public:
|
||||
static WindowScriptingInterface* getInstance();
|
||||
int getInnerWidth();
|
||||
int getInnerHeight();
|
||||
|
||||
public slots:
|
||||
QScriptValue alert(const QString& message = "");
|
||||
QScriptValue confirm(const QString& message = "");
|
||||
QScriptValue prompt(const QString& message = "", const QString& defaultText = "");
|
||||
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
|
||||
private slots:
|
||||
QScriptValue showAlert(const QString& message);
|
||||
QScriptValue showConfirm(const QString& message);
|
||||
QScriptValue showPrompt(const QString& message, const QString& defaultText);
|
||||
QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter);
|
||||
};
|
||||
|
||||
#endif // hifi_WindowScriptingInterface_h
|
|
@ -254,7 +254,7 @@ void ChatWindow::messageReceived(const QXmppMessage& message) {
|
|||
}
|
||||
|
||||
// Update background if this is a message from the current user
|
||||
bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getUsername();
|
||||
bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getAccountInfo().getUsername();
|
||||
|
||||
// Create message area
|
||||
ChatMessageArea* messageArea = new ChatMessageArea(true);
|
||||
|
|
|
@ -16,10 +16,12 @@ const int RESIZE_HANDLE_WIDTH = 7;
|
|||
|
||||
FramelessDialog::FramelessDialog(QWidget *parent, Qt::WindowFlags flags, Position position) :
|
||||
QDialog(parent, flags | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint),
|
||||
_allowResize(true),
|
||||
_isResizing(false),
|
||||
_resizeInitialWidth(0),
|
||||
_selfHidden(false),
|
||||
_position(position) {
|
||||
_position(position),
|
||||
_hideOnBlur(true) {
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
|
@ -43,7 +45,7 @@ bool FramelessDialog::eventFilter(QObject* sender, QEvent* event) {
|
|||
}
|
||||
break;
|
||||
case QEvent::WindowStateChange:
|
||||
if (parentWidget()->isMinimized()) {
|
||||
if (_hideOnBlur && parentWidget()->isMinimized()) {
|
||||
if (isVisible()) {
|
||||
_selfHidden = true;
|
||||
setHidden(true);
|
||||
|
@ -55,7 +57,7 @@ bool FramelessDialog::eventFilter(QObject* sender, QEvent* event) {
|
|||
break;
|
||||
case QEvent::ApplicationDeactivate:
|
||||
// hide on minimize and focus lost
|
||||
if (isVisible()) {
|
||||
if (_hideOnBlur && isVisible()) {
|
||||
_selfHidden = true;
|
||||
setHidden(true);
|
||||
}
|
||||
|
@ -84,18 +86,23 @@ void FramelessDialog::setStyleSheetFile(const QString& fileName) {
|
|||
|
||||
void FramelessDialog::showEvent(QShowEvent* event) {
|
||||
resizeAndPosition();
|
||||
QDialog::showEvent(event);
|
||||
}
|
||||
|
||||
void FramelessDialog::resizeAndPosition(bool resizeParent) {
|
||||
// keep full app height
|
||||
setFixedHeight(parentWidget()->size().height());
|
||||
// keep full app height or width depending on position
|
||||
if (_position == POSITION_LEFT || _position == POSITION_RIGHT) {
|
||||
setFixedHeight(parentWidget()->size().height());
|
||||
} else {
|
||||
setFixedWidth(parentWidget()->size().width());
|
||||
}
|
||||
|
||||
// resize parrent if width is smaller than this dialog
|
||||
if (resizeParent && parentWidget()->size().width() < size().width()) {
|
||||
parentWidget()->resize(size().width(), parentWidget()->size().height());
|
||||
}
|
||||
|
||||
if (_position == POSITION_LEFT) {
|
||||
if (_position == POSITION_LEFT || _position == POSITION_TOP) {
|
||||
// move to upper left corner
|
||||
move(parentWidget()->geometry().topLeft());
|
||||
} else if (_position == POSITION_RIGHT) {
|
||||
|
@ -104,16 +111,26 @@ void FramelessDialog::resizeAndPosition(bool resizeParent) {
|
|||
pos.setX(pos.x() - size().width());
|
||||
move(pos);
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
void FramelessDialog::mousePressEvent(QMouseEvent* mouseEvent) {
|
||||
if (mouseEvent->button() == Qt::LeftButton) {
|
||||
bool hitLeft = _position == POSITION_LEFT && abs(mouseEvent->pos().x() - size().width()) < RESIZE_HANDLE_WIDTH;
|
||||
bool hitRight = _position == POSITION_RIGHT && mouseEvent->pos().x() < RESIZE_HANDLE_WIDTH;
|
||||
if (hitLeft || hitRight) {
|
||||
_isResizing = true;
|
||||
_resizeInitialWidth = size().width();
|
||||
QApplication::setOverrideCursor(Qt::SizeHorCursor);
|
||||
if (_allowResize && mouseEvent->button() == Qt::LeftButton) {
|
||||
if (_position == POSITION_LEFT || _position == POSITION_RIGHT) {
|
||||
bool hitLeft = (_position == POSITION_LEFT) && (abs(mouseEvent->pos().x() - size().width()) < RESIZE_HANDLE_WIDTH);
|
||||
bool hitRight = (_position == POSITION_RIGHT) && (mouseEvent->pos().x() < RESIZE_HANDLE_WIDTH);
|
||||
if (hitLeft || hitRight) {
|
||||
_isResizing = true;
|
||||
_resizeInitialWidth = size().width();
|
||||
QApplication::setOverrideCursor(Qt::SizeHorCursor);
|
||||
}
|
||||
} else {
|
||||
bool hitTop = (_position == POSITION_TOP) && (abs(mouseEvent->pos().y() - size().height()) < RESIZE_HANDLE_WIDTH);
|
||||
if (hitTop) {
|
||||
_isResizing = true;
|
||||
_resizeInitialWidth = size().height();
|
||||
QApplication::setOverrideCursor(Qt::SizeHorCursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -133,6 +150,8 @@ void FramelessDialog::mouseMoveEvent(QMouseEvent* mouseEvent) {
|
|||
resizeAndPosition();
|
||||
_resizeInitialWidth = size().width();
|
||||
setUpdatesEnabled(true);
|
||||
} else if (_position == POSITION_TOP) {
|
||||
resize(size().width(), mouseEvent->pos().y());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,17 @@
|
|||
|
||||
class FramelessDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Position { POSITION_LEFT, POSITION_RIGHT };
|
||||
|
||||
FramelessDialog(QWidget* parent = 0, Qt::WindowFlags flags = 0, Position position = POSITION_LEFT);
|
||||
public:
|
||||
enum Position { POSITION_LEFT, POSITION_RIGHT, POSITION_TOP };
|
||||
|
||||
FramelessDialog(QWidget* parent, Qt::WindowFlags flags = 0, Position position = POSITION_LEFT);
|
||||
void setStyleSheetFile(const QString& fileName);
|
||||
void setAllowResize(bool allowResize) { _allowResize = allowResize; }
|
||||
bool getAllowResize() { return _allowResize; }
|
||||
void setHideOnBlur(bool hideOnBlur) { _hideOnBlur = hideOnBlur; }
|
||||
bool getHideOnBlur() { return _hideOnBlur; }
|
||||
void resizeAndPosition(bool resizeParent = true);
|
||||
|
||||
protected:
|
||||
virtual void mouseMoveEvent(QMouseEvent* mouseEvent);
|
||||
|
@ -33,12 +38,12 @@ protected:
|
|||
bool eventFilter(QObject* sender, QEvent* event);
|
||||
|
||||
private:
|
||||
void resizeAndPosition(bool resizeParent = true);
|
||||
|
||||
bool _allowResize;
|
||||
bool _isResizing;
|
||||
int _resizeInitialWidth;
|
||||
bool _selfHidden; ///< true when the dialog itself because of a window event (deactivation or minimization)
|
||||
Position _position;
|
||||
bool _hideOnBlur;
|
||||
|
||||
};
|
||||
|
||||
|
|
92
interface/src/ui/LoginDialog.cpp
Normal file
92
interface/src/ui/LoginDialog.cpp
Normal file
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// LoginDialog.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Ryan Huffman on 4/23/14.
|
||||
// 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 <QWidget>
|
||||
#include <QPushButton>
|
||||
#include <QPixmap>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Menu.h"
|
||||
#include "AccountManager.h"
|
||||
#include "ui_loginDialog.h"
|
||||
|
||||
#include "LoginDialog.h"
|
||||
|
||||
const QString FORGOT_PASSWORD_URL = "https://data-web.highfidelity.io/password/new";
|
||||
|
||||
LoginDialog::LoginDialog(QWidget* parent) :
|
||||
FramelessDialog(parent, 0, FramelessDialog::POSITION_TOP),
|
||||
_ui(new Ui::LoginDialog) {
|
||||
|
||||
_ui->setupUi(this);
|
||||
_ui->errorLabel->hide();
|
||||
_ui->emailLineEdit->setFocus();
|
||||
_ui->logoLabel->setPixmap(QPixmap(Application::resourcesPath() + "images/hifi-logo.svg"));
|
||||
_ui->loginButton->setIcon(QIcon(Application::resourcesPath() + "images/login.svg"));
|
||||
_ui->infoLabel->setVisible(false);
|
||||
_ui->errorLabel->setVisible(false);
|
||||
|
||||
setModal(true);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setHideOnBlur(false);
|
||||
|
||||
connect(&AccountManager::getInstance(), &AccountManager::loginComplete,
|
||||
this, &LoginDialog::handleLoginCompleted);
|
||||
connect(&AccountManager::getInstance(), &AccountManager::loginFailed,
|
||||
this, &LoginDialog::handleLoginFailed);
|
||||
connect(_ui->loginButton, &QPushButton::clicked,
|
||||
this, &LoginDialog::handleLoginClicked);
|
||||
connect(_ui->closeButton, &QPushButton::clicked,
|
||||
this, &LoginDialog::close);
|
||||
};
|
||||
|
||||
LoginDialog::~LoginDialog() {
|
||||
delete _ui;
|
||||
};
|
||||
|
||||
void LoginDialog::handleLoginCompleted(const QUrl& authURL) {
|
||||
_ui->infoLabel->setVisible(false);
|
||||
_ui->errorLabel->setVisible(false);
|
||||
close();
|
||||
};
|
||||
|
||||
void LoginDialog::handleLoginFailed() {
|
||||
_ui->infoLabel->setVisible(false);
|
||||
_ui->errorLabel->setVisible(true);
|
||||
|
||||
_ui->errorLabel->show();
|
||||
_ui->loginArea->setDisabled(false);
|
||||
|
||||
// Move focus to password and select the entire line
|
||||
_ui->passwordLineEdit->setFocus();
|
||||
_ui->passwordLineEdit->setSelection(0, _ui->emailLineEdit->maxLength());
|
||||
};
|
||||
|
||||
void LoginDialog::handleLoginClicked() {
|
||||
// If the email or password inputs are empty, move focus to them, otherwise attempt to login.
|
||||
if (_ui->emailLineEdit->text().isEmpty()) {
|
||||
_ui->emailLineEdit->setFocus();
|
||||
} else if (_ui->passwordLineEdit->text().isEmpty()) {
|
||||
_ui->passwordLineEdit->setFocus();
|
||||
} else {
|
||||
_ui->infoLabel->setVisible(true);
|
||||
_ui->errorLabel->setVisible(false);
|
||||
|
||||
_ui->loginArea->setDisabled(true);
|
||||
AccountManager::getInstance().requestAccessToken(_ui->emailLineEdit->text(), _ui->passwordLineEdit->text());
|
||||
}
|
||||
};
|
||||
|
||||
void LoginDialog::moveEvent(QMoveEvent* event) {
|
||||
// Modal dialogs seemed to get repositioned automatically. Combat this by moving the window if needed.
|
||||
resizeAndPosition();
|
||||
};
|
42
interface/src/ui/LoginDialog.h
Normal file
42
interface/src/ui/LoginDialog.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// LoginDialog.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Ryan Huffman on 4/23/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_LoginDialog_h
|
||||
#define hifi_LoginDialog_h
|
||||
|
||||
#include <QObject>
|
||||
#include "FramelessDialog.h"
|
||||
|
||||
namespace Ui {
|
||||
class LoginDialog;
|
||||
}
|
||||
|
||||
class LoginDialog : public FramelessDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LoginDialog(QWidget* parent);
|
||||
~LoginDialog();
|
||||
|
||||
public slots:
|
||||
void handleLoginClicked();
|
||||
void handleLoginCompleted(const QUrl& authURL);
|
||||
void handleLoginFailed();
|
||||
|
||||
protected:
|
||||
void moveEvent(QMoveEvent* event);
|
||||
|
||||
private:
|
||||
Ui::LoginDialog* _ui;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_LoginDialog_h
|
116
interface/src/ui/OAuthWebViewHandler.cpp
Normal file
116
interface/src/ui/OAuthWebViewHandler.cpp
Normal file
|
@ -0,0 +1,116 @@
|
|||
//
|
||||
// OAuthWebViewHandler.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-05-01.
|
||||
// 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 <QtWebKitWidgets/QWebView>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include "OAuthWebViewHandler.h"
|
||||
|
||||
OAuthWebViewHandler& OAuthWebViewHandler::getInstance() {
|
||||
static OAuthWebViewHandler sharedInstance;
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
OAuthWebViewHandler::OAuthWebViewHandler() :
|
||||
_activeWebView(NULL),
|
||||
_webViewRedisplayTimer(),
|
||||
_lastAuthorizationURL()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig() {
|
||||
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
|
||||
|
||||
// add the High Fidelity root CA to the list of trusted CA certificates
|
||||
QByteArray highFidelityCACertificate(reinterpret_cast<char*>(DTLSSession::highFidelityCADatum()->data),
|
||||
DTLSSession::highFidelityCADatum()->size);
|
||||
sslConfig.setCaCertificates(sslConfig.caCertificates() + QSslCertificate::fromData(highFidelityCACertificate));
|
||||
|
||||
// set the modified configuration
|
||||
QSslConfiguration::setDefaultConfiguration(sslConfig);
|
||||
}
|
||||
|
||||
const int WEB_VIEW_REDISPLAY_ELAPSED_MSECS = 5 * 1000;
|
||||
|
||||
void OAuthWebViewHandler::displayWebviewForAuthorizationURL(const QUrl& authorizationURL) {
|
||||
if (!_activeWebView) {
|
||||
|
||||
if (!_lastAuthorizationURL.isEmpty()) {
|
||||
if (_lastAuthorizationURL.host() == authorizationURL.host()
|
||||
&& _webViewRedisplayTimer.elapsed() < WEB_VIEW_REDISPLAY_ELAPSED_MSECS) {
|
||||
// this would be re-displaying an OAuth dialog for the same auth URL inside of the redisplay ms
|
||||
// so return instead
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_lastAuthorizationURL = authorizationURL;
|
||||
|
||||
_activeWebView = new QWebView;
|
||||
|
||||
// keep the window on top and delete it when it closes
|
||||
_activeWebView->setWindowFlags(Qt::WindowStaysOnTopHint);
|
||||
_activeWebView->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
qDebug() << "Displaying QWebView for OAuth authorization at" << authorizationURL.toString();
|
||||
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
|
||||
QUrl codedAuthorizationURL = authorizationURL;
|
||||
|
||||
// check if we have an access token for this host - if so we can bypass login by adding it to the URL
|
||||
if (accountManager.getAuthURL().host() == authorizationURL.host()
|
||||
&& accountManager.hasValidAccessToken()) {
|
||||
|
||||
const QString ACCESS_TOKEN_QUERY_STRING_KEY = "access_token";
|
||||
|
||||
QUrlQuery authQuery(codedAuthorizationURL);
|
||||
authQuery.addQueryItem(ACCESS_TOKEN_QUERY_STRING_KEY, accountManager.getAccountInfo().getAccessToken().token);
|
||||
|
||||
codedAuthorizationURL.setQuery(authQuery);
|
||||
}
|
||||
|
||||
_activeWebView->load(codedAuthorizationURL);
|
||||
_activeWebView->show();
|
||||
|
||||
connect(_activeWebView->page()->networkAccessManager(), &QNetworkAccessManager::sslErrors,
|
||||
this, &OAuthWebViewHandler::handleSSLErrors);
|
||||
connect(_activeWebView.data(), &QWebView::loadFinished, this, &OAuthWebViewHandler::handleLoadFinished);
|
||||
|
||||
// connect to the destroyed signal so after the web view closes we can start a timer
|
||||
connect(_activeWebView.data(), &QWebView::destroyed, this, &OAuthWebViewHandler::handleWebViewDestroyed);
|
||||
}
|
||||
}
|
||||
|
||||
void OAuthWebViewHandler::handleSSLErrors(QNetworkReply* networkReply, const QList<QSslError>& errorList) {
|
||||
qDebug() << "SSL Errors:" << errorList;
|
||||
}
|
||||
|
||||
void OAuthWebViewHandler::handleLoadFinished(bool success) {
|
||||
if (success && _activeWebView->url().host() == NodeList::getInstance()->getDomainHandler().getHostname()) {
|
||||
qDebug() << "OAuth authorization code passed successfully to domain-server.";
|
||||
|
||||
// grab the UUID that is set as the state parameter in the auth URL
|
||||
// since that is our new session UUID
|
||||
QUrlQuery authQuery(_activeWebView->url());
|
||||
|
||||
const QString AUTH_STATE_QUERY_KEY = "state";
|
||||
NodeList::getInstance()->setSessionUUID(QUuid(authQuery.queryItemValue(AUTH_STATE_QUERY_KEY)));
|
||||
|
||||
_activeWebView->close();
|
||||
}
|
||||
}
|
||||
|
||||
void OAuthWebViewHandler::handleWebViewDestroyed(QObject* destroyedObject) {
|
||||
_webViewRedisplayTimer.restart();
|
||||
}
|
41
interface/src/ui/OAuthWebViewHandler.h
Normal file
41
interface/src/ui/OAuthWebViewHandler.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// OAuthWebviewHandler.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-05-01.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_OAuthWebviewHandler_h
|
||||
#define hifi_OAuthWebviewHandler_h
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
class QWebView;
|
||||
|
||||
class OAuthWebViewHandler : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
OAuthWebViewHandler();
|
||||
static OAuthWebViewHandler& getInstance();
|
||||
static void addHighFidelityRootCAToSSLConfig();
|
||||
|
||||
void clearLastAuthorizationURL() { _lastAuthorizationURL = QUrl(); }
|
||||
|
||||
public slots:
|
||||
void displayWebviewForAuthorizationURL(const QUrl& authorizationURL);
|
||||
|
||||
private slots:
|
||||
void handleSSLErrors(QNetworkReply* networkReply, const QList<QSslError>& errorList);
|
||||
void handleLoadFinished(bool success);
|
||||
void handleWebViewDestroyed(QObject* destroyedObject);
|
||||
private:
|
||||
QPointer<QWebView> _activeWebView;
|
||||
QElapsedTimer _webViewRedisplayTimer;
|
||||
QUrl _lastAuthorizationURL;
|
||||
};
|
||||
|
||||
#endif // hifi_OAuthWebviewHandler_h
|
|
@ -232,6 +232,9 @@ void OctreeStatsDialog::showAllOctreeServers() {
|
|||
showOctreeServersOfType(serverCount, NodeType::ParticleServer, "Particle",
|
||||
Application::getInstance()->getParticleServerJurisdictions());
|
||||
|
||||
showOctreeServersOfType(serverCount, NodeType::ModelServer, "Model",
|
||||
Application::getInstance()->getModelServerJurisdictions());
|
||||
|
||||
if (_voxelServerLabelsCount > serverCount) {
|
||||
for (int i = serverCount; i < _voxelServerLabelsCount; i++) {
|
||||
int serverLabel = _voxelServerLables[i];
|
||||
|
|
|
@ -12,39 +12,35 @@
|
|||
#include "ui_runningScriptsWidget.h"
|
||||
#include "RunningScriptsWidget.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QKeyEvent>
|
||||
#include <QPainter>
|
||||
#include <QTableWidgetItem>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
RunningScriptsWidget::RunningScriptsWidget(QDockWidget *parent) :
|
||||
QDockWidget(parent),
|
||||
ui(new Ui::RunningScriptsWidget)
|
||||
{
|
||||
RunningScriptsWidget::RunningScriptsWidget(QWidget* parent) :
|
||||
FramelessDialog(parent, 0, POSITION_LEFT),
|
||||
ui(new Ui::RunningScriptsWidget) {
|
||||
ui->setupUi(this);
|
||||
|
||||
// remove the title bar (see the Qt docs on setTitleBarWidget)
|
||||
setTitleBarWidget(new QWidget());
|
||||
setAllowResize(false);
|
||||
|
||||
ui->runningScriptsTableWidget->setColumnCount(2);
|
||||
ui->runningScriptsTableWidget->verticalHeader()->setVisible(false);
|
||||
ui->runningScriptsTableWidget->horizontalHeader()->setVisible(false);
|
||||
ui->runningScriptsTableWidget->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
ui->runningScriptsTableWidget->setShowGrid(false);
|
||||
ui->runningScriptsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
ui->runningScriptsTableWidget->setColumnWidth(0, 235);
|
||||
ui->runningScriptsTableWidget->setColumnWidth(1, 25);
|
||||
connect(ui->runningScriptsTableWidget, &QTableWidget::cellClicked, this, &RunningScriptsWidget::stopScript);
|
||||
ui->hideWidgetButton->setIcon(QIcon(Application::resourcesPath() + "images/close.svg"));
|
||||
ui->reloadAllButton->setIcon(QIcon(Application::resourcesPath() + "images/reload.svg"));
|
||||
ui->stopAllButton->setIcon(QIcon(Application::resourcesPath() + "images/stop.svg"));
|
||||
ui->loadScriptButton->setIcon(QIcon(Application::resourcesPath() + "images/plus-white.svg"));
|
||||
|
||||
ui->recentlyLoadedScriptsTableWidget->setColumnCount(2);
|
||||
ui->recentlyLoadedScriptsTableWidget->verticalHeader()->setVisible(false);
|
||||
ui->recentlyLoadedScriptsTableWidget->horizontalHeader()->setVisible(false);
|
||||
ui->recentlyLoadedScriptsTableWidget->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
ui->recentlyLoadedScriptsTableWidget->setShowGrid(false);
|
||||
ui->recentlyLoadedScriptsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
ui->recentlyLoadedScriptsTableWidget->setColumnWidth(0, 25);
|
||||
ui->recentlyLoadedScriptsTableWidget->setColumnWidth(1, 235);
|
||||
connect(ui->recentlyLoadedScriptsTableWidget, &QTableWidget::cellClicked,
|
||||
_runningScriptsTable = new ScriptsTableWidget(ui->runningScriptsTableWidget);
|
||||
_runningScriptsTable->setColumnCount(2);
|
||||
_runningScriptsTable->setColumnWidth(0, 245);
|
||||
_runningScriptsTable->setColumnWidth(1, 22);
|
||||
connect(_runningScriptsTable, &QTableWidget::cellClicked, this, &RunningScriptsWidget::stopScript);
|
||||
|
||||
_recentlyLoadedScriptsTable = new ScriptsTableWidget(ui->recentlyLoadedScriptsTableWidget);
|
||||
_recentlyLoadedScriptsTable->setColumnCount(1);
|
||||
_recentlyLoadedScriptsTable->setColumnWidth(0, 265);
|
||||
connect(_recentlyLoadedScriptsTable, &QTableWidget::cellClicked,
|
||||
this, &RunningScriptsWidget::loadScript);
|
||||
|
||||
connect(ui->hideWidgetButton, &QPushButton::clicked,
|
||||
|
@ -53,118 +49,126 @@ RunningScriptsWidget::RunningScriptsWidget(QDockWidget *parent) :
|
|||
Application::getInstance(), &Application::reloadAllScripts);
|
||||
connect(ui->stopAllButton, &QPushButton::clicked,
|
||||
this, &RunningScriptsWidget::allScriptsStopped);
|
||||
connect(ui->loadScriptButton, &QPushButton::clicked,
|
||||
Application::getInstance(), &Application::loadDialog);
|
||||
}
|
||||
|
||||
RunningScriptsWidget::~RunningScriptsWidget()
|
||||
{
|
||||
RunningScriptsWidget::~RunningScriptsWidget() {
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::setRunningScripts(const QStringList& list)
|
||||
{
|
||||
ui->runningScriptsTableWidget->setRowCount(list.size());
|
||||
void RunningScriptsWidget::setBoundary(const QRect& rect) {
|
||||
_boundary = rect;
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::setRunningScripts(const QStringList& list) {
|
||||
_runningScriptsTable->setRowCount(list.size());
|
||||
|
||||
ui->noRunningScriptsLabel->setVisible(list.isEmpty());
|
||||
ui->currentlyRunningLabel->setVisible(!list.isEmpty());
|
||||
ui->line1->setVisible(!list.isEmpty());
|
||||
ui->runningScriptsTableWidget->setVisible(!list.isEmpty());
|
||||
ui->reloadAllButton->setVisible(!list.isEmpty());
|
||||
ui->stopAllButton->setVisible(!list.isEmpty());
|
||||
|
||||
const int CLOSE_ICON_HEIGHT = 12;
|
||||
|
||||
for (int i = 0; i < list.size(); ++i) {
|
||||
QTableWidgetItem *scriptName = new QTableWidgetItem;
|
||||
scriptName->setText(list.at(i));
|
||||
scriptName->setText(QFileInfo(list.at(i)).fileName());
|
||||
scriptName->setToolTip(list.at(i));
|
||||
scriptName->setTextAlignment(Qt::AlignCenter);
|
||||
scriptName->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
QTableWidgetItem *closeIcon = new QTableWidgetItem;
|
||||
closeIcon->setIcon(QIcon(Application::resourcesPath() + "/images/kill-script.svg"));
|
||||
closeIcon->setIcon(QIcon(QPixmap(Application::resourcesPath() + "images/kill-script.svg").scaledToHeight(CLOSE_ICON_HEIGHT)));
|
||||
|
||||
ui->runningScriptsTableWidget->setItem(i, 0, scriptName);
|
||||
ui->runningScriptsTableWidget->setItem(i, 1, closeIcon);
|
||||
_runningScriptsTable->setItem(i, 0, scriptName);
|
||||
_runningScriptsTable->setItem(i, 1, closeIcon);
|
||||
}
|
||||
|
||||
const int RUNNING_SCRIPTS_TABLE_LEFT_MARGIN = 12;
|
||||
const int RECENTLY_LOADED_TOP_MARGIN = 61;
|
||||
const int RECENTLY_LOADED_LABEL_TOP_MARGIN = 19;
|
||||
|
||||
int y = ui->runningScriptsTableWidget->y() + RUNNING_SCRIPTS_TABLE_LEFT_MARGIN;
|
||||
for (int i = 0; i < _runningScriptsTable->rowCount(); ++i) {
|
||||
y += _runningScriptsTable->rowHeight(i);
|
||||
}
|
||||
|
||||
ui->runningScriptsTableWidget->resize(ui->runningScriptsTableWidget->width(), y - RUNNING_SCRIPTS_TABLE_LEFT_MARGIN);
|
||||
_runningScriptsTable->resize(_runningScriptsTable->width(), y - RUNNING_SCRIPTS_TABLE_LEFT_MARGIN);
|
||||
ui->reloadAllButton->move(ui->reloadAllButton->x(), y);
|
||||
ui->stopAllButton->move(ui->stopAllButton->x(), y);
|
||||
ui->recentlyLoadedLabel->move(ui->recentlyLoadedLabel->x(),
|
||||
ui->stopAllButton->y() + ui->stopAllButton->height() + RECENTLY_LOADED_TOP_MARGIN);
|
||||
ui->recentlyLoadedScriptsTableWidget->move(ui->recentlyLoadedScriptsTableWidget->x(),
|
||||
ui->recentlyLoadedLabel->y() + RECENTLY_LOADED_LABEL_TOP_MARGIN);
|
||||
|
||||
|
||||
createRecentlyLoadedScriptsTable();
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::keyPressEvent(QKeyEvent *e)
|
||||
void RunningScriptsWidget::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
switch(e->key()) {
|
||||
int loadScriptNumber = -1;
|
||||
switch(event->key()) {
|
||||
case Qt::Key_Escape:
|
||||
Application::getInstance()->toggleRunningScriptsWidget();
|
||||
break;
|
||||
|
||||
case Qt::Key_1:
|
||||
if (_recentlyLoadedScripts.size() > 0) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(0));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_2:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 2) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(1));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_3:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 3) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(2));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_4:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 4) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(3));
|
||||
}
|
||||
break;
|
||||
case Qt::Key_5:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 5) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(4));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_6:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 6) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(5));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_7:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 7) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(6));
|
||||
}
|
||||
break;
|
||||
case Qt::Key_8:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 8) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(7));
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_9:
|
||||
if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 9) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(8));
|
||||
}
|
||||
loadScriptNumber = event->key() - Qt::Key_1;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (loadScriptNumber >= 0) {
|
||||
if (_recentlyLoadedScripts.size() > loadScriptNumber) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScripts.at(loadScriptNumber));
|
||||
}
|
||||
}
|
||||
|
||||
FramelessDialog::keyPressEvent(event);
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::stopScript(int row, int column)
|
||||
{
|
||||
void RunningScriptsWidget::paintEvent(QPaintEvent* event) {
|
||||
QPainter painter(this);
|
||||
painter.setPen(QColor::fromRgb(225, 225, 225)); // #e1e1e1
|
||||
|
||||
if (ui->currentlyRunningLabel->isVisible()) {
|
||||
// line below the 'Currently Running' label
|
||||
painter.drawLine(36, ui->currentlyRunningLabel->y() + ui->currentlyRunningLabel->height(),
|
||||
300, ui->currentlyRunningLabel->y() + ui->currentlyRunningLabel->height());
|
||||
}
|
||||
|
||||
if (ui->recentlyLoadedLabel->isVisible()) {
|
||||
// line below the 'Recently loaded' label
|
||||
painter.drawLine(36, ui->recentlyLoadedLabel->y() + ui->recentlyLoadedLabel->height(),
|
||||
300, ui->recentlyLoadedLabel->y() + ui->recentlyLoadedLabel->height());
|
||||
}
|
||||
|
||||
painter.end();
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::stopScript(int row, int column) {
|
||||
if (column == 1) { // make sure the user has clicked on the close icon
|
||||
_lastStoppedScript = ui->runningScriptsTableWidget->item(row, 0)->text();
|
||||
emit stopScriptName(ui->runningScriptsTableWidget->item(row, 0)->text());
|
||||
_lastStoppedScript = _runningScriptsTable->item(row, 0)->toolTip();
|
||||
emit stopScriptName(_runningScriptsTable->item(row, 0)->toolTip());
|
||||
}
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::loadScript(int row, int column)
|
||||
{
|
||||
Application::getInstance()->loadScript(ui->recentlyLoadedScriptsTableWidget->item(row, column)->text());
|
||||
void RunningScriptsWidget::loadScript(int row, int column) {
|
||||
Application::getInstance()->loadScript(_recentlyLoadedScriptsTable->item(row, column)->toolTip());
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::allScriptsStopped()
|
||||
{
|
||||
void RunningScriptsWidget::allScriptsStopped() {
|
||||
QStringList list = Application::getInstance()->getRunningScripts();
|
||||
for (int i = 0; i < list.size(); ++i) {
|
||||
_recentlyLoadedScripts.prepend(list.at(i));
|
||||
|
@ -173,8 +177,7 @@ void RunningScriptsWidget::allScriptsStopped()
|
|||
Application::getInstance()->stopAllScripts();
|
||||
}
|
||||
|
||||
void RunningScriptsWidget::createRecentlyLoadedScriptsTable()
|
||||
{
|
||||
void RunningScriptsWidget::createRecentlyLoadedScriptsTable() {
|
||||
if (!_recentlyLoadedScripts.contains(_lastStoppedScript) && !_lastStoppedScript.isEmpty()) {
|
||||
_recentlyLoadedScripts.prepend(_lastStoppedScript);
|
||||
_lastStoppedScript = "";
|
||||
|
@ -187,21 +190,28 @@ void RunningScriptsWidget::createRecentlyLoadedScriptsTable()
|
|||
}
|
||||
|
||||
ui->recentlyLoadedLabel->setVisible(!_recentlyLoadedScripts.isEmpty());
|
||||
ui->line2->setVisible(!_recentlyLoadedScripts.isEmpty());
|
||||
ui->recentlyLoadedScriptsTableWidget->setVisible(!_recentlyLoadedScripts.isEmpty());
|
||||
ui->recentlyLoadedInstruction->setVisible(!_recentlyLoadedScripts.isEmpty());
|
||||
|
||||
int limit = _recentlyLoadedScripts.size() > 9 ? 9 : _recentlyLoadedScripts.size();
|
||||
ui->recentlyLoadedScriptsTableWidget->setRowCount(limit);
|
||||
for (int i = 0; i < limit; ++i) {
|
||||
_recentlyLoadedScriptsTable->setRowCount(limit);
|
||||
for (int i = 0; i < limit; i++) {
|
||||
QTableWidgetItem *scriptName = new QTableWidgetItem;
|
||||
scriptName->setText(_recentlyLoadedScripts.at(i));
|
||||
scriptName->setText(QString::number(i + 1) + ". " + QFileInfo(_recentlyLoadedScripts.at(i)).fileName());
|
||||
scriptName->setToolTip(_recentlyLoadedScripts.at(i));
|
||||
scriptName->setTextAlignment(Qt::AlignCenter);
|
||||
QTableWidgetItem *number = new QTableWidgetItem;
|
||||
number->setText(QString::number(i+1) + ".");
|
||||
scriptName->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
|
||||
ui->recentlyLoadedScriptsTableWidget->setItem(i, 0, number);
|
||||
ui->recentlyLoadedScriptsTableWidget->setItem(i, 1, scriptName);
|
||||
_recentlyLoadedScriptsTable->setItem(i, 0, scriptName);
|
||||
}
|
||||
|
||||
int y = ui->recentlyLoadedScriptsTableWidget->y() + 15;
|
||||
for (int i = 0; i < _recentlyLoadedScriptsTable->rowCount(); ++i) {
|
||||
y += _recentlyLoadedScriptsTable->rowHeight(i);
|
||||
}
|
||||
|
||||
ui->recentlyLoadedInstruction->setGeometry(36, y,
|
||||
ui->recentlyLoadedInstruction->width(),
|
||||
ui->recentlyLoadedInstruction->height());
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
|
|
@ -12,18 +12,18 @@
|
|||
#ifndef hifi_RunningScriptsWidget_h
|
||||
#define hifi_RunningScriptsWidget_h
|
||||
|
||||
// Qt
|
||||
#include <QDockWidget>
|
||||
#include "FramelessDialog.h"
|
||||
#include "ScriptsTableWidget.h"
|
||||
|
||||
namespace Ui {
|
||||
class RunningScriptsWidget;
|
||||
}
|
||||
|
||||
class RunningScriptsWidget : public QDockWidget
|
||||
class RunningScriptsWidget : public FramelessDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RunningScriptsWidget(QDockWidget *parent = 0);
|
||||
explicit RunningScriptsWidget(QWidget* parent = NULL);
|
||||
~RunningScriptsWidget();
|
||||
|
||||
void setRunningScripts(const QStringList& list);
|
||||
|
@ -32,7 +32,11 @@ signals:
|
|||
void stopScriptName(const QString& name);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *e);
|
||||
virtual void keyPressEvent(QKeyEvent* event);
|
||||
virtual void paintEvent(QPaintEvent* event);
|
||||
|
||||
public slots:
|
||||
void setBoundary(const QRect& rect);
|
||||
|
||||
private slots:
|
||||
void stopScript(int row, int column);
|
||||
|
@ -40,9 +44,12 @@ private slots:
|
|||
void allScriptsStopped();
|
||||
|
||||
private:
|
||||
Ui::RunningScriptsWidget *ui;
|
||||
Ui::RunningScriptsWidget* ui;
|
||||
ScriptsTableWidget* _runningScriptsTable;
|
||||
ScriptsTableWidget* _recentlyLoadedScriptsTable;
|
||||
QStringList _recentlyLoadedScripts;
|
||||
QString _lastStoppedScript;
|
||||
QRect _boundary;
|
||||
|
||||
void createRecentlyLoadedScriptsTable();
|
||||
};
|
||||
|
|
108
interface/src/ui/ScriptEditBox.cpp
Normal file
108
interface/src/ui/ScriptEditBox.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
//
|
||||
// ScriptEditBox.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Thijs Wenker on 4/30/14.
|
||||
// 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 "ScriptEditBox.h"
|
||||
#include "ScriptLineNumberArea.h"
|
||||
#include "Application.h"
|
||||
|
||||
ScriptEditBox::ScriptEditBox(QWidget* parent) :
|
||||
QPlainTextEdit(parent)
|
||||
{
|
||||
_scriptLineNumberArea = new ScriptLineNumberArea(this);
|
||||
|
||||
connect(this, &ScriptEditBox::blockCountChanged, this, &ScriptEditBox::updateLineNumberAreaWidth);
|
||||
connect(this, &ScriptEditBox::updateRequest, this, &ScriptEditBox::updateLineNumberArea);
|
||||
connect(this, &ScriptEditBox::cursorPositionChanged, this, &ScriptEditBox::highlightCurrentLine);
|
||||
|
||||
updateLineNumberAreaWidth(0);
|
||||
highlightCurrentLine();
|
||||
}
|
||||
|
||||
int ScriptEditBox::lineNumberAreaWidth() {
|
||||
int digits = 1;
|
||||
const int SPACER_PIXELS = 3;
|
||||
const int BASE_TEN = 10;
|
||||
int max = qMax(1, blockCount());
|
||||
while (max >= BASE_TEN) {
|
||||
max /= BASE_TEN;
|
||||
digits++;
|
||||
}
|
||||
return SPACER_PIXELS + fontMetrics().width(QLatin1Char('H')) * digits;
|
||||
}
|
||||
|
||||
void ScriptEditBox::updateLineNumberAreaWidth(int blockCount) {
|
||||
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
|
||||
}
|
||||
|
||||
void ScriptEditBox::updateLineNumberArea(const QRect& rect, int deltaY) {
|
||||
if (deltaY) {
|
||||
_scriptLineNumberArea->scroll(0, deltaY);
|
||||
} else {
|
||||
_scriptLineNumberArea->update(0, rect.y(), _scriptLineNumberArea->width(), rect.height());
|
||||
}
|
||||
|
||||
if (rect.contains(viewport()->rect())) {
|
||||
updateLineNumberAreaWidth(0);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEditBox::resizeEvent(QResizeEvent* event) {
|
||||
QPlainTextEdit::resizeEvent(event);
|
||||
|
||||
QRect localContentsRect = contentsRect();
|
||||
_scriptLineNumberArea->setGeometry(QRect(localContentsRect.left(), localContentsRect.top(), lineNumberAreaWidth(),
|
||||
localContentsRect.height()));
|
||||
}
|
||||
|
||||
void ScriptEditBox::highlightCurrentLine() {
|
||||
QList<QTextEdit::ExtraSelection> extraSelections;
|
||||
|
||||
if (!isReadOnly()) {
|
||||
QTextEdit::ExtraSelection selection;
|
||||
|
||||
QColor lineColor = QColor(Qt::gray).lighter();
|
||||
|
||||
selection.format.setBackground(lineColor);
|
||||
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
|
||||
selection.cursor = textCursor();
|
||||
selection.cursor.clearSelection();
|
||||
extraSelections.append(selection);
|
||||
}
|
||||
|
||||
setExtraSelections(extraSelections);
|
||||
}
|
||||
|
||||
void ScriptEditBox::lineNumberAreaPaintEvent(QPaintEvent* event)
|
||||
{
|
||||
QPainter painter(_scriptLineNumberArea);
|
||||
painter.fillRect(event->rect(), Qt::lightGray);
|
||||
QTextBlock block = firstVisibleBlock();
|
||||
int blockNumber = block.blockNumber();
|
||||
int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
|
||||
int bottom = top + (int) blockBoundingRect(block).height();
|
||||
|
||||
while (block.isValid() && top <= event->rect().bottom()) {
|
||||
if (block.isVisible() && bottom >= event->rect().top()) {
|
||||
QFont font = painter.font();
|
||||
font.setBold(this->textCursor().blockNumber() == block.blockNumber());
|
||||
painter.setFont(font);
|
||||
QString number = QString::number(blockNumber + 1);
|
||||
painter.setPen(Qt::black);
|
||||
painter.drawText(0, top, _scriptLineNumberArea->width(), fontMetrics().height(),
|
||||
Qt::AlignRight, number);
|
||||
}
|
||||
|
||||
block = block.next();
|
||||
top = bottom;
|
||||
bottom = top + (int) blockBoundingRect(block).height();
|
||||
blockNumber++;
|
||||
}
|
||||
}
|
38
interface/src/ui/ScriptEditBox.h
Normal file
38
interface/src/ui/ScriptEditBox.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// ScriptEditBox.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Thijs Wenker on 4/30/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_ScriptEditBox_h
|
||||
#define hifi_ScriptEditBox_h
|
||||
|
||||
#include <QPlainTextEdit>
|
||||
|
||||
class ScriptEditBox : public QPlainTextEdit {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ScriptEditBox(QWidget* parent = NULL);
|
||||
|
||||
void lineNumberAreaPaintEvent(QPaintEvent* event);
|
||||
int lineNumberAreaWidth();
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* event);
|
||||
|
||||
private slots:
|
||||
void updateLineNumberAreaWidth(int blockCount);
|
||||
void highlightCurrentLine();
|
||||
void updateLineNumberArea(const QRect& rect, int deltaY);
|
||||
|
||||
private:
|
||||
QWidget* _scriptLineNumberArea;
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptEditBox_h
|
|
@ -32,8 +32,10 @@ ScriptEditorWidget::ScriptEditorWidget() :
|
|||
{
|
||||
_scriptEditorWidgetUI->setupUi(this);
|
||||
|
||||
connect(_scriptEditorWidgetUI->scriptEdit->document(), SIGNAL(modificationChanged(bool)), this, SIGNAL(scriptModified()));
|
||||
connect(_scriptEditorWidgetUI->scriptEdit->document(), SIGNAL(contentsChanged()), this, SLOT(onScriptModified()));
|
||||
connect(_scriptEditorWidgetUI->scriptEdit->document(), &QTextDocument::modificationChanged, this,
|
||||
&ScriptEditorWidget::scriptModified);
|
||||
connect(_scriptEditorWidgetUI->scriptEdit->document(), &QTextDocument::contentsChanged, this,
|
||||
&ScriptEditorWidget::onScriptModified);
|
||||
|
||||
// remove the title bar (see the Qt docs on setTitleBarWidget)
|
||||
setTitleBarWidget(new QWidget());
|
||||
|
@ -68,16 +70,19 @@ bool ScriptEditorWidget::setRunning(bool run) {
|
|||
return false;
|
||||
}
|
||||
// Clean-up old connections.
|
||||
disconnect(this, SLOT(onScriptError(const QString&)));
|
||||
disconnect(this, SLOT(onScriptPrint(const QString&)));
|
||||
if (_scriptEngine != NULL) {
|
||||
disconnect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
|
||||
disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
|
||||
disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
|
||||
}
|
||||
|
||||
if (run) {
|
||||
_scriptEngine = Application::getInstance()->loadScript(_currentScript, true);
|
||||
connect(_scriptEngine, SIGNAL(runningStateChanged()), this, SIGNAL(runningStateChanged()));
|
||||
connect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
|
||||
|
||||
// Make new connections.
|
||||
connect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(onScriptError(const QString&)));
|
||||
connect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(onScriptPrint(const QString&)));
|
||||
connect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
|
||||
connect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
|
||||
} else {
|
||||
Application::getInstance()->stopScript(_currentScript);
|
||||
_scriptEngine = NULL;
|
||||
|
@ -101,25 +106,44 @@ bool ScriptEditorWidget::saveFile(const QString &scriptPath) {
|
|||
}
|
||||
|
||||
void ScriptEditorWidget::loadFile(const QString& scriptPath) {
|
||||
QFile file(scriptPath);
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QMessageBox::warning(this, tr("Interface"), tr("Cannot read script %1:\n%2.").arg(scriptPath).arg(file.errorString()));
|
||||
return;
|
||||
QUrl url(scriptPath);
|
||||
|
||||
// if the scheme length is one or lower, maybe they typed in a file, let's try
|
||||
const int WINDOWS_DRIVE_LETTER_SIZE = 1;
|
||||
if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) {
|
||||
QFile file(scriptPath);
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QMessageBox::warning(this, tr("Interface"), tr("Cannot read script %1:\n%2.").arg(scriptPath)
|
||||
.arg(file.errorString()));
|
||||
return;
|
||||
}
|
||||
QTextStream in(&file);
|
||||
_scriptEditorWidgetUI->scriptEdit->setPlainText(in.readAll());
|
||||
setScriptFile(scriptPath);
|
||||
|
||||
if (_scriptEngine != NULL) {
|
||||
disconnect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
|
||||
disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
|
||||
disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
|
||||
}
|
||||
} else {
|
||||
QNetworkAccessManager* networkManager = new QNetworkAccessManager(this);
|
||||
QNetworkReply* reply = networkManager->get(QNetworkRequest(url));
|
||||
qDebug() << "Downloading included script at" << scriptPath;
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
_scriptEditorWidgetUI->scriptEdit->setPlainText(reply->readAll());
|
||||
if (!saveAs()) {
|
||||
static_cast<ScriptEditorWindow*>(this->parent()->parent()->parent())->terminateCurrentTab();
|
||||
}
|
||||
}
|
||||
|
||||
QTextStream in(&file);
|
||||
_scriptEditorWidgetUI->scriptEdit->setPlainText(in.readAll());
|
||||
|
||||
setScriptFile(scriptPath);
|
||||
|
||||
disconnect(this, SLOT(onScriptError(const QString&)));
|
||||
disconnect(this, SLOT(onScriptPrint(const QString&)));
|
||||
|
||||
_scriptEngine = Application::getInstance()->getScriptEngine(scriptPath);
|
||||
_scriptEngine = Application::getInstance()->getScriptEngine(_currentScript);
|
||||
if (_scriptEngine != NULL) {
|
||||
connect(_scriptEngine, SIGNAL(runningStateChanged()), this, SIGNAL(runningStateChanged()));
|
||||
connect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(onScriptError(const QString&)));
|
||||
connect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(onScriptPrint(const QString&)));
|
||||
connect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
|
||||
connect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
|
||||
connect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,8 +152,15 @@ bool ScriptEditorWidget::save() {
|
|||
}
|
||||
|
||||
bool ScriptEditorWidget::saveAs() {
|
||||
QString fileName = QFileDialog::getSaveFileName(this, tr("Save script"), QString(), tr("Javascript (*.js)"));
|
||||
return !fileName.isEmpty() ? saveFile(fileName) : false;
|
||||
QString fileName = QFileDialog::getSaveFileName(this, tr("Save script"),
|
||||
Application::getInstance()->getPreviousScriptLocation(),
|
||||
tr("JavaScript Files (*.js)"));
|
||||
if (!fileName.isEmpty()) {
|
||||
Application::getInstance()->setPreviousScriptLocation(fileName);
|
||||
return saveFile(fileName);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEditorWidget::setScriptFile(const QString& scriptPath) {
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#define hifi_ScriptEditorWidget_h
|
||||
|
||||
#include <QDockWidget>
|
||||
#include "ScriptEditorWidget.h"
|
||||
#include "ScriptEngine.h"
|
||||
|
||||
namespace Ui {
|
||||
|
|
|
@ -34,19 +34,20 @@ ScriptEditorWindow::ScriptEditorWindow() :
|
|||
_saveMenu(new QMenu)
|
||||
{
|
||||
_ScriptEditorWindowUI->setupUi(this);
|
||||
this->setWindowFlags(Qt::Tool);
|
||||
show();
|
||||
addScriptEditorWidget("New script");
|
||||
connect(_loadMenu, SIGNAL(aboutToShow()), this, SLOT(loadMenuAboutToShow()));
|
||||
connect(_loadMenu, &QMenu::aboutToShow, this, &ScriptEditorWindow::loadMenuAboutToShow);
|
||||
_ScriptEditorWindowUI->loadButton->setMenu(_loadMenu);
|
||||
|
||||
_saveMenu->addAction("Save as..", this, SLOT(saveScriptAsClicked()), Qt::CTRL | Qt::SHIFT | Qt::Key_S);
|
||||
|
||||
_ScriptEditorWindowUI->saveButton->setMenu(_saveMenu);
|
||||
|
||||
connect(new QShortcut(QKeySequence("Ctrl+N"), this), SIGNAL(activated()), this, SLOT(newScriptClicked()));
|
||||
connect(new QShortcut(QKeySequence("Ctrl+S"), this), SIGNAL(activated()), this, SLOT(saveScriptClicked()));
|
||||
connect(new QShortcut(QKeySequence("Ctrl+O"), this), SIGNAL(activated()), this, SLOT(loadScriptClicked()));
|
||||
connect(new QShortcut(QKeySequence("F5"), this), SIGNAL(activated()), this, SLOT(toggleRunScriptClicked()));
|
||||
connect(new QShortcut(QKeySequence("Ctrl+N"), this), &QShortcut::activated, this, &ScriptEditorWindow::newScriptClicked);
|
||||
connect(new QShortcut(QKeySequence("Ctrl+S"), this), &QShortcut::activated, this,&ScriptEditorWindow::saveScriptClicked);
|
||||
connect(new QShortcut(QKeySequence("Ctrl+O"), this), &QShortcut::activated, this, &ScriptEditorWindow::loadScriptClicked);
|
||||
connect(new QShortcut(QKeySequence("F5"), this), &QShortcut::activated, this, &ScriptEditorWindow::toggleRunScriptClicked);
|
||||
}
|
||||
|
||||
ScriptEditorWindow::~ScriptEditorWindow() {
|
||||
|
@ -73,8 +74,11 @@ void ScriptEditorWindow::loadScriptMenu(const QString& scriptName) {
|
|||
}
|
||||
|
||||
void ScriptEditorWindow::loadScriptClicked() {
|
||||
QString scriptName = QFileDialog::getOpenFileName(this, tr("Interface"), QString(), tr("Javascript (*.js)"));
|
||||
QString scriptName = QFileDialog::getOpenFileName(this, tr("Interface"),
|
||||
Application::getInstance()->getPreviousScriptLocation(),
|
||||
tr("JavaScript Files (*.js)"));
|
||||
if (!scriptName.isEmpty()) {
|
||||
Application::getInstance()->setPreviousScriptLocation(scriptName);
|
||||
addScriptEditorWidget("loading...")->loadFile(scriptName);
|
||||
updateButtons();
|
||||
}
|
||||
|
@ -126,9 +130,9 @@ void ScriptEditorWindow::saveScriptAsClicked() {
|
|||
|
||||
ScriptEditorWidget* ScriptEditorWindow::addScriptEditorWidget(QString title) {
|
||||
ScriptEditorWidget* newScriptEditorWidget = new ScriptEditorWidget();
|
||||
connect(newScriptEditorWidget, SIGNAL(scriptnameChanged()), this, SLOT(updateScriptNameOrStatus()));
|
||||
connect(newScriptEditorWidget, SIGNAL(scriptModified()), this, SLOT(updateScriptNameOrStatus()));
|
||||
connect(newScriptEditorWidget, SIGNAL(runningStateChanged()), this, SLOT(updateButtons()));
|
||||
connect(newScriptEditorWidget, &ScriptEditorWidget::scriptnameChanged, this, &ScriptEditorWindow::updateScriptNameOrStatus);
|
||||
connect(newScriptEditorWidget, &ScriptEditorWidget::scriptModified, this, &ScriptEditorWindow::updateScriptNameOrStatus);
|
||||
connect(newScriptEditorWidget, &ScriptEditorWidget::runningStateChanged, this, &ScriptEditorWindow::updateButtons);
|
||||
_ScriptEditorWindowUI->tabWidget->addTab(newScriptEditorWidget, title);
|
||||
_ScriptEditorWindowUI->tabWidget->setCurrentWidget(newScriptEditorWidget);
|
||||
newScriptEditorWidget->setUpdatesEnabled(true);
|
||||
|
@ -198,3 +202,10 @@ void ScriptEditorWindow::updateScriptNameOrStatus() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEditorWindow::terminateCurrentTab() {
|
||||
if (_ScriptEditorWindowUI->tabWidget->currentIndex() != -1) {
|
||||
_ScriptEditorWindowUI->tabWidget->removeTab(_ScriptEditorWindowUI->tabWidget->currentIndex());
|
||||
this->raise();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ public:
|
|||
ScriptEditorWindow();
|
||||
~ScriptEditorWindow();
|
||||
|
||||
void terminateCurrentTab();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent* event);
|
||||
|
||||
|
|
28
interface/src/ui/ScriptLineNumberArea.cpp
Normal file
28
interface/src/ui/ScriptLineNumberArea.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// ScriptLineNumberArea.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Thijs Wenker on 4/30/14.
|
||||
// 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 "ScriptLineNumberArea.h"
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
ScriptLineNumberArea::ScriptLineNumberArea(ScriptEditBox* scriptEditBox) :
|
||||
QWidget(scriptEditBox)
|
||||
{
|
||||
_scriptEditBox = scriptEditBox;
|
||||
}
|
||||
|
||||
QSize ScriptLineNumberArea::sizeHint() const {
|
||||
return QSize(_scriptEditBox->lineNumberAreaWidth(), 0);
|
||||
}
|
||||
|
||||
void ScriptLineNumberArea::paintEvent(QPaintEvent* event) {
|
||||
_scriptEditBox->lineNumberAreaPaintEvent(event);
|
||||
}
|
31
interface/src/ui/ScriptLineNumberArea.h
Normal file
31
interface/src/ui/ScriptLineNumberArea.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// ScriptLineNumberArea.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Thijs Wenker on 4/30/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_ScriptLineNumberArea_h
|
||||
#define hifi_ScriptLineNumberArea_h
|
||||
|
||||
#include <QWidget>
|
||||
#include "ScriptEditBox.h"
|
||||
|
||||
class ScriptLineNumberArea : public QWidget {
|
||||
|
||||
public:
|
||||
ScriptLineNumberArea(ScriptEditBox* scriptEditBox);
|
||||
QSize sizeHint() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event);
|
||||
|
||||
private:
|
||||
ScriptEditBox* _scriptEditBox;
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptLineNumberArea_h
|
49
interface/src/ui/ScriptsTableWidget.cpp
Normal file
49
interface/src/ui/ScriptsTableWidget.cpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// ScriptsTableWidget.cpp
|
||||
// interface
|
||||
//
|
||||
// Created by Mohammed Nafees on 04/03/2014.
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDebug>
|
||||
#include <QHeaderView>
|
||||
#include <QKeyEvent>
|
||||
#include <QPainter>
|
||||
|
||||
#include "ScriptsTableWidget.h"
|
||||
|
||||
ScriptsTableWidget::ScriptsTableWidget(QWidget* parent) :
|
||||
QTableWidget(parent) {
|
||||
verticalHeader()->setVisible(false);
|
||||
horizontalHeader()->setVisible(false);
|
||||
setShowGrid(false);
|
||||
setSelectionMode(QAbstractItemView::NoSelection);
|
||||
setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
setStyleSheet("QTableWidget { background: transparent; color: #333333; } QToolTip { color: #000000; background: #f9f6e4; padding: 2px; }");
|
||||
setToolTipDuration(200);
|
||||
setWordWrap(true);
|
||||
setGeometry(0, 0, parent->width(), parent->height());
|
||||
}
|
||||
|
||||
void ScriptsTableWidget::paintEvent(QPaintEvent* event) {
|
||||
QPainter painter(viewport());
|
||||
painter.setPen(QColor::fromRgb(225, 225, 225)); // #e1e1e1
|
||||
|
||||
int y = 0;
|
||||
for (int i = 0; i < rowCount(); i++) {
|
||||
painter.drawLine(5, rowHeight(i) + y, width(), rowHeight(i) + y);
|
||||
y += rowHeight(i);
|
||||
}
|
||||
painter.end();
|
||||
|
||||
QTableWidget::paintEvent(event);
|
||||
}
|
||||
|
||||
void ScriptsTableWidget::keyPressEvent(QKeyEvent* event) {
|
||||
// Ignore keys so they will propagate correctly
|
||||
event->ignore();
|
||||
}
|
28
interface/src/ui/ScriptsTableWidget.h
Normal file
28
interface/src/ui/ScriptsTableWidget.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// ScriptsTableWidget.h
|
||||
// interface
|
||||
//
|
||||
// Created by Mohammed Nafees on 04/03/2014.
|
||||
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi__ScriptsTableWidget_h
|
||||
#define hifi__ScriptsTableWidget_h
|
||||
|
||||
#include <QDebug>
|
||||
#include <QTableWidget>
|
||||
|
||||
class ScriptsTableWidget : public QTableWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ScriptsTableWidget(QWidget* parent);
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent* event);
|
||||
virtual void keyPressEvent(QKeyEvent* event);
|
||||
};
|
||||
|
||||
#endif // hifi__ScriptsTableWidget_h
|
|
@ -86,7 +86,7 @@ void Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
|
|||
// replace decimal . with '-'
|
||||
formattedLocation.replace('.', '-');
|
||||
|
||||
QString username = AccountManager::getInstance().getUsername();
|
||||
QString username = AccountManager::getInstance().getAccountInfo().getUsername();
|
||||
// normalize username, replace all non alphanumeric with '-'
|
||||
username.replace(QRegExp("[^A-Za-z0-9_]"), "-");
|
||||
|
||||
|
|
|
@ -237,6 +237,7 @@ void Stats::display(
|
|||
int voxelServerCount = 0;
|
||||
|
||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||
// TODO: this should also support particles and models
|
||||
if (node->getType() == NodeType::VoxelServer) {
|
||||
totalPingVoxel += node->getPingMs();
|
||||
voxelServerCount++;
|
||||
|
|
|
@ -33,9 +33,9 @@ void Sphere3DOverlay::render() {
|
|||
|
||||
glDisable(GL_LIGHTING);
|
||||
glPushMatrix();
|
||||
glTranslatef(_position.x + _size * 0.5f,
|
||||
_position.y + _size * 0.5f,
|
||||
_position.z + _size * 0.5f);
|
||||
glTranslatef(_position.x,
|
||||
_position.y,
|
||||
_position.z);
|
||||
glLineWidth(_lineWidth);
|
||||
const int slices = 15;
|
||||
if (_isSolid) {
|
||||
|
|
|
@ -44,7 +44,6 @@ void VoxelPacketProcessor::processPacket(const SharedNodePointer& sendingNode, c
|
|||
// immediately following them inside the same packet. So, we process the PacketType_OCTREE_STATS first
|
||||
// then process any remaining bytes as if it was another packet
|
||||
if (voxelPacketType == PacketTypeOctreeStats) {
|
||||
|
||||
int statsMessageLength = app->parseOctreeStats(mutablePacket, sendingNode);
|
||||
wasStatsPacket = true;
|
||||
if (messageLength > statsMessageLength) {
|
||||
|
@ -77,6 +76,14 @@ void VoxelPacketProcessor::processPacket(const SharedNodePointer& sendingNode, c
|
|||
app->_particles.processDatagram(mutablePacket, sendingNode);
|
||||
} break;
|
||||
|
||||
case PacketTypeModelErase: {
|
||||
app->_models.processEraseMessage(mutablePacket, sendingNode);
|
||||
} break;
|
||||
|
||||
case PacketTypeModelData: {
|
||||
app->_models.processDatagram(mutablePacket, sendingNode);
|
||||
} break;
|
||||
|
||||
case PacketTypeEnvironmentData: {
|
||||
app->_environment.parseData(*sendingNode->getActiveSocket(), mutablePacket);
|
||||
} break;
|
||||
|
|
491
interface/ui/loginDialog.ui
Normal file
491
interface/ui/loginDialog.ui
Normal file
|
@ -0,0 +1,491 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>LoginDialog</class>
|
||||
<widget class="QDialog" name="LoginDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1003</width>
|
||||
<height>130</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>130</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font-family: Helvetica, Arial, sans-serif;</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="statusArea" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="errorMessages" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>825</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="infoLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Helvetica,Arial,sans-serif</family>
|
||||
<pointsize>17</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Authenticating...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="errorLabel">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Helvetica,Arial,sans-serif</family>
|
||||
<pointsize>17</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #992800;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><style type="text/css">
|
||||
a { text-decoration: none; color: #267077;}
|
||||
</style>
|
||||
Invalid username or password. <a href="https://data-web.highfidelity.io/password/new">Recover?</a></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="closeButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>SplitHCursor</cursorShape>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>../resources/images/close.svg</normaloff>../resources/images/close.svg</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="loginAreaContainer" native="true">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>102</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>30</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="spacerLeft">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="logoArea" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="logoLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap>../resources/images/hifi-logo.png</pixmap>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="loginArea" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>931</width>
|
||||
<height>60</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QLineEdit {
|
||||
padding: 10px;
|
||||
border-width: 1px; border-style: solid; border-radius: 3px; border-color: #aaa;
|
||||
}</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="emailLineEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>54</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Helvetica,Arial,sans-serif</family>
|
||||
<pointsize>20</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">padding-top: 14px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Username or Email</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="passwordLineEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>54</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Helvetica,Arial,sans-serif</family>
|
||||
<pointsize>20</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">padding-top: 14px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="loginButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>141</width>
|
||||
<height>54</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Helvetica,Arial,sans-serif</family>
|
||||
<pointsize>20</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">
|
||||
background: #0e7077;
|
||||
color: #e7eeee;
|
||||
border-radius: 4px; padding-top: 1px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> Login</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>../resources/images/login.svg</normaloff>../resources/images/login.svg</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Helvetica,Arial,sans-serif</family>
|
||||
<pointsize>17</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><style type="text/css">
|
||||
a { text-decoration: none; color: #267077;}
|
||||
</style>
|
||||
<a href="https://data-web.highfidelity.io/password/new">Recover password?</a></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacerRight">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -6,44 +6,58 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>310</width>
|
||||
<height>651</height>
|
||||
<width>323</width>
|
||||
<height>894</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: #f7f7f7;
|
||||
font-family: Helvetica, Arial, "DejaVu Sans"; </string>
|
||||
<string notr="true">* {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
QWidget {
|
||||
background: #f7f7f7;
|
||||
}</string>
|
||||
</property>
|
||||
<widget class="QLabel" name="widgetTitle">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>10</y>
|
||||
<width>221</width>
|
||||
<height>31</height>
|
||||
<x>37</x>
|
||||
<y>29</y>
|
||||
<width>251</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #0e7077;</string>
|
||||
<string notr="true">color: #0e7077;
|
||||
font-size: 20pt;
|
||||
background: transparent;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-size:18pt;">Running Scripts</span></p></body></html></string>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="currentlyRunningLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>40</y>
|
||||
<width>301</width>
|
||||
<x>36</x>
|
||||
<y>110</y>
|
||||
<width>270</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #0e7077;</string>
|
||||
<string notr="true">color: #0e7077;
|
||||
font: bold 14pt;
|
||||
background: transparent;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:600;">Currently running</span></p></body></html></string>
|
||||
|
@ -52,22 +66,27 @@ font-family: Helvetica, Arial, "DejaVu Sans"; </string>
|
|||
<widget class="QPushButton" name="reloadAllButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>230</y>
|
||||
<x>36</x>
|
||||
<y>270</y>
|
||||
<width>111</width>
|
||||
<height>31</height>
|
||||
<height>35</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: #0e7077;
|
||||
color: #fff;
|
||||
border-radius: 6px;</string>
|
||||
border-radius: 4px;
|
||||
font: bold 14pt;
|
||||
padding-top: 3px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reload All</string>
|
||||
<string>Reload all</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
|
@ -78,9 +97,9 @@ border-radius: 6px;</string>
|
|||
<property name="geometry">
|
||||
<rect>
|
||||
<x>160</x>
|
||||
<y>230</y>
|
||||
<width>101</width>
|
||||
<height>31</height>
|
||||
<y>270</y>
|
||||
<width>93</width>
|
||||
<height>35</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
|
@ -89,10 +108,12 @@ border-radius: 6px;</string>
|
|||
<property name="styleSheet">
|
||||
<string notr="true">background: #0e7077;
|
||||
color: #fff;
|
||||
border-radius: 6px;</string>
|
||||
border-radius: 4px;
|
||||
font: bold 14pt;
|
||||
padding-top: 3px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Stop All</string>
|
||||
<string>Stop all</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
|
@ -102,46 +123,32 @@ border-radius: 6px;</string>
|
|||
<widget class="QLabel" name="recentlyLoadedLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>280</y>
|
||||
<width>301</width>
|
||||
<x>36</x>
|
||||
<y>320</y>
|
||||
<width>265</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #0e7077;</string>
|
||||
<string notr="true">color: #0e7077;
|
||||
font: bold 14pt;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="Line" name="line2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>300</y>
|
||||
<width>271</width>
|
||||
<height>8</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="recentlyLoadedInstruction">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>590</y>
|
||||
<width>271</width>
|
||||
<x>36</x>
|
||||
<y>630</y>
|
||||
<width>211</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: #95a5a6;</string>
|
||||
<string notr="true">color: #95a5a6;
|
||||
font-size: 14pt;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>(click a script or use the 1-9 keys to load and run it)</string>
|
||||
|
@ -153,10 +160,10 @@ border-radius: 6px;</string>
|
|||
<widget class="QPushButton" name="hideWidgetButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>270</x>
|
||||
<y>10</y>
|
||||
<width>31</width>
|
||||
<height>31</height>
|
||||
<x>285</x>
|
||||
<y>29</y>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
|
@ -165,81 +172,101 @@ border-radius: 6px;</string>
|
|||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>../resources/images/close.svg</normaloff>../resources/images/close.svg</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTableWidget" name="runningScriptsTableWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>70</y>
|
||||
<width>271</width>
|
||||
<height>141</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: transparent;</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="Line" name="line1">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>60</y>
|
||||
<width>271</width>
|
||||
<height>8</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTableWidget" name="recentlyLoadedScriptsTableWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>310</y>
|
||||
<width>271</width>
|
||||
<height>281</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: transparent;</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="noRunningScriptsLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>40</y>
|
||||
<x>36</x>
|
||||
<y>110</y>
|
||||
<width>271</width>
|
||||
<height>51</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font: 14px;</string>
|
||||
<string notr="true">font: 14pt;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>There are no scripts currently running.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QWidget" name="recentlyLoadedScriptsTableWidget" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>340</y>
|
||||
<width>272</width>
|
||||
<height>280</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: transparent;
|
||||
font-size: 14pt;</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QWidget" name="runningScriptsTableWidget" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>128</y>
|
||||
<width>272</width>
|
||||
<height>161</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: transparent;
|
||||
font-size: 14pt;</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="loadScriptButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>36</x>
|
||||
<y>70</y>
|
||||
<width>111</width>
|
||||
<height>35</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background: #0e7077;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
font: bold 14pt;
|
||||
padding-top: 3px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Load script</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>../resources/images/plus.svg</normaloff>../resources/images/plus.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<zorder>widgetTitle</zorder>
|
||||
<zorder>currentlyRunningLabel</zorder>
|
||||
<zorder>recentlyLoadedLabel</zorder>
|
||||
<zorder>recentlyLoadedInstruction</zorder>
|
||||
<zorder>hideWidgetButton</zorder>
|
||||
<zorder>recentlyLoadedScriptsTableWidget</zorder>
|
||||
<zorder>runningScriptsTableWidget</zorder>
|
||||
<zorder>noRunningScriptsLabel</zorder>
|
||||
<zorder>reloadAllButton</zorder>
|
||||
<zorder>stopAllButton</zorder>
|
||||
<zorder>loadScriptButton</zorder>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>541</width>
|
||||
<height>238</height>
|
||||
<width>690</width>
|
||||
<height>328</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
|
@ -35,7 +35,7 @@
|
|||
<string>Edit Script</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="dockWidgetContents">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="5,0,1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
|
@ -52,18 +52,18 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="scriptEdit">
|
||||
<widget class="ScriptEditBox" name="scriptEdit">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Courier</family>
|
||||
<pointsize>9</pointsize>
|
||||
<pointsize>-1</pointsize>
|
||||
<weight>50</weight>
|
||||
<italic>false</italic>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font: 9pt "Courier";</string>
|
||||
<string notr="true">font: 16px "Courier";</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -86,6 +86,9 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font: 13px "Helvetica","Arial","sans-serif";</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Debug Log:</string>
|
||||
</property>
|
||||
|
@ -93,8 +96,20 @@
|
|||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="onTheFlyCheckBox">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Helvetica,Arial,sans-serif</family>
|
||||
<pointsize>-1</pointsize>
|
||||
<weight>50</weight>
|
||||
<italic>false</italic>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font: 13px "Helvetica","Arial","sans-serif";</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Run on the fly (Careful: Any valid change made to the code will run immediately)</string>
|
||||
<string>Run on the fly (Careful: Any valid change made to the code will run immediately) </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -115,17 +130,33 @@
|
|||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="debugText">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font: 8pt "Courier";</string>
|
||||
<string notr="true">font: 15px "Courier";</string>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="plainText">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ScriptEditBox</class>
|
||||
<extends>QTextEdit</extends>
|
||||
<header>ui/ScriptEditBox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
<class>ScriptEditorWindow</class>
|
||||
<widget class="QWidget" name="ScriptEditorWindow">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::WindowModal</enum>
|
||||
<enum>Qt::NonModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>706</width>
|
||||
<height>682</height>
|
||||
<width>780</width>
|
||||
<height>717</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
|
@ -29,16 +29,7 @@
|
|||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
|
|
|
@ -46,7 +46,8 @@ AvatarData::AvatarData() :
|
|||
_isChatCirclingEnabled(false),
|
||||
_hasNewJointRotations(true),
|
||||
_headData(NULL),
|
||||
_handData(NULL),
|
||||
_handData(NULL),
|
||||
_faceModelURL("http://invalid.com"),
|
||||
_displayNameBoundingRect(),
|
||||
_displayNameTargetAlpha(0.0f),
|
||||
_displayNameAlpha(0.0f),
|
||||
|
@ -640,7 +641,7 @@ bool AvatarData::hasBillboardChangedAfterParsing(const QByteArray& packet) {
|
|||
}
|
||||
|
||||
void AvatarData::setFaceModelURL(const QUrl& faceModelURL) {
|
||||
_faceModelURL = faceModelURL.isEmpty() ? DEFAULT_HEAD_MODEL_URL : faceModelURL;
|
||||
_faceModelURL = faceModelURL;
|
||||
|
||||
qDebug() << "Changing face model for avatar to" << _faceModelURL.toString();
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@ HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager)
|
|||
_parentManager(parentManager),
|
||||
_socket(socket),
|
||||
_stream(socket),
|
||||
_address(socket->peerAddress()) {
|
||||
|
||||
_address(socket->peerAddress())
|
||||
{
|
||||
// take over ownership of the socket
|
||||
_socket->setParent(this);
|
||||
|
||||
|
@ -42,7 +42,7 @@ HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager)
|
|||
HTTPConnection::~HTTPConnection() {
|
||||
// log the destruction
|
||||
if (_socket->error() != QAbstractSocket::UnknownSocketError) {
|
||||
qDebug() << _socket->errorString();
|
||||
qDebug() << _socket->errorString() << "-" << _socket->error();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
|
||||
#include <QDataStream>
|
||||
#include <QHash>
|
||||
#include <QHostAddress>
|
||||
#include <QtNetwork/QHostAddress>
|
||||
#include <QIODevice>
|
||||
#include <QList>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QUrl>
|
||||
|
|
|
@ -18,116 +18,6 @@
|
|||
#include "HTTPConnection.h"
|
||||
#include "HTTPManager.h"
|
||||
|
||||
bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url) {
|
||||
if (_requestHandler && _requestHandler->handleHTTPRequest(connection, url)) {
|
||||
// this request was handled by our _requestHandler object
|
||||
// so we don't need to attempt to do so in the document root
|
||||
return true;
|
||||
}
|
||||
|
||||
// check to see if there is a file to serve from the document root for this path
|
||||
QString subPath = url.path();
|
||||
|
||||
// remove any slash at the beginning of the path
|
||||
if (subPath.startsWith('/')) {
|
||||
subPath.remove(0, 1);
|
||||
}
|
||||
|
||||
QString filePath;
|
||||
|
||||
if (QFileInfo(_documentRoot + subPath).isFile()) {
|
||||
filePath = _documentRoot + subPath;
|
||||
} else if (subPath.size() > 0 && !subPath.endsWith('/')) {
|
||||
// this could be a directory with a trailing slash
|
||||
// send a redirect to the path with a slash so we can
|
||||
QString redirectLocation = '/' + subPath + '/';
|
||||
|
||||
if (!url.query().isEmpty()) {
|
||||
redirectLocation += "?" + url.query();
|
||||
}
|
||||
|
||||
QHash<QByteArray, QByteArray> redirectHeader;
|
||||
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader);
|
||||
}
|
||||
|
||||
// if the last thing is a trailing slash then we want to look for index file
|
||||
if (subPath.endsWith('/') || subPath.size() == 0) {
|
||||
QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml";
|
||||
|
||||
foreach (const QString& possibleIndexFilename, possibleIndexFiles) {
|
||||
if (QFileInfo(_documentRoot + subPath + possibleIndexFilename).exists()) {
|
||||
filePath = _documentRoot + subPath + possibleIndexFilename;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!filePath.isEmpty()) {
|
||||
// file exists, serve it
|
||||
static QMimeDatabase mimeDatabase;
|
||||
|
||||
QFile localFile(filePath);
|
||||
localFile.open(QIODevice::ReadOnly);
|
||||
QByteArray localFileData = localFile.readAll();
|
||||
|
||||
QFileInfo localFileInfo(filePath);
|
||||
|
||||
if (localFileInfo.completeSuffix() == "shtml") {
|
||||
// this is a file that may have some SSI statements
|
||||
// the only thing we support is the include directive, but check the contents for that
|
||||
|
||||
// setup our static QRegExp that will catch <!--#include virtual ... --> and <!--#include file .. --> directives
|
||||
const QString includeRegExpString = "<!--\\s*#include\\s+(virtual|file)\\s?=\\s?\"(\\S+)\"\\s*-->";
|
||||
QRegExp includeRegExp(includeRegExpString);
|
||||
|
||||
int matchPosition = 0;
|
||||
|
||||
QString localFileString(localFileData);
|
||||
|
||||
while ((matchPosition = includeRegExp.indexIn(localFileString, matchPosition)) != -1) {
|
||||
// check if this is a file or vitual include
|
||||
bool isFileInclude = includeRegExp.cap(1) == "file";
|
||||
|
||||
// setup the correct file path for the included file
|
||||
QString includeFilePath = isFileInclude
|
||||
? localFileInfo.canonicalPath() + "/" + includeRegExp.cap(2)
|
||||
: _documentRoot + includeRegExp.cap(2);
|
||||
|
||||
QString replacementString;
|
||||
|
||||
if (QFileInfo(includeFilePath).isFile()) {
|
||||
|
||||
QFile includedFile(includeFilePath);
|
||||
includedFile.open(QIODevice::ReadOnly);
|
||||
|
||||
replacementString = QString(includedFile.readAll());
|
||||
} else {
|
||||
qDebug() << "SSI include directive referenced a missing file:" << includeFilePath;
|
||||
}
|
||||
|
||||
// replace the match with the contents of the file, or an empty string if the file was not found
|
||||
localFileString.replace(matchPosition, includeRegExp.matchedLength(), replacementString);
|
||||
|
||||
// push the match position forward so we can check the next match
|
||||
matchPosition += includeRegExp.matchedLength();
|
||||
}
|
||||
|
||||
localFileData = localFileString.toLocal8Bit();
|
||||
}
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, localFileData,
|
||||
qPrintable(mimeDatabase.mimeTypeForFile(filePath).name()));
|
||||
} else {
|
||||
|
||||
// respond with a 404
|
||||
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) :
|
||||
QTcpServer(parent),
|
||||
_documentRoot(documentRoot),
|
||||
|
@ -138,14 +28,131 @@ HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestH
|
|||
qDebug() << "Failed to open HTTP server socket:" << errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
// connect the connection signal
|
||||
connect(this, SIGNAL(newConnection()), SLOT(acceptConnections()));
|
||||
}
|
||||
|
||||
void HTTPManager::acceptConnections() {
|
||||
QTcpSocket* socket;
|
||||
while ((socket = nextPendingConnection()) != 0) {
|
||||
void HTTPManager::incomingConnection(qintptr socketDescriptor) {
|
||||
QTcpSocket* socket = new QTcpSocket(this);
|
||||
|
||||
if (socket->setSocketDescriptor(socketDescriptor)) {
|
||||
new HTTPConnection(socket, this);
|
||||
} else {
|
||||
delete socket;
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url) {
|
||||
if (requestHandledByRequestHandler(connection, url)) {
|
||||
// this request was handled by our request handler object
|
||||
// so we don't need to attempt to do so in the document root
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_documentRoot.isEmpty()) {
|
||||
// check to see if there is a file to serve from the document root for this path
|
||||
QString subPath = url.path();
|
||||
|
||||
// remove any slash at the beginning of the path
|
||||
if (subPath.startsWith('/')) {
|
||||
subPath.remove(0, 1);
|
||||
}
|
||||
|
||||
QString filePath;
|
||||
|
||||
if (QFileInfo(_documentRoot + subPath).isFile()) {
|
||||
filePath = _documentRoot + subPath;
|
||||
} else if (subPath.size() > 0 && !subPath.endsWith('/')) {
|
||||
// this could be a directory with a trailing slash
|
||||
// send a redirect to the path with a slash so we can
|
||||
QString redirectLocation = '/' + subPath + '/';
|
||||
|
||||
if (!url.query().isEmpty()) {
|
||||
redirectLocation += "?" + url.query();
|
||||
}
|
||||
|
||||
QHash<QByteArray, QByteArray> redirectHeader;
|
||||
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader);
|
||||
}
|
||||
|
||||
// if the last thing is a trailing slash then we want to look for index file
|
||||
if (subPath.endsWith('/') || subPath.size() == 0) {
|
||||
QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml";
|
||||
|
||||
foreach (const QString& possibleIndexFilename, possibleIndexFiles) {
|
||||
if (QFileInfo(_documentRoot + subPath + possibleIndexFilename).exists()) {
|
||||
filePath = _documentRoot + subPath + possibleIndexFilename;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!filePath.isEmpty()) {
|
||||
// file exists, serve it
|
||||
static QMimeDatabase mimeDatabase;
|
||||
|
||||
QFile localFile(filePath);
|
||||
localFile.open(QIODevice::ReadOnly);
|
||||
QByteArray localFileData = localFile.readAll();
|
||||
|
||||
QFileInfo localFileInfo(filePath);
|
||||
|
||||
if (localFileInfo.completeSuffix() == "shtml") {
|
||||
// this is a file that may have some SSI statements
|
||||
// the only thing we support is the include directive, but check the contents for that
|
||||
|
||||
// setup our static QRegExp that will catch <!--#include virtual ... --> and <!--#include file .. --> directives
|
||||
const QString includeRegExpString = "<!--\\s*#include\\s+(virtual|file)\\s?=\\s?\"(\\S+)\"\\s*-->";
|
||||
QRegExp includeRegExp(includeRegExpString);
|
||||
|
||||
int matchPosition = 0;
|
||||
|
||||
QString localFileString(localFileData);
|
||||
|
||||
while ((matchPosition = includeRegExp.indexIn(localFileString, matchPosition)) != -1) {
|
||||
// check if this is a file or vitual include
|
||||
bool isFileInclude = includeRegExp.cap(1) == "file";
|
||||
|
||||
// setup the correct file path for the included file
|
||||
QString includeFilePath = isFileInclude
|
||||
? localFileInfo.canonicalPath() + "/" + includeRegExp.cap(2)
|
||||
: _documentRoot + includeRegExp.cap(2);
|
||||
|
||||
QString replacementString;
|
||||
|
||||
if (QFileInfo(includeFilePath).isFile()) {
|
||||
|
||||
QFile includedFile(includeFilePath);
|
||||
includedFile.open(QIODevice::ReadOnly);
|
||||
|
||||
replacementString = QString(includedFile.readAll());
|
||||
} else {
|
||||
qDebug() << "SSI include directive referenced a missing file:" << includeFilePath;
|
||||
}
|
||||
|
||||
// replace the match with the contents of the file, or an empty string if the file was not found
|
||||
localFileString.replace(matchPosition, includeRegExp.matchedLength(), replacementString);
|
||||
|
||||
// push the match position forward so we can check the next match
|
||||
matchPosition += includeRegExp.matchedLength();
|
||||
}
|
||||
|
||||
localFileData = localFileString.toLocal8Bit();
|
||||
}
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, localFileData,
|
||||
qPrintable(mimeDatabase.mimeTypeForFile(filePath).name()));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// respond with a 404
|
||||
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HTTPManager::requestHandledByRequestHandler(HTTPConnection* connection, const QUrl& url) {
|
||||
return _requestHandler && _requestHandler->handleHTTPRequest(connection, url);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue