This commit is contained in:
Andrzej Kapolka 2014-11-03 17:00:42 -08:00
commit 38a04b53d2
27 changed files with 871 additions and 172 deletions

View file

@ -96,17 +96,14 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
assignmentServerPort =
argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION).toString().toUInt();
}
HifiSockAddr assignmentServerSocket(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME, assignmentServerPort);
// check for an overriden assignment server hostname
if (argumentVariantMap.contains(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION)) {
_assignmentServerHostname = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION).toString();
// change the hostname for our assignment server
assignmentServerSocket = HifiSockAddr(_assignmentServerHostname, assignmentServerSocket.getPort());
_assignmentServerHostname = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION).toString();
}
HifiSockAddr assignmentServerSocket(_assignmentServerHostname, assignmentServerPort, true);
nodeList->setAssignmentServerSocket(assignmentServerSocket);
qDebug() << "Assignment server socket is" << assignmentServerSocket;

View file

@ -388,7 +388,7 @@ void DomainServer::setupAutomaticNetworking() {
const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000;
QTimer* dataHeartbeatTimer = new QTimer(this);
connect(dataHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToDataServer);
connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToDataServer()));
dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS);
}
@ -637,7 +637,7 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
}
if (allowedUsers.count() > 0) {
if (allowedUsers.contains(username)) {
if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
// it's possible this user can be allowed to connect, but we need to check their username signature
QByteArray publicKeyArray = _userPublicKeys.value(username);
@ -657,7 +657,7 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
rsaPublicKey, RSA_PKCS1_PADDING);
if (decryptResult != -1) {
if (username == decryptedArray) {
if (username.toLower() == decryptedArray) {
qDebug() << "Username signature matches for" << username << "- allowing connection.";
// free up the public key before we return
@ -1091,10 +1091,10 @@ QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) {
const QString DOMAIN_UPDATE_AUTOMATIC_NETWORKING_KEY = "automatic_networking";
void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr) {
updateDomainInDataServer(newPublicSockAddr.getAddress().toString());
sendHeartbeatToDataServer(newPublicSockAddr.getAddress().toString());
}
void DomainServer::updateDomainInDataServer(const QString& networkAddress) {
void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
const QUuid& domainID = LimitedNodeList::getInstance()->getSessionUUID();
@ -1109,6 +1109,21 @@ void DomainServer::updateDomainInDataServer(const QString& networkAddress) {
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
// add the number of currently connected agent users
int numConnectedAuthedUsers = 0;
foreach(const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) {
if (node->getLinkedData() && !static_cast<DomainServerNodeData*>(node->getLinkedData())->getUsername().isEmpty()) {
++numConnectedAuthedUsers;
}
}
const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
const QString HEARTBEAT_NUM_USERS_KEY = "num_users";
QJsonObject heartbeatObject;
heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedAuthedUsers;
domainObject[DOMAIN_HEARTBEAT_KEY] = heartbeatObject;
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
AccountManager::getInstance().authenticatedRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),

View file

@ -66,7 +66,7 @@ private slots:
void requestCurrentPublicSocketViaSTUN();
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
void performICEUpdates();
void sendHeartbeatToDataServer() { updateDomainInDataServer(); }
void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); }
void sendHeartbeatToIceServer();
void sendICEPingPackets();
private:
@ -77,7 +77,7 @@ private:
bool optionallySetupAssignmentPayment();
void setupAutomaticNetworking();
void updateDomainInDataServer(const QString& networkAddress = QString());
void sendHeartbeatToDataServer(const QString& networkAddress);
void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
void processICEHeartbeatResponse(const QByteArray& packet);

View file

@ -0,0 +1,38 @@
//
// changeColorOnHover.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 11/1/14.
// Copyright 2014 High Fidelity, Inc.
//
// This is an example of an entity script which when assigned to a non-model entity like a box or sphere, will
// change the color of the entity when you hover over it. This script uses the JavaScript prototype/class functionality
// to construct an object that has methods for hoverEnterEntity and hoverLeaveEntity;
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
this.oldColor = {};
this.oldColorKnown = false;
this.storeOldColor = function(entityID) {
var oldProperties = Entities.getEntityProperties(entityID);
this.oldColor = oldProperties.color;
this.oldColorKnown = true;
print("storing old color... this.oldColor=" + this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue);
};
this.hoverEnterEntity = function(entityID, mouseEvent) {
if (!this.oldColorKnown) {
this.storeOldColor(entityID);
}
Entities.editEntity(entityID, { color: { red: 0, green: 255, blue: 255} });
};
this.hoverLeaveEntity = function(entityID, mouseEvent) {
if (this.oldColorKnown) {
print("leave restoring old color... this.oldColor="
+ this.oldColor.red + "," + this.oldColor.green + "," + this.oldColor.blue);
Entities.editEntity(entityID, { color: this.oldColor });
}
};
})

View file

@ -0,0 +1,57 @@
//
// crazylegsOnClick.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 11/3/14.
// Copyright 2014 High Fidelity, Inc.
//
// This is an example of an entity script which when assigned to an entity, that entity will make your avatar do the
// crazyLegs dance if you click on it.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
var cumulativeTime = 0.0;
var FREQUENCY = 5.0;
var AMPLITUDE = 45.0;
var jointList = MyAvatar.getJointNames();
var jointMappings = "\n# Joint list start";
for (var i = 0; i < jointList.length; i++) {
jointMappings = jointMappings + "\njointIndex = " + jointList[i] + " = " + i;
}
print(jointMappings + "\n# Joint list end");
this.crazyLegsUpdate = function(deltaTime) {
print("crazyLegsUpdate... deltaTime:" + deltaTime);
cumulativeTime += deltaTime;
print("crazyLegsUpdate... cumulativeTime:" + cumulativeTime);
MyAvatar.setJointData("RightUpLeg", Quat.fromPitchYawRollDegrees(AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY), 0.0, 0.0));
MyAvatar.setJointData("LeftUpLeg", Quat.fromPitchYawRollDegrees(-AMPLITUDE * Math.sin(cumulativeTime * FREQUENCY), 0.0, 0.0));
MyAvatar.setJointData("RightLeg", Quat.fromPitchYawRollDegrees(
AMPLITUDE * (1.0 + Math.sin(cumulativeTime * FREQUENCY)),0.0, 0.0));
MyAvatar.setJointData("LeftLeg", Quat.fromPitchYawRollDegrees(
AMPLITUDE * (1.0 - Math.sin(cumulativeTime * FREQUENCY)),0.0, 0.0));
};
this.stopCrazyLegs = function() {
MyAvatar.clearJointData("RightUpLeg");
MyAvatar.clearJointData("LeftUpLeg");
MyAvatar.clearJointData("RightLeg");
MyAvatar.clearJointData("LeftLeg");
};
this.clickDownOnEntity = function(entityID, mouseEvent) {
print("clickDownOnEntity()...");
cumulativeTime = 0.0;
Script.update.connect(this.crazyLegsUpdate);
};
this.clickReleaseOnEntity = function(entityID, mouseEvent) {
print("clickReleaseOnEntity()...");
this.stopCrazyLegs();
Script.update.disconnect(this.crazyLegsUpdate);
};
})

