3
0
Fork 0
mirror of https://github.com/lubosz/overte.git synced 2025-04-26 10:55:37 +02:00

Merge branch 'master' of https://github.com/worklist/hifi into 19507

This commit is contained in:
Stojce Slavkovski 2014-05-02 19:03:11 +02:00
commit 852756e84d
153 changed files with 7791 additions and 1595 deletions
assignment-client
domain-server/src
examples
interface
libraries

View file

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

View file

@ -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,7 +184,9 @@ 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;

View file

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

View file

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

View file

@ -17,8 +17,6 @@
#include "AssignmentClientMonitor.h"
int main(int argc, char* argv[]) {
initialiseUsecTimestampNow();
#ifndef WIN32
setvbuf(stdout, NULL, _IOLBF, 0);
#endif

View 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

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

View 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

View 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

View file

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

View file

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

View file

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

View file

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

View file

@ -341,7 +341,7 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
}
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) {

View file

@ -23,8 +23,6 @@
#include "DomainServer.h"
int main(int argc, char* argv[]) {
initialiseUsecTimestampNow();
#ifndef WIN32
setvbuf(stdout, NULL, _IOLBF, 0);
#endif

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

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

View file

@ -1088,7 +1088,6 @@ function keyPressEvent(event) {
red: colors[whichColor].red,
green: colors[whichColor].green,
blue: colors[whichColor].blue };
Voxels.eraseVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s);
Voxels.setVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s, newVoxel.red, newVoxel.green, newVoxel.blue);
setAudioPosition();
initialVoxelSound.playRandom();
@ -1394,7 +1393,6 @@ function checkControllers() {
if (Vec3.length(Vec3.subtract(fingerTipPosition,lastFingerAddVoxel)) > (FINGERTIP_VOXEL_SIZE / 2)) {
newColor = { red: colors[whichColor].red, green: colors[whichColor].green, blue: colors[whichColor].blue };
Voxels.eraseVoxel(fingerTipPosition.x, fingerTipPosition.y, fingerTipPosition.z, FINGERTIP_VOXEL_SIZE);
Voxels.setVoxel(fingerTipPosition.x, fingerTipPosition.y, fingerTipPosition.z, FINGERTIP_VOXEL_SIZE,
newColor.red, newColor.green, newColor.blue);

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

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

View file

@ -0,0 +1,220 @@
//
// 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 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.25;
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",
];
var currentModelURL = 0;
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(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 },
};
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: 0.25,
modelURL: modelURLs[currentModelURL]
};
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(">>>>> " + handMessage + "-MODEL IN HAND, grabbing, hold and move");
var modelPosition = getModelHoldPosition(whichSide);
var properties = { position: { x: modelPosition.x,
y: modelPosition.y,
z: modelPosition.z },
};
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);
}
// register the call back so it fires before each data send
Script.update.connect(checkController);

View file

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

View file

@ -0,0 +1,291 @@
//
// swissArmyJetpack.js
// examples
//
// Created by Andrew Meadows 2014.04.24
// Copyright 2014 High Fidelity, Inc.
//
// This is a work in progress. It will eventually be able to move the avatar around,
// toggle collision groups, modify avatar movement options, and other stuff (maybe trigger animations).
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// misc global constants
var NUMBER_OF_COLLISION_BUTTONS = 3;
var NUMBER_OF_BUTTONS = 4;
var DOWN = { x: 0.0, y: -1.0, z: 0.0 };
var MAX_VOXEL_SCAN_DISTANCE = 30.0;
// behavior transition thresholds
var MIN_FLYING_SPEED = 3.0;
var MIN_COLLISIONLESS_SPEED = 5.0;
var MAX_WALKING_SPEED = 30.0;
var MAX_COLLIDABLE_SPEED = 35.0;
// button URL and geometry/UI tuning
var BUTTON_IMAGE_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/testing-swatches.svg";
var DISABLED_OFFSET_Y = 12;
var ENABLED_OFFSET_Y = 55 + 12;
var UI_BUFFER = 1;
var OFFSET_X = UI_BUFFER;
var OFFSET_Y = 200;
var BUTTON_WIDTH = 30;
var BUTTON_HEIGHT = 30;
var TEXT_OFFSET_X = OFFSET_X + BUTTON_WIDTH + UI_BUFFER;
var TEXT_HEIGHT = BUTTON_HEIGHT;
var TEXT_WIDTH = 210;
var MSEC_PER_SECOND = 1000;
var RAYCAST_EXPIRY_PERIOD = MSEC_PER_SECOND / 16;
var COLLISION_EXPIRY_PERIOD = 2 * MSEC_PER_SECOND;
var GRAVITY_ON_EXPIRY_PERIOD = MSEC_PER_SECOND / 2;
var GRAVITY_OFF_EXPIRY_PERIOD = MSEC_PER_SECOND / 8;
var dater = new Date();
var raycastExpiry = dater.getTime() + RAYCAST_EXPIRY_PERIOD;
var gravityOnExpiry = dater.getTime() + GRAVITY_ON_EXPIRY_PERIOD;
var gravityOffExpiry = dater.getTime() + GRAVITY_OFF_EXPIRY_PERIOD;
var collisionOnExpiry = dater.getTime() + COLLISION_EXPIRY_PERIOD;
// avatar state
var velocity = { x: 0.0, y: 0.0, z: 0.0 };
var standing = false;
// speedometer globals
var speed = 0.0;
var lastPosition = MyAvatar.position;
var speedometer = Overlays.addOverlay("text", {
x: OFFSET_X,
y: OFFSET_Y - BUTTON_HEIGHT,
width: BUTTON_WIDTH + UI_BUFFER + TEXT_WIDTH,
height: TEXT_HEIGHT,
color: { red: 0, green: 0, blue: 0 },
textColor: { red: 255, green: 0, blue: 0},
topMargin: 4,
leftMargin: 4,
text: "Speed: 0.0"
});
// collision group buttons
var buttons = new Array();
var labels = new Array();
var labelContents = new Array();
labelContents[0] = "Collide with Avatars";
labelContents[1] = "Collide with Voxels";
labelContents[2] = "Collide with Particles";
labelContents[3] = "Use local gravity";
var groupBits = 0;
var enabledColors = new Array();
enabledColors[0] = { red: 255, green: 0, blue: 0};
enabledColors[1] = { red: 0, green: 255, blue: 0};
enabledColors[2] = { red: 0, green: 0, blue: 255};
enabledColors[3] = { red: 255, green: 255, blue: 0};
var disabledColors = new Array();
disabledColors[0] = { red: 90, green: 75, blue: 75};
disabledColors[1] = { red: 75, green: 90, blue: 75};
disabledColors[2] = { red: 75, green: 75, blue: 90};
disabledColors[3] = { red: 90, green: 90, blue: 75};
var buttonStates = new Array();
for (i = 0; i < NUMBER_OF_BUTTONS; i++) {
var offsetS = 12
var offsetT = DISABLED_OFFSET_Y;
buttons[i] = Overlays.addOverlay("image", {
x: OFFSET_X,
y: OFFSET_Y + (BUTTON_HEIGHT * i),
width: BUTTON_WIDTH,
height: BUTTON_HEIGHT,
subImage: { x: offsetS, y: offsetT, width: BUTTON_WIDTH, height: BUTTON_HEIGHT },
imageURL: BUTTON_IMAGE_URL,
color: disabledColors[i],
alpha: 1,
});
labels[i] = Overlays.addOverlay("text", {
x: TEXT_OFFSET_X,
y: OFFSET_Y + (BUTTON_HEIGHT * i),
width: TEXT_WIDTH,
height: TEXT_HEIGHT,
color: { red: 0, green: 0, blue: 0},
textColor: { red: 255, green: 0, blue: 0},
topMargin: 4,
leftMargin: 4,
text: labelContents[i]
});
buttonStates[i] = false;
}
// functions
function updateButton(i, enabled) {
var offsetY = DISABLED_OFFSET_Y;
var buttonColor = disabledColors[i];
if (enabled) {
offsetY = ENABLED_OFFSET_Y;
buttonColor = enabledColors[i];
if (i == 0) {
groupBits |= COLLISION_GROUP_AVATARS;
} else if (i == 1) {
groupBits |= COLLISION_GROUP_VOXELS;
} else if (i == 2) {
groupBits |= COLLISION_GROUP_PARTICLES;
}
} else {
if (i == 0) {
groupBits &= ~COLLISION_GROUP_AVATARS;
} else if (i == 1) {
groupBits &= ~COLLISION_GROUP_VOXELS;
} else if (i == 2) {
groupBits &= ~COLLISION_GROUP_PARTICLES;
}
}
if (groupBits != MyAvatar.collisionGroups) {
MyAvatar.collisionGroups = groupBits;
}
Overlays.editOverlay(buttons[i], { subImage: { y: offsetY } } );
Overlays.editOverlay(buttons[i], { color: buttonColor } );
buttonStates[i] = enabled;
}
// When our script shuts down, we should clean up all of our overlays
function scriptEnding() {
for (i = 0; i < NUMBER_OF_BUTTONS; i++) {
Overlays.deleteOverlay(buttons[i]);
Overlays.deleteOverlay(labels[i]);
}
Overlays.deleteOverlay(speedometer);
}
Script.scriptEnding.connect(scriptEnding);
function updateSpeedometerDisplay() {
Overlays.editOverlay(speedometer, { text: "Speed: " + speed.toFixed(2) });
}
Script.setInterval(updateSpeedometerDisplay, 100);
function disableArtificialGravity() {
MyAvatar.motionBehaviors = MyAvatar.motionBehaviors & ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
updateButton(3, false);
}
function enableArtificialGravity() {
// NOTE: setting the gravity automatically sets the AVATAR_MOTION_OBEY_LOCAL_GRAVITY behavior bit.
MyAvatar.gravity = DOWN;
updateButton(3, true);
// also enable collisions with voxels
groupBits |= COLLISION_GROUP_VOXELS;
updateButton(1, groupBits & COLLISION_GROUP_VOXELS);
}
// Our update() function is called at approximately 60fps, and we will use it to animate our various overlays
function update(deltaTime) {
if (groupBits != MyAvatar.collisionGroups) {
groupBits = MyAvatar.collisionGroups;
updateButton(0, groupBits & COLLISION_GROUP_AVATARS);
updateButton(1, groupBits & COLLISION_GROUP_VOXELS);
updateButton(2, groupBits & COLLISION_GROUP_PARTICLES);
}
// measure speed
var distance = Vec3.distance(MyAvatar.position, lastPosition);
speed = 0.8 * speed + 0.2 * distance / deltaTime;
lastPosition = MyAvatar.position;
dater = new Date();
var now = dater.getTime();
// transition gravity
if (raycastExpiry < now) {
// scan for landing platform
ray = { origin: MyAvatar.position, direction: DOWN };
var intersection = Voxels.findRayIntersection(ray);
// NOTE: it is possible for intersection.intersects to be false when it should be true
// (perhaps the raycast failed to lock the octree thread?). To workaround this problem
// we only transition on repeated failures.
if (intersection.intersects) {
// compute distance to voxel
var v = intersection.voxel;
var maxCorner = Vec3.sum({ x: v.x, y: v.y, z: v.z }, {x: v.s, y: v.s, z: v.s });
var distance = lastPosition.y - maxCorner.y;
if (distance < MAX_VOXEL_SCAN_DISTANCE) {
if (speed < MIN_FLYING_SPEED &&
gravityOnExpiry < now &&
!(MyAvatar.motionBehaviors & AVATAR_MOTION_OBEY_LOCAL_GRAVITY)) {
enableArtificialGravity();
}
if (speed < MAX_WALKING_SPEED) {
gravityOffExpiry = now + GRAVITY_OFF_EXPIRY_PERIOD;
} else if (gravityOffExpiry < now && MyAvatar.motionBehaviors & AVATAR_MOTION_OBEY_LOCAL_GRAVITY) {
disableArtificialGravity();
}
} else {
// distance too far
if (gravityOffExpiry < now && MyAvatar.motionBehaviors & AVATAR_MOTION_OBEY_LOCAL_GRAVITY) {
disableArtificialGravity();
}
gravityOnExpiry = now + GRAVITY_ON_EXPIRY_PERIOD;
}
} else {
// no intersection
if (gravityOffExpiry < now && MyAvatar.motionBehaviors & AVATAR_MOTION_OBEY_LOCAL_GRAVITY) {
disableArtificialGravity();
}
gravityOnExpiry = now + GRAVITY_ON_EXPIRY_PERIOD;
}
}
if (speed > MAX_WALKING_SPEED && gravityOffExpiry < now) {
if (MyAvatar.motionBehaviors & AVATAR_MOTION_OBEY_LOCAL_GRAVITY) {
// turn off gravity
MyAvatar.motionBehaviors = MyAvatar.motionBehaviors & ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
updateButton(3, false);
}
gravityOnExpiry = now + GRAVITY_ON_EXPIRY_PERIOD;
}
// transition collidability with voxels
if (speed < MIN_COLLISIONLESS_SPEED) {
if (collisionOnExpiry < now && !(MyAvatar.collisionGroups & COLLISION_GROUP_VOXELS)) {
// TODO: check to make sure not already colliding
// enable collision with voxels
groupBits |= COLLISION_GROUP_VOXELS;
updateButton(1, groupBits & COLLISION_GROUP_VOXELS);
}
} else {
collisionOnExpiry = now + COLLISION_EXPIRY_PERIOD;
}
if (speed > MAX_COLLIDABLE_SPEED) {
if (MyAvatar.collisionGroups & COLLISION_GROUP_VOXELS) {
// disable collisions with voxels
groupBits &= ~COLLISION_GROUP_VOXELS;
updateButton(1, groupBits & COLLISION_GROUP_VOXELS);
}
}
}
Script.update.connect(update);
// we also handle click detection in our mousePressEvent()
function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
for (i = 0; i < NUMBER_OF_COLLISION_BUTTONS; i++) {
if (clickedOverlay == buttons[i]) {
var enabled = !(buttonStates[i]);
updateButton(i, enabled);
}
}
}
Controller.mousePressEvent.connect(mousePressEvent);

