mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 03:44:02 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into assignment-client-keep-a-spare-x
This commit is contained in:
commit
a6c79203bd
17 changed files with 477 additions and 132 deletions
|
@ -88,8 +88,17 @@ endif ()
|
|||
|
||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${QT_CMAKE_PREFIX_PATH})
|
||||
|
||||
# set our OS X deployment target to
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.8)
|
||||
if (APPLE)
|
||||
# set our OS X deployment target
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.8)
|
||||
|
||||
# find the 10.9 SDK path
|
||||
execute_process(COMMAND xcodebuild -sdk -version OUTPUT_VARIABLE XCODE_SDK_VERSIONS)
|
||||
string(REGEX MATCH \\/.+MacOSX10.9.sdk OSX_SDK_PATH ${XCODE_SDK_VERSIONS})
|
||||
|
||||
# set that as the SDK to use
|
||||
set(CMAKE_OSX_SYSROOT ${OSX_SDK_PATH})
|
||||
endif ()
|
||||
|
||||
# Find includes in corresponding build directories
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
|
|
@ -396,6 +396,10 @@ var toolBar = (function () {
|
|||
return handled;
|
||||
}
|
||||
|
||||
Window.domainChanged.connect(function() {
|
||||
that.setActive(false);
|
||||
});
|
||||
|
||||
that.cleanup = function () {
|
||||
toolBar.cleanup();
|
||||
};
|
||||
|
|
|
@ -38,11 +38,13 @@
|
|||
// function onIncomingMessage(user, message) {
|
||||
// //do stuff here;
|
||||
// var text = "This is a notification";
|
||||
// wordWrap(text);
|
||||
// var wrappedText = wordWrap(text);
|
||||
// createNotification(wrappedText, NotificationType.SNAPSHOT);
|
||||
// }
|
||||
//
|
||||
// This new function must call wordWrap(text) if the length of message is longer than 42 chars or unknown.
|
||||
// wordWrap() will format the text to fit the notifications overlay and send it to createNotification(text).
|
||||
// wordWrap() will format the text to fit the notifications overlay and return it
|
||||
// after that we will send it to createNotification(text).
|
||||
// If the message is 42 chars or less you should bypass wordWrap() and call createNotification() directly.
|
||||
|
||||
|
||||
|
@ -50,12 +52,12 @@
|
|||
//
|
||||
// 1. Add a key to the keyPressEvent(key).
|
||||
// 2. Declare a text string.
|
||||
// 3. Call createNotifications(text) parsing the text.
|
||||
// 3. Call createNotifications(text, NotificationType) parsing the text.
|
||||
// example:
|
||||
// var welcome;
|
||||
// if (key.text == "q") { //queries number of users online
|
||||
// var welcome = "There are " + GlobalServices.onlineUsers.length + " users online now.";
|
||||
// createNotification(welcome);
|
||||
// createNotification(welcome, NotificationType.USERS_ONLINE);
|
||||
// }
|
||||
Script.include("./libraries/globals.js");
|
||||
Script.include("./libraries/soundArray.js");
|
||||
|
@ -83,6 +85,46 @@ var last_users = GlobalServices.onlineUsers;
|
|||
var users = [];
|
||||
var ctrlIsPressed = false;
|
||||
var ready = true;
|
||||
var MENU_NAME = 'Tools > Notifications';
|
||||
var PLAY_NOTIFICATION_SOUNDS_MENU_ITEM = "Play Notification Sounds";
|
||||
var NOTIFICATION_MENU_ITEM_POST = " Notifications";
|
||||
var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds";
|
||||
var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_";
|
||||
|
||||
var NotificationType = {
|
||||
UNKNOWN: 0,
|
||||
USER_JOINS: 1,
|
||||
USER_LEAVES: 2,
|
||||
MUTE_TOGGLE: 3,
|
||||
CHAT_MENTION: 4,
|
||||
USERS_ONLINE: 5,
|
||||
SNAPSHOT: 6,
|
||||
WINDOW_RESIZE: 7,
|
||||
properties: [
|
||||
{ text: "User Join" },
|
||||
{ text: "User Leave" },
|
||||
{ text: "Mute Toggle" },
|
||||
{ text: "Chat Mention" },
|
||||
{ text: "Users Online" },
|
||||
{ text: "Snapshot" },
|
||||
{ text: "Window Resize" }
|
||||
],
|
||||
getTypeFromMenuItem: function(menuItemName) {
|
||||
if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) {
|
||||
return NotificationType.UNKNOWN;
|
||||
}
|
||||
var preMenuItemName = menuItemName.substr(0, menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length);
|
||||
for (type in this.properties) {
|
||||
if (this.properties[type].text === preMenuItemName) {
|
||||
return parseInt(type) + 1;
|
||||
}
|
||||
}
|
||||
return NotificationType.UNKNOWN;
|
||||
},
|
||||
getMenuString: function(type) {
|
||||
return this.properties[type - 1].text + NOTIFICATION_MENU_ITEM_POST;
|
||||
}
|
||||
};
|
||||
|
||||
var randomSounds = new SoundArray({ localOnly: true }, true);
|
||||
var numberOfSounds = 2;
|
||||
|
@ -90,15 +132,6 @@ for (var i = 1; i <= numberOfSounds; i++) {
|
|||
randomSounds.addSound(HIFI_PUBLIC_BUCKET + "sounds/UI/notification-general" + i + ".raw");
|
||||
}
|
||||
|
||||
// When our script shuts down, we should clean up all of our overlays
|
||||
function scriptEnding() {
|
||||
for (i = 0; i < notifications.length; i++) {
|
||||
Overlays.deleteOverlay(notifications[i]);
|
||||
Overlays.deleteOverlay(buttons[i]);
|
||||
}
|
||||
}
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
|
||||
var notifications = [];
|
||||
var buttons = [];
|
||||
var times = [];
|
||||
|
@ -211,8 +244,6 @@ function notify(notice, button, height) {
|
|||
positions,
|
||||
last;
|
||||
|
||||
randomSounds.playRandom();
|
||||
|
||||
if (isOnHMD) {
|
||||
// Calculate 3D values from 2D overlay properties.
|
||||
|
||||
|
@ -257,7 +288,7 @@ function notify(notice, button, height) {
|
|||
}
|
||||
|
||||
// This function creates and sizes the overlays
|
||||
function createNotification(text) {
|
||||
function createNotification(text, notificationType) {
|
||||
var count = (text.match(/\n/g) || []).length,
|
||||
breakPoint = 43.0, // length when new line is added
|
||||
extraLine = 0,
|
||||
|
@ -307,6 +338,12 @@ function createNotification(text) {
|
|||
alpha: backgroundAlpha
|
||||
};
|
||||
|
||||
if (Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) &&
|
||||
Menu.isOptionChecked(NotificationType.getMenuString(notificationType)))
|
||||
{
|
||||
randomSounds.playRandom();
|
||||
}
|
||||
|
||||
notify(noticeProperties, buttonProperties, height);
|
||||
}
|
||||
|
||||
|
@ -345,8 +382,7 @@ function stringDivider(str, slotWidth, spaceReplacer) {
|
|||
|
||||
// formats string to add newline every 43 chars
|
||||
function wordWrap(str) {
|
||||
var result = stringDivider(str, 43.0, "\n");
|
||||
createNotification(result);
|
||||
return stringDivider(str, 43.0, "\n");
|
||||
}
|
||||
|
||||
// This fires a notification on window resize
|
||||
|
@ -358,7 +394,7 @@ function checkSize() {
|
|||
windowDimensions = Controller.getViewportDimensions();
|
||||
overlayLocationX = (windowDimensions.x - (width + 60.0));
|
||||
buttonLocationX = overlayLocationX + (width - 35.0);
|
||||
createNotification(windowResize);
|
||||
createNotification(windowResize, NotificationType.WINDOW_RESIZE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -442,10 +478,7 @@ var STARTUP_TIMEOUT = 500, // ms
|
|||
|
||||
// This reports the number of users online at startup
|
||||
function reportUsers() {
|
||||
var welcome;
|
||||
|
||||
welcome = "Welcome! There are " + GlobalServices.onlineUsers.length + " users online now.";
|
||||
createNotification(welcome);
|
||||
createNotification("Welcome! There are " + GlobalServices.onlineUsers.length + " users online now.", NotificationType.USERS_ONLINE);
|
||||
}
|
||||
|
||||
function finishStartup() {
|
||||
|
@ -472,13 +505,13 @@ function onOnlineUsersChanged(users) {
|
|||
if (!isStartingUp()) { // Skip user notifications at startup.
|
||||
for (i = 0; i < users.length; i += 1) {
|
||||
if (last_users.indexOf(users[i]) === -1.0) {
|
||||
createNotification(users[i] + " has joined");
|
||||
createNotification(users[i] + " has joined", NotificationType.USER_JOINS);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < last_users.length; i += 1) {
|
||||
if (users.indexOf(last_users[i]) === -1.0) {
|
||||
createNotification(last_users[i] + " has left");
|
||||
createNotification(last_users[i] + " has left", NotificationType.USER_LEAVES);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -497,7 +530,7 @@ function onIncomingMessage(user, message) {
|
|||
thisAlert = user + ": " + myMessage;
|
||||
|
||||
if (myMessage.indexOf(alertMe) > -1.0) {
|
||||
wordWrap(thisAlert);
|
||||
CreateNotification(wordWrap(thisAlert), NotificationType.CHAT_MENTION);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -506,9 +539,9 @@ function onMuteStateChanged() {
|
|||
var muteState,
|
||||
muteString;
|
||||
|
||||
muteState = AudioDevice.getMuted() ? "muted" : "unmuted";
|
||||
muteState = AudioDevice.getMuted() ? "muted" : "unmuted";
|
||||
muteString = "Microphone is now " + muteState;
|
||||
createNotification(muteString);
|
||||
createNotification(muteString, NotificationType.MUTE_TOGGLE);
|
||||
}
|
||||
|
||||
// handles mouse clicks on buttons
|
||||
|
@ -551,25 +584,58 @@ function keyPressEvent(key) {
|
|||
if (key.text === "q") { //queries number of users online
|
||||
numUsers = GlobalServices.onlineUsers.length;
|
||||
welcome = "There are " + numUsers + " users online now.";
|
||||
createNotification(welcome);
|
||||
createNotification(welcome, NotificationType.USERS_ONLINE);
|
||||
}
|
||||
|
||||
if (key.text === "s") {
|
||||
if (ctrlIsPressed === true) {
|
||||
noteString = "Snapshot taken.";
|
||||
createNotification(noteString);
|
||||
createNotification(noteString, NotificationType.SNAPSHOT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setup() {
|
||||
Menu.addMenu(MENU_NAME);
|
||||
var checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING);
|
||||
checked = checked === '' ? true : checked;
|
||||
Menu.addMenuItem({
|
||||
menuName: MENU_NAME,
|
||||
menuItemName: PLAY_NOTIFICATION_SOUNDS_MENU_ITEM,
|
||||
isCheckable: true,
|
||||
isChecked: Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING)
|
||||
});
|
||||
Menu.addSeparator(MENU_NAME, "Play sounds for:");
|
||||
for (type in NotificationType.properties) {
|
||||
checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + (parseInt(type) + 1));
|
||||
checked = checked === '' ? true : checked;
|
||||
Menu.addMenuItem({
|
||||
menuName: MENU_NAME,
|
||||
menuItemName: NotificationType.properties[type].text + NOTIFICATION_MENU_ITEM_POST,
|
||||
isCheckable: true,
|
||||
isChecked: checked
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// When our script shuts down, we should clean up all of our overlays
|
||||
function scriptEnding() {
|
||||
var i;
|
||||
|
||||
for (i = 0; i < notifications.length; i += 1) {
|
||||
for (var i = 0; i < notifications.length; i++) {
|
||||
Overlays.deleteOverlay(notifications[i]);
|
||||
Overlays.deleteOverlay(buttons[i]);
|
||||
}
|
||||
Menu.removeMenu(MENU_NAME);
|
||||
}
|
||||
|
||||
function menuItemEvent(menuItem) {
|
||||
if (menuItem === PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) {
|
||||
Settings.setValue(PLAY_NOTIFICATION_SOUNDS_SETTING, Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM));
|
||||
return;
|
||||
}
|
||||
var notificationType = NotificationType.getTypeFromMenuItem(menuItem);
|
||||
if (notificationType !== notificationType.UNKNOWN) {
|
||||
Settings.setValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + notificationType, Menu.isOptionChecked(menuItem));
|
||||
}
|
||||
}
|
||||
|
||||
AudioDevice.muteToggled.connect(onMuteStateChanged);
|
||||
|
@ -580,3 +646,6 @@ GlobalServices.incomingMessage.connect(onIncomingMessage);
|
|||
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
||||
Script.update.connect(update);
|
||||
Script.scriptEnding.connect(scriptEnding);
|
||||
Menu.menuItemEvent.connect(menuItemEvent);
|
||||
|
||||
setup();
|
||||
|
|
|
@ -533,6 +533,10 @@ void Application::aboutToQuit() {
|
|||
}
|
||||
|
||||
void Application::cleanupBeforeQuit() {
|
||||
|
||||
_entities.shutdown(); // tell the entities system we're shutting down, so it will stop running scripts
|
||||
ScriptEngine::stopAllScripts(this); // stop all currently running global scripts
|
||||
|
||||
// first stop all timers directly or by invokeMethod
|
||||
// depending on what thread they run in
|
||||
locationUpdateTimer->stop();
|
||||
|
@ -597,13 +601,11 @@ Application::~Application() {
|
|||
|
||||
DependencyManager::destroy<GLCanvas>();
|
||||
|
||||
qDebug() << "start destroying ResourceCaches Application::~Application() line:" << __LINE__;
|
||||
DependencyManager::destroy<AnimationCache>();
|
||||
DependencyManager::destroy<TextureCache>();
|
||||
DependencyManager::destroy<GeometryCache>();
|
||||
DependencyManager::destroy<ScriptCache>();
|
||||
DependencyManager::destroy<SoundCache>();
|
||||
qDebug() << "done destroying ResourceCaches Application::~Application() line:" << __LINE__;
|
||||
}
|
||||
|
||||
void Application::initializeGL() {
|
||||
|
@ -1469,6 +1471,10 @@ void Application::checkFPS() {
|
|||
|
||||
void Application::idle() {
|
||||
PerformanceTimer perfTimer("idle");
|
||||
|
||||
if (_aboutToQuit) {
|
||||
return; // bail early, nothing to do here.
|
||||
}
|
||||
|
||||
// Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing
|
||||
// details if we're in ExtraDebugging mode. However, the ::update() and it's subcomponents will show their timing
|
||||
|
@ -3535,19 +3541,20 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance());
|
||||
#endif
|
||||
|
||||
// TODO: Consider moving some of this functionality into the ScriptEngine class instead. It seems wrong that this
|
||||
// work is being done in the Application class when really these dependencies are more related to the ScriptEngine's
|
||||
// implementation
|
||||
QThread* workerThread = new QThread(this);
|
||||
workerThread->setObjectName("Script Engine Thread");
|
||||
QString scriptEngineName = QString("Script Thread:") + scriptEngine->getFilename();
|
||||
workerThread->setObjectName(scriptEngineName);
|
||||
|
||||
// when the worker thread is started, call our engine's run..
|
||||
connect(workerThread, &QThread::started, scriptEngine, &ScriptEngine::run);
|
||||
|
||||
// when the thread is terminated, add both scriptEngine and thread to the deleteLater queue
|
||||
connect(scriptEngine, SIGNAL(finished(const QString&)), scriptEngine, SLOT(deleteLater()));
|
||||
connect(scriptEngine, SIGNAL(doneRunning()), scriptEngine, SLOT(deleteLater()));
|
||||
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
|
||||
|
||||
// when the application is about to quit, stop our script engine so it unwinds properly
|
||||
connect(this, SIGNAL(aboutToQuit()), scriptEngine, SLOT(stop()));
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
connect(nodeList.data(), &NodeList::nodeKilled, scriptEngine, &ScriptEngine::nodeKilled);
|
||||
|
||||
|
@ -3558,7 +3565,12 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
}
|
||||
|
||||
ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded,
|
||||
bool loadScriptFromEditor, bool activateMainWindow) {
|
||||
bool loadScriptFromEditor, bool activateMainWindow) {
|
||||
|
||||
if (isAboutToQuit()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
QUrl scriptUrl(scriptFilename);
|
||||
const QString& scriptURLString = scriptUrl.toString();
|
||||
if (_scriptEnginesHash.contains(scriptURLString) && loadScriptFromEditor
|
||||
|
@ -3749,8 +3761,8 @@ void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject
|
|||
voxelWalletUUID = QUuid(voxelObject[VOXEL_WALLET_UUID].toString());
|
||||
}
|
||||
|
||||
qDebug() << "Voxel costs are" << satoshisPerVoxel << "per voxel and" << satoshisPerMeterCubed << "per meter cubed";
|
||||
qDebug() << "Destination wallet UUID for voxel payments is" << voxelWalletUUID;
|
||||
qDebug() << "Octree edits costs are" << satoshisPerVoxel << "per octree cell and" << satoshisPerMeterCubed << "per meter cubed";
|
||||
qDebug() << "Destination wallet UUID for edit payments is" << voxelWalletUUID;
|
||||
}
|
||||
|
||||
QString Application::getPreviousScriptLocation() {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <QScrollArea>
|
||||
|
||||
#include "Application.h"
|
||||
#include "DomainHandler.h"
|
||||
#include "MainWindow.h"
|
||||
#include "Menu.h"
|
||||
#include "ui/ModelsBrowser.h"
|
||||
|
@ -34,6 +35,8 @@ WindowScriptingInterface::WindowScriptingInterface() :
|
|||
_nonBlockingFormActive(false),
|
||||
_formResult(QDialog::Rejected)
|
||||
{
|
||||
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
||||
connect(&domainHandler, &DomainHandler::hostnameChanged, this, &WindowScriptingInterface::domainChanged);
|
||||
}
|
||||
|
||||
WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height) {
|
||||
|
|
|
@ -57,6 +57,7 @@ public slots:
|
|||
QScriptValue peekNonBlockingFormResult(QScriptValue array);
|
||||
|
||||
signals:
|
||||
void domainChanged(const QString& domainHostname);
|
||||
void inlineButtonClicked(const QString& name);
|
||||
void nonBlockingFormClosed();
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
//
|
||||
|
||||
#include "Application.h"
|
||||
#include "GeometryUtil.h"
|
||||
#include "PlaneShape.h"
|
||||
|
||||
#include "BillboardOverlay.h"
|
||||
|
||||
|
@ -191,19 +193,25 @@ bool BillboardOverlay::findRayIntersection(const glm::vec3& origin, const glm::v
|
|||
float& distance, BoxFace& face) {
|
||||
|
||||
if (_texture) {
|
||||
glm::quat rotation;
|
||||
if (_isFacingAvatar) {
|
||||
// rotate about vertical to face the camera
|
||||
rotation = Application::getInstance()->getCamera()->getRotation();
|
||||
rotation *= glm::angleAxis(glm::pi<float>(), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
} else {
|
||||
rotation = _rotation;
|
||||
}
|
||||
|
||||
// Produce the dimensions of the billboard based on the image's aspect ratio and the overlay's scale.
|
||||
bool isNull = _fromImage.isNull();
|
||||
float width = isNull ? _texture->getWidth() : _fromImage.width();
|
||||
float height = isNull ? _texture->getHeight() : _fromImage.height();
|
||||
|
||||
float maxSize = glm::max(width, height);
|
||||
float x = width / (2.0f * maxSize);
|
||||
float y = -height / (2.0f * maxSize);
|
||||
float maxDimension = glm::max(x,y);
|
||||
float scaledDimension = maxDimension * _scale;
|
||||
glm::vec3 corner = getCenter() - glm::vec3(scaledDimension, scaledDimension, scaledDimension) ;
|
||||
AACube myCube(corner, scaledDimension * 2.0f);
|
||||
return myCube.findRayIntersection(origin, direction, distance, face);
|
||||
glm::vec2 dimensions = _scale * glm::vec2(width / maxSize, height / maxSize);
|
||||
|
||||
return findRayRectangleIntersection(origin, direction, rotation, _position, dimensions);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <SharedUtil.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include "GeometryUtil.h"
|
||||
|
||||
#include "Planar3DOverlay.h"
|
||||
|
||||
const float DEFAULT_SIZE = 1.0f;
|
||||
|
@ -93,29 +95,5 @@ QScriptValue Planar3DOverlay::getProperty(const QString& property) {
|
|||
|
||||
bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face) {
|
||||
RayIntersectionInfo rayInfo;
|
||||
rayInfo._rayStart = origin;
|
||||
rayInfo._rayDirection = direction;
|
||||
rayInfo._rayLength = std::numeric_limits<float>::max();
|
||||
|
||||
PlaneShape plane;
|
||||
|
||||
const glm::vec3 UNROTATED_NORMAL(0.0f, 0.0f, -1.0f);
|
||||
glm::vec3 normal = _rotation * UNROTATED_NORMAL;
|
||||
plane.setNormal(normal);
|
||||
plane.setPoint(_position); // the position is definitely a point on our plane
|
||||
|
||||
bool intersects = plane.findRayIntersection(rayInfo);
|
||||
|
||||
if (intersects) {
|
||||
distance = rayInfo._hitDistance;
|
||||
|
||||
glm::vec3 hitPosition = origin + (distance * direction);
|
||||
glm::vec3 localHitPosition = glm::inverse(_rotation) * (hitPosition - _position);
|
||||
glm::vec2 halfDimensions = _dimensions / 2.0f;
|
||||
|
||||
intersects = -halfDimensions.x <= localHitPosition.x && localHitPosition.x <= halfDimensions.x
|
||||
&& -halfDimensions.y <= localHitPosition.y && localHitPosition.y <= halfDimensions.y;
|
||||
}
|
||||
return intersects;
|
||||
return findRayRectangleIntersection(origin, direction, _rotation, _position, _dimensions);
|
||||
}
|
||||
|
|
|
@ -59,10 +59,17 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf
|
|||
}
|
||||
|
||||
EntityTreeRenderer::~EntityTreeRenderer() {
|
||||
// NOTE: we don't need to delete _entitiesScriptEngine because it's owned by the application and gets cleaned up
|
||||
// automatically but we do need to delete our sandbox script engine.
|
||||
delete _sandboxScriptEngine;
|
||||
_sandboxScriptEngine = NULL;
|
||||
// NOTE: we don't need to delete _entitiesScriptEngine because it is registered with the application and has a
|
||||
// signal tied to call it's deleteLater on doneRunning
|
||||
if (_sandboxScriptEngine) {
|
||||
// TODO: consider reworking how _sandboxScriptEngine is managed. It's treated differently than _entitiesScriptEngine
|
||||
// because we don't call registerScriptEngineWithApplicationServices() for it. This implementation is confusing and
|
||||
// potentially error prone because it's not a full fledged ScriptEngine that has been fully connected to the
|
||||
// application. We did this so that scripts that were ill-formed could be evaluated but not execute against the
|
||||
// application services. But this means it's shutdown behavior is different from other ScriptEngines
|
||||
delete _sandboxScriptEngine;
|
||||
_sandboxScriptEngine = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::clear() {
|
||||
|
@ -97,6 +104,11 @@ void EntityTreeRenderer::init() {
|
|||
connect(entityTree, &EntityTree::changingEntityID, this, &EntityTreeRenderer::changingEntityID);
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::shutdown() {
|
||||
_shuttingDown = true;
|
||||
}
|
||||
|
||||
|
||||
QScriptValue EntityTreeRenderer::loadEntityScript(const EntityItemID& entityItemID) {
|
||||
EntityItem* entity = static_cast<EntityTree*>(_tree)->findEntityByEntityItemID(entityItemID);
|
||||
return loadEntityScript(entity);
|
||||
|
@ -156,6 +168,10 @@ QString EntityTreeRenderer::loadScriptContents(const QString& scriptMaybeURLorTe
|
|||
|
||||
|
||||
QScriptValue EntityTreeRenderer::loadEntityScript(EntityItem* entity) {
|
||||
if (_shuttingDown) {
|
||||
return QScriptValue(); // since we're shutting down, we don't load any more scripts
|
||||
}
|
||||
|
||||
if (!entity) {
|
||||
return QScriptValue(); // no entity...
|
||||
}
|
||||
|
@ -235,7 +251,7 @@ void EntityTreeRenderer::setTree(Octree* newTree) {
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::update() {
|
||||
if (_tree) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
EntityTree* tree = static_cast<EntityTree*>(_tree);
|
||||
tree->update();
|
||||
|
||||
|
@ -258,7 +274,7 @@ void EntityTreeRenderer::update() {
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||
if (_tree) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
_tree->lockForWrite(); // so that our scripts can do edits if they want
|
||||
glm::vec3 avatarPosition = _viewState->getAvatarPosition() / (float) TREE_SCALE;
|
||||
|
||||
|
@ -309,7 +325,7 @@ void EntityTreeRenderer::checkEnterLeaveEntities() {
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::leaveAllEntities() {
|
||||
if (_tree) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
_tree->lockForWrite(); // so that our scripts can do edits if they want
|
||||
|
||||
// for all of our previous containing entities, if they are no longer containing then send them a leave event
|
||||
|
@ -330,7 +346,7 @@ void EntityTreeRenderer::leaveAllEntities() {
|
|||
}
|
||||
}
|
||||
void EntityTreeRenderer::render(RenderArgs::RenderMode renderMode, RenderArgs::RenderSide renderSide) {
|
||||
if (_tree) {
|
||||
if (_tree && !_shuttingDown) {
|
||||
Model::startScene(renderSide);
|
||||
RenderArgs args = { this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode, renderSide,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
|
@ -700,9 +716,14 @@ QScriptValueList EntityTreeRenderer::createEntityArgs(const EntityItemID& entity
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
|
||||
// If we don't have a tree, or we're in the process of shutting down, then don't
|
||||
// process these events.
|
||||
if (!_tree || _shuttingDown) {
|
||||
return;
|
||||
}
|
||||
PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent");
|
||||
PickRay ray = _viewState->computePickRay(event->x(), event->y());
|
||||
|
||||
|
||||
bool precisionPicking = !_dontDoPrecisionPicking;
|
||||
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock, precisionPicking);
|
||||
if (rayPickResult.intersects) {
|
||||
|
@ -714,7 +735,7 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device
|
|||
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()) {
|
||||
|
@ -726,6 +747,11 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event, unsigned int device
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) {
|
||||
// If we don't have a tree, or we're in the process of shutting down, then don't
|
||||
// process these events.
|
||||
if (!_tree || _shuttingDown) {
|
||||
return;
|
||||
}
|
||||
PerformanceTimer perfTimer("EntityTreeRenderer::mouseReleaseEvent");
|
||||
PickRay ray = _viewState->computePickRay(event->x(), event->y());
|
||||
bool precisionPicking = !_dontDoPrecisionPicking;
|
||||
|
@ -740,7 +766,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi
|
|||
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()) {
|
||||
|
@ -752,7 +778,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi
|
|||
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();
|
||||
_lastMouseEvent = MouseEvent(*event, deviceID);
|
||||
|
@ -760,10 +786,15 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event, unsigned int devi
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
|
||||
// If we don't have a tree, or we're in the process of shutting down, then don't
|
||||
// process these events.
|
||||
if (!_tree || _shuttingDown) {
|
||||
return;
|
||||
}
|
||||
PerformanceTimer perfTimer("EntityTreeRenderer::mouseMoveEvent");
|
||||
|
||||
PickRay ray = _viewState->computePickRay(event->x(), event->y());
|
||||
|
||||
|
||||
bool precisionPicking = false; // for mouse moves we do not do precision picking
|
||||
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking);
|
||||
if (rayPickResult.intersects) {
|
||||
|
@ -774,15 +805,15 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
|||
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) {
|
||||
|
@ -832,7 +863,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
|||
_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()) {
|
||||
|
@ -850,29 +881,37 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event, unsigned int deviceI
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
||||
checkAndCallUnload(entityID);
|
||||
if (_tree && !_shuttingDown) {
|
||||
checkAndCallUnload(entityID);
|
||||
}
|
||||
_entityScripts.remove(entityID);
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID) {
|
||||
checkAndCallUnload(entityID);
|
||||
checkAndCallPreload(entityID);
|
||||
if (_tree && !_shuttingDown) {
|
||||
checkAndCallUnload(entityID);
|
||||
checkAndCallPreload(entityID);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID) {
|
||||
// load the entity script if needed...
|
||||
QScriptValue entityScript = loadEntityScript(entityID);
|
||||
if (entityScript.property("preload").isValid()) {
|
||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
||||
entityScript.property("preload").call(entityScript, entityArgs);
|
||||
if (_tree && !_shuttingDown) {
|
||||
// load the entity script if needed...
|
||||
QScriptValue entityScript = loadEntityScript(entityID);
|
||||
if (entityScript.property("preload").isValid()) {
|
||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
||||
entityScript.property("preload").call(entityScript, entityArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::checkAndCallUnload(const EntityItemID& entityID) {
|
||||
QScriptValue entityScript = getPreviouslyLoadedEntityScript(entityID);
|
||||
if (entityScript.property("unload").isValid()) {
|
||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
||||
entityScript.property("unload").call(entityScript, entityArgs);
|
||||
if (_tree && !_shuttingDown) {
|
||||
QScriptValue entityScript = getPreviouslyLoadedEntityScript(entityID);
|
||||
if (entityScript.property("unload").isValid()) {
|
||||
QScriptValueList entityArgs = createEntityArgs(entityID);
|
||||
entityScript.property("unload").call(entityScript, entityArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -887,6 +926,11 @@ void EntityTreeRenderer::changingEntityID(const EntityItemID& oldEntityID, const
|
|||
|
||||
void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB,
|
||||
const Collision& collision) {
|
||||
// If we don't have a tree, or we're in the process of shutting down, then don't
|
||||
// process these events.
|
||||
if (!_tree || _shuttingDown) {
|
||||
return;
|
||||
}
|
||||
QScriptValue entityScriptA = loadEntityScript(idA);
|
||||
if (entityScriptA.property("collisionWithEntity").isValid()) {
|
||||
QScriptValueList args;
|
||||
|
|
|
@ -46,6 +46,7 @@ public:
|
|||
virtual int getBoundaryLevelAdjust() const;
|
||||
virtual void setTree(Octree* newTree);
|
||||
|
||||
void shutdown();
|
||||
void update();
|
||||
|
||||
EntityTree* getTree() { return static_cast<EntityTree*>(_tree); }
|
||||
|
@ -154,6 +155,8 @@ private:
|
|||
bool _displayModelElementProxy;
|
||||
bool _dontDoPrecisionPicking;
|
||||
|
||||
bool _shuttingDown = false;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_EntityTreeRenderer_h
|
||||
|
|
|
@ -1002,7 +1002,7 @@ void EntityItem::computeShapeInfo(ShapeInfo& info) const {
|
|||
}
|
||||
|
||||
const float MIN_POSITION_DELTA = 0.0001f;
|
||||
const float MIN_ALIGNMENT_DOT = 0.9999f;
|
||||
const float MIN_ALIGNMENT_DOT = 0.999999f;
|
||||
const float MIN_VELOCITY_DELTA = 0.01f;
|
||||
const float MIN_DAMPING_DELTA = 0.001f;
|
||||
const float MIN_GRAVITY_DELTA = 0.001f;
|
||||
|
|
|
@ -1959,7 +1959,7 @@ HeightfieldNode* HeightfieldNode::fillHeight(const glm::vec3& translation, const
|
|||
} else {
|
||||
colorWidth = innerHeightWidth + HeightfieldData::SHARED_EDGE;
|
||||
colorHeight = innerHeightHeight + HeightfieldData::SHARED_EDGE;
|
||||
newColorContents = QByteArray(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF);
|
||||
newColorContents = QByteArray(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFFu);
|
||||
}
|
||||
int innerColorWidth = colorWidth - HeightfieldData::SHARED_EDGE;
|
||||
int innerColorHeight = colorHeight - HeightfieldData::SHARED_EDGE;
|
||||
|
@ -2185,7 +2185,7 @@ HeightfieldNode* HeightfieldNode::setMaterial(const glm::vec3& translation, cons
|
|||
} else {
|
||||
colorWidth = innerHeightWidth + HeightfieldData::SHARED_EDGE;
|
||||
colorHeight = innerHeightHeight + HeightfieldData::SHARED_EDGE;
|
||||
newColorContents = QByteArray(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF);
|
||||
newColorContents = QByteArray(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFFu);
|
||||
}
|
||||
int innerColorWidth = colorWidth - HeightfieldData::SHARED_EDGE;
|
||||
int innerColorHeight = colorHeight - HeightfieldData::SHARED_EDGE;
|
||||
|
@ -2428,7 +2428,7 @@ HeightfieldNode* HeightfieldNode::setMaterial(const glm::vec3& translation, cons
|
|||
}
|
||||
}
|
||||
}
|
||||
bool nextAlphaY = stackDest->getEntryAlpha(y + 1, voxelHeight);
|
||||
int nextAlphaY = stackDest->getEntryAlpha(y + 1, voxelHeight);
|
||||
if (nextAlphaY == currentAlpha) {
|
||||
entryDest->hermiteY = 0;
|
||||
|
||||
|
@ -2447,7 +2447,7 @@ HeightfieldNode* HeightfieldNode::setMaterial(const glm::vec3& translation, cons
|
|||
}
|
||||
int nextStackZ = (int)stackZ + 1;
|
||||
if (nextStackZ <= innerStackHeight) {
|
||||
bool nextAlphaZ = newStackContents.at(nextStackZ * stackWidth + (int)stackX).getEntryAlpha(
|
||||
int nextAlphaZ = newStackContents.at(nextStackZ * stackWidth + (int)stackX).getEntryAlpha(
|
||||
y, nextVoxelHeightZ);
|
||||
if (nextAlphaZ == currentAlpha) {
|
||||
entryDest->hermiteZ = 0;
|
||||
|
@ -2828,7 +2828,7 @@ void HeightfieldNode::mergeChildren(bool height, bool colorMaterial) {
|
|||
_height.reset();
|
||||
}
|
||||
if (colorWidth > 0 && colorMaterial) {
|
||||
QByteArray colorContents(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF);
|
||||
QByteArray colorContents(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFFu);
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
HeightfieldColorPointer childColor = _children[i]->getColor();
|
||||
if (!childColor) {
|
||||
|
@ -3266,7 +3266,7 @@ HeightfieldNode* HeightfieldNode::subdivide(const QVector<quint16>& heightConten
|
|||
}
|
||||
for (int i = 0; i < CHILD_COUNT; i++) {
|
||||
QVector<quint16> childHeightContents(heightWidth * heightHeight);
|
||||
QByteArray childColorContents(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFF);
|
||||
QByteArray childColorContents(colorWidth * colorHeight * DataBlock::COLOR_BYTES, 0xFFu);
|
||||
QByteArray childMaterialContents(materialWidth * materialHeight, 0);
|
||||
QVector<StackArray> childStackContents(stackWidth * stackHeight);
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
|
||||
#include "MIDIEvent.h"
|
||||
|
||||
|
||||
EntityScriptingInterface ScriptEngine::_entityScriptingInterface;
|
||||
|
||||
static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){
|
||||
|
@ -94,8 +95,109 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam
|
|||
_isUserLoaded(false),
|
||||
_arrayBufferClass(new ArrayBufferClass(this))
|
||||
{
|
||||
_allScriptsMutex.lock();
|
||||
_allKnownScriptEngines.insert(this);
|
||||
_allScriptsMutex.unlock();
|
||||
}
|
||||
|
||||
ScriptEngine::~ScriptEngine() {
|
||||
// If we're not already in the middle of stopping all scripts, then we should remove ourselves
|
||||
// from the list of running scripts. We don't do this if we're in the process of stopping all scripts
|
||||
// because that method removes scripts from its list as it iterates them
|
||||
if (!_stoppingAllScripts) {
|
||||
_allScriptsMutex.lock();
|
||||
_allKnownScriptEngines.remove(this);
|
||||
_allScriptsMutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
QSet<ScriptEngine*> ScriptEngine::_allKnownScriptEngines;
|
||||
QMutex ScriptEngine::_allScriptsMutex;
|
||||
bool ScriptEngine::_stoppingAllScripts = false;
|
||||
bool ScriptEngine::_doneRunningThisScript = false;
|
||||
|
||||
void ScriptEngine::stopAllScripts(QObject* application) {
|
||||
_allScriptsMutex.lock();
|
||||
_stoppingAllScripts = true;
|
||||
|
||||
QMutableSetIterator<ScriptEngine*> i(_allKnownScriptEngines);
|
||||
while (i.hasNext()) {
|
||||
ScriptEngine* scriptEngine = i.next();
|
||||
|
||||
QString scriptName = scriptEngine->getFilename();
|
||||
|
||||
// NOTE: typically all script engines are running. But there's at least one known exception to this, the
|
||||
// "entities sandbox" which is only used to evaluate entities scripts to test their validity before using
|
||||
// them. We don't need to stop scripts that aren't running.
|
||||
if (scriptEngine->isRunning()) {
|
||||
|
||||
// If the script is running, but still evaluating then we need to wait for its evaluation step to
|
||||
// complete. After that we can handle the stop process appropriately
|
||||
if (scriptEngine->evaluatePending()) {
|
||||
while (scriptEngine->evaluatePending()) {
|
||||
|
||||
// This event loop allows any started, but not yet finished evaluate() calls to complete
|
||||
// we need to let these complete so that we can be guaranteed that the script engine isn't
|
||||
// in a partially setup state, which can confuse our shutdown unwinding.
|
||||
QEventLoop loop;
|
||||
QObject::connect(scriptEngine, &ScriptEngine::evaluationFinished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
}
|
||||
}
|
||||
|
||||
// We disconnect any script engine signals from the application because we don't want to do any
|
||||
// extra stopScript/loadScript processing that the Application normally does when scripts start
|
||||
// and stop. We can safely short circuit this because we know we're in the "quitting" process
|
||||
scriptEngine->disconnect(application);
|
||||
|
||||
// Calling stop on the script engine will set it's internal _isFinished state to true, and result
|
||||
// in the ScriptEngine gracefully ending it's run() method.
|
||||
scriptEngine->stop();
|
||||
|
||||
// We need to wait for the engine to be done running before we proceed, because we don't
|
||||
// want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing
|
||||
// any application state after we leave this stopAllScripts() method
|
||||
scriptEngine->waitTillDoneRunning();
|
||||
|
||||
// If the script is stopped, we can remove it from our set
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
_stoppingAllScripts = false;
|
||||
_allScriptsMutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
void ScriptEngine::waitTillDoneRunning() {
|
||||
QString scriptName = getFilename();
|
||||
|
||||
// If the script never started running or finished running before we got here, we don't need to wait for it
|
||||
if (_isRunning) {
|
||||
|
||||
_doneRunningThisScript = false; // NOTE: this is static, we serialize our waiting for scripts to finish
|
||||
|
||||
// NOTE: waitTillDoneRunning() will be called on the main Application thread, inside of stopAllScripts()
|
||||
// we want the application thread to continue to process events, because the scripts will likely need to
|
||||
// marshall messages across to the main thread. For example if they access Settings or Meny in any of their
|
||||
// shutdown code.
|
||||
while (!_doneRunningThisScript) {
|
||||
|
||||
// process events for the main application thread, allowing invokeMethod calls to pass between threads
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString ScriptEngine::getFilename() const {
|
||||
QStringList fileNameParts = _fileNameString.split("/");
|
||||
QString lastPart;
|
||||
if (!fileNameParts.isEmpty()) {
|
||||
lastPart = fileNameParts.last();
|
||||
}
|
||||
return lastPart;
|
||||
}
|
||||
|
||||
|
||||
void ScriptEngine::setIsAvatar(bool isAvatar) {
|
||||
_isAvatar = isAvatar;
|
||||
|
||||
|
@ -295,12 +397,18 @@ void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::Func
|
|||
}
|
||||
|
||||
void ScriptEngine::evaluate() {
|
||||
if (_stoppingAllScripts) {
|
||||
return; // bail early
|
||||
}
|
||||
|
||||
if (!_isInitialized) {
|
||||
init();
|
||||
}
|
||||
|
||||
QScriptValue result = evaluate(_scriptContents);
|
||||
|
||||
// TODO: why do we check this twice? It seems like the call to clearExcpetions() in the lower level evaluate call
|
||||
// will cause this code to never actually run...
|
||||
if (hasUncaughtException()) {
|
||||
int line = uncaughtExceptionLineNumber();
|
||||
qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();
|
||||
|
@ -310,11 +418,17 @@ void ScriptEngine::evaluate() {
|
|||
}
|
||||
|
||||
QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) {
|
||||
if (_stoppingAllScripts) {
|
||||
return QScriptValue(); // bail early
|
||||
}
|
||||
|
||||
_evaluatesPending++;
|
||||
QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber);
|
||||
if (hasUncaughtException()) {
|
||||
int line = uncaughtExceptionLineNumber();
|
||||
qDebug() << "Uncaught exception at (" << _fileNameString << " : " << fileName << ") line" << line << ": " << result.toString();
|
||||
}
|
||||
_evaluatesPending--;
|
||||
emit evaluationFinished(result, hasUncaughtException());
|
||||
clearExceptions();
|
||||
return result;
|
||||
|
@ -333,6 +447,9 @@ void ScriptEngine::sendAvatarBillboardPacket() {
|
|||
}
|
||||
|
||||
void ScriptEngine::run() {
|
||||
// TODO: can we add a short circuit for _stoppingAllScripts here? What does it mean to not start running if
|
||||
// we're in the process of stopping?
|
||||
|
||||
if (!_isInitialized) {
|
||||
init();
|
||||
}
|
||||
|
@ -341,12 +458,6 @@ void ScriptEngine::run() {
|
|||
emit runningStateChanged();
|
||||
|
||||
QScriptValue result = evaluate(_scriptContents);
|
||||
if (hasUncaughtException()) {
|
||||
int line = uncaughtExceptionLineNumber();
|
||||
qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();
|
||||
emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString());
|
||||
clearExceptions();
|
||||
}
|
||||
|
||||
QElapsedTimer startTime;
|
||||
startTime.start();
|
||||
|
@ -373,7 +484,7 @@ void ScriptEngine::run() {
|
|||
break;
|
||||
}
|
||||
|
||||
if (_entityScriptingInterface.getEntityPacketSender()->serversExist()) {
|
||||
if (!_isFinished && _entityScriptingInterface.getEntityPacketSender()->serversExist()) {
|
||||
// release the queue of edit entity messages.
|
||||
_entityScriptingInterface.getEntityPacketSender()->releaseQueuedMessages();
|
||||
|
||||
|
@ -383,7 +494,7 @@ void ScriptEngine::run() {
|
|||
}
|
||||
}
|
||||
|
||||
if (_isAvatar && _avatarData) {
|
||||
if (!_isFinished && _isAvatar && _avatarData) {
|
||||
|
||||
const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE)
|
||||
/ (1000 * 1000)) + 0.5);
|
||||
|
@ -493,9 +604,14 @@ void ScriptEngine::run() {
|
|||
clearExceptions();
|
||||
}
|
||||
|
||||
emit update(deltaTime);
|
||||
if (!_isFinished) {
|
||||
emit update(deltaTime);
|
||||
}
|
||||
lastUpdate = now;
|
||||
|
||||
}
|
||||
|
||||
stopAllTimers(); // make sure all our timers are stopped if the script is ending
|
||||
emit scriptEnding();
|
||||
|
||||
// kill the avatar identity timer
|
||||
|
@ -520,6 +636,21 @@ void ScriptEngine::run() {
|
|||
|
||||
_isRunning = false;
|
||||
emit runningStateChanged();
|
||||
|
||||
emit doneRunning();
|
||||
|
||||
_doneRunningThisScript = true;
|
||||
}
|
||||
|
||||
// NOTE: This is private because it must be called on the same thread that created the timers, which is why
|
||||
// we want to only call it in our own run "shutdown" processing.
|
||||
void ScriptEngine::stopAllTimers() {
|
||||
QMutableHashIterator<QTimer*, QScriptValue> i(_timerFunctionMap);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
QTimer* timer = i.key();
|
||||
stopTimer(timer);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::stop() {
|
||||
|
@ -560,10 +691,20 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int
|
|||
}
|
||||
|
||||
QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) {
|
||||
if (_stoppingAllScripts) {
|
||||
qDebug() << "Script.setInterval() while shutting down is ignored... parent script:" << getFilename();
|
||||
return NULL; // bail early
|
||||
}
|
||||
|
||||
return setupTimerWithInterval(function, intervalMS, false);
|
||||
}
|
||||
|
||||
QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) {
|
||||
if (_stoppingAllScripts) {
|
||||
qDebug() << "Script.setTimeout() while shutting down is ignored... parent script:" << getFilename();
|
||||
return NULL; // bail early
|
||||
}
|
||||
|
||||
return setupTimerWithInterval(function, timeoutMS, true);
|
||||
}
|
||||
|
||||
|
@ -604,13 +745,16 @@ void ScriptEngine::print(const QString& message) {
|
|||
emit printedMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a callback is specified, the included files will be loaded asynchronously and the callback will be called
|
||||
* when all of the files have finished loading.
|
||||
* If no callback is specified, the included files will be loaded synchronously and will block execution until
|
||||
* all of the files have finished loading.
|
||||
*/
|
||||
// If a callback is specified, the included files will be loaded asynchronously and the callback will be called
|
||||
// when all of the files have finished loading.
|
||||
// If no callback is specified, the included files will be loaded synchronously and will block execution until
|
||||
// all of the files have finished loading.
|
||||
void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) {
|
||||
if (_stoppingAllScripts) {
|
||||
qDebug() << "Script.include() while shutting down is ignored..."
|
||||
<< "includeFiles:" << includeFiles << "parent script:" << getFilename();
|
||||
return; // bail early
|
||||
}
|
||||
QList<QUrl> urls;
|
||||
for (QString file : includeFiles) {
|
||||
urls.append(resolvePath(file));
|
||||
|
@ -650,12 +794,27 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
|
|||
}
|
||||
|
||||
void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
|
||||
if (_stoppingAllScripts) {
|
||||
qDebug() << "Script.include() while shutting down is ignored... "
|
||||
<< "includeFile:" << includeFile << "parent script:" << getFilename();
|
||||
return; // bail early
|
||||
}
|
||||
|
||||
QStringList urls;
|
||||
urls.append(includeFile);
|
||||
include(urls, callback);
|
||||
}
|
||||
|
||||
// NOTE: The load() command is similar to the include() command except that it loads the script
|
||||
// as a stand-alone script. To accomplish this, the ScriptEngine class just emits a signal which
|
||||
// the Application or other context will connect to in order to know to actually load the script
|
||||
void ScriptEngine::load(const QString& loadFile) {
|
||||
if (_stoppingAllScripts) {
|
||||
qDebug() << "Script.load() while shutting down is ignored... "
|
||||
<< "loadFile:" << loadFile << "parent script:" << getFilename();
|
||||
return; // bail early
|
||||
}
|
||||
|
||||
QUrl url = resolvePath(loadFile);
|
||||
emit loadScript(url.toString(), false);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QWaitCondition>
|
||||
#include <QtScript/QScriptEngine>
|
||||
|
||||
#include <AnimationCache.h>
|
||||
|
@ -43,6 +44,8 @@ public:
|
|||
const QString& fileNameString = QString(""),
|
||||
AbstractControllerScriptingInterface* controllerScriptingInterface = NULL);
|
||||
|
||||
~ScriptEngine();
|
||||
|
||||
/// Access the EntityScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener
|
||||
static EntityScriptingInterface* getEntityScriptingInterface() { return &_entityScriptingInterface; }
|
||||
|
||||
|
@ -83,11 +86,18 @@ public:
|
|||
|
||||
bool isFinished() const { return _isFinished; }
|
||||
bool isRunning() const { return _isRunning; }
|
||||
bool evaluatePending() const { return _evaluatesPending > 0; }
|
||||
|
||||
void setUserLoaded(bool isUserLoaded) { _isUserLoaded = isUserLoaded; }
|
||||
bool isUserLoaded() const { return _isUserLoaded; }
|
||||
|
||||
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
|
||||
|
||||
QString getFilename() const;
|
||||
|
||||
static void stopAllScripts(QObject* application);
|
||||
|
||||
void waitTillDoneRunning();
|
||||
|
||||
public slots:
|
||||
void loadURL(const QUrl& scriptURL);
|
||||
|
@ -118,12 +128,14 @@ signals:
|
|||
void runningStateChanged();
|
||||
void evaluationFinished(QScriptValue result, bool isException);
|
||||
void loadScript(const QString& scriptName, bool isUserLoaded);
|
||||
void doneRunning();
|
||||
|
||||
protected:
|
||||
QString _scriptContents;
|
||||
QString _parentURL;
|
||||
bool _isFinished;
|
||||
bool _isRunning;
|
||||
int _evaluatesPending = 0;
|
||||
bool _isInitialized;
|
||||
bool _isAvatar;
|
||||
QTimer* _avatarIdentityTimer;
|
||||
|
@ -134,6 +146,7 @@ protected:
|
|||
int _numAvatarSoundSentBytes;
|
||||
|
||||
private:
|
||||
void stopAllTimers();
|
||||
void sendAvatarIdentityPacket();
|
||||
void sendAvatarBillboardPacket();
|
||||
|
||||
|
@ -156,6 +169,12 @@ private:
|
|||
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||
private slots:
|
||||
void handleScriptDownload();
|
||||
|
||||
private:
|
||||
static QSet<ScriptEngine*> _allKnownScriptEngines;
|
||||
static QMutex _allScriptsMutex;
|
||||
static bool _stoppingAllScripts;
|
||||
static bool _doneRunningThisScript;
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptEngine_h
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
#include <cstring>
|
||||
#include <cmath>
|
||||
|
||||
#include "SharedUtil.h"
|
||||
#include "GeometryUtil.h"
|
||||
#include "PlaneShape.h"
|
||||
#include "RayIntersectionInfo.h"
|
||||
#include "SharedUtil.h"
|
||||
|
||||
glm::vec3 computeVectorFromPointToSegment(const glm::vec3& point, const glm::vec3& start, const glm::vec3& end) {
|
||||
// compute the projection of the point vector onto the segment vector
|
||||
|
@ -491,3 +493,34 @@ void PolygonClip::copyCleanArray(int& lengthA, glm::vec2* vertexArrayA, int& len
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::quat& rotation, const glm::vec3& position, const glm::vec2& dimensions) {
|
||||
RayIntersectionInfo rayInfo;
|
||||
rayInfo._rayStart = origin;
|
||||
rayInfo._rayDirection = direction;
|
||||
rayInfo._rayLength = std::numeric_limits<float>::max();
|
||||
|
||||
PlaneShape plane;
|
||||
|
||||
const glm::vec3 UNROTATED_NORMAL(0.0f, 0.0f, -1.0f);
|
||||
glm::vec3 normal = rotation * UNROTATED_NORMAL;
|
||||
plane.setNormal(normal);
|
||||
plane.setPoint(position); // the position is definitely a point on our plane
|
||||
|
||||
bool intersects = plane.findRayIntersection(rayInfo);
|
||||
|
||||
if (intersects) {
|
||||
float distance = rayInfo._hitDistance;
|
||||
|
||||
glm::vec3 hitPosition = origin + (distance * direction);
|
||||
glm::vec3 localHitPosition = glm::inverse(rotation) * (hitPosition - position);
|
||||
|
||||
glm::vec2 halfDimensions = 0.5f * dimensions;
|
||||
|
||||
intersects = -halfDimensions.x <= localHitPosition.x && localHitPosition.x <= halfDimensions.x
|
||||
&& -halfDimensions.y <= localHitPosition.y && localHitPosition.y <= halfDimensions.y;
|
||||
}
|
||||
|
||||
return intersects;
|
||||
}
|
||||
|
|
|
@ -76,6 +76,9 @@ bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& directi
|
|||
bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& start, const glm::vec3& end, float radius, float& distance);
|
||||
|
||||
bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::quat& rotation, const glm::vec3& position, const glm::vec2& dimensions);
|
||||
|
||||
bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include <iostream>
|
||||
#ifdef _WINDOWS
|
||||
#include <winsock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
|
@ -76,7 +76,7 @@ void runSend(const char* addressOption, int port, int gap, int size, int report)
|
|||
|
||||
memset(&servaddr, 0, sizeof(servaddr));
|
||||
servaddr.sin_family = AF_INET;
|
||||
servaddr.sin_addr.s_addr = inet_addr(addressOption);
|
||||
inet_pton(AF_INET, addressOption, &servaddr.sin_addr);
|
||||
servaddr.sin_port = htons(port);
|
||||
|
||||
const int SAMPLES_FOR_SECOND = 1000000 / gap;
|
||||
|
|
Loading…
Reference in a new issue