View file

@ -0,0 +1,24 @@
//
// playSoundOnClick.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 11/3/14.
// Copyright 2014 High Fidelity, Inc.
//
// This is an example of an entity script which when assigned to an entity, that entity will play a sound in world when
// you click on it.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
var bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw");
this.clickDownOnEntity = function(entityID, mouseEvent) {
print("clickDownOnEntity()...");
var options = new AudioInjectionOptions();
var position = MyAvatar.position;
options.position = position;
options.volume = 0.5;
Audio.playSound(bird, options);
};
})

View file

@ -0,0 +1,32 @@
//
// playSoundOnEnterOrLeave.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 11/3/14.
// Copyright 2014 High Fidelity, Inc.
//
// This is an example of an entity script which when assigned to an entity, that entity will play a sound in world when
// your avatar enters or leaves the bounds of the entity.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
var bird = new Sound("http://s3.amazonaws.com/hifi-public/sounds/Animals/bushtit_1.raw");
function playSound(entityID) {
var options = new AudioInjectionOptions();
var position = MyAvatar.position;
options.position = position;
options.volume = 0.5;
Audio.playSound(bird, options);
};
this.enterEntity = function(entityID) {
playSound();
};
this.leaveEntity = function(entityID) {
playSound();
};
})

View file

@ -0,0 +1,18 @@
//
// teleportOnClick.js
// examples/entityScripts
//
// Created by Brad Hefta-Gaub on 11/3/14.
// Copyright 2014 High Fidelity, Inc.
//
// This is an example of an entity script which when assigned to an entity, that entity will teleport your avatar if you
// click on it the entity.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function(){
this.clickDownOnEntity = function(entityID, mouseEvent) {
MyAvatar.position = Entities.getEntityProperties(entityID).position;
};
})

View file