12
examples/windowExample.js Normal file
View 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);

View file

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

View file

@ -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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:18pt;&quot;&gt;Running Scripts&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="ui/runningScriptsWidget.ui" line="63"/>
<location filename="../build/interface/ui_runningScriptsWidget.h" line="142"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Currently running&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Recently loaded&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>

View 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

(image error) Size: 3.6 KiB

View 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

(image error) Size: 1 KiB

View file

@ -1,98 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="44px"
height="44px"
viewBox="0 0 44 44"
version="1.1"
id="svg2"
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="close.svg">
<metadata
id="metadata16">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Slice 1</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="640"
inkscape:window-height="480"
id="namedview14"
showgrid="false"
inkscape:zoom="3.7926636"
inkscape:cx="57.156875"
inkscape:cy="33.978935"
inkscape:window-x="536"
inkscape:window-y="258"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<title
id="title4">Slice 1</title>
<description
id="description6">Created with Sketch (http://www.bohemiancoding.com/sketch)</description>
<defs
id="defs8" />
<g
id="Page-1"
sketch:type="MSPage"
transform="translate(0.52733383,0.52733392)"
style="fill:none;stroke:none" />
<rect
id="rect2993"
width="5.7796612"
height="26.101694"
x="19.730724"
y="16.374792"
ry="3.371469" />
<rect
id="rect2995"
width="35.423729"
height="2.9830508"
x="4.6290293"
y="2.3917408"
ry="1.4915254" />
<rect
id="rect2999"
width="31.135593"
height="21.067797"
x="6.6798768"
y="4.0697069"
ry="1.4915254" />
<rect
style="fill:#ff0000"
id="rect3003"
width="27.779657"
height="17.711861"
x="8.171401"
y="5.3747911"
ry="0.46610171" />
<rect
style="fill:#999999"
id="rect3011"
width="3.1694915"
height="13.983051"
x="21.035809"
y="25.883266"
ry="0.46610171" />
</svg>

Before

(image error) Size: 2.5 KiB

View file

@ -1,106 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="44px"
height="44px"
viewBox="0 0 44 44"
version="1.1"
id="svg2"
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="pin.svg">
<metadata
id="metadata16">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Slice 1</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1058"
id="namedview14"
showgrid="false"
inkscape:zoom="3.7926636"
inkscape:cx="39.754857"
inkscape:cy="33.978935"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<title
id="title4">Slice 1</title>
<description
id="description6">Created with Sketch (http://www.bohemiancoding.com/sketch)</description>
<defs
id="defs8" />
<g
id="Page-1"
sketch:type="MSPage"
transform="translate(0.52733383,0.52733392)"
style="fill:none;stroke:none" />
<rect
style="fill:#b3b3b3"
id="rect3013"
width="43.768692"
height="44.296078"
x="0.26366693"
y="-0.032378189"
ry="0.46610171" />
<rect
id="rect2993"
width="5.7796612"
height="26.101694"
x="19.730724"
y="16.374792"
ry="3.371469" />
<rect
id="rect2995"
width="35.423729"
height="2.9830508"
x="4.6290293"
y="2.3917408"
ry="1.4915254" />
<rect
id="rect2999"
width="31.135593"
height="21.067797"
x="6.6798768"
y="4.0697069"
ry="1.4915254" />
<rect
style="fill:#ff0000"
id="rect3003"
width="27.779657"
height="17.711861"
x="8.171401"
y="5.3747911"
ry="0.46610171" />
<rect
style="fill:#999999"
id="rect3011"
width="3.1694915"
height="13.983051"
x="21.035809"
y="25.883266"
ry="0.46610171" />
</svg>

Before

(image error) Size: 2.7 KiB

View 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

(image error) Size: 565 B

View file

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

View file

@ -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,6 +77,8 @@
#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/Snapshot.h"
@ -132,7 +134,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 +166,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 +267,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 +317,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 +341,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 +361,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
QMutexLocker locker(&_settingsMutex);
_previousScriptLocation = _settings->value("LastScriptLocation", QVariant("")).toString();
}
//When -url in command line, teleport to location
urlGoTo(argc, constArgv);
connect(_window, &MainWindow::windowGeometryChanged,
_runningScriptsWidget, &RunningScriptsWidget::setBoundary);
//When -url in command line, teleport to location
urlGoTo(argc, constArgv);
}
Application::~Application() {
@ -387,6 +400,7 @@ Application::~Application() {
_voxelHideShowThread.terminate();
_voxelEditSender.terminate();
_particleEditSender.terminate();
_modelEditSender.terminate();
storeSizeAndPosition();
saveScripts();
@ -487,6 +501,8 @@ void Application::initializeGL() {
_voxelEditSender.initialize(_enableProcessVoxelsThread);
_voxelHideShowThread.initialize(_enableProcessVoxelsThread);
_particleEditSender.initialize(_enableProcessVoxelsThread);
_modelEditSender.initialize(_enableProcessVoxelsThread);
if (_enableProcessVoxelsThread) {
qDebug("Voxel parsing thread created.");
}
@ -757,6 +773,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:
@ -857,7 +875,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_G:
if (isShifted) {
Menu::getInstance()->triggerOption(MenuOption::Gravity);
Menu::getInstance()->triggerOption(MenuOption::ObeyEnvironmentalGravity);
}
break;
@ -1263,8 +1281,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 +1674,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 +1819,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 +1917,7 @@ void Application::updateThreads(float deltaTime) {
_voxelHideShowThread.threadRoutine();
_voxelEditSender.threadRoutine();
_particleEditSender.threadRoutine();
_modelEditSender.threadRoutine();
}
}
@ -1986,6 +2036,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 +2076,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 +2378,7 @@ void Application::updateShadowMap() {
_avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE);
_particles.render();
_models.render();
glPopMatrix();
@ -2446,7 +2500,6 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
"Application::displaySide() ... atmosphere...");
_environment.renderAtmospheres(whichCamera);
}
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
@ -2491,6 +2544,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 +2690,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 +2762,7 @@ void Application::displayOverlay() {
if (Menu::getInstance()->isOptionChecked(MenuOption::HeadMouse)) {
_myAvatar->renderHeadMouse();
_myAvatar->renderHeadMouse(_glWidget->width(), _glWidget->height());
}
// Display stats and log text onscreen
@ -3086,6 +3145,9 @@ void Application::domainChanged(const QString& domainHostname) {
// reset the particle renderer
_particles.clear();
// reset the model renderer
_models.clear();
// reset the voxels renderer
_voxels.killLocalVoxels();
}
@ -3162,7 +3224,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 +3235,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 +3290,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 +3316,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 +3423,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 +3484,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 +3499,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 +3540,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 +3551,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);
}
}
@ -3520,35 +3629,30 @@ void Application::parseVersionXml() {
QString operatingSystem("ubuntu");
#endif
QString releaseDate;
QString releaseNotes;
QString latestVersion;
QUrl downloadUrl;
QString releaseNotes("Unavailable");
QObject* sender = QObject::sender();
QXmlStreamReader xml(qobject_cast<QNetworkReply*>(sender));
while (!xml.atEnd() && !xml.hasError()) {
QXmlStreamReader::TokenType token = xml.readNext();
if (token == QXmlStreamReader::StartElement) {
if (xml.name() == "ReleaseDate") {
if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == operatingSystem) {
while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == operatingSystem)) {
if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "version") {
xml.readNext();
latestVersion = xml.text().toString();
}
if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "url") {
xml.readNext();
downloadUrl = QUrl(xml.text().toString());
}
xml.readNext();
releaseDate = xml.text().toString();
}
if (xml.name() == "ReleaseNotes") {
xml.readNext();
releaseNotes = xml.text().toString();
}
if (xml.name() == "Version") {
xml.readNext();
latestVersion = xml.text().toString();
}
if (xml.name() == operatingSystem) {
xml.readNext();
downloadUrl = QUrl(xml.text().toString());
}
}
xml.readNext();
}
if (!shouldSkipVersion(latestVersion) && applicationVersion() != latestVersion) {
new UpdateDialog(_glWidget, releaseNotes, latestVersion, downloadUrl);
}
@ -3591,34 +3695,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);
}
}
}
}