@ -140,6 +140,9 @@ EntityPropertyDialogBox = (function () {
array.push({ label: "Visible:", value: properties.visible });
index++;
array.push({ label: "Script:", value: properties.script });
index++;
if (properties.type == "Box" || properties.type == "Sphere") {
array.push({ label: "Color:", type: "header" });
@ -282,6 +285,7 @@ EntityPropertyDialogBox = (function () {
properties.lifetime = array[index++].value;
properties.visible = array[index++].value;
properties.script = array[index++].value;
if (properties.type == "Box" || properties.type == "Sphere") {
index++; // skip header

View file

@ -107,9 +107,6 @@ static unsigned STARFIELD_SEED = 1;
static const int BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH = 6; // farther dragged clicks are ignored
const int IDLE_SIMULATE_MSECS = 16; // How often should call simulate and other stuff
// in the idle loop? (60 FPS is default)
const unsigned MAXIMUM_CACHE_SIZE = 10737418240; // 10GB
static QTimer* idleTimer = NULL;
@ -151,7 +148,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_voxelImporter(),
_importSucceded(false),
_sharedVoxelSystem(TREE_SCALE, DEFAULT_MAX_VOXELS_PER_SYSTEM, &_clipboard),
_entityClipboardRenderer(),
_entities(true),
_entityCollisionSystem(),
_entityClipboardRenderer(false),
_entityClipboard(),
_wantToKillLocalVoxels(false),
_viewFrustum(),
@ -185,9 +184,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
_trayIcon(new QSystemTrayIcon(_window)),
_lastNackTime(usecTimestampNow()),
_lastSendDownstreamAudioStats(usecTimestampNow()),
_renderTargetFramerate(0),
_isVSyncOn(true),
_renderResolutionScale(1.0f)
_isVSyncOn(true)
{
// read the ApplicationInfo.ini file for Name/Version/Domain information
@ -604,7 +601,7 @@ void Application::paintGL() {
if (OculusManager::isConnected()) {
_textureCache.setFrameBufferSize(OculusManager::getRenderTargetSize());
} else {
QSize fbSize = _glWidget->getDeviceSize() * _renderResolutionScale;
QSize fbSize = _glWidget->getDeviceSize() * getRenderResolutionScale();
_textureCache.setFrameBufferSize(fbSize);
}
@ -1251,6 +1248,8 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
if (_lastMouseMoveWasSimulated) {
showMouse = false;
}
_entities.mouseMoveEvent(event, deviceID);
_controllerScriptingInterface.emitMouseMoveEvent(event, deviceID); // send events to any registered scripts
@ -1272,6 +1271,9 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
}
void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
_entities.mousePressEvent(event, deviceID);
_controllerScriptingInterface.emitMousePressEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
@ -1309,6 +1311,9 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
}
void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) {
_entities.mouseReleaseEvent(event, deviceID);
_controllerScriptingInterface.emitMouseReleaseEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
@ -1474,12 +1479,11 @@ void Application::idle() {
bool showWarnings = getLogger()->extraDebugging();
PerformanceWarning warn(showWarnings, "idle()");
// Only run simulation code if more than IDLE_SIMULATE_MSECS have passed since last time we ran
// Only run simulation code if more than the targetFramePeriod have passed since last time we ran
double targetFramePeriod = 0.0;
if (_renderTargetFramerate > 0) {
targetFramePeriod = 1000.0 / _renderTargetFramerate;
} else if (_renderTargetFramerate < 0) {
targetFramePeriod = IDLE_SIMULATE_MSECS;
unsigned int targetFramerate = getRenderTargetFramerate();
if (targetFramerate > 0) {
targetFramePeriod = 1000.0 / targetFramerate;
}
double timeSinceLastUpdate = (double)_lastTimeUpdated.nsecsElapsed() / 1000000.0;
if (timeSinceLastUpdate > targetFramePeriod) {
@ -1943,6 +1947,10 @@ void Application::init() {
connect(&_entityCollisionSystem, &EntityCollisionSystem::entityCollisionWithEntity,
ScriptEngine::getEntityScriptingInterface(), &EntityScriptingInterface::entityCollisionWithEntity);
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
// of events related clicking, hovering over, and entering entities
_entities.connectSignalsToSlots(ScriptEngine::getEntityScriptingInterface());
_entityClipboardRenderer.init();
_entityClipboardRenderer.setViewFrustum(getViewFrustum());
_entityClipboardRenderer.setTree(&_entityClipboard);
@ -3179,7 +3187,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) {
} else {
// if not rendering the billboard, the region is in device independent coordinates; must convert to device
QSize size = getTextureCache()->getFrameBufferSize();
float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio() * _renderResolutionScale;
float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio() * getRenderResolutionScale();
int x = region.x() * ratio, y = region.y() * ratio, width = region.width() * ratio, height = region.height() * ratio;
glViewport(x, size.height() - y - height, width, height);
glScissor(x, size.height() - y - height, width, height);
@ -3822,35 +3830,7 @@ void joystickFromScriptValue(const QScriptValue &object, Joystick* &out) {
out = qobject_cast<Joystick*>(object.toQObject());
}
ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded,
bool loadScriptFromEditor, bool activateMainWindow) {
QUrl scriptUrl(scriptFilename);
const QString& scriptURLString = scriptUrl.toString();
if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor
&& !_scriptEnginesHash[scriptURLString]->isFinished()) {
return _scriptEnginesHash[scriptURLString];
}
ScriptEngine* scriptEngine;
if (scriptFilename.isNull()) {
scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface);
} else {
// start the script on a new thread...
scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface);
if (!scriptEngine->hasScript()) {
qDebug() << "Application::loadScript(), script failed to load...";
QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load.");
return NULL;
}
_scriptEnginesHash.insertMulti(scriptURLString, scriptEngine);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
UserActivityLogger::getInstance().loadedScript(scriptURLString);
}
scriptEngine->setUserLoaded(isUserLoaded);
void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) {
// setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so
// we can use the same ones from the application.
scriptEngine->getVoxelsScriptingInterface()->setPacketSender(&_voxelEditSender);
@ -3932,6 +3912,38 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser
// Starts an event loop, and emits workerThread->started()
workerThread->start();
}
ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded,
bool loadScriptFromEditor, bool activateMainWindow) {
QUrl scriptUrl(scriptFilename);
const QString& scriptURLString = scriptUrl.toString();
if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor
&& !_scriptEnginesHash[scriptURLString]->isFinished()) {
return _scriptEnginesHash[scriptURLString];
}
ScriptEngine* scriptEngine;
if (scriptFilename.isNull()) {
scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface);
} else {
// start the script on a new thread...
scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface);
if (!scriptEngine->hasScript()) {
qDebug() << "Application::loadScript(), script failed to load...";
QMessageBox::warning(getWindow(), "Error Loading Script", scriptURLString + " failed to load.");
return NULL;
}
_scriptEnginesHash.insertMulti(scriptURLString, scriptEngine);
_runningScriptsWidget->setRunningScripts(getRunningScripts());
UserActivityLogger::getInstance().loadedScript(scriptURLString);
}
scriptEngine->setUserLoaded(isUserLoaded);
registerScriptEngineWithApplicationServices(scriptEngine);
// restore the main window's active state
if (activateMainWindow && !loadScriptFromEditor) {
@ -4237,37 +4249,56 @@ void Application::takeSnapshot() {
_snapshotShareDialog->show();
}
void Application::setRenderTargetFramerate(unsigned int framerate, bool vsyncOn) {
if (vsyncOn != _isVSyncOn) {
void Application::setVSyncEnabled(bool vsyncOn) {
#if defined(Q_OS_WIN)
if (wglewGetExtension("WGL_EXT_swap_control")) {
wglSwapIntervalEXT(vsyncOn);
int swapInterval = wglGetSwapIntervalEXT();
_isVSyncOn = swapInterval;
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
} else {
qDebug("V-Sync is FORCED ON on this system\n");
}
#elif defined(Q_OS_LINUX)
// TODO: write the poper code for linux
/*
if (glQueryExtension.... ("GLX_EXT_swap_control")) {
glxSwapIntervalEXT(vsyncOn);
int swapInterval = xglGetSwapIntervalEXT();
_isVSyncOn = swapInterval;
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
} else {
if (wglewGetExtension("WGL_EXT_swap_control")) {
wglSwapIntervalEXT(vsyncOn);
int swapInterval = wglGetSwapIntervalEXT();
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
} else {
qDebug("V-Sync is FORCED ON on this system\n");
}
*/
#else
qDebug("V-Sync is FORCED ON on this system\n");
#endif
}
_renderTargetFramerate = framerate;
#elif defined(Q_OS_LINUX)
// TODO: write the poper code for linux
/*
if (glQueryExtension.... ("GLX_EXT_swap_control")) {
glxSwapIntervalEXT(vsyncOn);
int swapInterval = xglGetSwapIntervalEXT();
_isVSyncOn = swapInterval;
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
} else {
qDebug("V-Sync is FORCED ON on this system\n");
}
*/
#else
qDebug("V-Sync is FORCED ON on this system\n");
#endif
}
bool Application::isVSyncEditable() {
bool Application::isVSyncOn() const {
#if defined(Q_OS_WIN)
if (wglewGetExtension("WGL_EXT_swap_control")) {
int swapInterval = wglGetSwapIntervalEXT();
return (swapInterval > 0);
} else {
return true;
}
#elif defined(Q_OS_LINUX)
// TODO: write the poper code for linux
/*
if (glQueryExtension.... ("GLX_EXT_swap_control")) {
int swapInterval = xglGetSwapIntervalEXT();
return (swapInterval > 0);
} else {
return true;
}
*/
#else
return true;
#endif
}
bool Application::isVSyncEditable() const {
#if defined(Q_OS_WIN)
if (wglewGetExtension("WGL_EXT_swap_control")) {
return true;
@ -4284,6 +4315,33 @@ bool Application::isVSyncEditable() {
return false;
}
void Application::setRenderResolutionScale(float scale) {
_renderResolutionScale = scale;
unsigned int Application::getRenderTargetFramerate() const {
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerateUnlimited)) {
return 0;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate60)) {
return 60;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate50)) {
return 50;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate40)) {
return 40;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerate30)) {
return 30;
}
return 0;
}
float Application::getRenderResolutionScale() const {
if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionOne)) {
return 1.f;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionTwoThird)) {
return 0.666f;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionHalf)) {
return 0.5f;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionThird)) {
return 0.333f;
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionQuarter)) {
return 0.25f;
} else {
return 1.f;
}
}

View file