View file

@ -16,6 +16,7 @@
#include <time.h>
#include <QApplication>
#include <QMainWindow>
#include <QAction>
#include <QHash>
#include <QImage>
@ -24,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>
@ -37,6 +40,7 @@
#include <ViewFrustum.h>
#include <VoxelEditPacketSender.h>
#include "MainWindow.h"
#include "Audio.h"
#include "AudioReflector.h"
#include "BuckyBalls.h"
@ -48,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"
@ -57,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 +90,6 @@ class QAction;
class QActionGroup;
class QGLWidget;
class QKeyEvent;
class QMainWindow;
class QMouseEvent;
class QNetworkAccessManager;
class QSettings;
@ -124,6 +128,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 +142,7 @@ public:
void keyReleaseEvent(QKeyEvent* event);
void focusOutEvent(QFocusEvent* event);
void focusInEvent(QFocusEvent* event);
void mouseMoveEvent(QMouseEvent* event);
void mousePressEvent(QMouseEvent* event);
@ -173,6 +180,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 +200,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 +251,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 +321,8 @@ private slots:
void parseVersionXml();
void manageRunningScriptsWidgetVisibility(bool shown);
private:
void resetCamerasOnResizeGL(Camera& camera, int width, int height);
void updateProjectionMatrix();
@ -372,7 +383,7 @@ private:
void displayRearMirrorTools();
QMainWindow* _window;
MainWindow* _window;
GLCanvas* _glWidget; // our GLCanvas has a couple extra features
BandwidthMeter _bandwidthMeter;
@ -386,7 +397,7 @@ private:
int _numChangedSettings;
QUndoStack _undoStack;
glm::vec3 _gravity;
// Frame Rate Measurement
@ -411,6 +422,8 @@ private:
ParticleTreeRenderer _particles;
ParticleCollisionSystem _particleCollisionSystem;
ModelTreeRenderer _models;
QByteArray _voxelsFilename;
bool _wantToKillLocalVoxels;
@ -431,7 +444,7 @@ private:
Faceplus _faceplus;
Faceshift _faceshift;
Visage _visage;
SixenseManager _sixenseManager;
Camera _myCamera; // My view onto the world
@ -488,6 +501,7 @@ private:
VoxelHideShowThread _voxelHideShowThread;
VoxelEditPacketSender _voxelEditSender;
ParticleEditPacketSender _particleEditSender;
ModelEditPacketSender _modelEditSender;
int _packetsPerSecond;
int _bytesPerSecond;
@ -500,6 +514,7 @@ private:
NodeToJurisdictionMap _voxelServerJurisdictions;
NodeToJurisdictionMap _particleServerJurisdictions;
NodeToJurisdictionMap _modelServerJurisdictions;
NodeToOctreeSceneStats _octreeServerSceneStats;
QReadWriteLock _octreeSceneStatsLock;
@ -520,9 +535,11 @@ private:
TouchEvent _lastTouchEvent;
Overlays _overlays;
AudioReflector _audioReflector;
RunningScriptsWidget* _runningScriptsWidget;
QHash<QString, ScriptEngine*> _scriptEnginesHash;
bool _runningScriptsWidgetWasVisible;
};
#endif // hifi_Application_h

View file

@ -644,10 +644,10 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
const int NUM_INITIAL_PACKETS_DISCARD = 3;
const int STANDARD_DEVIATION_SAMPLE_COUNT = 500;
_timeSinceLastRecieved.start();
_totalPacketsReceived++;
double timeDiff = (double)_timeSinceLastRecieved.nsecsElapsed() / 1000000.0; // ns to ms
double timeDiff = (double)_timeSinceLastReceived.nsecsElapsed() / 1000000.0; // ns to ms
_timeSinceLastReceived.start();
// Discard first few received packets for computing jitter (often they pile up on start)
if (_totalPacketsReceived > NUM_INITIAL_PACKETS_DISCARD) {
@ -1265,7 +1265,7 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo)
// setup a procedural audio output device
_proceduralAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this);
_timeSinceLastRecieved.start();
_timeSinceLastReceived.start();
// setup spatial audio ringbuffer
int numFrameSamples = _outputFormat.sampleRate() * _desiredOutputFormat.channelCount();

View file

@ -129,7 +129,7 @@ private:
QString _outputAudioDeviceName;
StDev _stdev;
QElapsedTimer _timeSinceLastRecieved;
QElapsedTimer _timeSinceLastReceived;
float _averagedLatency;
float _measuredJitter;
int16_t _jitterBufferSamples;

View file

@ -56,9 +56,17 @@ 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:

View file

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

View file

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

View file

@ -0,0 +1,67 @@
//
// 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 <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);
}
} else if (event->type() == QEvent::ActivationChange) {
if (isActiveWindow()) {
emit windowShown(true);
} else {
emit windowShown(false);
}
}
QMainWindow::changeEvent(event);
}

View 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__) */

View file

@ -27,6 +27,7 @@
#include <QSlider>
#include <QUuid>
#include <QHBoxLayout>
#include <QDesktopServices>
#include <AccountManager.h>
#include <XmppClient.h>
@ -39,6 +40,7 @@
#include "ui/InfoView.h"
#include "ui/MetavoxelEditor.h"
#include "ui/ModelsBrowser.h"
#include "ui/LoginDialog.h"
Menu* Menu::_instance = NULL;
@ -168,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);
@ -186,9 +188,9 @@ Menu::Menu() :
QAction::PreferencesRole);
addDisabledActionAndSeparator(editMenu, "Physics");
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, false);
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, true,
avatar, SLOT(updateMotionBehaviors()));
addAvatarCollisionSubMenu(editMenu);
@ -278,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");
@ -319,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);
@ -507,7 +510,7 @@ void Menu::loadSettings(QSettings* settings) {
// MyAvatar caches some menu options, so we have to update them whenever we load settings.
// TODO: cache more settings in MyAvatar that are checked with very high frequency.
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
myAvatar->updateCollisionFlags();
myAvatar->updateCollisionGroups();
if (lockedSettings) {
Application::getInstance()->unlockSettings();
@ -722,31 +725,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;
}
@ -812,42 +815,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() {
@ -926,50 +899,70 @@ 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);
connect(manager, &LocationManager::multipleDestinationsFound, this, &Menu::multipleDestinationsDecision);
}
/// Open a url, shortcutting any "hifi" scheme URLs to the local application.
void Menu::openUrl(const QUrl& url) {
if (url.scheme() == "hifi") {
QString path = url.toString(QUrl::RemoveScheme);
path = path.remove(QRegExp("^:?/*"));
goTo(path);
} else {
QDesktopServices::openUrl(url);
}
}
void Menu::multipleDestinationsDecision(const QJsonObject& userData, const QJsonObject& placeData) {
QMessageBox msgBox;
msgBox.setText("Both user and location exists with same name");
@ -1145,23 +1138,22 @@ void Menu::showScriptEditor() {
void Menu::showChat() {
QMainWindow* mainWindow = Application::getInstance()->getWindow();
if (!_chatWindow) {
mainWindow->addDockWidget(Qt::RightDockWidgetArea, _chatWindow = new ChatWindow());
_chatWindow = new ChatWindow(mainWindow);
}
if (!_chatWindow->toggleViewAction()->isChecked()) {
const QRect& windowGeometry = mainWindow->geometry();
_chatWindow->move(windowGeometry.topRight().x() - _chatWindow->width(),
windowGeometry.topRight().y() + (windowGeometry.height() / 2) - (_chatWindow->height() / 2));
_chatWindow->resize(0, _chatWindow->height());
_chatWindow->toggleViewAction()->trigger();
if (_chatWindow->isHidden()) {
_chatWindow->show();
}
}
void Menu::toggleChat() {
#ifdef HAVE_QXMPP
_chatAction->setEnabled(XmppClient::getInstance().getXMPPClient().isConnected());
if (!_chatAction->isEnabled() && _chatWindow && _chatWindow->toggleViewAction()->isChecked()) {
_chatWindow->toggleViewAction()->trigger();
if (!_chatAction->isEnabled() && _chatWindow) {
if (_chatWindow->isHidden()) {
_chatWindow->show();
} else {
_chatWindow->hide();
}
}
#endif
}
@ -1263,7 +1255,7 @@ void Menu::autoAdjustLOD(float currentFPS) {
_avatarLODDistanceMultiplier - DISTANCE_DECREASE_RATE);
}
}
bool changed = false;
quint64 elapsed = now - _lastAdjust;
@ -1370,13 +1362,13 @@ void Menu::addAvatarCollisionSubMenu(QMenu* overMenu) {
Application* appInstance = Application::getInstance();
QObject* avatar = appInstance->getAvatar();
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment,
0, false, avatar, SLOT(updateCollisionFlags()));
0, false, avatar, SLOT(updateCollisionGroups()));
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars,
0, true, avatar, SLOT(updateCollisionFlags()));
0, true, avatar, SLOT(updateCollisionGroups()));
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels,
0, false, avatar, SLOT(updateCollisionFlags()));
0, false, avatar, SLOT(updateCollisionGroups()));
addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles,
0, true, avatar, SLOT(updateCollisionFlags()));
0, true, avatar, SLOT(updateCollisionGroups()));
}
QAction* Menu::getActionFromName(const QString& menuName, QMenu* menu) {

View file

@ -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,8 +152,10 @@ public slots:
void importSettings();
void exportSettings();
void goTo();
bool goToURL(QString location);
void goToUser(const QString& user);
void pasteToVoxel();
void openUrl(const QUrl& url);
void toggleLoginMenuItem();
@ -275,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";
@ -314,7 +313,7 @@ namespace MenuOption {
const QString GoTo = "Go To...";
const QString GoToDomain = "Go To Domain...";
const QString GoToLocation = "Go To Location...";
const QString Gravity = "Use Gravity";
const QString ObeyEnvironmentalGravity = "Obey Environmental Gravity";
const QString HandsCollideWithSelf = "Collide With Self";
const QString HeadMouse = "Head Mouse";
const QString IncreaseAvatarSize = "Increase Avatar Size";
@ -329,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";

View file

@ -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,160 @@ 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, 10.0);
}
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
if (!joints.contains("jointEyeLeft")) {
joints.insert("jointEyeLeft", "LeftEye");
}
if (!joints.contains("jointEyeRight")) {
joints.insert("jointEyeRight", "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")) {
joints.insert("jointHead", geometry.applicationName == "mixamo.com" ? "HeadTop_End" : "HeadEnd");
}
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 +266,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 +426,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 +447,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 +457,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 +495,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 +509,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, 1.0).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);
}
}

View file

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

View file

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

View file

@ -34,6 +34,7 @@ protected:
void formatTrueFalse(const QString& text);
private:
QRegExp _alphacharRegex;
QRegExp _keywordRegex;
QRegExp _qoutedTextRegex;
QRegExp _multiLineCommentBegin;

View file

@ -47,16 +47,14 @@ Avatar::Avatar() :
AvatarData(),
_skeletonModel(this),
_bodyYawDelta(0.0f),
_mode(AVATAR_MODE_STANDING),
_velocity(0.0f, 0.0f, 0.0f),
_thrust(0.0f, 0.0f, 0.0f),
_leanScale(0.5f),
_scale(1.0f),
_worldUpDirection(DEFAULT_UP_DIRECTION),
_mouseRayOrigin(0.0f, 0.0f, 0.0f),
_mouseRayDirection(0.0f, 0.0f, 0.0f),
_moving(false),
_collisionFlags(0),
_collisionGroups(0),
_initialized(false),
_shouldRenderBillboard(true)
{
@ -138,20 +136,9 @@ void Avatar::simulate(float deltaTime) {
head->simulate(deltaTime, false, _shouldRenderBillboard);
}
// use speed and angular velocity to determine walking vs. standing
float speed = glm::length(_velocity);
if (speed + fabs(_bodyYawDelta) > 0.2) {
_mode = AVATAR_MODE_WALKING;
} else {
_mode = AVATAR_MODE_INTERACTING;
}
// update position by velocity, and subtract the change added earlier for gravity
_position += _velocity * deltaTime;
// Zero thrust out now that we've added it to velocity in this frame
_thrust = glm::vec3(0, 0, 0);
// update animation for display name fade in/out
if ( _displayNameTargetAlpha != _displayNameAlpha) {
// the alpha function is
@ -166,7 +153,7 @@ void Avatar::simulate(float deltaTime) {
// Fading in
_displayNameAlpha = 1 - (1 - _displayNameAlpha) * coef;
}
_displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01? _displayNameTargetAlpha : _displayNameAlpha;
_displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha;
}
}
@ -219,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();
@ -339,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 {
@ -464,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,
@ -479,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;
@ -563,7 +554,7 @@ bool Avatar::findCollisions(const QVector<const Shape*>& shapes, CollisionList&
}
bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float particleRadius, CollisionList& collisions) {
if (_collisionFlags & COLLISION_GROUP_PARTICLES) {
if (_collisionGroups & COLLISION_GROUP_PARTICLES) {
return false;
}
bool collided = false;
@ -753,19 +744,19 @@ void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2,
glEnd();
}
void Avatar::updateCollisionFlags() {
_collisionFlags = 0;
void Avatar::updateCollisionGroups() {
_collisionGroups = 0;
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithEnvironment)) {
_collisionFlags |= COLLISION_GROUP_ENVIRONMENT;
_collisionGroups |= COLLISION_GROUP_ENVIRONMENT;
}
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithAvatars)) {
_collisionFlags |= COLLISION_GROUP_AVATARS;
_collisionGroups |= COLLISION_GROUP_AVATARS;
}
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithVoxels)) {
_collisionFlags |= COLLISION_GROUP_VOXELS;
_collisionGroups |= COLLISION_GROUP_VOXELS;
}
if (Menu::getInstance()->isOptionChecked(MenuOption::CollideWithParticles)) {
_collisionFlags |= COLLISION_GROUP_PARTICLES;
_collisionGroups |= COLLISION_GROUP_PARTICLES;
}
}

View file