@ -303,7 +303,14 @@ public:
bool isLookingAtMyAvatar(Avatar* avatar);
float getRenderResolutionScale() const { return _renderResolutionScale; }
float getRenderResolutionScale() const;
unsigned int getRenderTargetFramerate() const;
bool isVSyncOn() const;
bool isVSyncEditable() const;
void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine);
signals:
@ -367,12 +374,7 @@ public slots:
void domainSettingsReceived(const QJsonObject& domainSettingsObject);
void setRenderTargetFramerate(unsigned int framerate, bool vsyncOn = true);
bool isVSyncOn() { return _isVSyncOn; }
bool isVSyncEditable();
unsigned int getRenderTargetFramerate() const { return _renderTargetFramerate; }
void setRenderResolutionScale(float scale);
void setVSyncEnabled(bool vsyncOn);
void resetSensors();
@ -624,9 +626,7 @@ private:
quint64 _lastNackTime;
quint64 _lastSendDownstreamAudioStats;
int _renderTargetFramerate;
bool _isVSyncOn;
float _renderResolutionScale;
};
#endif // hifi_Application_h

View file

@ -377,13 +377,12 @@ Menu::Menu() :
{
QMenu* framerateMenu = renderOptionsMenu->addMenu(MenuOption::RenderTargetFramerate);
QActionGroup* framerateGroup = new QActionGroup(framerateMenu);
framerateGroup->setExclusive(true);
framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerateUnlimited, 0, true));
framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate60, 0, false));
framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate50, 0, false));
framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate40, 0, false));
framerateGroup->addAction(addCheckableActionToQMenuAndActionHash(framerateMenu, MenuOption::RenderTargetFramerate30, 0, false));
connect(framerateMenu, SIGNAL(triggered(QAction*)), this, SLOT(changeRenderTargetFramerate(QAction*)));
#if defined(Q_OS_MAC)
#else
@ -394,12 +393,12 @@ Menu::Menu() :
QMenu* resolutionMenu = renderOptionsMenu->addMenu(MenuOption::RenderResolution);
QActionGroup* resolutionGroup = new QActionGroup(resolutionMenu);
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionOne, 0, false));
resolutionGroup->setExclusive(true);
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionOne, 0, true));
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionTwoThird, 0, false));
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionHalf, 0, false));
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, true));
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false));
resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false));
connect(resolutionMenu, SIGNAL(triggered(QAction*)), this, SLOT(changeRenderResolution(QAction*)));
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, Qt::Key_Asterisk, true);
addCheckableActionToQMenuAndActionHash(renderOptionsMenu,
@ -1261,46 +1260,7 @@ void Menu::muteEnvironment() {
}
void Menu::changeVSync() {
Application::getInstance()->setRenderTargetFramerate(
Application::getInstance()->getRenderTargetFramerate(),
isOptionChecked(MenuOption::RenderTargetFramerateVSyncOn));
}
void Menu::changeRenderTargetFramerate(QAction* action) {
bool vsynOn = Application::getInstance()->isVSyncOn();
QString text = action->text();
if (text == MenuOption::RenderTargetFramerateUnlimited) {
Application::getInstance()->setRenderTargetFramerate(0, vsynOn);
}
else if (text == MenuOption::RenderTargetFramerate60) {
Application::getInstance()->setRenderTargetFramerate(60, vsynOn);
}
else if (text == MenuOption::RenderTargetFramerate50) {
Application::getInstance()->setRenderTargetFramerate(50, vsynOn);
}
else if (text == MenuOption::RenderTargetFramerate40) {
Application::getInstance()->setRenderTargetFramerate(40, vsynOn);
}
else if (text == MenuOption::RenderTargetFramerate30) {
Application::getInstance()->setRenderTargetFramerate(30, vsynOn);
}
}
void Menu::changeRenderResolution(QAction* action) {
QString text = action->text();
if (text == MenuOption::RenderResolutionOne) {
Application::getInstance()->setRenderResolutionScale(1.f);
} else if (text == MenuOption::RenderResolutionTwoThird) {
Application::getInstance()->setRenderResolutionScale(0.666f);
} else if (text == MenuOption::RenderResolutionHalf) {
Application::getInstance()->setRenderResolutionScale(0.5f);
} else if (text == MenuOption::RenderResolutionThird) {
Application::getInstance()->setRenderResolutionScale(0.333f);
} else if (text == MenuOption::RenderResolutionQuarter) {
Application::getInstance()->setRenderResolutionScale(0.25f);
} else {
Application::getInstance()->setRenderResolutionScale(1.f);
}
Application::getInstance()->setVSyncEnabled(isOptionChecked(MenuOption::RenderTargetFramerateVSyncOn));
}
void Menu::displayNameLocationResponse(const QString& errorString) {

View file

@ -230,9 +230,7 @@ private slots:
void displayAddressOfflineMessage();
void displayAddressNotFoundMessage();
void muteEnvironment();
void changeRenderTargetFramerate(QAction* action);
void changeVSync();
void changeRenderResolution(QAction* action);
private:
static Menu* _instance;

View file

@ -11,19 +11,25 @@
#include <glm/gtx/quaternion.hpp>
#include <QScriptSyntaxCheckResult>
#include <FBXReader.h>
#include "InterfaceConfig.h"
#include <BoxEntityItem.h>
#include <ModelEntityItem.h>
#include <MouseEvent.h>
#include <PerfStat.h>
#include <RenderArgs.h>
#include "Menu.h"
#include "NetworkAccessManager.h"
#include "EntityTreeRenderer.h"
#include "devices/OculusManager.h"
#include "RenderableBoxEntityItem.h"
#include "RenderableLightEntityItem.h"
#include "RenderableModelEntityItem.h"
@ -36,15 +42,21 @@ QThread* EntityTreeRenderer::getMainThread() {
EntityTreeRenderer::EntityTreeRenderer() :
OctreeRenderer() {
EntityTreeRenderer::EntityTreeRenderer(bool wantScripts) :
OctreeRenderer(),
_wantScripts(wantScripts),
_entitiesScriptEngine(NULL) {
REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, RenderableBoxEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, RenderableSphereEntityItem::factory)
REGISTER_ENTITY_TYPE_WITH_FACTORY(Light, RenderableLightEntityItem::factory)
_currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID
_currentClickingOnEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID
}
EntityTreeRenderer::~EntityTreeRenderer() {
// do we need to delete the _entitiesScriptEngine?? or is it deleted by default
}
void EntityTreeRenderer::clear() {
@ -54,6 +66,117 @@ void EntityTreeRenderer::clear() {
void EntityTreeRenderer::init() {
OctreeRenderer::init();
static_cast<EntityTree*>(_tree)->setFBXService(this);
if (_wantScripts) {
_entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities",
Application::getInstance()->getControllerScriptingInterface());
Application::getInstance()->registerScriptEngineWithApplicationServices(_entitiesScriptEngine);
}
// make sure our "last avatar position" is something other than our current position, so that on our
// first chance, we'll check for enter/leave entity events.
glm::vec3 avatarPosition = Application::getInstance()->getAvatar()->getPosition();
_lastAvatarPosition = avatarPosition + glm::vec3(1.f, 1.f, 1.f);
}
QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID) {
EntityItem* entity = static_cast<EntityTree*>(_tree)->findEntityByEntityItemID(entityItemID);
return loadEntityScript(entity);
}
QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorText) {
QUrl url(scriptMaybeURLorText);
// If the url is not valid, this must be script text...
if (!url.isValid()) {
return scriptMaybeURLorText;
}
QString scriptContents; // assume empty
// 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) {
url = QUrl::fromLocalFile(scriptMaybeURLorText);
}
// ok, let's see if it's valid... and if so, load it
if (url.isValid()) {
if (url.scheme() == "file") {
QString fileName = url.toLocalFile();
QFile scriptFile(fileName);
if (scriptFile.open(QFile::ReadOnly | QFile::Text)) {
qDebug() << "Loading file:" << fileName;
QTextStream in(&scriptFile);
scriptContents = in.readAll();
} else {
qDebug() << "ERROR Loading file:" << fileName;
}
} else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url));
qDebug() << "Downloading script at" << url;
QEventLoop loop;
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
scriptContents = reply->readAll();
} else {
qDebug() << "ERROR Loading file:" << url.toString();
}
}
}
return scriptContents;
}
QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
if (!entity) {
return QScriptValue(); // no entity...
}
EntityItemID entityID = entity->getEntityItemID();
if (_entityScripts.contains(entityID)) {
EntityScriptDetails details = _entityScripts[entityID];
// check to make sure our script text hasn't changed on us since we last loaded it
if (details.scriptText == entity->getScript()) {
return details.scriptObject; // previously loaded
}
// if we got here, then we previously loaded a script, but the entity's script value
// has changed and so we need to reload it.
_entityScripts.remove(entityID);
}
if (entity->getScript().isEmpty()) {
return QScriptValue(); // no script
}
QString scriptContents = loadScriptContents(entity->getScript());
if (QScriptEngine::checkSyntax(scriptContents).state() != QScriptSyntaxCheckResult::Valid) {
qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID;
qDebug() << " INVALID SYNTAX";
qDebug() << " SCRIPT:" << scriptContents;
return QScriptValue(); // invalid script
}
QScriptValue entityScriptConstructor = _entitiesScriptEngine->evaluate(scriptContents);
if (!entityScriptConstructor.isFunction()) {
qDebug() << "EntityTreeRenderer::loadEntityScript() entity:" << entityID;
qDebug() << " NOT CONSTRUCTOR";
qDebug() << " SCRIPT:" << scriptContents;
return QScriptValue(); // invalid script
}
QScriptValue entityScriptObject = entityScriptConstructor.construct();
EntityScriptDetails newDetails = { entity->getScript(), entityScriptObject };
_entityScripts[entityID] = newDetails;
return entityScriptObject; // newly constructed
}
void EntityTreeRenderer::setTree(Octree* newTree) {
@ -65,6 +188,58 @@ void EntityTreeRenderer::update() {
if (_tree) {
EntityTree* tree = static_cast<EntityTree*>(_tree);
tree->update();
// check to see if the avatar has moved and if we need to handle enter/leave entity logic
checkEnterLeaveEntities();
}
}
void EntityTreeRenderer::checkEnterLeaveEntities() {
if (_tree) {
glm::vec3 avatarPosition = Application::getInstance()->getAvatar()->getPosition() / (float) TREE_SCALE;
if (avatarPosition != _lastAvatarPosition) {
float radius = 1.0f / (float) TREE_SCALE; // for now, assume 1 meter radius
QVector<const EntityItem*> foundEntities;
QVector<EntityItemID> entitiesContainingAvatar;
// find the entities near us
static_cast<EntityTree*>(_tree)->findEntities(avatarPosition, radius, foundEntities);
// create a list of entities that actually contain the avatar's position
foreach(const EntityItem* entity, foundEntities) {
if (entity->contains(avatarPosition)) {
entitiesContainingAvatar << entity->getEntityItemID();
}
}
// for all of our previous containing entities, if they are no longer containing then send them a leave event
foreach(const EntityItemID& entityID, _currentEntitiesInside) {
if (!entitiesContainingAvatar.contains(entityID)) {
emit leaveEntity(entityID);
QScriptValueList entityArgs = createEntityArgs(entityID);
QScriptValue entityScript = loadEntityScript(entityID);
if (entityScript.property("leaveEntity").isValid()) {
entityScript.property("leaveEntity").call(entityScript, entityArgs);
}
}
}
// for all of our new containing entities, if they weren't previously containing then send them an enter event
foreach(const EntityItemID& entityID, entitiesContainingAvatar) {
if (!_currentEntitiesInside.contains(entityID)) {
emit enterEntity(entityID);
QScriptValueList entityArgs = createEntityArgs(entityID);
QScriptValue entityScript = loadEntityScript(entityID);
if (entityScript.property("enterEntity").isValid()) {
entityScript.property("enterEntity").call(entityScript, entityArgs);
}
}
}
_currentEntitiesInside = entitiesContainingAvatar;
_lastAvatarPosition = avatarPosition;
}
}
}
@ -101,8 +276,8 @@ const Model* EntityTreeRenderer::getModelForEntityItem(const EntityItem* entityI
}
void renderElementProxy(EntityTreeElement* entityTreeElement) {
glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter() * (float)TREE_SCALE;
float elementSize = entityTreeElement->getScale() * (float)TREE_SCALE;
glm::vec3 elementCenter = entityTreeElement->getAACube().calcCenter() * (float) TREE_SCALE;
float elementSize = entityTreeElement->getScale() * (float) TREE_SCALE;
glColor3f(1.0f, 0.0f, 0.0f);
glPushMatrix();
glTranslatef(elementCenter.x, elementCenter.y, elementCenter.z);
@ -175,9 +350,9 @@ void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* arg
AACube minCube = entity->getMinimumAACube();
AABox entityBox = entity->getAABox();
maxCube.scale((float)TREE_SCALE);
minCube.scale((float)TREE_SCALE);
entityBox.scale((float)TREE_SCALE);
maxCube.scale((float) TREE_SCALE);
minCube.scale((float) TREE_SCALE);
entityBox.scale((float) TREE_SCALE);
glm::vec3 maxCenter = maxCube.calcCenter();
glm::vec3 minCenter = minCube.calcCenter();
@ -207,9 +382,9 @@ void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* arg
glPopMatrix();
glm::vec3 position = entity->getPosition() * (float)TREE_SCALE;
glm::vec3 center = entity->getCenter() * (float)TREE_SCALE;
glm::vec3 dimensions = entity->getDimensions() * (float)TREE_SCALE;
glm::vec3 position = entity->getPosition() * (float) TREE_SCALE;
glm::vec3 center = entity->getCenter() * (float) TREE_SCALE;
glm::vec3 dimensions = entity->getDimensions() * (float) TREE_SCALE;
glm::quat rotation = entity->getRotation();
glColor4f(1.0f, 0.0f, 1.0f, 1.0f);
@ -382,4 +557,207 @@ void EntityTreeRenderer::deleteReleasedModels() {
}
}
PickRay EntityTreeRenderer::computePickRay(float x, float y) {
float screenWidth = Application::getInstance()->getGLWidget()->width();
float screenHeight = Application::getInstance()->getGLWidget()->height();
PickRay result;
if (OculusManager::isConnected()) {
Camera* camera = Application::getInstance()->getCamera();
result.origin = camera->getPosition();
Application::getInstance()->getApplicationOverlay().computeOculusPickRay(x / screenWidth, y / screenHeight, result.direction);
} else {
ViewFrustum* viewFrustum = Application::getInstance()->getViewFrustum();
viewFrustum->computePickRay(x / screenWidth, y / screenHeight, result.origin, result.direction);
}
return result;
}
RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType) {
RayToEntityIntersectionResult result;
if (_tree) {
EntityTree* entityTree = static_cast<EntityTree*>(_tree);
OctreeElement* element;
EntityItem* intersectedEntity = NULL;
result.intersects = entityTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face,
(void**)&intersectedEntity, lockType, &result.accurate);
if (result.intersects && intersectedEntity) {
result.entityID = intersectedEntity->getEntityItemID();
result.properties = intersectedEntity->getProperties();
result.intersection = ray.origin + (ray.direction * result.distance);
result.entity = intersectedEntity;
}
}
return result;
}
void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface) {
connect(this, &EntityTreeRenderer::mousePressOnEntity, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity);
connect(this, &EntityTreeRenderer::mouseMoveOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity);
connect(this, &EntityTreeRenderer::mouseReleaseOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity);
connect(this, &EntityTreeRenderer::clickDownOnEntity, entityScriptingInterface, &EntityScriptingInterface::clickDownOnEntity);
connect(this, &EntityTreeRenderer::holdingClickOnEntity, entityScriptingInterface, &EntityScriptingInterface::holdingClickOnEntity);
connect(this, &EntityTreeRenderer::clickReleaseOnEntity, entityScriptingInterface, &EntityScriptingInterface::clickReleaseOnEntity);
connect(this, &EntityTreeRenderer::hoverEnterEntity, entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity);
connect(this, &EntityTreeRenderer::hoverOverEntity, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity);
connect(this, &EntityTreeRenderer::hoverLeaveEntity, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity);
connect(this, &EntityTreeRenderer::enterEntity, entityScriptingInterface, &EntityScriptingInterface::enterEntity);
connect(this, &EntityTreeRenderer::leaveEntity, entityScriptingInterface, &EntityScriptingInterface::leaveEntity);
}
QScriptValueList EntityTreeRenderer::createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID) {
QScriptValueList args;
args << entityID.toScriptValue(_entitiesScriptEngine);
args << MouseEvent(*event, deviceID).toScriptValue(_entitiesScriptEngine);
return args;
}
QScriptValueList EntityTreeRenderer::createEntityArgs(const EntityItemID& entityID) {
QScriptValueList args;
args << entityID.toScriptValue(_entitiesScriptEngine);
return args;
}
void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent");
PickRay ray = computePickRay(event->x(), event->y());
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock);
if (rayPickResult.intersects) {
//qDebug() << "mousePressEvent over entity:" << rayPickResult.entityID;
emit mousePressOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
if (entityScript.property("mousePressOnEntity").isValid()) {
entityScript.property("mousePressOnEntity").call(entityScript, entityScriptArgs);
}
_currentClickingOnEntityID = rayPickResult.entityID;
emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
if (entityScript.property("clickDownOnEntity").isValid()) {
entityScript.property("clickDownOnEntity").call(entityScript, entityScriptArgs);
}
}
}
void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) {
PerformanceTimer perfTimer("EntityTreeRenderer::mouseReleaseEvent");
PickRay ray = computePickRay(event->x(), event->y());
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock);
if (rayPickResult.intersects) {
//qDebug() << "mouseReleaseEvent over entity:" << rayPickResult.entityID;
emit mouseReleaseOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
if (entityScript.property("mouseReleaseOnEntity").isValid()) {
entityScript.property("mouseReleaseOnEntity").call(entityScript, entityScriptArgs);
}
}
// Even if we're no longer intersecting with an entity, if we started clicking on it, and now
// we're releasing the button, then this is considered a clickOn event
if (!_currentClickingOnEntityID.isInvalidID()) {
emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID);
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
if (currentClickingEntity.property("clickReleaseOnEntity").isValid()) {
currentClickingEntity.property("clickReleaseOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
}
}
// makes it the unknown ID, we just released so we can't be clicking on anything
_currentClickingOnEntityID = EntityItemID::createInvalidEntityID();
}
void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
PerformanceTimer perfTimer("EntityTreeRenderer::mouseMoveEvent");
PickRay ray = computePickRay(event->x(), event->y());
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock);
if (rayPickResult.intersects) {
QScriptValueList entityScriptArgs = createMouseEventArgs(rayPickResult.entityID, event, deviceID);
// load the entity script if needed...
QScriptValue entityScript = loadEntityScript(rayPickResult.entity);
if (entityScript.property("mouseMoveEvent").isValid()) {
entityScript.property("mouseMoveEvent").call(entityScript, entityScriptArgs);
}
//qDebug() << "mouseMoveEvent over entity:" << rayPickResult.entityID;
emit mouseMoveOnEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
if (entityScript.property("mouseMoveOnEntity").isValid()) {
entityScript.property("mouseMoveOnEntity").call(entityScript, entityScriptArgs);
}
// handle the hover logic...
// if we were previously hovering over an entity, and this new entity is not the same as our previous entity
// then we need to send the hover leave.
if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) {
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID);
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs);
}
}
// If the new hover entity does not match the previous hover entity then we are entering the new one
// this is true if the _currentHoverOverEntityID is known or unknown
if (rayPickResult.entityID != _currentHoverOverEntityID) {
emit hoverEnterEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
if (entityScript.property("hoverEnterEntity").isValid()) {
entityScript.property("hoverEnterEntity").call(entityScript, entityScriptArgs);
}
}
// and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and
// we should send our hover over event
emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event, deviceID));
if (entityScript.property("hoverOverEntity").isValid()) {
entityScript.property("hoverOverEntity").call(entityScript, entityScriptArgs);
}
// remember what we're hovering over
_currentHoverOverEntityID = rayPickResult.entityID;
} else {
// handle the hover logic...
// if we were previously hovering over an entity, and we're no longer hovering over any entity then we need to
// send the hover leave for our previous entity
if (!_currentHoverOverEntityID.isInvalidID()) {
emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event, deviceID));
QScriptValueList currentHoverEntityArgs = createMouseEventArgs(_currentHoverOverEntityID, event, deviceID);
QScriptValue currentHoverEntity = loadEntityScript(_currentHoverOverEntityID);
if (currentHoverEntity.property("hoverLeaveEntity").isValid()) {
currentHoverEntity.property("hoverLeaveEntity").call(currentHoverEntity, currentHoverEntityArgs);
}
_currentHoverOverEntityID = EntityItemID::createInvalidEntityID(); // makes it the unknown ID
}
}
// Even if we're no longer intersecting with an entity, if we started clicking on an entity and we have
// not yet released the hold then this is still considered a holdingClickOnEntity event
if (!_currentClickingOnEntityID.isInvalidID()) {
emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event, deviceID));
QScriptValueList currentClickingEntityArgs = createMouseEventArgs(_currentClickingOnEntityID, event, deviceID);
QScriptValue currentClickingEntity = loadEntityScript(_currentClickingOnEntityID);
if (currentClickingEntity.property("holdingClickOnEntity").isValid()) {
currentClickingEntity.property("holdingClickOnEntity").call(currentClickingEntity, currentClickingEntityArgs);
}
}
}