@ -46,13 +46,6 @@ enum DriveKeys {
MAX_DRIVE_KEYS
};
enum AvatarMode {
AVATAR_MODE_STANDING = 0,
AVATAR_MODE_WALKING,
AVATAR_MODE_INTERACTING,
NUM_AVATAR_MODES
};
enum ScreenTintLayer {
SCREEN_TINT_BEFORE_LANDSCAPE = 0,
SCREEN_TINT_BEFORE_AVATARS,
@ -70,6 +63,7 @@ class Texture;
class Avatar : public AvatarData {
Q_OBJECT
Q_PROPERTY(quint32 collisionGroups READ getCollisionGroups WRITE setCollisionGroups)
public:
Avatar();
@ -155,8 +149,11 @@ public:
virtual float getBoundingRadius() const;
void updateShapePositions();
quint32 getCollisionGroups() const { return _collisionGroups; }
virtual void setCollisionGroups(quint32 collisionGroups) { _collisionGroups = (collisionGroups & VALID_COLLISION_GROUPS); }
public slots:
void updateCollisionFlags();
void updateCollisionGroups();
signals:
void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision);
@ -164,9 +161,7 @@ signals:
protected:
SkeletonModel _skeletonModel;
float _bodyYawDelta;
AvatarMode _mode;
glm::vec3 _velocity;
glm::vec3 _thrust;
float _leanScale;
float _scale;
glm::vec3 _worldUpDirection;
@ -175,7 +170,7 @@ protected:
float _stringLength;
bool _moving; ///< set when position is changing
uint32_t _collisionFlags;
quint32 _collisionGroups;
// protected methods...
glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
@ -190,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();

View file

@ -12,6 +12,7 @@
#include <algorithm>
#include <vector>
#include <QMessageBox>
#include <QBuffer>
#include <glm/gtx/norm.hpp>
@ -54,16 +55,13 @@ MyAvatar::MyAvatar() :
_shouldJump(false),
_gravity(0.0f, -1.0f, 0.0f),
_distanceToNearestAvatar(std::numeric_limits<float>::max()),
_elapsedTimeMoving(0.0f),
_elapsedTimeStopped(0.0f),
_elapsedTimeSinceCollision(0.0f),
_lastCollisionPosition(0, 0, 0),
_speedBrakes(false),
_thrust(0.0f),
_isThrustOn(false),
_thrustMultiplier(1.0f),
_moveTarget(0,0,0),
_motionBehaviors(0),
_lastBodyPenetration(0.0f),
_moveTargetStepCounter(0),
_lookAtTargetAvatar(),
_shouldRender(true),
_billboardValid(false),
@ -84,11 +82,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;
@ -97,11 +92,6 @@ void MyAvatar::reset() {
setOrientation(glm::quat(glm::vec3(0.0f)));
}
void MyAvatar::setMoveTarget(const glm::vec3 moveTarget) {
_moveTarget = moveTarget;
_moveTargetStepCounter = 0;
}
void MyAvatar::update(float deltaTime) {
Head* head = getHead();
head->relaxLean(deltaTime);
@ -110,32 +100,14 @@ 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());
head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
if (Menu::getInstance()->isOptionChecked(MenuOption::Gravity)) {
if (_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY) {
setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()));
} else {
setGravity(glm::vec3(0.0f, 0.0f, 0.0f));
}
simulate(deltaTime);
@ -145,17 +117,6 @@ void MyAvatar::simulate(float deltaTime) {
glm::quat orientation = getOrientation();
// Update movement timers
_elapsedTimeSinceCollision += deltaTime;
const float VELOCITY_MOVEMENT_TIMER_THRESHOLD = 0.2f;
if (glm::length(_velocity) < VELOCITY_MOVEMENT_TIMER_THRESHOLD) {
_elapsedTimeMoving = 0.0f;
_elapsedTimeStopped += deltaTime;
} else {
_elapsedTimeStopped = 0.0f;
_elapsedTimeMoving += deltaTime;
}
if (_scale != _targetScale) {
float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale;
setScale(scale);
@ -269,34 +230,11 @@ void MyAvatar::simulate(float deltaTime) {
// update the euler angles
setOrientation(orientation);
const float WALKING_SPEED_THRESHOLD = 0.2f;
// use speed and angular velocity to determine walking vs. standing
float speed = glm::length(_velocity);
if (speed + fabs(_bodyYawDelta) > WALKING_SPEED_THRESHOLD) {
_mode = AVATAR_MODE_WALKING;
} else {
_mode = AVATAR_MODE_INTERACTING;
}
// update moving flag based on speed
const float MOVING_SPEED_THRESHOLD = 0.01f;
float speed = glm::length(_velocity);
_moving = speed > MOVING_SPEED_THRESHOLD;
// If a move target is set, update position explicitly
const float MOVE_FINISHED_TOLERANCE = 0.1f;
const float MOVE_SPEED_FACTOR = 2.0f;
const int MOVE_TARGET_MAX_STEPS = 250;
if ((glm::length(_moveTarget) > EPSILON) && (_moveTargetStepCounter < MOVE_TARGET_MAX_STEPS)) {
if (glm::length(_position - _moveTarget) > MOVE_FINISHED_TOLERANCE) {
_position += (_moveTarget - _position) * (deltaTime * MOVE_SPEED_FACTOR);
_moveTargetStepCounter++;
} else {
// Move completed
_moveTarget = glm::vec3(0,0,0);
_moveTargetStepCounter = 0;
}
}
updateChatCircle(deltaTime);
_position += _velocity * deltaTime;
@ -324,10 +262,10 @@ void MyAvatar::simulate(float deltaTime) {
head->simulate(deltaTime, true);
// Zero thrust out now that we've added it to velocity in this frame
_thrust = glm::vec3(0.0f);
_thrust *= glm::vec3(0.0f);
// now that we're done stepping the avatar forward in time, compute new collisions
if (_collisionFlags != 0) {
if (_collisionGroups != 0) {
Camera* myCamera = Application::getInstance()->getCamera();
float radius = getSkeletonHeight() * COLLISION_RADIUS_SCALE;
@ -335,15 +273,15 @@ void MyAvatar::simulate(float deltaTime) {
radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.0f));
radius *= COLLISION_RADIUS_SCALAR;
}
if (_collisionFlags) {
if (_collisionGroups) {
updateShapePositions();
if (_collisionFlags & COLLISION_GROUP_ENVIRONMENT) {
if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) {
updateCollisionWithEnvironment(deltaTime, radius);
}
if (_collisionFlags & COLLISION_GROUP_VOXELS) {
if (_collisionGroups & COLLISION_GROUP_VOXELS) {
updateCollisionWithVoxels(deltaTime, radius);
}
if (_collisionFlags & COLLISION_GROUP_AVATARS) {
if (_collisionGroups & COLLISION_GROUP_AVATARS) {
updateCollisionWithAvatars(deltaTime);
}
}
@ -451,8 +389,6 @@ void MyAvatar::renderDebugBodyPoints() {
glTranslatef(position.x, position.y, position.z);
glutSolidSphere(0.15, 10, 10);
glPopMatrix();
}
// virtual
@ -467,32 +403,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);
@ -503,7 +448,27 @@ void MyAvatar::renderHeadMouse() const {
glEnd();
}
*/
}
void MyAvatar::setLocalGravity(glm::vec3 gravity) {
_motionBehaviors |= AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
// Environmental and Local gravities are incompatible. Since Local is being set here
// the environmental setting must be removed.
_motionBehaviors &= ~AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY;
setGravity(gravity);
}
void MyAvatar::setGravity(const glm::vec3& gravity) {
_gravity = gravity;
getHead()->setGravity(_gravity);
// use the gravity to determine the new world up direction, if possible
float gravityLength = glm::length(gravity);
if (gravityLength > EPSILON) {
_worldUpDirection = _gravity / -gravityLength;
} else {
_worldUpDirection = DEFAULT_UP_DIRECTION;
}
}
void MyAvatar::saveData(QSettings* settings) {
@ -552,7 +517,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());
@ -590,23 +555,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);
}
}
@ -642,7 +608,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
}
@ -830,7 +796,6 @@ void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity
// cancel out the velocity component in the direction of penetration
float penetrationLength = glm::length(penetration);
if (penetrationLength > EPSILON) {
_elapsedTimeSinceCollision = 0.0f;
glm::vec3 direction = penetration / penetrationLength;
_velocity -= glm::dot(_velocity, direction) * direction * (1.0f + elasticity);
_velocity *= glm::clamp(1.0f - damping, 0.0f, 1.0f);
@ -867,7 +832,7 @@ void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTim
std::min(COLLISION_LOUDNESS * velocityTowardCollision, 1.0f),
frequency * (1.0f + velocityTangentToCollision / velocityTowardCollision),
std::min(velocityTangentToCollision / velocityTowardCollision * NOISE_SCALING, 1.0f),
1.0f - DURATION_SCALING * powf(frequency, 0.5f) / velocityTowardCollision, true);
1.0f - DURATION_SCALING * powf(frequency, 0.5f) / velocityTowardCollision, false);
}
}
@ -1090,19 +1055,6 @@ void MyAvatar::maybeUpdateBillboard() {
sendBillboardPacket();
}
void MyAvatar::setGravity(glm::vec3 gravity) {
_gravity = gravity;
getHead()->setGravity(_gravity);
// use the gravity to determine the new world up direction, if possible
float gravityLength = glm::length(gravity);
if (gravityLength > EPSILON) {
_worldUpDirection = _gravity / -gravityLength;
} else {
_worldUpDirection = DEFAULT_UP_DIRECTION;
}
}
void MyAvatar::goHome() {
qDebug("Going Home!");
setPosition(START_LOCATION);
@ -1184,8 +1136,43 @@ void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) {
coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER;
setPosition(newPosition);
emit transformChanged();
} else {
QMessageBox::warning(Application::getInstance()->getWindow(), "", "That user or location could not be found.");
}
}
void MyAvatar::updateMotionBehaviors() {
_motionBehaviors = 0;
if (Menu::getInstance()->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) {
_motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY;
// Environmental and Local gravities are incompatible. Environmental setting trumps local.
_motionBehaviors &= ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
}
if (! (_motionBehaviors & (AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | AVATAR_MOTION_OBEY_LOCAL_GRAVITY))) {
setGravity(glm::vec3(0.0f));
}
}
void MyAvatar::setCollisionGroups(quint32 collisionGroups) {
Avatar::setCollisionGroups(collisionGroups & VALID_COLLISION_GROUPS);
Menu* menu = Menu::getInstance();
menu->setIsOptionChecked(MenuOption::CollideWithEnvironment, (bool)(_collisionGroups & COLLISION_GROUP_ENVIRONMENT));
menu->setIsOptionChecked(MenuOption::CollideWithAvatars, (bool)(_collisionGroups & COLLISION_GROUP_AVATARS));
menu->setIsOptionChecked(MenuOption::CollideWithVoxels, (bool)(_collisionGroups & COLLISION_GROUP_VOXELS));
menu->setIsOptionChecked(MenuOption::CollideWithParticles, (bool)(_collisionGroups & COLLISION_GROUP_PARTICLES));
}
void MyAvatar::setMotionBehaviors(quint32 flags) {
_motionBehaviors = flags;
Menu* menu = Menu::getInstance();
menu->setIsOptionChecked(MenuOption::ObeyEnvironmentalGravity, (bool)(_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY));
// Environmental and Local gravities are incompatible. Environmental setting trumps local.
if (_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY) {
_motionBehaviors &= ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()));
} else if (! (_motionBehaviors & (AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | AVATAR_MOTION_OBEY_LOCAL_GRAVITY))) {
setGravity(glm::vec3(0.0f));
}
}
void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) {

View file

@ -28,6 +28,8 @@ enum AvatarHandState
class MyAvatar : public Avatar {
Q_OBJECT
Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
Q_PROPERTY(quint32 motionBehaviors READ getMotionBehaviors WRITE setMotionBehaviors)
Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setLocalGravity)
public:
MyAvatar();
@ -40,24 +42,20 @@ 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; }
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
void setLeanScale(float scale) { _leanScale = scale; }
void setGravity(glm::vec3 gravity);
void setMoveTarget(const glm::vec3 moveTarget);
void setLocalGravity(glm::vec3 gravity);
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; }
// getters
AvatarMode getMode() const { return _mode; }
float getLeanScale() const { return _leanScale; }
float getElapsedTimeStopped() const { return _elapsedTimeStopped; }
float getElapsedTimeMoving() const { return _elapsedTimeMoving; }
const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; }
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
glm::vec3 getGravity() const { return _gravity; }
@ -91,6 +89,10 @@ public:
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
virtual void setCollisionGroups(quint32 collisionGroups);
void setMotionBehaviors(quint32 flags);
quint32 getMotionBehaviors() const { return _motionBehaviors; }
void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration);
public slots:
@ -107,6 +109,8 @@ public slots:
glm::vec3 getThrust() { return _thrust; };
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
void updateMotionBehaviors();
signals:
void transformChanged();
@ -117,17 +121,19 @@ private:
bool _shouldJump;
float _driveKeys[MAX_DRIVE_KEYS];
glm::vec3 _gravity;
glm::vec3 _environmentGravity;
float _distanceToNearestAvatar; // How close is the nearest avatar?
float _elapsedTimeMoving; // Timers to drive camera transitions when moving
float _elapsedTimeStopped;
float _elapsedTimeSinceCollision;
// motion stuff
glm::vec3 _lastCollisionPosition;
bool _speedBrakes;
glm::vec3 _thrust; // final acceleration for the current frame
bool _isThrustOn;
float _thrustMultiplier;
glm::vec3 _moveTarget;
quint32 _motionBehaviors;
glm::vec3 _lastBodyPenetration;
int _moveTargetStepCounter;
QWeakPointer<AvatarData> _lookAtTargetAvatar;
glm::vec3 _targetAvatarPosition;
bool _shouldRender;
@ -144,6 +150,7 @@ private:
void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency);
void updateChatCircle(float deltaTime);
void maybeUpdateBillboard();
void setGravity(const glm::vec3& gravity);
};
#endif // hifi_MyAvatar_h

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QMessageBox>
#include "Application.h"
#include "LocationManager.h"
@ -118,6 +120,8 @@ void LocationManager::checkForMultipleDestinations() {
Application::getInstance()->getAvatar()->goToLocationFromResponse(_placeData);
return;
}
QMessageBox::warning(Application::getInstance()->getWindow(), "", "That user or location could not be found.");
}
}

View file

@ -16,7 +16,6 @@
#include <SharedUtil.h>
int main(int argc, const char * argv[]) {
initialiseUsecTimestampNow();
QElapsedTimer startupTime;
startupTime.start();

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

View 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

View file

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

View file

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

View file

@ -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;
@ -770,9 +809,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 = getMeshExtents();
// 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);
}
}

View file

@ -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; }
@ -203,6 +218,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 +251,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);

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

View 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

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

View 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

View file

@ -0,0 +1,19 @@
//
// ChatInputArea.cpp
// interface/src/ui
//
// Created by Ryan Huffman 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
//
#include "ChatInputArea.h"
ChatInputArea::ChatInputArea(QWidget* parent) : QTextEdit(parent) {
};
void ChatInputArea::insertFromMimeData(const QMimeData* source) {
insertPlainText(source->text());
};

View file

@ -0,0 +1,27 @@
//
// ChatInputArea.h
// interface/src/ui
//
// Created by Ryan Huffman on 4/11/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_ChatInputArea_h
#define hifi_ChatInputArea_h
#include <QTextBrowser>
#include <QMimeData>
class ChatInputArea : public QTextEdit {
Q_OBJECT
public:
ChatInputArea(QWidget* parent);
protected:
void insertFromMimeData(const QMimeData* source);
};
#endif // hifi_ChatInputArea_h

View file

@ -9,13 +9,18 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Application.h"
#include "ChatMessageArea.h"
#include <QAbstractTextDocumentLayout>
#include <QWheelEvent>
ChatMessageArea::ChatMessageArea() : QTextBrowser() {
ChatMessageArea::ChatMessageArea(bool useFixedHeight) : QTextBrowser(), _useFixedHeight(useFixedHeight) {
setOpenLinks(false);
connect(document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged,
this, &ChatMessageArea::updateLayout);
connect(this, &QTextBrowser::anchorClicked,
Menu::getInstance(), &Menu::openUrl);
}
void ChatMessageArea::setHtml(const QString& html) {
@ -34,7 +39,15 @@ void ChatMessageArea::setHtml(const QString& html) {
}
void ChatMessageArea::updateLayout() {
setFixedHeight(document()->size().height());
if (_useFixedHeight) {
setFixedHeight(document()->size().height());
updateGeometry();
emit sizeChanged(size());
}
}
void ChatMessageArea::setSize(const QSize& size) {
setFixedHeight(size.height());
updateGeometry();
}

View file

@ -19,15 +19,19 @@ const int CHAT_MESSAGE_LINE_HEIGHT = 130;
class ChatMessageArea : public QTextBrowser {
Q_OBJECT
public:
ChatMessageArea();
ChatMessageArea(bool useFixedHeight = true);
virtual void setHtml(const QString& html);
public slots:
void updateLayout();
void setSize(const QSize& size);
signals:
void sizeChanged(QSize newSize);
protected:
virtual void wheelEvent(QWheelEvent* event);
bool _useFixedHeight;
};
#endif // hifi_ChatMessageArea_h

View file

@ -12,12 +12,10 @@
#include <QGridLayout>
#include <QFrame>
#include <QLayoutItem>
#include <QMainWindow>
#include <QPalette>
#include <QScrollBar>
#include <QSizePolicy>
#include <QTimer>
#include <QWidget>
#include "Application.h"
#include "FlowLayout.h"
@ -31,32 +29,40 @@
const int NUM_MESSAGES_TO_TIME_STAMP = 20;
const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)");
const QRegularExpression regexHifiLinks("([#@]\\S+)");
ChatWindow::ChatWindow() :
ChatWindow::ChatWindow(QWidget* parent) :
FramelessDialog(parent, 0, POSITION_RIGHT),
ui(new Ui::ChatWindow),
numMessagesAfterLastTimeStamp(0),
_mousePressed(false),
_mouseStartPosition()
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, false);
// remove the title bar (see the Qt docs on setTitleBarWidget), but we keep it for undocking
//
titleBar = titleBarWidget();
setTitleBarWidget(new QWidget());
ui->setupUi(this);
FlowLayout* flowLayout = new FlowLayout(0, 4, 4);
ui->usersWidget->setLayout(flowLayout);
ui->messagesGridLayout->setColumnStretch(0, 1);
ui->messagesGridLayout->setColumnStretch(1, 3);
ui->messagePlainTextEdit->installEventFilter(this);
ui->messagePlainTextEdit->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
QTextCursor cursor(ui->messagePlainTextEdit->textCursor());
cursor.movePosition(QTextCursor::Start);
QTextBlockFormat format = cursor.blockFormat();
format.setLineHeight(130, QTextBlockFormat::ProportionalHeight);
cursor.setBlockFormat(format);
ui->messagePlainTextEdit->setTextCursor(cursor);
if (!AccountManager::getInstance().isLoggedIn()) {
ui->connectingToXMPPLabel->setText(tr("You must be logged in to chat with others."));
}
#ifdef HAVE_QXMPP
const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient();
if (xmppClient.isConnected()) {
@ -89,36 +95,17 @@ ChatWindow::~ChatWindow() {
delete ui;
}
void ChatWindow::mousePressEvent(QMouseEvent *e) {
if (e->button() == Qt::LeftButton && isFloating()) {
_mousePressed = true;
_mouseStartPosition = e->pos();
}
}
void ChatWindow::mouseMoveEvent(QMouseEvent *e) {
if (_mousePressed) {
move(mapToParent(e->pos() - _mouseStartPosition));
}
}
void ChatWindow::mouseReleaseEvent( QMouseEvent *e ) {
if ( e->button() == Qt::LeftButton ) {
_mousePressed = false;
}
}
void ChatWindow::keyPressEvent(QKeyEvent* event) {
QDockWidget::keyPressEvent(event);
if (event->key() == Qt::Key_Escape) {
hide();
} else {
FramelessDialog::keyPressEvent(event);
}
}
void ChatWindow::showEvent(QShowEvent* event) {
QDockWidget::showEvent(event);
FramelessDialog::showEvent(event);
if (!event->spontaneous()) {
activateWindow();
ui->messagePlainTextEdit->setFocus();
}
}
@ -141,18 +128,20 @@ bool ChatWindow::eventFilter(QObject* sender, QEvent* event) {
message.setBody(messageText);
XmppClient::getInstance().getXMPPClient().sendPacket(message);
#endif
ui->messagePlainTextEdit->document()->clear();
QTextCursor cursor = ui->messagePlainTextEdit->textCursor();
cursor.select(QTextCursor::Document);
cursor.removeSelectedText();
}
return true;
}
} else {
if (event->type() != QEvent::MouseButtonRelease) {
return false;
} else if (event->type() == QEvent::MouseButtonRelease) {
QVariant userVar = sender->property("user");
if (userVar.isValid()) {
Menu::getInstance()->goToUser("@" + userVar.toString());
return true;
}
QString user = sender->property("user").toString();
Menu::getInstance()->goToUser(user);
}
return false;
return FramelessDialog::eventFilter(sender, event);
}
#ifdef HAVE_QXMPP
@ -175,16 +164,17 @@ void ChatWindow::addTimeStamp() {
timeString.chop(1);
if (!timeString.isEmpty()) {
QLabel* timeLabel = new QLabel(timeString);
timeLabel->setStyleSheet("color: palette(shadow);"
"background-color: palette(highlight);"
timeLabel->setStyleSheet("color: #333333;"
"background-color: white;"
"font-size: 14pt;"
"padding: 4px;");
timeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
timeLabel->setAlignment(Qt::AlignHCenter);
timeLabel->setAlignment(Qt::AlignLeft);
bool atBottom = isAtBottom();
bool atBottom = isNearBottom();
ui->messagesGridLayout->addWidget(timeLabel, ui->messagesGridLayout->rowCount(), 0, 1, 2);
ui->messagesGridLayout->parentWidget()->updateGeometry();
ui->messagesVBoxLayout->addWidget(timeLabel);
ui->messagesVBoxLayout->parentWidget()->updateGeometry();
Application::processEvents();
numMessagesAfterLastTimeStamp = 0;
@ -249,6 +239,7 @@ void ChatWindow::participantsChanged() {
"padding-bottom: 2px;"
"padding-left: 2px;"
"border: 1px solid palette(shadow);"
"font-size: 14pt;"
"font-weight: bold");
userLabel->setProperty("user", participantName);
userLabel->setCursor(Qt::PointingHandCursor);
@ -262,15 +253,11 @@ void ChatWindow::messageReceived(const QXmppMessage& message) {
return;
}
QLabel* userLabel = new QLabel(getParticipantName(message.from()));
userLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
userLabel->setStyleSheet("padding: 2px; font-weight: bold");
userLabel->setAlignment(Qt::AlignTop | Qt::AlignRight);
// Update background if this is a message from the current user
bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getUsername();
ChatMessageArea* messageArea = new ChatMessageArea();
messageArea->setOpenLinks(true);
messageArea->setOpenExternalLinks(true);
// Create message area
ChatMessageArea* messageArea = new ChatMessageArea(true);
messageArea->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
messageArea->setTextInteractionFlags(Qt::TextBrowserInteraction);
messageArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
@ -281,22 +268,30 @@ void ChatWindow::messageReceived(const QXmppMessage& message) {
"padding-left: 2px;"
"padding-top: 2px;"
"padding-right: 20px;"
"margin: 0px;"
"color: #333333;"
"font-size: 14pt;"
"background-color: rgba(0, 0, 0, 0%);"
"border: 0;");
bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getUsername();
QString userLabel = getParticipantName(message.from());
if (fromSelf) {
userLabel->setStyleSheet(userLabel->styleSheet() + "; background-color: #e1e8ea");
messageArea->setStyleSheet(messageArea->styleSheet() + "; background-color: #e1e8ea");
userLabel = "<b style=\"color: #4a6f91\">" + userLabel + ": </b>";
messageArea->setStyleSheet(messageArea->styleSheet() + "background-color: #e1e8ea");
} else {
userLabel = "<b>" + userLabel + ": </b>";
}
messageArea->setHtml(message.body().replace(regexLinks, "<a href=\"\\1\">\\1</a>"));
messageArea->document()->setDefaultStyleSheet("a { text-decoration: none; font-weight: bold; color: #267077;}");
QString messageText = message.body().toHtmlEscaped();
messageText = messageText.replace(regexLinks, "<a href=\"\\1\">\\1</a>");
messageText = messageText.replace(regexHifiLinks, "<a href=\"hifi://\\1\">\\1</a>");
messageArea->setHtml(userLabel + messageText);
bool atBottom = isAtBottom();
ui->messagesGridLayout->addWidget(userLabel, ui->messagesGridLayout->rowCount(), 0);
ui->messagesGridLayout->addWidget(messageArea, ui->messagesGridLayout->rowCount() - 1, 1);
bool atBottom = isNearBottom();
ui->messagesGridLayout->parentWidget()->updateGeometry();
ui->messagesVBoxLayout->addWidget(messageArea);
ui->messagesVBoxLayout->parentWidget()->updateGeometry();
Application::processEvents();
if (atBottom || fromSelf) {
@ -313,26 +308,13 @@ void ChatWindow::messageReceived(const QXmppMessage& message) {
#endif
bool ChatWindow::isAtBottom() {
bool ChatWindow::isNearBottom() {
QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar();
return verticalScrollBar->sliderPosition() == verticalScrollBar->maximum();
return verticalScrollBar->value() >= verticalScrollBar->maximum() - Ui::AUTO_SCROLL_THRESHOLD;
}
// Scroll chat message area to bottom.
void ChatWindow::scrollToBottom() {
QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar();
verticalScrollBar->setSliderPosition(verticalScrollBar->maximum());
}
void ChatWindow::togglePinned() {
QMainWindow* mainWindow = Application::getInstance()->getWindow();
mainWindow->removeDockWidget(this);
if (ui->togglePinnedButton->isChecked()) {
mainWindow->addDockWidget(ui->togglePinnedButton->isChecked() ? Qt::RightDockWidgetArea : Qt::NoDockWidgetArea, this);
}
if (!this->toggleViewAction()->isChecked()) {
this->toggleViewAction()->trigger();
}
this->setFloating(!ui->togglePinnedButton->isChecked());
setTitleBarWidget(ui->togglePinnedButton->isChecked()?new QWidget():titleBar);
verticalScrollBar->setValue(verticalScrollBar->maximum());
}

View file

@ -17,6 +17,7 @@
#include <QTimer>
#include <Application.h>
#include "FramelessDialog.h"
#ifdef HAVE_QXMPP
@ -26,37 +27,38 @@
#endif
namespace Ui {
// Maximum amount the chat can be scrolled up in order to auto scroll.
const int AUTO_SCROLL_THRESHOLD = 20;
class ChatWindow;
}
class ChatWindow : public QDockWidget {
class ChatWindow : public FramelessDialog {
Q_OBJECT
public:
ChatWindow();
ChatWindow(QWidget* parent);
~ChatWindow();
virtual void keyPressEvent(QKeyEvent *event);
virtual void showEvent(QShowEvent* event);
virtual void mousePressEvent(QMouseEvent *e);
virtual void mouseMoveEvent(QMouseEvent *e);
virtual void mouseReleaseEvent(QMouseEvent *e);
protected:
bool eventFilter(QObject* sender, QEvent* event);
virtual void keyPressEvent(QKeyEvent *event);
virtual void showEvent(QShowEvent* event);
private:
#ifdef HAVE_QXMPP
QString getParticipantName(const QString& participant);
#endif
void startTimerForTimeStamps();
void addTimeStamp();
bool isAtBottom();
bool isNearBottom();
void scrollToBottom();
Ui::ChatWindow* ui;
QWidget* titleBar;
int numMessagesAfterLastTimeStamp;
QDateTime lastMessageStamp;
bool _mousePressed;
@ -65,7 +67,6 @@ private:
private slots:
void connected();
void timeout();
void togglePinned();
#ifdef HAVE_QXMPP
void error(QXmppClient::Error error);
void participantsChanged();

View file

@ -14,8 +14,15 @@
const int RESIZE_HANDLE_WIDTH = 7;
FramelessDialog::FramelessDialog(QWidget *parent, Qt::WindowFlags flags) :
QDialog(parent, flags | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint) {
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),
_hideOnBlur(true) {
setAttribute(Qt::WA_DeleteOnClose);
// handle rezize and move events
@ -29,29 +36,37 @@ bool FramelessDialog::eventFilter(QObject* sender, QEvent* event) {
switch (event->type()) {
case QEvent::Move:
if (sender == parentWidget()) {
// move to upper left corner on app move
move(parentWidget()->geometry().topLeft());
resizeAndPosition(false);
}
break;
case QEvent::Resize:
if (sender == parentWidget()) {
// keep full app height on resizing the app
setFixedHeight(parentWidget()->size().height());
resizeAndPosition(false);
}
break;
case QEvent::WindowStateChange:
if (parentWidget()->isMinimized()) {
setHidden(true);
} else {
if (_hideOnBlur && parentWidget()->isMinimized()) {
if (isVisible()) {
_selfHidden = true;
setHidden(true);
}
} else if (_selfHidden) {
_selfHidden = false;
setHidden(false);
}
break;
case QEvent::ApplicationDeactivate:
// hide on minimize and focus lost
setHidden(true);
if (_hideOnBlur && isVisible()) {
_selfHidden = true;
setHidden(true);
}
break;
case QEvent::ApplicationActivate:
setHidden(false);
if (_selfHidden) {
_selfHidden = false;
setHidden(false);
}
break;
default:
break;
@ -70,21 +85,53 @@ void FramelessDialog::setStyleSheetFile(const QString& fileName) {
}
void FramelessDialog::showEvent(QShowEvent* event) {
// move to upper left corner
move(parentWidget()->geometry().topLeft());
resizeAndPosition();
QDialog::showEvent(event);
}
// keep full app height
setFixedHeight(parentWidget()->size().height());
void FramelessDialog::resizeAndPosition(bool resizeParent) {
// 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 (parentWidget()->size().width() < size().width()) {
if (resizeParent && parentWidget()->size().width() < size().width()) {
parentWidget()->resize(size().width(), parentWidget()->size().height());
}
if (_position == POSITION_LEFT || _position == POSITION_TOP) {
// move to upper left corner
move(parentWidget()->geometry().topLeft());
} else if (_position == POSITION_RIGHT) {
// move to upper right corner
QPoint pos = parentWidget()->geometry().topRight();
pos.setX(pos.x() - size().width());
move(pos);
}
repaint();
}
void FramelessDialog::mousePressEvent(QMouseEvent* mouseEvent) {
if (abs(mouseEvent->pos().x() - size().width()) < RESIZE_HANDLE_WIDTH && mouseEvent->button() == Qt::LeftButton) {
_isResizing = true;
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);
}
}
}
}
@ -95,6 +142,16 @@ void FramelessDialog::mouseReleaseEvent(QMouseEvent* mouseEvent) {
void FramelessDialog::mouseMoveEvent(QMouseEvent* mouseEvent) {
if (_isResizing) {
resize(mouseEvent->pos().x(), size().height());
if (_position == POSITION_LEFT) {
resize(mouseEvent->pos().x(), size().height());
} else if (_position == POSITION_RIGHT) {
setUpdatesEnabled(false);
resize(_resizeInitialWidth - mouseEvent->pos().x(), size().height());
resizeAndPosition();
_resizeInitialWidth = size().width();
setUpdatesEnabled(true);
} else if (_position == POSITION_TOP) {
resize(size().width(), mouseEvent->pos().y());
}
}
}

View file

@ -17,10 +17,17 @@
class FramelessDialog : public QDialog {
Q_OBJECT
public:
FramelessDialog(QWidget* parent = 0, Qt::WindowFlags flags = 0);
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);
@ -31,7 +38,12 @@ protected:
bool eventFilter(QObject* sender, QEvent* event);
private:
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;
};

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

View 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

View file

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

View file

@ -19,7 +19,7 @@ const int SCROLL_PANEL_BOTTOM_MARGIN = 30;
const int OK_BUTTON_RIGHT_MARGIN = 30;
const int BUTTONS_TOP_MARGIN = 24;
PreferencesDialog::PreferencesDialog(QWidget* parent, Qt::WindowFlags flags) : FramelessDialog(parent, flags) {
PreferencesDialog::PreferencesDialog(QWidget* parent, Qt::WindowFlags flags) : FramelessDialog(parent, flags, POSITION_LEFT) {
ui.setupUi(this);
setStyleSheetFile("styles/preferences.qss");
@ -38,26 +38,34 @@ void PreferencesDialog::accept() {
void PreferencesDialog::setHeadUrl(QString modelUrl) {
ui.faceURLEdit->setText(modelUrl);
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
}
void PreferencesDialog::setSkeletonUrl(QString modelUrl) {
ui.skeletonURLEdit->setText(modelUrl);
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
}
void PreferencesDialog::openHeadModelBrowser() {
setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
show();
ModelsBrowser modelBrowser(Head);
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setHeadUrl);
modelBrowser.browse();
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
show();
}
void PreferencesDialog::openBodyModelBrowser() {
setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint);
show();
ModelsBrowser modelBrowser(Skeleton);
connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setSkeletonUrl);
modelBrowser.browse();
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
show();
}
void PreferencesDialog::openSnapshotLocationBrowser() {
@ -176,6 +184,7 @@ void PreferencesDialog::savePreferences() {
Menu::getInstance()->setMaxVoxelPacketsPerSecond(ui.maxVoxelsPPSSpin->value());
Menu::getInstance()->setAudioJitterBufferSamples(ui.audioJitterSpin->value());
Application::getInstance()->getAudio()->setJitterBufferSamples(ui.audioJitterSpin->value());
Application::getInstance()->resizeGL(Application::getInstance()->getGLWidget()->width(),
Application::getInstance()->getGLWidget()->height());

View file

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

View file

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

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

View 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

View file

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

View file

@ -13,7 +13,6 @@
#define hifi_ScriptEditorWidget_h
#include <QDockWidget>
#include "ScriptEditorWidget.h"
#include "ScriptEngine.h"
namespace Ui {

View file

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

View file

@ -25,6 +25,8 @@ public:
ScriptEditorWindow();
~ScriptEditorWindow();
void terminateCurrentTab();
protected:
void closeEvent(QCloseEvent* event);

View 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() {
return QSize(_scriptEditBox->lineNumberAreaWidth(), 0);
}
void ScriptLineNumberArea::paintEvent(QPaintEvent* event) {
_scriptEditBox->lineNumberAreaPaintEvent(event);
}

View 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();
protected:
void paintEvent(QPaintEvent* event);
private:
ScriptEditBox* _scriptEditBox;
};
#endif // hifi_ScriptLineNumberArea_h

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

View 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

View file

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

View file

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

View file

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ChatWindow</class>
<widget class="QDockWidget" name="ChatWindow">
<widget class="QDialog" name="ChatWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>608</height>
<height>440</height>
</rect>
</property>
<property name="minimumSize">
@ -16,127 +16,95 @@
<height>238</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">font-family: Helvetica, Arial, sans-serif;</string>
</property>
<property name="features">
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
</property>
<property name="allowedAreas">
<set>Qt::NoDockWidgetArea</set>
</property>
<property name="windowTitle">
<string>Chat</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>8</number>
</property>
<property name="topMargin">
<number>8</number>
</property>
<property name="rightMargin">
<number>8</number>
</property>
<property name="bottomMargin">
<number>8</number>
</property>
<item>
<widget class="QLabel" name="connectingToXMPPLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Connecting to XMPP...</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="numOnlineLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">font-weight: bold; color: palette(shadow); margin-bottom: 4px;</string>
</property>
<property name="text">
<string> online now:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="togglePinnedButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>../resources/images/pin.svg</normaloff>
<normalon>../resources/images/pinned.svg</normalon>../resources/images/pin.svg</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="default">
<bool>false</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</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="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
<property name="styleSheet">
<string notr="true">font-family: Helvetica, Arial, sans-serif;</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<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 row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>8</number>
</property>
<property name="topMargin">
<number>8</number>
</property>
<property name="rightMargin">
<number>8</number>
</property>
<property name="bottomMargin">
<number>8</number>
</property>
<item>
<widget class="QLabel" name="connectingToXMPPLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Connecting to XMPP...</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="numOnlineLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">font-weight: bold; color: palette(shadow); margin-bottom: 4px;</string>
</property>
<property name="text">
<string> online now:</string>
</property>
</widget>
</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="maximumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
background-color: rgba( 0, 0, 0, 0% );
border: none;
image: url(../resources/images/close.svg)
@ -148,50 +116,104 @@ QPushButton:pressed {
border: none;
image: url(../resources/images/close_down.svg)
}</string>
</property>
<property name="text">
<string/>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="usersWidget" native="true"/>
</item>
<item>
<widget class="QScrollArea" name="messagesScrollArea">
<property name="styleSheet">
<string notr="true">margin-top: 12px;</string>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>382</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="usersWidget" native="true">
<property name="styleSheet">
<string notr="true">#usersWidget {
margin-right: 20px;
}</string>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="messagesScrollArea">
<property name="styleSheet">
<string notr="true">margin-top: 12px;</string>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>382</width>
<height>16</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">margin-top: 0px;</string>
</property>
<layout class="QVBoxLayout" name="messagesVBoxLayout">
<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>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QFrame" name="chatFrame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">margin-top: 0px;</string>
<property name="minimumSize">
<size>
<width>0</width>
<height>78</height>
</size>
</property>
<layout class="QGridLayout" name="messagesGridLayout">
<property name="styleSheet">
<string notr="true">#chatFrame {
border-color: palette(dark); border-style: solid; border-left-width: 1px; border-right-width: 1px; border-bottom-width: 1px;
}</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
@ -204,69 +226,65 @@ QPushButton:pressed {
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="ChatInputArea" name="messagePlainTextEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>60</height>
</size>
</property>
<property name="font">
<font>
<family>Helvetica,Arial,sans-serif</family>
<pointsize>14</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="tabChangesFocus">
<bool>true</bool>
</property>
<property name="acceptRichText">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="messagePlainTextEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>60</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">border-color: palette(dark); border-style: solid; border-left-width: 1px; border-right-width: 1px; border-bottom-width: 1px;</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="tabChangesFocus">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ChatInputArea</class>
<extends>QTextEdit</extends>
<header>ui/ChatInputArea.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>messagePlainTextEdit</tabstop>
<tabstop>messagesScrollArea</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>togglePinnedButton</sender>
<signal>clicked()</signal>
<receiver>ChatWindow</receiver>
<slot>togglePinned()</slot>
<hints>
<hint type="sourcelabel">
<x>390</x>
<y>42</y>
</hint>
<hint type="destinationlabel">
<x>550</x>
<y>42</y>
</hint>
</hints>
</connection>
<connection>
<sender>closeButton</sender>
<signal>clicked()</signal>

491
interface/ui/loginDialog.ui Normal file
View 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>&lt;style type=&quot;text/css&quot;&gt;
a { text-decoration: none; color: #267077;}
&lt;/style&gt;
Invalid username or password. &lt;a href=&quot;https://data-web.highfidelity.io/password/new&quot;&gt;Recover?&lt;/a&gt;</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>&lt;style type=&quot;text/css&quot;&gt;
a { text-decoration: none; color: #267077;}
&lt;/style&gt;
&lt;a href=&quot;https://data-web.highfidelity.io/password/new&quot;&gt;Recover password?&lt;/a&gt;</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>

View file

@ -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, &quot;DejaVu Sans&quot;; </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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:18pt;&quot;&gt;Running Scripts&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Currently running&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -52,22 +66,27 @@ font-family: Helvetica, Arial, &quot;DejaVu Sans&quot;; </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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Recently loaded&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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/>

View file

@ -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 &quot;Courier&quot;;</string>
<string notr="true">font: 16px &quot;Courier&quot;;</string>
</property>
</widget>
</item>
@ -86,6 +86,9 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">font: 13px &quot;Helvetica&quot;,&quot;Arial&quot;,&quot;sans-serif&quot;;</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 &quot;Helvetica&quot;,&quot;Arial&quot;,&quot;sans-serif&quot;;</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 &quot;Courier&quot;;</string>
<string notr="true">font: 15px &quot;Courier&quot;;</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>

View file

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

View file

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

View file

@ -50,6 +50,10 @@ typedef unsigned long long quint64;
#include "HeadData.h"
#include "HandData.h"
// avatar motion behaviors
const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 0;
const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 1;
// First bitset
const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits
const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits

View file

@ -584,13 +584,16 @@ public:
};
glm::mat4 getGlobalTransform(const QMultiHash<QString, QString>& parentMap,
const QHash<QString, FBXModel>& models, QString nodeID) {
const QHash<QString, FBXModel>& models, QString nodeID, bool mixamoHack) {
glm::mat4 globalTransform;
while (!nodeID.isNull()) {
const FBXModel& model = models.value(nodeID);
globalTransform = glm::translate(model.translation) * model.preTransform * glm::mat4_cast(model.preRotation *
model.rotation * model.postRotation) * model.postTransform * globalTransform;
if (mixamoHack) {
// there's something weird about the models from Mixamo Fuse; they don't skin right with the full transform
return globalTransform;
}
QList<QString> parentIDs = parentMap.values(nodeID);
nodeID = QString();
foreach (const QString& parentID, parentIDs) {
@ -1006,9 +1009,25 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
}
}
QMultiHash<QString, WeightedIndex> blendshapeChannelIndices;
FBXGeometry geometry;
foreach (const FBXNode& child, node.children) {
if (child.name == "Objects") {
if (child.name == "FBXHeaderExtension") {
foreach (const FBXNode& object, child.children) {
if (object.name == "SceneInfo") {
foreach (const FBXNode& subobject, object.children) {
if (subobject.name == "Properties70") {
foreach (const FBXNode& subsubobject, subobject.children) {
if (subsubobject.name == "P" && subsubobject.properties.size() >= 5 &&
subsubobject.properties.at(0) == "Original|ApplicationName") {
geometry.applicationName = subsubobject.properties.at(4).toString();
}
}
}
}
}
}
} else if (child.name == "Objects") {
foreach (const FBXNode& object, child.children) {
if (object.name == "Geometry") {
if (object.properties.at(2) == "Mesh") {
@ -1317,7 +1336,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
}
// get offset transform from mapping
FBXGeometry geometry;
float offsetScale = mapping.value("scale", 1.0f).toFloat();
glm::quat offsetRotation = glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(),
mapping.value("ry").toFloat(), mapping.value("rz").toFloat())));
@ -1466,7 +1484,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
// accumulate local transforms
QString modelID = models.contains(it.key()) ? it.key() : parentMap.value(it.key());
glm::mat4 modelTransform = getGlobalTransform(parentMap, models, modelID);
glm::mat4 modelTransform = getGlobalTransform(parentMap, models, modelID, geometry.applicationName == "mixamo.com");
// compute the mesh extents from the transformed vertices
foreach (const glm::vec3& vertex, extracted.mesh.vertices) {
@ -1803,6 +1821,33 @@ QVariantHash readMapping(const QByteArray& data) {
return parseMapping(&buffer);
}
QByteArray writeMapping(const QVariantHash& mapping) {
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
for (QVariantHash::const_iterator first = mapping.constBegin(); first != mapping.constEnd(); first++) {
QByteArray key = first.key().toUtf8() + " = ";
QVariantHash hashValue = first.value().toHash();
if (hashValue.isEmpty()) {
buffer.write(key + first.value().toByteArray() + "\n");
continue;
}
for (QVariantHash::const_iterator second = hashValue.constBegin(); second != hashValue.constEnd(); second++) {
QByteArray extendedKey = key + second.key().toUtf8();
QVariantList listValue = second.value().toList();
if (listValue.isEmpty()) {
buffer.write(extendedKey + " = " + second.value().toByteArray() + "\n");
continue;
}
buffer.write(extendedKey);
for (QVariantList::const_iterator third = listValue.constBegin(); third != listValue.constEnd(); third++) {
buffer.write(" = " + third->toByteArray());
}
buffer.write("\n");
}
}
return buffer.data();
}
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping) {
QBuffer buffer(const_cast<QByteArray*>(&model));
buffer.open(QIODevice::ReadOnly);

View file

@ -174,6 +174,8 @@ public:
class FBXGeometry {
public:
QString applicationName; ///< the name of the application that generated the model
QVector<FBXJoint> joints;
QHash<QString, int> jointIndices; ///< 1-based, so as to more easily detect missing indices
@ -218,6 +220,9 @@ Q_DECLARE_METATYPE(FBXGeometry)
/// Reads an FST mapping from the supplied data.
QVariantHash readMapping(const QByteArray& data);
/// Writes an FST mapping to a byte array.
QByteArray writeMapping(const QVariantHash& mapping);
/// Reads FBX geometry from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping);

View file

@ -0,0 +1,45 @@
cmake_minimum_required(VERSION 2.8)
if (WIN32)
cmake_policy (SET CMP0020 NEW)
endif (WIN32)
set(ROOT_DIR ../..)
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
set(TARGET_NAME models)
find_package(Qt5Widgets REQUIRED)
include(${MACRO_DIR}/AutoMTC.cmake)
auto_mtc(${TARGET_NAME} "${ROOT_DIR}")
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
setup_hifi_library(${TARGET_NAME} "${AUTOMTC_SRC}")
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} "${ROOT_DIR}")
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
# for streamable
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
# link ZLIB and GnuTLS
find_package(ZLIB)
find_package(GnuTLS REQUIRED)
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
if (WIN32)
add_definitions(-Dssize_t=long)
endif ()
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}")

Some files were not shown because too many files have changed in this diff Show more