View file

@ -16,6 +16,7 @@
#include <stdint.h>
#include <EntityTree.h>
#include <EntityScriptingInterface.h> // for RayToEntityIntersectionResult
#include <Octree.h>
#include <OctreePacketData.h>
#include <OctreeRenderer.h>
@ -26,11 +27,17 @@
#include "renderer/Model.h"
class EntityScriptDetails {
public:
QString scriptText;
QScriptValue scriptObject;
};
// Generic client side Octree renderer class.
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService {
Q_OBJECT
public:
EntityTreeRenderer();
EntityTreeRenderer(bool wantScripts);
virtual ~EntityTreeRenderer();
virtual Octree* createTree() { return new EntityTree(true); }
@ -75,9 +82,55 @@ public:
void releaseModel(Model* model);
void deleteReleasedModels();
// event handles which may generate entity related events
void mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID);
void mousePressEvent(QMouseEvent* event, unsigned int deviceID);
void mouseMoveEvent(QMouseEvent* event, unsigned int deviceID);
/// connect our signals to anEntityScriptingInterface for firing of events related clicking,
/// hovering over, and entering entities
void connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface);
signals:
void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mouseReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void clickReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverEnterEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void enterEntity(const EntityItemID& entityItemID);
void leaveEntity(const EntityItemID& entityItemID);
private:
QList<Model*> _releasedModels;
void renderProxies(const EntityItem* entity, RenderArgs* args);
PickRay computePickRay(float x, float y);
RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType);
EntityItemID _currentHoverOverEntityID;
EntityItemID _currentClickingOnEntityID;
QScriptValueList createEntityArgs(const EntityItemID& entityID);
void checkEnterLeaveEntities();
glm::vec3 _lastAvatarPosition;
QVector<EntityItemID> _currentEntitiesInside;
bool _wantScripts;
ScriptEngine* _entitiesScriptEngine;
QScriptValue loadEntityScript(EntityItem* entity);
QScriptValue loadEntityScript(const EntityItemID& entityItemID);
QString loadScriptContents(const QString& scriptMaybeURLorText);
QScriptValueList createMouseEventArgs(const EntityItemID& entityID, QMouseEvent* event, unsigned int deviceID);
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
};
#endif // hifi_EntityTreeRenderer_h

View file

@ -253,6 +253,7 @@ public:
void applyHardCollision(const CollisionInfo& collisionInfo);
virtual const Shape& getCollisionShapeInMeters() const { return _collisionShape; }
virtual bool contains(const glm::vec3& point) const { return getAABox().contains(point); }
protected:
virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init

View file

@ -124,6 +124,10 @@ void EntityItemID::handleAddEntityResponse(const QByteArray& packet) {
_tokenIDsToIDs[creatorTokenID] = entityID;
}
QScriptValue EntityItemID::toScriptValue(QScriptEngine* engine) const {
return EntityItemIDtoScriptValue(engine, *this);
}
QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID& id) {
QScriptValue obj = engine->newObject();
obj.setProperty("id", id.id.toString());

View file

@ -49,11 +49,16 @@ public:
EntityItemID convertToKnownIDVersion() const;
EntityItemID convertToCreatorTokenVersion() const;
bool isInvalidID() const { return id == UNKNOWN_ENTITY_ID && creatorTokenID == UNKNOWN_ENTITY_TOKEN && isKnownID == false; }
// these methods allow you to create models, and later edit them.
static EntityItemID createInvalidEntityID() { return EntityItemID(UNKNOWN_ENTITY_ID, UNKNOWN_ENTITY_TOKEN, false); }
static EntityItemID getIDfromCreatorTokenID(uint32_t creatorTokenID);
static uint32_t getNextCreatorTokenID();
static void handleAddEntityResponse(const QByteArray& packet);
static EntityItemID readEntityItemIDFromBuffer(const unsigned char* data, int bytesLeftToRead);
QScriptValue toScriptValue(QScriptEngine* engine) const;
private:
friend class EntityTree;
@ -68,12 +73,22 @@ inline bool operator<(const EntityItemID& a, const EntityItemID& b) {
}
inline bool operator==(const EntityItemID& a, const EntityItemID& b) {
if (a.isInvalidID() && b.isInvalidID()) {
return true;
}
if (a.isInvalidID() != b.isInvalidID()) {
return false;
}
if (a.id == UNKNOWN_ENTITY_ID || b.id == UNKNOWN_ENTITY_ID) {
return a.creatorTokenID == b.creatorTokenID;
}
return a.id == b.id;
}
inline bool operator!=(const EntityItemID& a, const EntityItemID& b) {
return !(a == b);
}
inline uint qHash(const EntityItemID& a, uint seed) {
QUuid temp;
if (a.id == UNKNOWN_ENTITY_ID) {
@ -94,5 +109,4 @@ Q_DECLARE_METATYPE(QVector<EntityItemID>);
QScriptValue EntityItemIDtoScriptValue(QScriptEngine* engine, const EntityItemID& properties);
void EntityItemIDfromScriptValue(const QScriptValue &object, EntityItemID& properties);
#endif // hifi_EntityItemID_h

View file

@ -217,7 +217,8 @@ RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
entityID(),
properties(),
distance(0),
face()
face(),
entity(NULL)
{
}

View file

@ -25,6 +25,7 @@
class EntityTree;
class MouseEvent;
class RayToEntityIntersectionResult {
@ -37,6 +38,7 @@ public:
float distance;
BoxFace face;
glm::vec3 intersection;
EntityItem* entity;
};
Q_DECLARE_METATYPE(RayToEntityIntersectionResult)
@ -101,6 +103,21 @@ signals:
void entityCollisionWithVoxel(const EntityItemID& entityID, const VoxelDetail& voxel, const CollisionInfo& collision);
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const CollisionInfo& collision);
void mousePressOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mouseMoveOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void mouseReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void clickDownOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void holdingClickOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void clickReleaseOnEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverEnterEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverOverEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void hoverLeaveEntity(const EntityItemID& entityItemID, const MouseEvent& event);
void enterEntity(const EntityItemID& entityItemID);
void leaveEntity(const EntityItemID& entityItemID);
private:
void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties);

View file

@ -53,6 +53,9 @@ public:
virtual const Shape& getCollisionShapeInMeters() const { return _sphereShape; }
// TODO: implement proper contains for 3D ellipsoid
//virtual bool contains(const glm::vec3& point) const;
protected:
virtual void recalculateCollisionShape();

View file

@ -130,11 +130,11 @@ const QByteArray& DataServerAccountInfo::getUsernameSignature() {
reinterpret_cast<const unsigned char**>(&privateKeyData),
_privateKey.size());
if (rsaPrivateKey) {
QByteArray usernameByteArray = _username.toUtf8();
QByteArray lowercaseUsername = _username.toLower().toUtf8();
_usernameSignature.resize(RSA_size(rsaPrivateKey));
int encryptReturn = RSA_private_encrypt(usernameByteArray.size(),
reinterpret_cast<const unsigned char*>(usernameByteArray.constData()),
int encryptReturn = RSA_private_encrypt(lowercaseUsername.size(),
reinterpret_cast<const unsigned char*>(lowercaseUsername.constData()),
reinterpret_cast<unsigned char*>(_usernameSignature.data()),
rsaPrivateKey, RSA_PKCS1_PADDING);

View file

@ -9,9 +9,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QDataStream>
#include <QtNetwork/QHostInfo>
#include <QtNetwork/QNetworkInterface>
#include <qdatastream.h>
#include <qhostinfo.h>
#include <qnetworkinterface.h>
#include "HifiSockAddr.h"
@ -36,13 +36,20 @@ HifiSockAddr::HifiSockAddr(const HifiSockAddr& otherSockAddr) {
_port = otherSockAddr._port;
}
HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort) {
// lookup the IP by the hostname
QHostInfo hostInfo = QHostInfo::fromName(hostname);
foreach(const QHostAddress& address, hostInfo.addresses()) {
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
_address = address;
_port = hostOrderPort;
HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup) :
_address(hostname),
_port(hostOrderPort)
{
// if we parsed an IPv4 address out of the hostname, don't look it up
if (_address.protocol() != QAbstractSocket::IPv4Protocol) {
// lookup the IP by the hostname
if (shouldBlockForLookup) {
qDebug() << "Asynchronously looking up IP address for hostname" << hostname;
QHostInfo result = QHostInfo::fromName(hostname);
handleLookupResult(result);
} else {
int lookupID = QHostInfo::lookupHost(hostname, this, SLOT(handleLookupResult(QHostInfo)));
qDebug() << "Synchronously looking up IP address for hostname" << hostname << "- lookup ID is" << lookupID;
}
}
}
@ -75,6 +82,22 @@ bool HifiSockAddr::operator==(const HifiSockAddr& rhsSockAddr) const {
return _address == rhsSockAddr._address && _port == rhsSockAddr._port;
}
void HifiSockAddr::handleLookupResult(const QHostInfo& hostInfo) {
if (hostInfo.error() != QHostInfo::NoError) {
qDebug() << "Lookup failed for" << hostInfo.lookupId() << ":" << hostInfo.errorString();
}
foreach(const QHostAddress& address, hostInfo.addresses()) {
// just take the first IPv4 address
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
_address = address;
qDebug() << "QHostInfo lookup result for"
<< hostInfo.hostName() << "with lookup ID" << hostInfo.lookupId() << "is" << address.toString();
break;
}
}
}
QDebug operator<<(QDebug debug, const HifiSockAddr& sockAddr) {
debug.nospace() << sockAddr._address.toString().toLocal8Bit().constData() << ":" << sockAddr._port;
return debug.space();

View file

@ -19,14 +19,15 @@
#include <netinet/in.h>
#endif
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QHostInfo>
class HifiSockAddr {
class HifiSockAddr : public QObject {
Q_OBJECT
public:
HifiSockAddr();
HifiSockAddr(const QHostAddress& address, quint16 port);
HifiSockAddr(const HifiSockAddr& otherSockAddr);
HifiSockAddr(const QString& hostname, quint16 hostOrderPort);
HifiSockAddr(const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup = false);
HifiSockAddr(const sockaddr* sockaddr);
bool isNull() const { return _address.isNull() && _port == 0; }
@ -51,6 +52,8 @@ public:
friend QDebug operator<<(QDebug debug, const HifiSockAddr& sockAddr);
friend QDataStream& operator<<(QDataStream& dataStream, const HifiSockAddr& sockAddr);
friend QDataStream& operator>>(QDataStream& dataStream, HifiSockAddr& sockAddr);
private slots:
void handleLookupResult(const QHostInfo& hostInfo);
private:
QHostAddress _address;
quint16 _port;

View file

@ -73,6 +73,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short
_dtlsSocket(NULL),
_localSockAddr(),
_publicSockAddr(),
_stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT),
_numCollectedPackets(0),
_numCollectedBytes(0),
_packetStatTimer()
@ -583,11 +584,8 @@ void LimitedNodeList::sendSTUNRequest() {
QUuid randomUUID = QUuid::createUuid();
memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES);
// lookup the IP for the STUN server
HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT);
_nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket),
stunSockAddr.getAddress(), stunSockAddr.getPort());
_stunSockAddr.getAddress(), _stunSockAddr.getPort());
}
void LimitedNodeList::rebindNodeSocket() {

View file

@ -163,6 +163,7 @@ protected:
QUdpSocket* _dtlsSocket;
HifiSockAddr _localSockAddr;
HifiSockAddr _publicSockAddr;
HifiSockAddr _stunSockAddr;
int _numCollectedPackets;
int _numCollectedBytes;
QElapsedTimer _packetStatTimer;

View file

@ -21,6 +21,8 @@ public:
static QScriptValue toScriptValue(QScriptEngine* engine, const MouseEvent& event);
static void fromScriptValue(const QScriptValue& object, MouseEvent& event);
QScriptValue toScriptValue(QScriptEngine* engine) const { return MouseEvent::toScriptValue(engine, *this); }
int x;
int y;