mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 15:29:32 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into improve-rotation-jitter
This commit is contained in:
commit
a18940142e
34 changed files with 980 additions and 457 deletions
|
@ -57,6 +57,7 @@ Agent::Agent(ReceivedMessage& message) :
|
||||||
ThreadedAssignment(message),
|
ThreadedAssignment(message),
|
||||||
_receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES)
|
_receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES)
|
||||||
{
|
{
|
||||||
|
_entityEditSender.setPacketsPerSecond(DEFAULT_ENTITY_PPS_PER_SCRIPT);
|
||||||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
||||||
|
|
||||||
ResourceManager::init();
|
ResourceManager::init();
|
||||||
|
|
|
@ -24,9 +24,6 @@
|
||||||
#include <ScriptEngine.h>
|
#include <ScriptEngine.h>
|
||||||
#include <ThreadedAssignment.h>
|
#include <ThreadedAssignment.h>
|
||||||
|
|
||||||
static const int DEFAULT_MAX_ENTITY_PPS = 9000;
|
|
||||||
static const int DEFAULT_ENTITY_PPS_PER_SCRIPT = 900;
|
|
||||||
|
|
||||||
class EntityScriptServer : public ThreadedAssignment {
|
class EntityScriptServer : public ThreadedAssignment {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
|
|
@ -563,11 +563,8 @@ const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
|
||||||
const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
|
const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
|
||||||
const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false;
|
const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false;
|
||||||
|
|
||||||
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) :
|
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
QApplication(argc, argv),
|
QApplication(argc, argv),
|
||||||
_shouldRunServer(runServer),
|
|
||||||
_runServerPath(runServerPathOption),
|
|
||||||
_runningMarker(this, RUNNING_MARKER_FILENAME),
|
|
||||||
_window(new MainWindow(desktop())),
|
_window(new MainWindow(desktop())),
|
||||||
_sessionRunTimer(startupTimer),
|
_sessionRunTimer(startupTimer),
|
||||||
_previousSessionCrashed(setupEssentials(argc, argv)),
|
_previousSessionCrashed(setupEssentials(argc, argv)),
|
||||||
|
@ -622,8 +619,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
// make sure the debug draw singleton is initialized on the main thread.
|
// make sure the debug draw singleton is initialized on the main thread.
|
||||||
DebugDraw::getInstance().removeMarker("");
|
DebugDraw::getInstance().removeMarker("");
|
||||||
|
|
||||||
_runningMarker.startRunningMarker();
|
|
||||||
|
|
||||||
PluginContainer* pluginContainer = dynamic_cast<PluginContainer*>(this); // set the container for any plugins that care
|
PluginContainer* pluginContainer = dynamic_cast<PluginContainer*>(this); // set the container for any plugins that care
|
||||||
PluginManager::getInstance()->setContainer(pluginContainer);
|
PluginManager::getInstance()->setContainer(pluginContainer);
|
||||||
|
|
||||||
|
@ -675,38 +670,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
static const QString OCULUS_STORE_ARG = "--oculus-store";
|
static const QString OCULUS_STORE_ARG = "--oculus-store";
|
||||||
setProperty(hifi::properties::OCULUS_STORE, arguments().indexOf(OCULUS_STORE_ARG) != -1);
|
setProperty(hifi::properties::OCULUS_STORE, arguments().indexOf(OCULUS_STORE_ARG) != -1);
|
||||||
|
|
||||||
static const QString NO_UPDATER_ARG = "--no-updater";
|
|
||||||
static const bool noUpdater = arguments().indexOf(NO_UPDATER_ARG) != -1;
|
|
||||||
static const bool wantsSandboxRunning = shouldRunServer();
|
|
||||||
static bool determinedSandboxState = false;
|
|
||||||
static bool sandboxIsRunning = false;
|
|
||||||
SandboxUtils sandboxUtils;
|
|
||||||
// updateHeartbeat() because we are going to poll shortly...
|
|
||||||
updateHeartbeat();
|
updateHeartbeat();
|
||||||
sandboxUtils.ifLocalSandboxRunningElse([&]() {
|
|
||||||
qCDebug(interfaceapp) << "Home sandbox appears to be running.....";
|
|
||||||
determinedSandboxState = true;
|
|
||||||
sandboxIsRunning = true;
|
|
||||||
}, [&]() {
|
|
||||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running....";
|
|
||||||
if (wantsSandboxRunning) {
|
|
||||||
QString contentPath = getRunServerPath();
|
|
||||||
SandboxUtils::runLocalSandbox(contentPath, true, RUNNING_MARKER_FILENAME, noUpdater);
|
|
||||||
sandboxIsRunning = true;
|
|
||||||
}
|
|
||||||
determinedSandboxState = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// SandboxUtils::runLocalSandbox currently has 2 sec delay after spawning sandbox, so 4
|
|
||||||
// sec here is ok I guess. TODO: ping sandbox so we know it is up, perhaps?
|
|
||||||
quint64 MAX_WAIT_TIME = USECS_PER_SECOND * 4;
|
|
||||||
auto startWaiting = usecTimestampNow();
|
|
||||||
while (!determinedSandboxState && (usecTimestampNow() - startWaiting <= MAX_WAIT_TIME)) {
|
|
||||||
QCoreApplication::processEvents();
|
|
||||||
// updateHeartbeat() while polling so we don't scare the deadlock watchdog
|
|
||||||
updateHeartbeat();
|
|
||||||
usleep(USECS_PER_MSEC * 50); // 20hz
|
|
||||||
}
|
|
||||||
|
|
||||||
// start the nodeThread so its event loop is running
|
// start the nodeThread so its event loop is running
|
||||||
QThread* nodeThread = new QThread(this);
|
QThread* nodeThread = new QThread(this);
|
||||||
|
@ -1223,6 +1187,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// If launched from Steam, let it handle updates
|
// If launched from Steam, let it handle updates
|
||||||
|
const QString HIFI_NO_UPDATER_COMMAND_LINE_KEY = "--no-updater";
|
||||||
|
bool noUpdater = arguments().indexOf(HIFI_NO_UPDATER_COMMAND_LINE_KEY) != -1;
|
||||||
if (!noUpdater) {
|
if (!noUpdater) {
|
||||||
auto applicationUpdater = DependencyManager::get<AutoUpdater>();
|
auto applicationUpdater = DependencyManager::get<AutoUpdater>();
|
||||||
connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog);
|
connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog);
|
||||||
|
@ -1465,110 +1431,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
const auto testScript = property(hifi::properties::TEST).toUrl();
|
const auto testScript = property(hifi::properties::TEST).toUrl();
|
||||||
scriptEngines->loadScript(testScript, false);
|
scriptEngines->loadScript(testScript, false);
|
||||||
} else {
|
} else {
|
||||||
enum HandControllerType {
|
PROFILE_RANGE(render, "GetSandboxStatus");
|
||||||
Vive,
|
auto reply = SandboxUtils::getStatus();
|
||||||
Oculus
|
connect(reply, &QNetworkReply::finished, this, [=] {
|
||||||
};
|
handleSandboxStatus(reply);
|
||||||
static const std::map<HandControllerType, int> MIN_CONTENT_VERSION = {
|
});
|
||||||
{ Vive, 1 },
|
|
||||||
{ Oculus, 27 }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get sandbox content set version
|
|
||||||
auto acDirPath = PathUtils::getAppDataPath() + "../../" + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/";
|
|
||||||
auto contentVersionPath = acDirPath + "content-version.txt";
|
|
||||||
qCDebug(interfaceapp) << "Checking " << contentVersionPath << " for content version";
|
|
||||||
int contentVersion = 0;
|
|
||||||
QFile contentVersionFile(contentVersionPath);
|
|
||||||
if (contentVersionFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
||||||
QString line = contentVersionFile.readAll();
|
|
||||||
contentVersion = line.toInt(); // returns 0 if conversion fails
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get controller availability
|
|
||||||
bool hasHandControllers = false;
|
|
||||||
HandControllerType handControllerType = Vive;
|
|
||||||
if (PluginUtils::isViveControllerAvailable()) {
|
|
||||||
hasHandControllers = true;
|
|
||||||
handControllerType = Vive;
|
|
||||||
} else if (PluginUtils::isOculusTouchControllerAvailable()) {
|
|
||||||
hasHandControllers = true;
|
|
||||||
handControllerType = Oculus;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check tutorial content versioning
|
|
||||||
bool hasTutorialContent = contentVersion >= MIN_CONTENT_VERSION.at(handControllerType);
|
|
||||||
|
|
||||||
// Check HMD use (may be technically available without being in use)
|
|
||||||
bool hasHMD = PluginUtils::isHMDAvailable();
|
|
||||||
bool isUsingHMD = hasHMD && hasHandControllers && _displayPlugin->isHmd();
|
|
||||||
|
|
||||||
Setting::Handle<bool> tutorialComplete { "tutorialComplete", false };
|
|
||||||
Setting::Handle<bool> firstRun { Settings::firstRun, true };
|
|
||||||
|
|
||||||
bool isTutorialComplete = tutorialComplete.get();
|
|
||||||
bool shouldGoToTutorial = isUsingHMD && hasTutorialContent && !isTutorialComplete;
|
|
||||||
|
|
||||||
qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMD;
|
|
||||||
qCDebug(interfaceapp) << "Tutorial version:" << contentVersion << ", sufficient:" << hasTutorialContent <<
|
|
||||||
", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial;
|
|
||||||
|
|
||||||
// when --url in command line, teleport to location
|
|
||||||
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
|
|
||||||
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
|
|
||||||
QString addressLookupString;
|
|
||||||
if (urlIndex != -1) {
|
|
||||||
addressLookupString = arguments().value(urlIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString TUTORIAL_PATH = "/tutorial_begin";
|
|
||||||
|
|
||||||
if (shouldGoToTutorial) {
|
|
||||||
if (sandboxIsRunning) {
|
|
||||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
|
||||||
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
|
|
||||||
} else {
|
|
||||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
|
||||||
if (firstRun.get()) {
|
|
||||||
showHelp();
|
|
||||||
}
|
|
||||||
if (addressLookupString.isEmpty()) {
|
|
||||||
DependencyManager::get<AddressManager>()->goToEntry();
|
|
||||||
} else {
|
|
||||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
bool isFirstRun = firstRun.get();
|
|
||||||
|
|
||||||
if (isFirstRun) {
|
|
||||||
showHelp();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a first run we short-circuit the address passed in
|
|
||||||
if (isFirstRun) {
|
|
||||||
if (isUsingHMD) {
|
|
||||||
if (sandboxIsRunning) {
|
|
||||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
|
||||||
DependencyManager::get<AddressManager>()->goToLocalSandbox();
|
|
||||||
} else {
|
|
||||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
|
||||||
DependencyManager::get<AddressManager>()->goToEntry();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DependencyManager::get<AddressManager>()->goToEntry();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
|
|
||||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_connectionMonitor.init();
|
|
||||||
|
|
||||||
// After all of the constructor is completed, then set firstRun to false.
|
|
||||||
firstRun.set(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Monitor model assets (e.g., from Clara.io) added to the world that may need resizing.
|
// Monitor model assets (e.g., from Clara.io) added to the world that may need resizing.
|
||||||
|
@ -2474,6 +2341,118 @@ void Application::resizeGL() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::handleSandboxStatus(QNetworkReply* reply) {
|
||||||
|
PROFILE_RANGE(render, "HandleSandboxStatus");
|
||||||
|
|
||||||
|
bool sandboxIsRunning = SandboxUtils::readStatus(reply->readAll());
|
||||||
|
qDebug() << "HandleSandboxStatus" << sandboxIsRunning;
|
||||||
|
|
||||||
|
enum HandControllerType {
|
||||||
|
Vive,
|
||||||
|
Oculus
|
||||||
|
};
|
||||||
|
static const std::map<HandControllerType, int> MIN_CONTENT_VERSION = {
|
||||||
|
{ Vive, 1 },
|
||||||
|
{ Oculus, 27 }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get sandbox content set version
|
||||||
|
auto acDirPath = PathUtils::getAppDataPath() + "../../" + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/";
|
||||||
|
auto contentVersionPath = acDirPath + "content-version.txt";
|
||||||
|
qCDebug(interfaceapp) << "Checking " << contentVersionPath << " for content version";
|
||||||
|
int contentVersion = 0;
|
||||||
|
QFile contentVersionFile(contentVersionPath);
|
||||||
|
if (contentVersionFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
QString line = contentVersionFile.readAll();
|
||||||
|
contentVersion = line.toInt(); // returns 0 if conversion fails
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get controller availability
|
||||||
|
bool hasHandControllers = false;
|
||||||
|
HandControllerType handControllerType = Vive;
|
||||||
|
if (PluginUtils::isViveControllerAvailable()) {
|
||||||
|
hasHandControllers = true;
|
||||||
|
handControllerType = Vive;
|
||||||
|
} else if (PluginUtils::isOculusTouchControllerAvailable()) {
|
||||||
|
hasHandControllers = true;
|
||||||
|
handControllerType = Oculus;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check tutorial content versioning
|
||||||
|
bool hasTutorialContent = contentVersion >= MIN_CONTENT_VERSION.at(handControllerType);
|
||||||
|
|
||||||
|
// Check HMD use (may be technically available without being in use)
|
||||||
|
bool hasHMD = PluginUtils::isHMDAvailable();
|
||||||
|
bool isUsingHMD = hasHMD && hasHandControllers && _displayPlugin->isHmd();
|
||||||
|
|
||||||
|
Setting::Handle<bool> tutorialComplete{ "tutorialComplete", false };
|
||||||
|
Setting::Handle<bool> firstRun{ Settings::firstRun, true };
|
||||||
|
|
||||||
|
bool isTutorialComplete = tutorialComplete.get();
|
||||||
|
bool shouldGoToTutorial = isUsingHMD && hasTutorialContent && !isTutorialComplete;
|
||||||
|
|
||||||
|
qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMD;
|
||||||
|
qCDebug(interfaceapp) << "Tutorial version:" << contentVersion << ", sufficient:" << hasTutorialContent <<
|
||||||
|
", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial;
|
||||||
|
|
||||||
|
// when --url in command line, teleport to location
|
||||||
|
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
|
||||||
|
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
|
||||||
|
QString addressLookupString;
|
||||||
|
if (urlIndex != -1) {
|
||||||
|
addressLookupString = arguments().value(urlIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString TUTORIAL_PATH = "/tutorial_begin";
|
||||||
|
|
||||||
|
if (shouldGoToTutorial) {
|
||||||
|
if (sandboxIsRunning) {
|
||||||
|
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||||
|
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
|
||||||
|
} else {
|
||||||
|
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||||
|
if (firstRun.get()) {
|
||||||
|
showHelp();
|
||||||
|
}
|
||||||
|
if (addressLookupString.isEmpty()) {
|
||||||
|
DependencyManager::get<AddressManager>()->goToEntry();
|
||||||
|
} else {
|
||||||
|
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
bool isFirstRun = firstRun.get();
|
||||||
|
|
||||||
|
if (isFirstRun) {
|
||||||
|
showHelp();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a first run we short-circuit the address passed in
|
||||||
|
if (isFirstRun) {
|
||||||
|
if (isUsingHMD) {
|
||||||
|
if (sandboxIsRunning) {
|
||||||
|
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||||
|
DependencyManager::get<AddressManager>()->goToLocalSandbox();
|
||||||
|
} else {
|
||||||
|
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||||
|
DependencyManager::get<AddressManager>()->goToEntry();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DependencyManager::get<AddressManager>()->goToEntry();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
|
||||||
|
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_connectionMonitor.init();
|
||||||
|
|
||||||
|
// After all of the constructor is completed, then set firstRun to false.
|
||||||
|
firstRun.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
bool Application::importJSONFromURL(const QString& urlString) {
|
bool Application::importJSONFromURL(const QString& urlString) {
|
||||||
// we only load files that terminate in just .json (not .svo.json and not .ava.json)
|
// we only load files that terminate in just .json (not .svo.json and not .ava.json)
|
||||||
// if they come from the High Fidelity Marketplace Assets CDN
|
// if they come from the High Fidelity Marketplace Assets CDN
|
||||||
|
|
|
@ -112,17 +112,7 @@ class Application : public QApplication,
|
||||||
// TODO? Get rid of those
|
// TODO? Get rid of those
|
||||||
friend class OctreePacketProcessor;
|
friend class OctreePacketProcessor;
|
||||||
|
|
||||||
private:
|
|
||||||
bool _shouldRunServer { false };
|
|
||||||
QString _runServerPath;
|
|
||||||
RunningMarker _runningMarker;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// startup related getter/setters
|
|
||||||
bool shouldRunServer() const { return _shouldRunServer; }
|
|
||||||
bool hasRunServerPath() const { return !_runServerPath.isEmpty(); }
|
|
||||||
QString getRunServerPath() const { return _runServerPath; }
|
|
||||||
|
|
||||||
// virtual functions required for PluginContainer
|
// virtual functions required for PluginContainer
|
||||||
virtual ui::Menu* getPrimaryMenu() override;
|
virtual ui::Menu* getPrimaryMenu() override;
|
||||||
virtual void requestReset() override { resetSensors(true); }
|
virtual void requestReset() override { resetSensors(true); }
|
||||||
|
@ -146,7 +136,7 @@ public:
|
||||||
static void initPlugins(const QStringList& arguments);
|
static void initPlugins(const QStringList& arguments);
|
||||||
static void shutdownPlugins();
|
static void shutdownPlugins();
|
||||||
|
|
||||||
Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runServer, QString runServerPathOption);
|
Application(int& argc, char** argv, QElapsedTimer& startup_time);
|
||||||
~Application();
|
~Application();
|
||||||
|
|
||||||
void postLambdaEvent(std::function<void()> f) override;
|
void postLambdaEvent(std::function<void()> f) override;
|
||||||
|
@ -451,6 +441,8 @@ private slots:
|
||||||
void addAssetToWorldInfoTimeout();
|
void addAssetToWorldInfoTimeout();
|
||||||
void addAssetToWorldErrorTimeout();
|
void addAssetToWorldErrorTimeout();
|
||||||
|
|
||||||
|
void handleSandboxStatus(QNetworkReply* reply);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void initDisplay();
|
static void initDisplay();
|
||||||
void init();
|
void init();
|
||||||
|
|
|
@ -110,6 +110,9 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
|
||||||
_realWorldFieldOfView("realWorldFieldOfView",
|
_realWorldFieldOfView("realWorldFieldOfView",
|
||||||
DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
|
DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
|
||||||
_useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", false),
|
_useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", false),
|
||||||
|
_smoothOrientationTimer(std::numeric_limits<float>::max()),
|
||||||
|
_smoothOrientationInitial(),
|
||||||
|
_smoothOrientationTarget(),
|
||||||
_hmdSensorMatrix(),
|
_hmdSensorMatrix(),
|
||||||
_hmdSensorOrientation(),
|
_hmdSensorOrientation(),
|
||||||
_hmdSensorPosition(),
|
_hmdSensorPosition(),
|
||||||
|
@ -265,6 +268,17 @@ QVariant MyAvatar::getOrientationVar() const {
|
||||||
return quatToVariant(Avatar::getOrientation());
|
return quatToVariant(Avatar::getOrientation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::quat MyAvatar::getOrientationOutbound() const {
|
||||||
|
// Allows MyAvatar to send out smoothed data to remote agents if required.
|
||||||
|
if (_smoothOrientationTimer > SMOOTH_TIME_ORIENTATION) {
|
||||||
|
return (getLocalOrientation());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smooth the remote avatar movement.
|
||||||
|
float t = _smoothOrientationTimer / SMOOTH_TIME_ORIENTATION;
|
||||||
|
float interp = Interpolate::easeInOutQuad(glm::clamp(t, 0.0f, 1.0f));
|
||||||
|
return (slerp(_smoothOrientationInitial, _smoothOrientationTarget, interp));
|
||||||
|
}
|
||||||
|
|
||||||
// virtual
|
// virtual
|
||||||
void MyAvatar::simulateAttachments(float deltaTime) {
|
void MyAvatar::simulateAttachments(float deltaTime) {
|
||||||
|
@ -393,6 +407,11 @@ void MyAvatar::update(float deltaTime) {
|
||||||
float tau = deltaTime / HMD_FACING_TIMESCALE;
|
float tau = deltaTime / HMD_FACING_TIMESCALE;
|
||||||
_hmdSensorFacingMovingAverage = lerp(_hmdSensorFacingMovingAverage, _hmdSensorFacing, tau);
|
_hmdSensorFacingMovingAverage = lerp(_hmdSensorFacingMovingAverage, _hmdSensorFacing, tau);
|
||||||
|
|
||||||
|
if (_smoothOrientationTimer < SMOOTH_TIME_ORIENTATION) {
|
||||||
|
_rotationChanged = usecTimestampNow();
|
||||||
|
_smoothOrientationTimer += deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE
|
#ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE
|
||||||
glm::vec3 p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacingMovingAverage.x, 0.0f, _hmdSensorFacingMovingAverage.y));
|
glm::vec3 p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacingMovingAverage.x, 0.0f, _hmdSensorFacingMovingAverage.y));
|
||||||
DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f));
|
DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f));
|
||||||
|
@ -1780,8 +1799,10 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
||||||
// Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll
|
// Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll
|
||||||
// get an instantaneous 15 degree turn. If you keep holding the key down you'll get another
|
// get an instantaneous 15 degree turn. If you keep holding the key down you'll get another
|
||||||
// snap turn every half second.
|
// snap turn every half second.
|
||||||
|
bool snapTurn = false;
|
||||||
if (getDriveKey(STEP_YAW) != 0.0f) {
|
if (getDriveKey(STEP_YAW) != 0.0f) {
|
||||||
totalBodyYaw += getDriveKey(STEP_YAW);
|
totalBodyYaw += getDriveKey(STEP_YAW);
|
||||||
|
snapTurn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// use head/HMD orientation to turn while flying
|
// use head/HMD orientation to turn while flying
|
||||||
|
@ -1814,10 +1835,17 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
||||||
totalBodyYaw += (speedFactor * deltaAngle * (180.0f / PI));
|
totalBodyYaw += (speedFactor * deltaAngle * (180.0f / PI));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// update body orientation by movement inputs
|
// update body orientation by movement inputs
|
||||||
|
glm::quat initialOrientation = getOrientationOutbound();
|
||||||
setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
|
setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
|
||||||
|
|
||||||
|
if (snapTurn) {
|
||||||
|
// Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position.
|
||||||
|
_smoothOrientationInitial = initialOrientation;
|
||||||
|
_smoothOrientationTarget = getOrientation();
|
||||||
|
_smoothOrientationTimer = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime);
|
getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime);
|
||||||
|
|
||||||
if (qApp->isHMDMode()) {
|
if (qApp->isHMDMode()) {
|
||||||
|
|
|
@ -190,6 +190,8 @@ public:
|
||||||
Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar);
|
Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar);
|
||||||
Q_INVOKABLE QVariant getOrientationVar() const;
|
Q_INVOKABLE QVariant getOrientationVar() const;
|
||||||
|
|
||||||
|
// A method intended to be overriden by MyAvatar for polling orientation for network transmission.
|
||||||
|
glm::quat getOrientationOutbound() const override;
|
||||||
|
|
||||||
// Pass a recent sample of the HMD to the avatar.
|
// Pass a recent sample of the HMD to the avatar.
|
||||||
// This can also update the avatar's position to follow the HMD
|
// This can also update the avatar's position to follow the HMD
|
||||||
|
@ -633,6 +635,14 @@ private:
|
||||||
Setting::Handle<float> _realWorldFieldOfView;
|
Setting::Handle<float> _realWorldFieldOfView;
|
||||||
Setting::Handle<bool> _useAdvancedMovementControls;
|
Setting::Handle<bool> _useAdvancedMovementControls;
|
||||||
|
|
||||||
|
// Smoothing.
|
||||||
|
const float SMOOTH_TIME_ORIENTATION = 0.5f;
|
||||||
|
|
||||||
|
// Smoothing data for blending from one position/orientation to another on remote agents.
|
||||||
|
float _smoothOrientationTimer;
|
||||||
|
glm::quat _smoothOrientationInitial;
|
||||||
|
glm::quat _smoothOrientationTarget;
|
||||||
|
|
||||||
// private methods
|
// private methods
|
||||||
void updateOrientation(float deltaTime);
|
void updateOrientation(float deltaTime);
|
||||||
void updateActionMotor(float deltaTime);
|
void updateActionMotor(float deltaTime);
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
|
#include <QtCore/QProcess>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
|
@ -20,6 +21,7 @@
|
||||||
|
|
||||||
#include <BuildInfo.h>
|
#include <BuildInfo.h>
|
||||||
#include <gl/OpenGLVersionChecker.h>
|
#include <gl/OpenGLVersionChecker.h>
|
||||||
|
#include <SandboxUtils.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,7 +30,6 @@
|
||||||
#include "InterfaceLogging.h"
|
#include "InterfaceLogging.h"
|
||||||
#include "UserActivityLogger.h"
|
#include "UserActivityLogger.h"
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
#include <QtCore/QProcess>
|
|
||||||
|
|
||||||
#ifdef HAS_BUGSPLAT
|
#ifdef HAS_BUGSPLAT
|
||||||
#include <BugSplat.h>
|
#include <BugSplat.h>
|
||||||
|
@ -50,50 +51,49 @@ int main(int argc, const char* argv[]) {
|
||||||
|
|
||||||
disableQtBearerPoll(); // Fixes wifi ping spikes
|
disableQtBearerPoll(); // Fixes wifi ping spikes
|
||||||
|
|
||||||
|
QElapsedTimer startupTime;
|
||||||
|
startupTime.start();
|
||||||
|
|
||||||
// Set application infos
|
// Set application infos
|
||||||
QCoreApplication::setApplicationName(BuildInfo::INTERFACE_NAME);
|
QCoreApplication::setApplicationName(BuildInfo::INTERFACE_NAME);
|
||||||
QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION);
|
QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION);
|
||||||
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
||||||
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
||||||
|
|
||||||
const QString& applicationName = getInterfaceSharedMemoryName();
|
|
||||||
|
|
||||||
bool instanceMightBeRunning = true;
|
|
||||||
|
|
||||||
QStringList arguments;
|
QStringList arguments;
|
||||||
for (int i = 0; i < argc; ++i) {
|
for (int i = 0; i < argc; ++i) {
|
||||||
arguments << argv[i];
|
arguments << argv[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
// Try to create a shared memory block - if it can't be created, there is an instance of
|
|
||||||
// interface already running. We only do this on Windows for now because of the potential
|
|
||||||
// for crashed instances to leave behind shared memory instances on unix.
|
|
||||||
QSharedMemory sharedMemory { applicationName };
|
|
||||||
instanceMightBeRunning = !sharedMemory.create(1, QSharedMemory::ReadOnly);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// allow multiple interfaces to run if this environment variable is set.
|
|
||||||
if (QProcessEnvironment::systemEnvironment().contains("HIFI_ALLOW_MULTIPLE_INSTANCES")) {
|
|
||||||
instanceMightBeRunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
|
QCommandLineOption urlOption("url", "", "value");
|
||||||
|
QCommandLineOption noUpdaterOption("no-updater", "Do not show auto-updater");
|
||||||
QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications");
|
QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications");
|
||||||
QCommandLineOption runServerOption("runServer", "Whether to run the server");
|
QCommandLineOption runServerOption("runServer", "Whether to run the server");
|
||||||
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath");
|
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath");
|
||||||
QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run");
|
QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run");
|
||||||
|
parser.addOption(urlOption);
|
||||||
|
parser.addOption(noUpdaterOption);
|
||||||
parser.addOption(checkMinSpecOption);
|
parser.addOption(checkMinSpecOption);
|
||||||
parser.addOption(runServerOption);
|
parser.addOption(runServerOption);
|
||||||
parser.addOption(serverContentPathOption);
|
parser.addOption(serverContentPathOption);
|
||||||
parser.addOption(allowMultipleInstancesOption);
|
parser.addOption(allowMultipleInstancesOption);
|
||||||
parser.parse(arguments);
|
parser.parse(arguments);
|
||||||
bool runServer = parser.isSet(runServerOption);
|
|
||||||
bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption);
|
|
||||||
QString serverContentPathOptionValue = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString();
|
|
||||||
bool allowMultipleInstances = parser.isSet(allowMultipleInstancesOption);
|
|
||||||
|
|
||||||
|
|
||||||
|
const QString& applicationName = getInterfaceSharedMemoryName();
|
||||||
|
bool instanceMightBeRunning = true;
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
// Try to create a shared memory block - if it can't be created, there is an instance of
|
||||||
|
// interface already running. We only do this on Windows for now because of the potential
|
||||||
|
// for crashed instances to leave behind shared memory instances on unix.
|
||||||
|
QSharedMemory sharedMemory{ applicationName };
|
||||||
|
instanceMightBeRunning = !sharedMemory.create(1, QSharedMemory::ReadOnly);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// allow multiple interfaces to run if this environment variable is set.
|
||||||
|
bool allowMultipleInstances = parser.isSet(allowMultipleInstancesOption) ||
|
||||||
|
QProcessEnvironment::systemEnvironment().contains("HIFI_ALLOW_MULTIPLE_INSTANCES");
|
||||||
if (allowMultipleInstances) {
|
if (allowMultipleInstances) {
|
||||||
instanceMightBeRunning = false;
|
instanceMightBeRunning = false;
|
||||||
}
|
}
|
||||||
|
@ -108,11 +108,6 @@ int main(int argc, const char* argv[]) {
|
||||||
|
|
||||||
// Try to connect - if we can't connect, interface has probably just gone down
|
// Try to connect - if we can't connect, interface has probably just gone down
|
||||||
if (socket.waitForConnected(LOCAL_SERVER_TIMEOUT_MS)) {
|
if (socket.waitForConnected(LOCAL_SERVER_TIMEOUT_MS)) {
|
||||||
QCommandLineParser parser;
|
|
||||||
QCommandLineOption urlOption("url", "", "value");
|
|
||||||
parser.addOption(urlOption);
|
|
||||||
parser.process(arguments);
|
|
||||||
|
|
||||||
if (parser.isSet(urlOption)) {
|
if (parser.isSet(urlOption)) {
|
||||||
QUrl url = QUrl(parser.value(urlOption));
|
QUrl url = QUrl(parser.value(urlOption));
|
||||||
if (url.isValid() && url.scheme() == HIFI_URL_SCHEME) {
|
if (url.isValid() && url.scheme() == HIFI_URL_SCHEME) {
|
||||||
|
@ -156,9 +151,6 @@ int main(int argc, const char* argv[]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QElapsedTimer startupTime;
|
|
||||||
startupTime.start();
|
|
||||||
|
|
||||||
// Debug option to demonstrate that the client's local time does not
|
// Debug option to demonstrate that the client's local time does not
|
||||||
// need to be in sync with any other network node. This forces clock
|
// need to be in sync with any other network node. This forces clock
|
||||||
// skew for the individual client
|
// skew for the individual client
|
||||||
|
@ -199,7 +191,21 @@ int main(int argc, const char* argv[]) {
|
||||||
|
|
||||||
int exitCode;
|
int exitCode;
|
||||||
{
|
{
|
||||||
Application app(argc, const_cast<char**>(argv), startupTime, runServer, serverContentPathOptionValue);
|
RunningMarker runningMarker(nullptr, RUNNING_MARKER_FILENAME);
|
||||||
|
runningMarker.writeRunningMarkerFile();
|
||||||
|
|
||||||
|
bool noUpdater = parser.isSet(noUpdaterOption);
|
||||||
|
bool runServer = parser.isSet(runServerOption);
|
||||||
|
bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption);
|
||||||
|
QString serverContentPath = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString();
|
||||||
|
if (runServer) {
|
||||||
|
SandboxUtils::runLocalSandbox(serverContentPath, true, RUNNING_MARKER_FILENAME, noUpdater);
|
||||||
|
}
|
||||||
|
|
||||||
|
Application app(argc, const_cast<char**>(argv), startupTime);
|
||||||
|
|
||||||
|
// Now that the main event loop is setup, launch running marker thread
|
||||||
|
runningMarker.startRunningMarker();
|
||||||
|
|
||||||
// If we failed the OpenGLVersion check, log it.
|
// If we failed the OpenGLVersion check, log it.
|
||||||
if (override) {
|
if (override) {
|
||||||
|
|
|
@ -338,34 +338,6 @@ void Avatar::simulate(float deltaTime, bool inView) {
|
||||||
_simulationInViewRate.increment();
|
_simulationInViewRate.increment();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isMyAvatar()) {
|
|
||||||
if (_smoothPositionTimer < _smoothPositionTime) {
|
|
||||||
// Smooth the remote avatar movement.
|
|
||||||
_smoothPositionTimer += deltaTime;
|
|
||||||
if (_smoothPositionTimer < _smoothPositionTime) {
|
|
||||||
AvatarData::setPosition(
|
|
||||||
lerp(_smoothPositionInitial,
|
|
||||||
_smoothPositionTarget,
|
|
||||||
easeInOutQuad(glm::clamp(_smoothPositionTimer / _smoothPositionTime, 0.0f, 1.0f)))
|
|
||||||
);
|
|
||||||
updateAttitude();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_smoothOrientationTimer < _smoothOrientationTime) {
|
|
||||||
// Smooth the remote avatar movement.
|
|
||||||
_smoothOrientationTimer += deltaTime;
|
|
||||||
if (_smoothOrientationTimer < _smoothOrientationTime) {
|
|
||||||
AvatarData::setOrientation(
|
|
||||||
slerp(_smoothOrientationInitial,
|
|
||||||
_smoothOrientationTarget,
|
|
||||||
easeInOutQuad(glm::clamp(_smoothOrientationTimer / _smoothOrientationTime, 0.0f, 1.0f)))
|
|
||||||
);
|
|
||||||
updateAttitude();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PerformanceTimer perfTimer("simulate");
|
PerformanceTimer perfTimer("simulate");
|
||||||
{
|
{
|
||||||
PROFILE_RANGE(simulation, "updateJoints");
|
PROFILE_RANGE(simulation, "updateJoints");
|
||||||
|
@ -1371,31 +1343,13 @@ glm::quat Avatar::getUncachedRightPalmRotation() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Avatar::setPosition(const glm::vec3& position) {
|
void Avatar::setPosition(const glm::vec3& position) {
|
||||||
if (isMyAvatar()) {
|
AvatarData::setPosition(position);
|
||||||
// This is the local avatar, no need to handle any position smoothing.
|
updateAttitude();
|
||||||
AvatarData::setPosition(position);
|
|
||||||
updateAttitude();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position.
|
|
||||||
_smoothPositionInitial = getPosition();
|
|
||||||
_smoothPositionTarget = position;
|
|
||||||
_smoothPositionTimer = 0.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Avatar::setOrientation(const glm::quat& orientation) {
|
void Avatar::setOrientation(const glm::quat& orientation) {
|
||||||
if (isMyAvatar()) {
|
AvatarData::setOrientation(orientation);
|
||||||
// This is the local avatar, no need to handle any position smoothing.
|
updateAttitude();
|
||||||
AvatarData::setOrientation(orientation);
|
|
||||||
updateAttitude();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position.
|
|
||||||
_smoothOrientationInitial = getOrientation();
|
|
||||||
_smoothOrientationTarget = orientation;
|
|
||||||
_smoothOrientationTimer = 0.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Avatar::updatePalms() {
|
void Avatar::updatePalms() {
|
||||||
|
|
|
@ -36,7 +36,6 @@ namespace render {
|
||||||
}
|
}
|
||||||
|
|
||||||
static const float SCALING_RATIO = .05f;
|
static const float SCALING_RATIO = .05f;
|
||||||
static const float SMOOTHING_RATIO = .05f; // 0 < ratio < 1
|
|
||||||
|
|
||||||
extern const float CHAT_MESSAGE_SCALE;
|
extern const float CHAT_MESSAGE_SCALE;
|
||||||
extern const float CHAT_MESSAGE_HEIGHT;
|
extern const float CHAT_MESSAGE_HEIGHT;
|
||||||
|
@ -239,17 +238,8 @@ public:
|
||||||
|
|
||||||
bool hasNewJointData() const { return _hasNewJointData; }
|
bool hasNewJointData() const { return _hasNewJointData; }
|
||||||
|
|
||||||
inline float easeInOutQuad(float lerpValue) {
|
|
||||||
assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f)));
|
|
||||||
|
|
||||||
if (lerpValue < 0.5f) {
|
|
||||||
return (2.0f * lerpValue * lerpValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f);
|
|
||||||
}
|
|
||||||
float getBoundingRadius() const;
|
float getBoundingRadius() const;
|
||||||
|
|
||||||
void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene);
|
void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene);
|
||||||
void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene);
|
void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene);
|
||||||
bool isInScene() const { return render::Item::isValidID(_renderItemID); }
|
bool isInScene() const { return render::Item::isValidID(_renderItemID); }
|
||||||
|
@ -271,9 +261,6 @@ public slots:
|
||||||
void setModelURLFinished(bool success);
|
void setModelURLFinished(bool success);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const float SMOOTH_TIME_POSITION = 0.125f;
|
|
||||||
const float SMOOTH_TIME_ORIENTATION = 0.075f;
|
|
||||||
|
|
||||||
virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send.
|
virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send.
|
||||||
QString _empty{};
|
QString _empty{};
|
||||||
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter!
|
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter!
|
||||||
|
@ -336,16 +323,6 @@ protected:
|
||||||
RateCounter<> _skeletonModelSimulationRate;
|
RateCounter<> _skeletonModelSimulationRate;
|
||||||
RateCounter<> _jointDataSimulationRate;
|
RateCounter<> _jointDataSimulationRate;
|
||||||
|
|
||||||
// Smoothing data for blending from one position/orientation to another on remote agents.
|
|
||||||
float _smoothPositionTime { SMOOTH_TIME_POSITION };
|
|
||||||
float _smoothPositionTimer { std::numeric_limits<float>::max() };
|
|
||||||
float _smoothOrientationTime { SMOOTH_TIME_ORIENTATION };
|
|
||||||
float _smoothOrientationTimer { std::numeric_limits<float>::max() };
|
|
||||||
glm::vec3 _smoothPositionInitial;
|
|
||||||
glm::vec3 _smoothPositionTarget;
|
|
||||||
glm::quat _smoothOrientationInitial;
|
|
||||||
glm::quat _smoothOrientationTarget;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class AvatarEntityDataHash {
|
class AvatarEntityDataHash {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -310,7 +310,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
||||||
|
|
||||||
if (hasAvatarOrientation) {
|
if (hasAvatarOrientation) {
|
||||||
auto startSection = destinationBuffer;
|
auto startSection = destinationBuffer;
|
||||||
auto localOrientation = getLocalOrientation();
|
auto localOrientation = getOrientationOutbound();
|
||||||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation);
|
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation);
|
||||||
|
|
||||||
int numBytes = destinationBuffer - startSection;
|
int numBytes = destinationBuffer - startSection;
|
||||||
|
@ -1489,6 +1489,10 @@ void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& ide
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::quat AvatarData::getOrientationOutbound() const {
|
||||||
|
return (getLocalOrientation());
|
||||||
|
}
|
||||||
|
|
||||||
static const QUrl emptyURL("");
|
static const QUrl emptyURL("");
|
||||||
QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const {
|
QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const {
|
||||||
// We don't put file urls on the wire, but instead convert to empty.
|
// We don't put file urls on the wire, but instead convert to empty.
|
||||||
|
@ -2052,11 +2056,13 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
|
||||||
setSkeletonModelURL(bodyModelURL);
|
setSkeletonModelURL(bodyModelURL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString newDisplayName = "";
|
||||||
if (json.contains(JSON_AVATAR_DISPLAY_NAME)) {
|
if (json.contains(JSON_AVATAR_DISPLAY_NAME)) {
|
||||||
auto newDisplayName = json[JSON_AVATAR_DISPLAY_NAME].toString();
|
newDisplayName = json[JSON_AVATAR_DISPLAY_NAME].toString();
|
||||||
if (newDisplayName != getDisplayName()) {
|
}
|
||||||
setDisplayName(newDisplayName);
|
if (newDisplayName != getDisplayName()) {
|
||||||
}
|
setDisplayName(newDisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto currentBasis = getRecordingBasis();
|
auto currentBasis = getRecordingBasis();
|
||||||
|
@ -2086,14 +2092,16 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
|
||||||
setTargetScale((float)json[JSON_AVATAR_SCALE].toDouble());
|
setTargetScale((float)json[JSON_AVATAR_SCALE].toDouble());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVector<AttachmentData> attachments;
|
||||||
if (json.contains(JSON_AVATAR_ATTACHMENTS) && json[JSON_AVATAR_ATTACHMENTS].isArray()) {
|
if (json.contains(JSON_AVATAR_ATTACHMENTS) && json[JSON_AVATAR_ATTACHMENTS].isArray()) {
|
||||||
QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHMENTS].toArray();
|
QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHMENTS].toArray();
|
||||||
QVector<AttachmentData> attachments;
|
|
||||||
for (auto attachmentJson : attachmentsJson) {
|
for (auto attachmentJson : attachmentsJson) {
|
||||||
AttachmentData attachment;
|
AttachmentData attachment;
|
||||||
attachment.fromJson(attachmentJson.toObject());
|
attachment.fromJson(attachmentJson.toObject());
|
||||||
attachments.push_back(attachment);
|
attachments.push_back(attachment);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (attachments != getAttachmentData()) {
|
||||||
setAttachmentData(attachments);
|
setAttachmentData(attachments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -604,6 +604,9 @@ public:
|
||||||
return _lastSentJointData;
|
return _lastSentJointData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A method intended to be overriden by MyAvatar for polling orientation for network transmission.
|
||||||
|
virtual glm::quat getOrientationOutbound() const;
|
||||||
|
|
||||||
static const float OUT_OF_VIEW_PENALTY;
|
static const float OUT_OF_VIEW_PENALTY;
|
||||||
|
|
||||||
static void sortAvatars(
|
static void sortAvatars(
|
||||||
|
|
|
@ -662,6 +662,25 @@ QVector<QUuid> EntityScriptingInterface::findEntitiesInFrustum(QVariantMap frust
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVector<QUuid> EntityScriptingInterface::findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const {
|
||||||
|
EntityTypes::EntityType type = EntityTypes::getEntityTypeFromName(entityType);
|
||||||
|
|
||||||
|
QVector<QUuid> result;
|
||||||
|
if (_entityTree) {
|
||||||
|
QVector<EntityItemPointer> entities;
|
||||||
|
_entityTree->withReadLock([&] {
|
||||||
|
_entityTree->findEntities(center, radius, entities);
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach(EntityItemPointer entity, entities) {
|
||||||
|
if (entity->getType() == type) {
|
||||||
|
result << entity->getEntityItemID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking,
|
RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking,
|
||||||
const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) {
|
const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) {
|
||||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||||
|
|
|
@ -212,9 +212,16 @@ public slots:
|
||||||
/// - orientation
|
/// - orientation
|
||||||
/// - projection
|
/// - projection
|
||||||
/// - centerRadius
|
/// - centerRadius
|
||||||
/// this function will not find any models in script engine contexts which don't have access to models
|
/// this function will not find any models in script engine contexts which don't have access to entities
|
||||||
Q_INVOKABLE QVector<QUuid> findEntitiesInFrustum(QVariantMap frustum) const;
|
Q_INVOKABLE QVector<QUuid> findEntitiesInFrustum(QVariantMap frustum) const;
|
||||||
|
|
||||||
|
/// finds entities of the indicated type within a sphere given by the center point and radius
|
||||||
|
/// @param {QString} string representation of entity type
|
||||||
|
/// @param {vec3} center point
|
||||||
|
/// @param {float} radius to search
|
||||||
|
/// this function will not find any entities in script engine contexts which don't have access to entities
|
||||||
|
Q_INVOKABLE QVector<QUuid> findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const;
|
||||||
|
|
||||||
/// If the scripting context has visible entities, this will determine a ray intersection, the results
|
/// If the scripting context has visible entities, this will determine a ray intersection, the results
|
||||||
/// may be inaccurate if the engine is unable to access the visible entities, in which case result.accurate
|
/// may be inaccurate if the engine is unable to access the visible entities, in which case result.accurate
|
||||||
/// will be false.
|
/// will be false.
|
||||||
|
|
|
@ -452,6 +452,13 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign
|
||||||
|
|
||||||
// NOTE: callers must lock the tree before using this method
|
// NOTE: callers must lock the tree before using this method
|
||||||
DeleteEntityOperator theOperator(getThisPointer(), entityID);
|
DeleteEntityOperator theOperator(getThisPointer(), entityID);
|
||||||
|
|
||||||
|
existingEntity->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||||
|
auto descendantID = descendant->getID();
|
||||||
|
theOperator.addEntityIDToDeleteList(descendantID);
|
||||||
|
emit deletingEntity(descendantID);
|
||||||
|
});
|
||||||
|
|
||||||
recurseTreeWithOperator(&theOperator);
|
recurseTreeWithOperator(&theOperator);
|
||||||
processRemovedEntities(theOperator);
|
processRemovedEntities(theOperator);
|
||||||
_isDirty = true;
|
_isDirty = true;
|
||||||
|
|
|
@ -9,63 +9,52 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <QDataStream>
|
#include "SandboxUtils.h"
|
||||||
#include <QDebug>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QThread>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
#include <RunningMarker.h>
|
#include <RunningMarker.h>
|
||||||
|
|
||||||
#include "SandboxUtils.h"
|
|
||||||
#include "NetworkAccessManager.h"
|
#include "NetworkAccessManager.h"
|
||||||
#include "NetworkLogging.h"
|
#include "NetworkLogging.h"
|
||||||
|
|
||||||
|
namespace SandboxUtils {
|
||||||
|
|
||||||
void SandboxUtils::ifLocalSandboxRunningElse(std::function<void()> localSandboxRunningDoThis,
|
QNetworkReply* getStatus() {
|
||||||
std::function<void()> localSandboxNotRunningDoThat) {
|
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||||
|
|
||||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
|
||||||
QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL);
|
QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL);
|
||||||
sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||||
sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||||
QNetworkReply* reply = networkAccessManager.get(sandboxStatus);
|
return networkAccessManager.get(sandboxStatus);
|
||||||
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [reply, localSandboxRunningDoThis, localSandboxNotRunningDoThat]() {
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
|
||||||
auto statusData = reply->readAll();
|
|
||||||
auto statusJson = QJsonDocument::fromJson(statusData);
|
|
||||||
if (!statusJson.isEmpty()) {
|
|
||||||
auto statusObject = statusJson.object();
|
|
||||||
auto serversValue = statusObject.value("servers");
|
|
||||||
if (!serversValue.isUndefined() && serversValue.isObject()) {
|
|
||||||
auto serversObject = serversValue.toObject();
|
|
||||||
auto serversCount = serversObject.size();
|
|
||||||
const int MINIMUM_EXPECTED_SERVER_COUNT = 5;
|
|
||||||
if (serversCount >= MINIMUM_EXPECTED_SERVER_COUNT) {
|
|
||||||
localSandboxRunningDoThis();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
localSandboxNotRunningDoThat();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool readStatus(QByteArray statusData) {
|
||||||
|
auto statusJson = QJsonDocument::fromJson(statusData);
|
||||||
|
|
||||||
void SandboxUtils::runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater) {
|
if (!statusJson.isEmpty()) {
|
||||||
QString applicationDirPath = QFileInfo(QCoreApplication::applicationFilePath()).path();
|
auto statusObject = statusJson.object();
|
||||||
QString serverPath = applicationDirPath + "/server-console/server-console.exe";
|
auto serversValue = statusObject.value("servers");
|
||||||
qCDebug(networking) << "Application dir path is: " << applicationDirPath;
|
if (!serversValue.isUndefined() && serversValue.isObject()) {
|
||||||
|
auto serversObject = serversValue.toObject();
|
||||||
|
auto serversCount = serversObject.size();
|
||||||
|
const int MINIMUM_EXPECTED_SERVER_COUNT = 5;
|
||||||
|
if (serversCount >= MINIMUM_EXPECTED_SERVER_COUNT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater) {
|
||||||
|
QString serverPath = "./server-console/server-console.exe";
|
||||||
|
qCDebug(networking) << "Running marker path is: " << runningMarkerName;
|
||||||
qCDebug(networking) << "Server path is: " << serverPath;
|
qCDebug(networking) << "Server path is: " << serverPath;
|
||||||
qCDebug(networking) << "autoShutdown: " << autoShutdown;
|
qCDebug(networking) << "autoShutdown: " << autoShutdown;
|
||||||
qCDebug(networking) << "noUpdater: " << noUpdater;
|
qCDebug(networking) << "noUpdater: " << noUpdater;
|
||||||
|
@ -80,7 +69,7 @@ void SandboxUtils::runLocalSandbox(QString contentPath, bool autoShutdown, QStri
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasContentPath) {
|
if (hasContentPath) {
|
||||||
QString serverContentPath = applicationDirPath + "/" + contentPath;
|
QString serverContentPath = "./" + contentPath;
|
||||||
args << "--contentPath" << serverContentPath;
|
args << "--contentPath" << serverContentPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,10 +82,8 @@ void SandboxUtils::runLocalSandbox(QString contentPath, bool autoShutdown, QStri
|
||||||
args << "--noUpdater";
|
args << "--noUpdater";
|
||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(networking) << applicationDirPath;
|
|
||||||
qCDebug(networking) << "Launching sandbox with:" << args;
|
qCDebug(networking) << "Launching sandbox with:" << args;
|
||||||
qCDebug(networking) << QProcess::startDetached(serverPath, args);
|
qCDebug(networking) << QProcess::startDetached(serverPath, args);
|
||||||
|
}
|
||||||
// Sleep a short amount of time to give the server a chance to start
|
|
||||||
usleep(2000000); /// do we really need this??
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,21 +12,16 @@
|
||||||
#ifndef hifi_SandboxUtils_h
|
#ifndef hifi_SandboxUtils_h
|
||||||
#define hifi_SandboxUtils_h
|
#define hifi_SandboxUtils_h
|
||||||
|
|
||||||
#include <functional>
|
#include <QtCore/QString>
|
||||||
#include <QtCore/QObject>
|
|
||||||
|
|
||||||
|
class QNetworkReply;
|
||||||
|
|
||||||
const QString SANDBOX_STATUS_URL = "http://localhost:60332/status";
|
namespace SandboxUtils {
|
||||||
|
const QString SANDBOX_STATUS_URL = "http://localhost:60332/status";
|
||||||
|
|
||||||
class SandboxUtils : public QObject {
|
QNetworkReply* getStatus();
|
||||||
Q_OBJECT
|
bool readStatus(QByteArray statusData);
|
||||||
public:
|
void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater);
|
||||||
/// determines if the local sandbox is likely running. It does not account for custom setups, and is only
|
|
||||||
/// intended to detect the standard local sandbox install.
|
|
||||||
void ifLocalSandboxRunningElse(std::function<void()> localSandboxRunningDoThis,
|
|
||||||
std::function<void()> localSandboxNotRunningDoThat);
|
|
||||||
|
|
||||||
static void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_SandboxUtils_h
|
#endif // hifi_SandboxUtils_h
|
||||||
|
|
|
@ -376,7 +376,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
||||||
glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f));
|
glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f));
|
||||||
glm::vec3 meshFrameDirection = glm::vec3(worldToMeshMatrix * glm::vec4(direction, 0.0f));
|
glm::vec3 meshFrameDirection = glm::vec3(worldToMeshMatrix * glm::vec4(direction, 0.0f));
|
||||||
|
|
||||||
for (const auto& triangleSet : _modelSpaceMeshTriangleSets) {
|
for (auto& triangleSet : _modelSpaceMeshTriangleSets) {
|
||||||
float triangleSetDistance = 0.0f;
|
float triangleSetDistance = 0.0f;
|
||||||
BoxFace triangleSetFace;
|
BoxFace triangleSetFace;
|
||||||
glm::vec3 triangleSetNormal;
|
glm::vec3 triangleSetNormal;
|
||||||
|
@ -1052,7 +1052,6 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||||
|
|
||||||
void Model::computeMeshPartLocalBounds() {
|
void Model::computeMeshPartLocalBounds() {
|
||||||
for (auto& part : _modelMeshRenderItems) {
|
for (auto& part : _modelMeshRenderItems) {
|
||||||
assert(part->_meshIndex < _modelMeshRenderItems.size());
|
|
||||||
const Model::MeshState& state = _meshStates.at(part->_meshIndex);
|
const Model::MeshState& state = _meshStates.at(part->_meshIndex);
|
||||||
part->computeAdjustedLocalBound(state.clusterMatrices);
|
part->computeAdjustedLocalBound(state.clusterMatrices);
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,8 @@ class QScriptEngineDebugger;
|
||||||
static const QString NO_SCRIPT("");
|
static const QString NO_SCRIPT("");
|
||||||
|
|
||||||
static const int SCRIPT_FPS = 60;
|
static const int SCRIPT_FPS = 60;
|
||||||
|
static const int DEFAULT_MAX_ENTITY_PPS = 9000;
|
||||||
|
static const int DEFAULT_ENTITY_PPS_PER_SCRIPT = 900;
|
||||||
|
|
||||||
class CallbackData {
|
class CallbackData {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -114,6 +114,10 @@ static bool isWithin(float value, float corner, float size) {
|
||||||
return value >= corner && value <= corner + size;
|
return value >= corner && value <= corner + size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AABox::contains(const Triangle& triangle) const {
|
||||||
|
return contains(triangle.v0) && contains(triangle.v1) && contains(triangle.v2);
|
||||||
|
}
|
||||||
|
|
||||||
bool AABox::contains(const glm::vec3& point) const {
|
bool AABox::contains(const glm::vec3& point) const {
|
||||||
return isWithin(point.x, _corner.x, _scale.x) &&
|
return isWithin(point.x, _corner.x, _scale.x) &&
|
||||||
isWithin(point.y, _corner.y, _scale.y) &&
|
isWithin(point.y, _corner.y, _scale.y) &&
|
||||||
|
@ -622,3 +626,40 @@ void AABox::transform(const glm::mat4& matrix) {
|
||||||
_corner = newCenter - newDir;
|
_corner = newCenter - newDir;
|
||||||
_scale = newDir * 2.0f;
|
_scale = newDir * 2.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AABox AABox::getOctreeChild(OctreeChild child) const {
|
||||||
|
AABox result(*this); // self
|
||||||
|
switch (child) {
|
||||||
|
case topLeftNear:
|
||||||
|
result._corner.y += _scale.y / 2.0f;
|
||||||
|
break;
|
||||||
|
case topLeftFar:
|
||||||
|
result._corner.y += _scale.y / 2.0f;
|
||||||
|
result._corner.z += _scale.z / 2.0f;
|
||||||
|
break;
|
||||||
|
case topRightNear:
|
||||||
|
result._corner.y += _scale.y / 2.0f;
|
||||||
|
result._corner.x += _scale.x / 2.0f;
|
||||||
|
break;
|
||||||
|
case topRightFar:
|
||||||
|
result._corner.y += _scale.y / 2.0f;
|
||||||
|
result._corner.x += _scale.x / 2.0f;
|
||||||
|
result._corner.z += _scale.z / 2.0f;
|
||||||
|
break;
|
||||||
|
case bottomLeftNear:
|
||||||
|
// _corner = same as parent
|
||||||
|
break;
|
||||||
|
case bottomLeftFar:
|
||||||
|
result._corner.z += _scale.z / 2.0f;
|
||||||
|
break;
|
||||||
|
case bottomRightNear:
|
||||||
|
result._corner.x += _scale.x / 2.0f;
|
||||||
|
break;
|
||||||
|
case bottomRightFar:
|
||||||
|
result._corner.x += _scale.x / 2.0f;
|
||||||
|
result._corner.z += _scale.z / 2.0f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result._scale /= 2.0f; // everything is half the scale
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
#include "BoxBase.h"
|
#include "BoxBase.h"
|
||||||
|
#include "GeometryUtil.h"
|
||||||
#include "StreamUtils.h"
|
#include "StreamUtils.h"
|
||||||
|
|
||||||
class AACube;
|
class AACube;
|
||||||
|
@ -58,6 +59,7 @@ public:
|
||||||
const glm::vec3& getMinimumPoint() const { return _corner; }
|
const glm::vec3& getMinimumPoint() const { return _corner; }
|
||||||
glm::vec3 getMaximumPoint() const { return calcTopFarLeft(); }
|
glm::vec3 getMaximumPoint() const { return calcTopFarLeft(); }
|
||||||
|
|
||||||
|
bool contains(const Triangle& triangle) const;
|
||||||
bool contains(const glm::vec3& point) const;
|
bool contains(const glm::vec3& point) const;
|
||||||
bool contains(const AABox& otherBox) const;
|
bool contains(const AABox& otherBox) const;
|
||||||
bool touches(const AABox& otherBox) const;
|
bool touches(const AABox& otherBox) const;
|
||||||
|
@ -112,6 +114,19 @@ public:
|
||||||
|
|
||||||
void clear() { _corner = INFINITY_VECTOR; _scale = glm::vec3(0.0f); }
|
void clear() { _corner = INFINITY_VECTOR; _scale = glm::vec3(0.0f); }
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
topLeftNear,
|
||||||
|
topLeftFar,
|
||||||
|
topRightNear,
|
||||||
|
topRightFar,
|
||||||
|
bottomLeftNear,
|
||||||
|
bottomLeftFar,
|
||||||
|
bottomRightNear,
|
||||||
|
bottomRightFar
|
||||||
|
} OctreeChild;
|
||||||
|
|
||||||
|
AABox getOctreeChild(OctreeChild child) const; // returns the AABox of the would be octree child of this AABox
|
||||||
|
|
||||||
private:
|
private:
|
||||||
glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const;
|
glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const;
|
||||||
glm::vec3 getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const;
|
glm::vec3 getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const;
|
||||||
|
|
|
@ -77,3 +77,13 @@ float Interpolate::calculateFadeRatio(quint64 start) {
|
||||||
const float EASING_SCALE = 1.001f;
|
const float EASING_SCALE = 1.001f;
|
||||||
return std::min(EASING_SCALE * fadeRatio, 1.0f);
|
return std::min(EASING_SCALE * fadeRatio, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float Interpolate::easeInOutQuad(float lerpValue) {
|
||||||
|
assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f)));
|
||||||
|
|
||||||
|
if (lerpValue < 0.5f) {
|
||||||
|
return (2.0f * lerpValue * lerpValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f);
|
||||||
|
}
|
|
@ -30,6 +30,9 @@ public:
|
||||||
static float simpleNonLinearBlend(float fraction);
|
static float simpleNonLinearBlend(float fraction);
|
||||||
|
|
||||||
static float calculateFadeRatio(quint64 start);
|
static float calculateFadeRatio(quint64 start);
|
||||||
|
|
||||||
|
// Basic ease-in-ease-out function for smoothing values.
|
||||||
|
static float easeInOutQuad(float lerpValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Interpolate_h
|
#endif // hifi_Interpolate_h
|
||||||
|
|
|
@ -33,11 +33,11 @@ void RunningMarker::startRunningMarker() {
|
||||||
_runningMarkerThread->setObjectName("Running Marker Thread");
|
_runningMarkerThread->setObjectName("Running Marker Thread");
|
||||||
_runningMarkerThread->start();
|
_runningMarkerThread->start();
|
||||||
|
|
||||||
writeRunningMarkerFiler(); // write the first file, even before timer
|
writeRunningMarkerFile(); // write the first file, even before timer
|
||||||
|
|
||||||
_runningMarkerTimer = new QTimer();
|
_runningMarkerTimer = new QTimer();
|
||||||
QObject::connect(_runningMarkerTimer, &QTimer::timeout, [=](){
|
QObject::connect(_runningMarkerTimer, &QTimer::timeout, [=](){
|
||||||
writeRunningMarkerFiler();
|
writeRunningMarkerFile();
|
||||||
});
|
});
|
||||||
_runningMarkerTimer->start(RUNNING_STATE_CHECK_IN_MSECS);
|
_runningMarkerTimer->start(RUNNING_STATE_CHECK_IN_MSECS);
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ RunningMarker::~RunningMarker() {
|
||||||
_runningMarkerThread->deleteLater();
|
_runningMarkerThread->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RunningMarker::writeRunningMarkerFiler() {
|
void RunningMarker::writeRunningMarkerFile() {
|
||||||
QFile runningMarkerFile(getFilePath());
|
QFile runningMarkerFile(getFilePath());
|
||||||
|
|
||||||
// always write, even it it exists, so that it touches the files
|
// always write, even it it exists, so that it touches the files
|
||||||
|
|
|
@ -27,10 +27,11 @@ public:
|
||||||
|
|
||||||
QString getFilePath();
|
QString getFilePath();
|
||||||
static QString getMarkerFilePath(QString name);
|
static QString getMarkerFilePath(QString name);
|
||||||
protected:
|
|
||||||
void writeRunningMarkerFiler();
|
void writeRunningMarkerFile();
|
||||||
void deleteRunningMarkerFile();
|
void deleteRunningMarkerFile();
|
||||||
|
|
||||||
|
private:
|
||||||
QObject* _parent { nullptr };
|
QObject* _parent { nullptr };
|
||||||
QString _name;
|
QString _name;
|
||||||
QThread* _runningMarkerThread { nullptr };
|
QThread* _runningMarkerThread { nullptr };
|
||||||
|
|
|
@ -12,9 +12,11 @@
|
||||||
#include "GLMHelpers.h"
|
#include "GLMHelpers.h"
|
||||||
#include "TriangleSet.h"
|
#include "TriangleSet.h"
|
||||||
|
|
||||||
void TriangleSet::insert(const Triangle& t) {
|
|
||||||
_triangles.push_back(t);
|
|
||||||
|
|
||||||
|
void TriangleSet::insert(const Triangle& t) {
|
||||||
|
_isBalanced = false;
|
||||||
|
|
||||||
|
_triangles.push_back(t);
|
||||||
_bounds += t.v0;
|
_bounds += t.v0;
|
||||||
_bounds += t.v1;
|
_bounds += t.v1;
|
||||||
_bounds += t.v2;
|
_bounds += t.v2;
|
||||||
|
@ -23,39 +25,31 @@ void TriangleSet::insert(const Triangle& t) {
|
||||||
void TriangleSet::clear() {
|
void TriangleSet::clear() {
|
||||||
_triangles.clear();
|
_triangles.clear();
|
||||||
_bounds.clear();
|
_bounds.clear();
|
||||||
|
_isBalanced = false;
|
||||||
|
|
||||||
|
_triangleOctree.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine of the given ray (origin/direction) in model space intersects with any triangles
|
|
||||||
// in the set. If an intersection occurs, the distance and surface normal will be provided.
|
|
||||||
bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) const {
|
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) {
|
||||||
|
|
||||||
bool intersectedSomething = false;
|
// reset our distance to be the max possible, lower level tests will store best distance here
|
||||||
float boxDistance = std::numeric_limits<float>::max();
|
distance = std::numeric_limits<float>::max();
|
||||||
float bestDistance = std::numeric_limits<float>::max();
|
|
||||||
|
|
||||||
if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) {
|
if (!_isBalanced) {
|
||||||
if (precision) {
|
balanceOctree();
|
||||||
for (const auto& triangle : _triangles) {
|
|
||||||
float thisTriangleDistance;
|
|
||||||
if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) {
|
|
||||||
if (thisTriangleDistance < bestDistance) {
|
|
||||||
bestDistance = thisTriangleDistance;
|
|
||||||
intersectedSomething = true;
|
|
||||||
surfaceNormal = triangle.getNormal();
|
|
||||||
distance = bestDistance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
intersectedSomething = true;
|
|
||||||
distance = boxDistance;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return intersectedSomething;
|
int trianglesTouched = 0;
|
||||||
}
|
auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched);
|
||||||
|
|
||||||
|
#if WANT_DEBUGGING
|
||||||
|
if (precision) {
|
||||||
|
qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population << "_triangles.size:" << _triangles.size();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
bool TriangleSet::convexHullContains(const glm::vec3& point) const {
|
bool TriangleSet::convexHullContains(const glm::vec3& point) const {
|
||||||
if (!_bounds.contains(point)) {
|
if (!_bounds.contains(point)) {
|
||||||
|
@ -74,3 +68,198 @@ bool TriangleSet::convexHullContains(const glm::vec3& point) const {
|
||||||
return insideMesh;
|
return insideMesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TriangleSet::debugDump() {
|
||||||
|
qDebug() << __FUNCTION__;
|
||||||
|
qDebug() << "bounds:" << getBounds();
|
||||||
|
qDebug() << "triangles:" << size() << "at top level....";
|
||||||
|
qDebug() << "----- _triangleOctree -----";
|
||||||
|
_triangleOctree.debugDump();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriangleSet::balanceOctree() {
|
||||||
|
_triangleOctree.reset(_bounds, 0);
|
||||||
|
|
||||||
|
// insert all the triangles
|
||||||
|
for (size_t i = 0; i < _triangles.size(); i++) {
|
||||||
|
_triangleOctree.insert(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isBalanced = true;
|
||||||
|
|
||||||
|
#if WANT_DEBUGGING
|
||||||
|
debugDump();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Determine of the given ray (origin/direction) in model space intersects with any triangles
|
||||||
|
// in the set. If an intersection occurs, the distance and surface normal will be provided.
|
||||||
|
bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
|
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) {
|
||||||
|
|
||||||
|
bool intersectedSomething = false;
|
||||||
|
float boxDistance = distance;
|
||||||
|
float bestDistance = distance;
|
||||||
|
|
||||||
|
if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) {
|
||||||
|
|
||||||
|
// if our bounding box intersects at a distance greater than the current known
|
||||||
|
// best distance, than we can safely not check any of our triangles
|
||||||
|
if (boxDistance > bestDistance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (precision) {
|
||||||
|
for (const auto& triangleIndex : _triangleIndices) {
|
||||||
|
const auto& triangle = _allTriangles[triangleIndex];
|
||||||
|
float thisTriangleDistance;
|
||||||
|
trianglesTouched++;
|
||||||
|
if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) {
|
||||||
|
if (thisTriangleDistance < bestDistance) {
|
||||||
|
bestDistance = thisTriangleDistance;
|
||||||
|
intersectedSomething = true;
|
||||||
|
surfaceNormal = triangle.getNormal();
|
||||||
|
distance = bestDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
intersectedSomething = true;
|
||||||
|
distance = boxDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intersectedSomething;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int MAX_DEPTH = 4; // for now
|
||||||
|
static const int MAX_CHILDREN = 8;
|
||||||
|
|
||||||
|
TriangleSet::TriangleOctreeCell::TriangleOctreeCell(std::vector<Triangle>& allTriangles, const AABox& bounds, int depth) :
|
||||||
|
_allTriangles(allTriangles)
|
||||||
|
{
|
||||||
|
reset(bounds, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriangleSet::TriangleOctreeCell::clear() {
|
||||||
|
_population = 0;
|
||||||
|
_triangleIndices.clear();
|
||||||
|
_bounds.clear();
|
||||||
|
_children.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriangleSet::TriangleOctreeCell::reset(const AABox& bounds, int depth) {
|
||||||
|
clear();
|
||||||
|
_bounds = bounds;
|
||||||
|
_depth = depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriangleSet::TriangleOctreeCell::debugDump() {
|
||||||
|
qDebug() << __FUNCTION__;
|
||||||
|
qDebug() << "bounds:" << getBounds();
|
||||||
|
qDebug() << "depth:" << _depth;
|
||||||
|
qDebug() << "population:" << _population << "this level or below"
|
||||||
|
<< " ---- triangleIndices:" << _triangleIndices.size() << "in this cell";
|
||||||
|
|
||||||
|
qDebug() << "child cells:" << _children.size();
|
||||||
|
if (_depth < MAX_DEPTH) {
|
||||||
|
int childNum = 0;
|
||||||
|
for (auto& child : _children) {
|
||||||
|
qDebug() << "child:" << childNum;
|
||||||
|
child.second.debugDump();
|
||||||
|
childNum++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) {
|
||||||
|
const Triangle& triangle = _allTriangles[triangleIndex];
|
||||||
|
_population++;
|
||||||
|
// if we're not yet at the max depth, then check which child the triangle fits in
|
||||||
|
if (_depth < MAX_DEPTH) {
|
||||||
|
|
||||||
|
for (int child = 0; child < MAX_CHILDREN; child++) {
|
||||||
|
AABox childBounds = getBounds().getOctreeChild((AABox::OctreeChild)child);
|
||||||
|
|
||||||
|
|
||||||
|
// if the child AABox would contain the triangle...
|
||||||
|
if (childBounds.contains(triangle)) {
|
||||||
|
// if the child cell doesn't yet exist, create it...
|
||||||
|
if (_children.find((AABox::OctreeChild)child) == _children.end()) {
|
||||||
|
_children.insert(
|
||||||
|
std::pair<AABox::OctreeChild, TriangleOctreeCell>
|
||||||
|
((AABox::OctreeChild)child, TriangleOctreeCell(_allTriangles, childBounds, _depth + 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert the triangleIndex in the child cell
|
||||||
|
_children.at((AABox::OctreeChild)child).insert(triangleIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// either we're at max depth, or the triangle doesn't fit in one of our
|
||||||
|
// children and so we want to just record it here
|
||||||
|
_triangleIndices.push_back(triangleIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
|
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) {
|
||||||
|
|
||||||
|
if (_population < 1) {
|
||||||
|
return false; // no triangles below here, so we can't intersect
|
||||||
|
}
|
||||||
|
|
||||||
|
float bestLocalDistance = distance;
|
||||||
|
BoxFace bestLocalFace;
|
||||||
|
glm::vec3 bestLocalNormal;
|
||||||
|
bool intersects = false;
|
||||||
|
|
||||||
|
// if the ray intersects our bounding box, then continue
|
||||||
|
if (getBounds().findRayIntersection(origin, direction, bestLocalDistance, bestLocalFace, bestLocalNormal)) {
|
||||||
|
|
||||||
|
// if the intersection with our bounding box, is greater than the current best distance (the distance passed in)
|
||||||
|
// then we know that none of our triangles can represent a better intersection and we can return
|
||||||
|
|
||||||
|
if (bestLocalDistance > distance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bestLocalDistance = distance;
|
||||||
|
|
||||||
|
float childDistance = distance;
|
||||||
|
BoxFace childFace;
|
||||||
|
glm::vec3 childNormal;
|
||||||
|
|
||||||
|
// if we're not yet at the max depth, then check which child the triangle fits in
|
||||||
|
if (_depth < MAX_DEPTH) {
|
||||||
|
for (auto& child : _children) {
|
||||||
|
// check each child, if there's an intersection, it will return some distance that we need
|
||||||
|
// to compare against the other results, because there might be multiple intersections and
|
||||||
|
// we will always choose the best (shortest) intersection
|
||||||
|
if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) {
|
||||||
|
if (childDistance < bestLocalDistance) {
|
||||||
|
bestLocalDistance = childDistance;
|
||||||
|
bestLocalFace = childFace;
|
||||||
|
bestLocalNormal = childNormal;
|
||||||
|
intersects = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// also check our local triangle set
|
||||||
|
if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) {
|
||||||
|
if (childDistance < bestLocalDistance) {
|
||||||
|
bestLocalDistance = childDistance;
|
||||||
|
bestLocalFace = childFace;
|
||||||
|
bestLocalNormal = childNormal;
|
||||||
|
intersects = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (intersects) {
|
||||||
|
distance = bestLocalDistance;
|
||||||
|
face = bestLocalFace;
|
||||||
|
surfaceNormal = bestLocalNormal;
|
||||||
|
}
|
||||||
|
return intersects;
|
||||||
|
}
|
||||||
|
|
|
@ -15,19 +15,64 @@
|
||||||
#include "GeometryUtil.h"
|
#include "GeometryUtil.h"
|
||||||
|
|
||||||
class TriangleSet {
|
class TriangleSet {
|
||||||
public:
|
|
||||||
void reserve(size_t size) { _triangles.reserve(size); } // reserve space in the datastructure for size number of triangles
|
|
||||||
size_t size() const { return _triangles.size(); }
|
|
||||||
|
|
||||||
const Triangle& getTriangle(size_t t) const { return _triangles[t]; }
|
class TriangleOctreeCell {
|
||||||
|
public:
|
||||||
|
TriangleOctreeCell(std::vector<Triangle>& allTriangles) :
|
||||||
|
_allTriangles(allTriangles)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
void insert(size_t triangleIndex);
|
||||||
|
void reset(const AABox& bounds, int depth = 0);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
|
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched);
|
||||||
|
|
||||||
|
const AABox& getBounds() const { return _bounds; }
|
||||||
|
|
||||||
|
void debugDump();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
TriangleOctreeCell(std::vector<Triangle>& allTriangles, const AABox& bounds, int depth);
|
||||||
|
|
||||||
|
// checks our internal list of triangles
|
||||||
|
bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
|
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched);
|
||||||
|
|
||||||
|
std::vector<Triangle>& _allTriangles;
|
||||||
|
std::map<AABox::OctreeChild, TriangleOctreeCell> _children;
|
||||||
|
int _depth{ 0 };
|
||||||
|
int _population{ 0 };
|
||||||
|
AABox _bounds;
|
||||||
|
std::vector<size_t> _triangleIndices;
|
||||||
|
|
||||||
|
friend class TriangleSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
TriangleSet() :
|
||||||
|
_triangleOctree(_triangles)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void debugDump();
|
||||||
|
|
||||||
void insert(const Triangle& t);
|
void insert(const Triangle& t);
|
||||||
|
|
||||||
|
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
|
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision);
|
||||||
|
|
||||||
|
void balanceOctree();
|
||||||
|
|
||||||
|
void reserve(size_t size) { _triangles.reserve(size); } // reserve space in the datastructure for size number of triangles
|
||||||
|
size_t size() const { return _triangles.size(); }
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
// Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an
|
// Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an
|
||||||
// intersection occurs, the distance and surface normal will be provided.
|
// intersection occurs, the distance and surface normal will be provided.
|
||||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
// note: this might side-effect internal structures
|
||||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) const;
|
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
|
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched);
|
||||||
|
|
||||||
// Determine if a point is "inside" all the triangles of a convex hull. It is the responsibility of the caller to
|
// Determine if a point is "inside" all the triangles of a convex hull. It is the responsibility of the caller to
|
||||||
// determine that the triangle set is indeed a convex hull. If the triangles added to this set are not in fact a
|
// determine that the triangle set is indeed a convex hull. If the triangles added to this set are not in fact a
|
||||||
|
@ -35,7 +80,10 @@ public:
|
||||||
bool convexHullContains(const glm::vec3& point) const;
|
bool convexHullContains(const glm::vec3& point) const;
|
||||||
const AABox& getBounds() const { return _bounds; }
|
const AABox& getBounds() const { return _bounds; }
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
|
|
||||||
|
bool _isBalanced{ false };
|
||||||
|
TriangleOctreeCell _triangleOctree;
|
||||||
std::vector<Triangle> _triangles;
|
std::vector<Triangle> _triangles;
|
||||||
AABox _bounds;
|
AABox _bounds;
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,7 +40,7 @@ void releaseOpenVrSystem();
|
||||||
|
|
||||||
|
|
||||||
static const char* CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b";
|
static const char* CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b";
|
||||||
const quint64 CALIBRATION_TIMELAPSE = 2 * USECS_PER_SECOND;
|
const quint64 CALIBRATION_TIMELAPSE = 1 * USECS_PER_SECOND;
|
||||||
|
|
||||||
static const char* MENU_PARENT = "Avatar";
|
static const char* MENU_PARENT = "Avatar";
|
||||||
static const char* MENU_NAME = "Vive Controllers";
|
static const char* MENU_NAME = "Vive Controllers";
|
||||||
|
|
131
scripts/developer/tests/performance/rayPickPerformance.js
Normal file
131
scripts/developer/tests/performance/rayPickPerformance.js
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
//
|
||||||
|
// rayPickingPerformance.js
|
||||||
|
// examples
|
||||||
|
//
|
||||||
|
// Created by Brad Hefta-Gaub on 5/13/2017
|
||||||
|
// Copyright 2017 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var MIN_RANGE = -3;
|
||||||
|
var MAX_RANGE = 3;
|
||||||
|
var RANGE_DELTA = 0.5;
|
||||||
|
var OUTER_LOOPS = 10;
|
||||||
|
|
||||||
|
// NOTE: These expected results depend completely on the model, and the range settings above
|
||||||
|
var EXPECTED_TESTS = 1385 * OUTER_LOOPS;
|
||||||
|
var EXPECTED_INTERSECTIONS = 1286 * OUTER_LOOPS;
|
||||||
|
|
||||||
|
|
||||||
|
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation())));
|
||||||
|
var model_url = "http://hifi-content.s3.amazonaws.com/caitlyn/production/Scansite/buddhaReduced.fbx";
|
||||||
|
|
||||||
|
var rayPickOverlays = Array();
|
||||||
|
|
||||||
|
var modelEntity = Entities.addEntity({
|
||||||
|
type: "Model",
|
||||||
|
modelURL: model_url,
|
||||||
|
dimensions: {
|
||||||
|
x: 0.671,
|
||||||
|
y: 1.21,
|
||||||
|
z: 0.938
|
||||||
|
},
|
||||||
|
position: center
|
||||||
|
});
|
||||||
|
|
||||||
|
function rayCastTest() {
|
||||||
|
var tests = 0;
|
||||||
|
var intersections = 0;
|
||||||
|
|
||||||
|
var testStart = Date.now();
|
||||||
|
for (var t = 0; t < OUTER_LOOPS; t++) {
|
||||||
|
print("beginning loop:" + t);
|
||||||
|
for (var x = MIN_RANGE; x < MAX_RANGE; x += RANGE_DELTA) {
|
||||||
|
for (var y = MIN_RANGE; y < MAX_RANGE; y += RANGE_DELTA) {
|
||||||
|
for (var z = MIN_RANGE; z < MAX_RANGE; z += RANGE_DELTA) {
|
||||||
|
if ((x <= -2 || x >= 2) ||
|
||||||
|
(y <= -2 || y >= 2) ||
|
||||||
|
(z <= -2 || z >= 2)) {
|
||||||
|
|
||||||
|
tests++;
|
||||||
|
|
||||||
|
var origin = { x: center.x + x,
|
||||||
|
y: center.y + y,
|
||||||
|
z: center.z + z };
|
||||||
|
var direction = Vec3.subtract(center, origin);
|
||||||
|
|
||||||
|
var pickRay = {
|
||||||
|
origin: origin,
|
||||||
|
direction: direction
|
||||||
|
};
|
||||||
|
|
||||||
|
var pickResults = Entities.findRayIntersection(pickRay, true);
|
||||||
|
|
||||||
|
var color;
|
||||||
|
var visible;
|
||||||
|
|
||||||
|
if (pickResults.intersects && pickResults.entityID == modelEntity) {
|
||||||
|
intersections++;
|
||||||
|
color = {
|
||||||
|
red: 0,
|
||||||
|
green: 255,
|
||||||
|
blue: 0
|
||||||
|
};
|
||||||
|
visible = false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
print("NO INTERSECTION?");
|
||||||
|
Vec3.print("origin:", origin);
|
||||||
|
Vec3.print("direction:", direction);
|
||||||
|
*/
|
||||||
|
|
||||||
|
color = {
|
||||||
|
red: 255,
|
||||||
|
green: 0,
|
||||||
|
blue: 0
|
||||||
|
};
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var overlayID = Overlays.addOverlay("line3d", {
|
||||||
|
color: color,
|
||||||
|
alpha: 1,
|
||||||
|
visible: visible,
|
||||||
|
lineWidth: 2,
|
||||||
|
start: origin,
|
||||||
|
end: Vec3.sum(origin,Vec3.multiply(5,direction))
|
||||||
|
});
|
||||||
|
|
||||||
|
rayPickOverlays.push(overlayID);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("ending loop:" + t);
|
||||||
|
}
|
||||||
|
var testEnd = Date.now();
|
||||||
|
var testElapsed = testEnd - testStart;
|
||||||
|
|
||||||
|
|
||||||
|
print("EXPECTED tests:" + EXPECTED_TESTS + " intersections:" + EXPECTED_INTERSECTIONS);
|
||||||
|
print("ACTUAL tests:" + tests + " intersections:" + intersections);
|
||||||
|
print("ELAPSED TIME:" + testElapsed + " ms");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
Entities.deleteEntity(modelEntity);
|
||||||
|
rayPickOverlays.forEach(function(item){
|
||||||
|
Overlays.deleteOverlay(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Script.scriptEnding.connect(cleanup);
|
||||||
|
|
||||||
|
rayCastTest(); // run ray cast test immediately
|
|
@ -309,7 +309,7 @@ var toolBar = (function () {
|
||||||
gravity: dynamic ? { x: 0, y: -10, z: 0 } : { x: 0, y: 0, z: 0 }
|
gravity: dynamic ? { x: 0, y: -10, z: 0 } : { x: 0, y: 0, z: 0 }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
|
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
|
||||||
|
@ -482,22 +482,52 @@ var toolBar = (function () {
|
||||||
createNewEntity({
|
createNewEntity({
|
||||||
type: "ParticleEffect",
|
type: "ParticleEffect",
|
||||||
isEmitting: true,
|
isEmitting: true,
|
||||||
|
emitterShouldTrail: true,
|
||||||
|
color: {
|
||||||
|
red: 200,
|
||||||
|
green: 200,
|
||||||
|
blue: 200
|
||||||
|
},
|
||||||
|
colorSpread: {
|
||||||
|
red: 0,
|
||||||
|
green: 0,
|
||||||
|
blue: 0
|
||||||
|
},
|
||||||
|
colorStart: {
|
||||||
|
red: 200,
|
||||||
|
green: 200,
|
||||||
|
blue: 200
|
||||||
|
},
|
||||||
|
colorFinish: {
|
||||||
|
red: 0,
|
||||||
|
green: 0,
|
||||||
|
blue: 0
|
||||||
|
},
|
||||||
emitAcceleration: {
|
emitAcceleration: {
|
||||||
x: 0,
|
x: -0.5,
|
||||||
y: -1,
|
y: 2.5,
|
||||||
z: 0
|
z: -0.5
|
||||||
},
|
},
|
||||||
accelerationSpread: {
|
accelerationSpread: {
|
||||||
x: 5,
|
x: 0.5,
|
||||||
y: 0,
|
y: 1,
|
||||||
z: 5
|
z: 0.5
|
||||||
},
|
},
|
||||||
emitSpeed: 1,
|
emitRate: 5.5,
|
||||||
lifespan: 1,
|
emitSpeed: 0,
|
||||||
particleRadius: 0.025,
|
speedSpread: 0,
|
||||||
|
lifespan: 1.5,
|
||||||
|
maxParticles: 10,
|
||||||
|
particleRadius: 0.25,
|
||||||
|
radiusStart: 0,
|
||||||
|
radiusFinish: 0.1,
|
||||||
|
radiusSpread: 0,
|
||||||
|
alpha: 0,
|
||||||
|
alphaStart: 1,
|
||||||
alphaFinish: 0,
|
alphaFinish: 0,
|
||||||
emitRate: 100,
|
polarStart: 0,
|
||||||
textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png"
|
polarFinish: 0,
|
||||||
|
textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -656,7 +686,7 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var data = JSON.parse(message);
|
var data = JSON.parse(message);
|
||||||
|
|
||||||
if (data.method === "selectOverlay") {
|
if (data.method === "selectOverlay") {
|
||||||
print("setting selection to overlay " + data.overlayID);
|
print("setting selection to overlay " + data.overlayID);
|
||||||
var entity = entityIconOverlayManager.findEntity(data.overlayID);
|
var entity = entityIconOverlayManager.findEntity(data.overlayID);
|
||||||
|
@ -664,7 +694,7 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) {
|
||||||
if (entity !== null) {
|
if (entity !== null) {
|
||||||
selectionManager.setSelections([entity]);
|
selectionManager.setSelections([entity]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Messages.subscribe("entityToolUpdates");
|
Messages.subscribe("entityToolUpdates");
|
||||||
|
@ -774,7 +804,7 @@ function wasTabletClicked(event) {
|
||||||
var result = Overlays.findRayIntersection(rayPick, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]);
|
var result = Overlays.findRayIntersection(rayPick, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]);
|
||||||
return result.intersects;
|
return result.intersects;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mouseClickEvent(event) {
|
function mouseClickEvent(event) {
|
||||||
var wantDebug = false;
|
var wantDebug = false;
|
||||||
var result, properties, tabletClicked;
|
var result, properties, tabletClicked;
|
||||||
|
@ -784,7 +814,7 @@ function mouseClickEvent(event) {
|
||||||
if (tabletClicked) {
|
if (tabletClicked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result === null || result === undefined) {
|
if (result === null || result === undefined) {
|
||||||
if (!event.isShifted) {
|
if (!event.isShifted) {
|
||||||
selectionManager.clearSelections();
|
selectionManager.clearSelections();
|
||||||
|
@ -2062,7 +2092,7 @@ function selectParticleEntity(entityID) {
|
||||||
|
|
||||||
selectedParticleEntity = entityID;
|
selectedParticleEntity = entityID;
|
||||||
particleExplorerTool.setActiveParticleEntity(entityID);
|
particleExplorerTool.setActiveParticleEntity(entityID);
|
||||||
particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
|
particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
|
||||||
|
|
||||||
// Switch to particle explorer
|
// Switch to particle explorer
|
||||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||||
|
|
|
@ -359,7 +359,7 @@ function showUploadingMessage(selectedID, destination) {
|
||||||
shareBarHelp.classList.add("uploading");
|
shareBarHelp.classList.add("uploading");
|
||||||
shareBarHelp.setAttribute("data-destination", destination);
|
shareBarHelp.setAttribute("data-destination", destination);
|
||||||
}
|
}
|
||||||
function hideUploadingMessageAndShare(selectedID, storyID) {
|
function hideUploadingMessageAndMaybeShare(selectedID, storyID) {
|
||||||
if (selectedID.id) {
|
if (selectedID.id) {
|
||||||
selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID
|
selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID
|
||||||
}
|
}
|
||||||
|
@ -382,21 +382,28 @@ function hideUploadingMessageAndShare(selectedID, storyID) {
|
||||||
var facebookButton = document.getElementById(selectedID + "facebookButton");
|
var facebookButton = document.getElementById(selectedID + "facebookButton");
|
||||||
window.open(facebookButton.getAttribute("href"), "_blank");
|
window.open(facebookButton.getAttribute("href"), "_blank");
|
||||||
shareBarHelp.innerHTML = facebookShareText;
|
shareBarHelp.innerHTML = facebookShareText;
|
||||||
|
// This emitWebEvent() call isn't necessary in the "hifi" and "blast" cases
|
||||||
|
// because the "removeFromStoryIDsToMaybeDelete()" call happens
|
||||||
|
// in snapshot.js when sharing with that method.
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify({
|
||||||
|
type: "snapshot",
|
||||||
|
action: "removeFromStoryIDsToMaybeDelete",
|
||||||
|
story_id: storyID
|
||||||
|
}));
|
||||||
break;
|
break;
|
||||||
case 'twitter':
|
case 'twitter':
|
||||||
var twitterButton = document.getElementById(selectedID + "twitterButton");
|
var twitterButton = document.getElementById(selectedID + "twitterButton");
|
||||||
window.open(twitterButton.getAttribute("href"), "_blank");
|
window.open(twitterButton.getAttribute("href"), "_blank");
|
||||||
shareBarHelp.innerHTML = twitterShareText;
|
shareBarHelp.innerHTML = twitterShareText;
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify({
|
||||||
|
type: "snapshot",
|
||||||
|
action: "removeFromStoryIDsToMaybeDelete",
|
||||||
|
story_id: storyID
|
||||||
|
}));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
shareBarHelp.setAttribute("data-destination", "");
|
shareBarHelp.setAttribute("data-destination", "");
|
||||||
|
|
||||||
EventBridge.emitWebEvent(JSON.stringify({
|
|
||||||
type: "snapshot",
|
|
||||||
action: "removeFromStoryIDsToMaybeDelete",
|
|
||||||
story_id: storyID
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function updateShareInfo(containerID, storyID) {
|
function updateShareInfo(containerID, storyID) {
|
||||||
|
@ -417,7 +424,7 @@ function updateShareInfo(containerID, storyID) {
|
||||||
twitterButton.setAttribute("target", "_blank");
|
twitterButton.setAttribute("target", "_blank");
|
||||||
twitterButton.setAttribute("href", 'https://twitter.com/intent/tweet?text=I%20just%20took%20a%20snapshot!&url=' + shareURL + '&via=highfidelityinc&hashtags=VR,HiFi');
|
twitterButton.setAttribute("href", 'https://twitter.com/intent/tweet?text=I%20just%20took%20a%20snapshot!&url=' + shareURL + '&via=highfidelityinc&hashtags=VR,HiFi');
|
||||||
|
|
||||||
hideUploadingMessageAndShare(containerID, storyID);
|
hideUploadingMessageAndMaybeShare(containerID, storyID);
|
||||||
}
|
}
|
||||||
function blastToConnections(selectedID, isGif) {
|
function blastToConnections(selectedID, isGif) {
|
||||||
if (selectedID.id) {
|
if (selectedID.id) {
|
||||||
|
@ -552,6 +559,12 @@ function shareButtonClicked(destination, selectedID) {
|
||||||
|
|
||||||
if (!storyID) {
|
if (!storyID) {
|
||||||
showUploadingMessage(selectedID, destination);
|
showUploadingMessage(selectedID, destination);
|
||||||
|
} else {
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify({
|
||||||
|
type: "snapshot",
|
||||||
|
action: "removeFromStoryIDsToMaybeDelete",
|
||||||
|
story_id: storyID
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1020,7 +1020,7 @@ function loaded() {
|
||||||
|
|
||||||
elTextText.value = properties.text;
|
elTextText.value = properties.text;
|
||||||
elTextLineHeight.value = properties.lineHeight.toFixed(4);
|
elTextLineHeight.value = properties.lineHeight.toFixed(4);
|
||||||
elTextFaceCamera = properties.faceCamera;
|
elTextFaceCamera.checked = properties.faceCamera;
|
||||||
elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + properties.textColor.green + "," + properties.textColor.blue + ")";
|
elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + properties.textColor.green + "," + properties.textColor.blue + ")";
|
||||||
elTextTextColorRed.value = properties.textColor.red;
|
elTextTextColorRed.value = properties.textColor.red;
|
||||||
elTextTextColorGreen.value = properties.textColor.green;
|
elTextTextColorGreen.value = properties.textColor.green;
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
var APP_NAME = "PLAYBACK",
|
var APP_NAME = "PLAYBACK",
|
||||||
HIFI_RECORDER_CHANNEL = "HiFi-Recorder-Channel",
|
HIFI_RECORDER_CHANNEL = "HiFi-Recorder-Channel",
|
||||||
|
RECORDER_COMMAND_ERROR = "error",
|
||||||
HIFI_PLAYER_CHANNEL = "HiFi-Player-Channel",
|
HIFI_PLAYER_CHANNEL = "HiFi-Player-Channel",
|
||||||
PLAYER_COMMAND_PLAY = "play",
|
PLAYER_COMMAND_PLAY = "play",
|
||||||
PLAYER_COMMAND_STOP = "stop",
|
PLAYER_COMMAND_STOP = "stop",
|
||||||
|
@ -47,9 +48,17 @@
|
||||||
searchState = SEARCH_IDLE,
|
searchState = SEARCH_IDLE,
|
||||||
otherPlayersPlaying,
|
otherPlayersPlaying,
|
||||||
otherPlayersPlayingCounts,
|
otherPlayersPlayingCounts,
|
||||||
pauseCount;
|
pauseCount,
|
||||||
|
isDestroyLater = false,
|
||||||
|
|
||||||
|
destroy;
|
||||||
|
|
||||||
function onUpdateTimestamp() {
|
function onUpdateTimestamp() {
|
||||||
|
if (isDestroyLater) {
|
||||||
|
destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
userData.timestamp = Date.now();
|
userData.timestamp = Date.now();
|
||||||
Entities.editEntity(entityID, { userData: JSON.stringify(userData) });
|
Entities.editEntity(entityID, { userData: JSON.stringify(userData) });
|
||||||
EntityViewer.queryOctree(); // Keep up to date ready for find().
|
EntityViewer.queryOctree(); // Keep up to date ready for find().
|
||||||
|
@ -69,12 +78,14 @@
|
||||||
|
|
||||||
if (sender !== scriptUUID) {
|
if (sender !== scriptUUID) {
|
||||||
message = JSON.parse(message);
|
message = JSON.parse(message);
|
||||||
index = otherPlayersPlaying.indexOf(message.entity);
|
if (message.playing !== undefined) {
|
||||||
if (index !== -1) {
|
index = otherPlayersPlaying.indexOf(message.entity);
|
||||||
otherPlayersPlayingCounts[index] += 1;
|
if (index !== -1) {
|
||||||
} else {
|
otherPlayersPlayingCounts[index] += 1;
|
||||||
otherPlayersPlaying.push(message.entity);
|
} else {
|
||||||
otherPlayersPlayingCounts.push(1);
|
otherPlayersPlaying.push(message.entity);
|
||||||
|
otherPlayersPlayingCounts.push(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,10 +94,11 @@
|
||||||
// Create a new persistence entity (even if already have one but that should never occur).
|
// Create a new persistence entity (even if already have one but that should never occur).
|
||||||
var properties;
|
var properties;
|
||||||
|
|
||||||
log("Create recording " + filename);
|
log("Create recording entity for " + filename);
|
||||||
|
|
||||||
if (updateTimestampTimer !== null) {
|
if (updateTimestampTimer !== null) { // Just in case.
|
||||||
Script.clearInterval(updateTimestampTimer); // Just in case.
|
Script.clearInterval(updateTimestampTimer);
|
||||||
|
updateTimestampTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchState = SEARCH_IDLE;
|
searchState = SEARCH_IDLE;
|
||||||
|
@ -114,6 +126,7 @@
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log("Could not create recording entity for " + filename);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +237,7 @@
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function destroy() {
|
destroy = function () {
|
||||||
// Delete current persistence entity.
|
// Delete current persistence entity.
|
||||||
if (entityID !== null) { // Just in case.
|
if (entityID !== null) { // Just in case.
|
||||||
Entities.deleteEntity(entityID);
|
Entities.deleteEntity(entityID);
|
||||||
|
@ -233,7 +246,13 @@
|
||||||
}
|
}
|
||||||
if (updateTimestampTimer !== null) { // Just in case.
|
if (updateTimestampTimer !== null) { // Just in case.
|
||||||
Script.clearInterval(updateTimestampTimer);
|
Script.clearInterval(updateTimestampTimer);
|
||||||
|
updateTimestampTimer = null;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function destroyLater() {
|
||||||
|
// Schedules a call to destroy() when timer threading suits.
|
||||||
|
isDestroyLater = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUp() {
|
function setUp() {
|
||||||
|
@ -254,6 +273,7 @@
|
||||||
create: create,
|
create: create,
|
||||||
find: find,
|
find: find,
|
||||||
destroy: destroy,
|
destroy: destroy,
|
||||||
|
destroyLater: destroyLater,
|
||||||
setUp: setUp,
|
setUp: setUp,
|
||||||
tearDown: tearDown
|
tearDown: tearDown
|
||||||
};
|
};
|
||||||
|
@ -261,41 +281,78 @@
|
||||||
|
|
||||||
Player = (function () {
|
Player = (function () {
|
||||||
// Recording playback functions.
|
// Recording playback functions.
|
||||||
var isPlayingRecording = false,
|
var userID = null,
|
||||||
|
isPlayingRecording = false,
|
||||||
recordingFilename = "",
|
recordingFilename = "",
|
||||||
autoPlayTimer = null,
|
autoPlayTimer = null,
|
||||||
|
|
||||||
|
autoPlay,
|
||||||
playRecording;
|
playRecording;
|
||||||
|
|
||||||
function play(recording, position, orientation) {
|
function error(message) {
|
||||||
|
// Send error message to user.
|
||||||
|
Messages.sendMessage(HIFI_RECORDER_CHANNEL, JSON.stringify({
|
||||||
|
command: RECORDER_COMMAND_ERROR,
|
||||||
|
user: userID,
|
||||||
|
message: message
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function play(user, recording, position, orientation) {
|
||||||
|
var errorMessage;
|
||||||
|
|
||||||
|
if (autoPlayTimer) { // Cancel auto-play.
|
||||||
|
// FIXME: Once in a while Script.clearTimeout() fails.
|
||||||
|
// [DEBUG] [hifi.scriptengine] [3748] [agent] stopTimer -- not in _timerFunctionMap QObject(0x0)
|
||||||
|
Script.clearTimeout(autoPlayTimer);
|
||||||
|
autoPlayTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
userID = user;
|
||||||
|
|
||||||
if (Entity.create(recording, position, orientation)) {
|
if (Entity.create(recording, position, orientation)) {
|
||||||
log("Play new recording " + recordingFilename);
|
log("Play recording " + recording);
|
||||||
isPlayingRecording = true;
|
isPlayingRecording = true; // Immediate feedback.
|
||||||
recordingFilename = recording;
|
recordingFilename = recording;
|
||||||
playRecording(recordingFilename, position, orientation);
|
playRecording(recordingFilename, position, orientation, true);
|
||||||
} else {
|
} else {
|
||||||
log("Could not create entity to play new recording " + recordingFilename);
|
errorMessage = "Could not persist recording " + recording.slice(4); // Remove leading "atp:".
|
||||||
|
log(errorMessage);
|
||||||
|
error(errorMessage);
|
||||||
|
|
||||||
|
autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL); // Resume auto-play later.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function autoPlay() {
|
autoPlay = function () {
|
||||||
var recording,
|
var recording,
|
||||||
AUTOPLAY_SEARCH_DELTA = 1000;
|
AUTOPLAY_SEARCH_DELTA = 1000;
|
||||||
|
|
||||||
// Random delay to help reduce collisions between AC scripts.
|
// Random delay to help reduce collisions between AC scripts.
|
||||||
Script.setTimeout(function () {
|
Script.setTimeout(function () {
|
||||||
|
// Guard against Script.clearTimeout() in play() not always working.
|
||||||
|
if (isPlayingRecording) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
recording = Entity.find();
|
recording = Entity.find();
|
||||||
if (recording) {
|
if (recording) {
|
||||||
log("Play persisted recording " + recordingFilename);
|
log("Play persisted recording " + recording.recording);
|
||||||
playRecording(recording.recording, recording.position, recording.orientation);
|
userID = null;
|
||||||
|
autoPlayTimer = null;
|
||||||
|
isPlayingRecording = true; // Immediate feedback.
|
||||||
|
recordingFilename = recording.recording;
|
||||||
|
playRecording(recording.recording, recording.position, recording.orientation, false);
|
||||||
} else {
|
} else {
|
||||||
autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_SEARCH_INTERVAL); // Try again soon.
|
autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_SEARCH_INTERVAL); // Try again soon.
|
||||||
}
|
}
|
||||||
}, Math.random() * AUTOPLAY_SEARCH_DELTA);
|
}, Math.random() * AUTOPLAY_SEARCH_DELTA);
|
||||||
}
|
};
|
||||||
|
|
||||||
playRecording = function (recording, position, orientation) {
|
playRecording = function (recording, position, orientation, isManual) {
|
||||||
Recording.loadRecording(recording, function (success) {
|
Recording.loadRecording(recording, function (success) {
|
||||||
|
var errorMessage;
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
Users.disableIgnoreRadius();
|
Users.disableIgnoreRadius();
|
||||||
|
|
||||||
|
@ -310,15 +367,22 @@
|
||||||
Recording.setPlayerLoop(true);
|
Recording.setPlayerLoop(true);
|
||||||
Recording.setPlayerUseSkeletonModel(true);
|
Recording.setPlayerUseSkeletonModel(true);
|
||||||
|
|
||||||
isPlayingRecording = true;
|
|
||||||
recordingFilename = recording;
|
|
||||||
|
|
||||||
Recording.setPlayerTime(0.0);
|
Recording.setPlayerTime(0.0);
|
||||||
Recording.startPlaying();
|
Recording.startPlaying();
|
||||||
|
|
||||||
UserActivityLogger.logAction("playRecordingAC_play_recording");
|
UserActivityLogger.logAction("playRecordingAC_play_recording");
|
||||||
} else {
|
} else {
|
||||||
log("Failed to load recording " + recording);
|
if (isManual) {
|
||||||
|
// Delete persistence entity if manual play request.
|
||||||
|
Entity.destroyLater(); // Schedule for deletion; works around timer threading issues.
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMessage = "Could not load recording " + recording.slice(4); // Remove leading "atp:".
|
||||||
|
log(errorMessage);
|
||||||
|
error(errorMessage);
|
||||||
|
|
||||||
|
isPlayingRecording = false;
|
||||||
|
recordingFilename = "";
|
||||||
autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL); // Try again later.
|
autoPlayTimer = Script.setTimeout(autoPlay, AUTOPLAY_ERROR_INTERVAL); // Try again later.
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -374,7 +438,15 @@
|
||||||
recording: Player.recording(),
|
recording: Player.recording(),
|
||||||
entity: Entity.id()
|
entity: Entity.id()
|
||||||
}));
|
}));
|
||||||
heartbeatTimer = Script.setTimeout(sendHeartbeat, HEARTBEAT_INTERVAL);
|
}
|
||||||
|
|
||||||
|
function onHeartbeatTimer() {
|
||||||
|
sendHeartbeat();
|
||||||
|
heartbeatTimer = Script.setTimeout(onHeartbeatTimer, HEARTBEAT_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startHeartbeat() {
|
||||||
|
onHeartbeatTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopHeartbeat() {
|
function stopHeartbeat() {
|
||||||
|
@ -394,7 +466,7 @@
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
case PLAYER_COMMAND_PLAY:
|
case PLAYER_COMMAND_PLAY:
|
||||||
if (!Player.isPlaying()) {
|
if (!Player.isPlaying()) {
|
||||||
Player.play(message.recording, message.position, message.orientation);
|
Player.play(sender, message.recording, message.position, message.orientation);
|
||||||
} else {
|
} else {
|
||||||
log("Didn't start playing " + message.recording + " because already playing " + Player.recording());
|
log("Didn't start playing " + message.recording + " because already playing " + Player.recording());
|
||||||
}
|
}
|
||||||
|
@ -418,7 +490,7 @@
|
||||||
Messages.subscribe(HIFI_PLAYER_CHANNEL);
|
Messages.subscribe(HIFI_PLAYER_CHANNEL);
|
||||||
|
|
||||||
Player.autoPlay();
|
Player.autoPlay();
|
||||||
sendHeartbeat();
|
startHeartbeat();
|
||||||
|
|
||||||
UserActivityLogger.logAction("playRecordingAC_script_load");
|
UserActivityLogger.logAction("playRecordingAC_script_load");
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,12 @@
|
||||||
Window.alert(message);
|
Window.alert(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function logDetails() {
|
||||||
|
return {
|
||||||
|
current_domain: location.placename
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
RecordingIndicator = (function () {
|
RecordingIndicator = (function () {
|
||||||
// Displays "recording" overlay.
|
// Displays "recording" overlay.
|
||||||
|
|
||||||
|
@ -181,7 +187,7 @@
|
||||||
|
|
||||||
recordingState = IDLE;
|
recordingState = IDLE;
|
||||||
log("Finish recording");
|
log("Finish recording");
|
||||||
UserActivityLogger.logAction("record_finish_recording");
|
UserActivityLogger.logAction("record_finish_recording", logDetails());
|
||||||
playSound(finishRecordingSound);
|
playSound(finishRecordingSound);
|
||||||
Recording.stopRecording();
|
Recording.stopRecording();
|
||||||
RecordingIndicator.hide();
|
RecordingIndicator.hide();
|
||||||
|
@ -269,6 +275,7 @@
|
||||||
|
|
||||||
Player = (function () {
|
Player = (function () {
|
||||||
var HIFI_RECORDER_CHANNEL = "HiFi-Recorder-Channel",
|
var HIFI_RECORDER_CHANNEL = "HiFi-Recorder-Channel",
|
||||||
|
RECORDER_COMMAND_ERROR = "error",
|
||||||
HIFI_PLAYER_CHANNEL = "HiFi-Player-Channel",
|
HIFI_PLAYER_CHANNEL = "HiFi-Player-Channel",
|
||||||
PLAYER_COMMAND_PLAY = "play",
|
PLAYER_COMMAND_PLAY = "play",
|
||||||
PLAYER_COMMAND_STOP = "stop",
|
PLAYER_COMMAND_STOP = "stop",
|
||||||
|
@ -277,7 +284,6 @@
|
||||||
playerIsPlayings = [], // True if AC player script is playing a recording.
|
playerIsPlayings = [], // True if AC player script is playing a recording.
|
||||||
playerRecordings = [], // Assignment client mappings of recordings being played.
|
playerRecordings = [], // Assignment client mappings of recordings being played.
|
||||||
playerTimestamps = [], // Timestamps of last heartbeat update from player script.
|
playerTimestamps = [], // Timestamps of last heartbeat update from player script.
|
||||||
playerStartupTimeouts = [], // Timers that check that recording has started playing.
|
|
||||||
|
|
||||||
updateTimer,
|
updateTimer,
|
||||||
UPDATE_INTERVAL = 5000; // Must be > player's HEARTBEAT_INTERVAL.
|
UPDATE_INTERVAL = 5000; // Must be > player's HEARTBEAT_INTERVAL.
|
||||||
|
@ -298,7 +304,6 @@
|
||||||
playerIsPlayings.splice(i, 1);
|
playerIsPlayings.splice(i, 1);
|
||||||
playerRecordings.splice(i, 1);
|
playerRecordings.splice(i, 1);
|
||||||
playerTimestamps.splice(i, 1);
|
playerTimestamps.splice(i, 1);
|
||||||
playerStartupTimeouts.splice(i, 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,8 +314,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function playRecording(recording, position, orientation) {
|
function playRecording(recording, position, orientation) {
|
||||||
var index,
|
var index;
|
||||||
CHECK_PLAYING_TIMEOUT = 10000;
|
|
||||||
|
|
||||||
// Optional function parameters.
|
// Optional function parameters.
|
||||||
if (position === undefined) {
|
if (position === undefined) {
|
||||||
|
@ -334,26 +338,9 @@
|
||||||
position: position,
|
position: position,
|
||||||
orientation: orientation
|
orientation: orientation
|
||||||
}));
|
}));
|
||||||
|
|
||||||
playerStartupTimeouts[index] = Script.setTimeout(function () {
|
|
||||||
if ((!playerIsPlayings[index] || playerRecordings[index] !== recording) && playerStartupTimeouts[index]) {
|
|
||||||
error("Didn't start playing recording "
|
|
||||||
+ recording.slice(4) + "!"); // Remove leading "atp:" from recording.
|
|
||||||
}
|
|
||||||
playerStartupTimeouts[index] = null;
|
|
||||||
}, CHECK_PLAYING_TIMEOUT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopPlayingRecording(playerID) {
|
function stopPlayingRecording(playerID) {
|
||||||
var index;
|
|
||||||
|
|
||||||
// Cancel check that recording started playing.
|
|
||||||
index = playerIDs.indexOf(playerID);
|
|
||||||
if (index !== -1 && playerStartupTimeouts[index] !== null) {
|
|
||||||
// Cannot clearTimeout() without program log error, so just set null.
|
|
||||||
playerStartupTimeouts[index] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Messages.sendMessage(HIFI_PLAYER_CHANNEL, JSON.stringify({
|
Messages.sendMessage(HIFI_PLAYER_CHANNEL, JSON.stringify({
|
||||||
player: playerID,
|
player: playerID,
|
||||||
command: PLAYER_COMMAND_STOP
|
command: PLAYER_COMMAND_STOP
|
||||||
|
@ -370,15 +357,21 @@
|
||||||
|
|
||||||
message = JSON.parse(message);
|
message = JSON.parse(message);
|
||||||
|
|
||||||
index = playerIDs.indexOf(sender);
|
if (message.command === RECORDER_COMMAND_ERROR) {
|
||||||
if (index === -1) {
|
if (message.user === MyAvatar.sessionUUID) {
|
||||||
index = playerIDs.length;
|
error(message.message);
|
||||||
playerIDs[index] = sender;
|
}
|
||||||
|
} else {
|
||||||
|
index = playerIDs.indexOf(sender);
|
||||||
|
if (index === -1) {
|
||||||
|
index = playerIDs.length;
|
||||||
|
playerIDs[index] = sender;
|
||||||
|
}
|
||||||
|
playerIsPlayings[index] = message.playing;
|
||||||
|
playerRecordings[index] = message.recording;
|
||||||
|
playerTimestamps[index] = Date.now();
|
||||||
|
Dialog.updatePlayerDetails(playerIsPlayings, playerRecordings, playerIDs);
|
||||||
}
|
}
|
||||||
playerIsPlayings[index] = message.playing;
|
|
||||||
playerRecordings[index] = message.recording;
|
|
||||||
playerTimestamps[index] = Date.now();
|
|
||||||
Dialog.updatePlayerDetails(playerIsPlayings, playerRecordings, playerIDs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
|
@ -386,7 +379,6 @@
|
||||||
playerIsPlayings = [];
|
playerIsPlayings = [];
|
||||||
playerRecordings = [];
|
playerRecordings = [];
|
||||||
playerTimestamps = [];
|
playerTimestamps = [];
|
||||||
playerStartupTimeouts = [];
|
|
||||||
Dialog.updatePlayerDetails(playerIsPlayings, playerRecordings, playerIDs);
|
Dialog.updatePlayerDetails(playerIsPlayings, playerRecordings, playerIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,10 +511,11 @@
|
||||||
value: Player.numberOfPlayers()
|
value: Player.numberOfPlayers()
|
||||||
}));
|
}));
|
||||||
updateRecordingStatus(!Recorder.isIdle());
|
updateRecordingStatus(!Recorder.isIdle());
|
||||||
UserActivityLogger.logAction("record_open_dialog");
|
UserActivityLogger.logAction("record_open_dialog", logDetails());
|
||||||
break;
|
break;
|
||||||
case STOP_PLAYING_RECORDING_ACTION:
|
case STOP_PLAYING_RECORDING_ACTION:
|
||||||
// Stop the specified player.
|
// Stop the specified player.
|
||||||
|
log("Unload recording " + message.value);
|
||||||
Player.stopPlayingRecording(message.value);
|
Player.stopPlayingRecording(message.value);
|
||||||
break;
|
break;
|
||||||
case LOAD_RECORDING_ACTION:
|
case LOAD_RECORDING_ACTION:
|
||||||
|
@ -530,7 +523,7 @@
|
||||||
recording = Window.browseAssets("Select Recording to Play", "recordings", "*.hfr");
|
recording = Window.browseAssets("Select Recording to Play", "recordings", "*.hfr");
|
||||||
if (recording) {
|
if (recording) {
|
||||||
log("Load recording " + recording);
|
log("Load recording " + recording);
|
||||||
UserActivityLogger.logAction("record_load_recording");
|
UserActivityLogger.logAction("record_load_recording", logDetails());
|
||||||
Player.playRecording("atp:" + recording, MyAvatar.position, MyAvatar.orientation);
|
Player.playRecording("atp:" + recording, MyAvatar.position, MyAvatar.orientation);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -660,7 +653,7 @@
|
||||||
isConnected = Window.location.isConnected;
|
isConnected = Window.location.isConnected;
|
||||||
Script.update.connect(onUpdate);
|
Script.update.connect(onUpdate);
|
||||||
|
|
||||||
UserActivityLogger.logAction("record_run_script");
|
UserActivityLogger.logAction("record_run_script", logDetails());
|
||||||
}
|
}
|
||||||
|
|
||||||
function tearDown() {
|
function tearDown() {
|
||||||
|
|
|
@ -51,6 +51,11 @@ function openLoginWindow() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeFromStoryIDsToMaybeDelete(story_id) {
|
||||||
|
storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1);
|
||||||
|
print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete));
|
||||||
|
}
|
||||||
|
|
||||||
function onMessage(message) {
|
function onMessage(message) {
|
||||||
// Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following:
|
// Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following:
|
||||||
// 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.)
|
// 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.)
|
||||||
|
@ -191,6 +196,7 @@ function onMessage(message) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
print("SUCCESS uploading announcement story! Story ID:", response.user_story.id);
|
print("SUCCESS uploading announcement story! Story ID:", response.user_story.id);
|
||||||
|
removeFromStoryIDsToMaybeDelete(message.story_id); // Don't delete original "for_url" story
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -230,13 +236,13 @@ function onMessage(message) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
print("SUCCESS changing audience" + (message.isAnnouncement ? " and posting announcement!" : "!"));
|
print("SUCCESS changing audience" + (message.isAnnouncement ? " and posting announcement!" : "!"));
|
||||||
|
removeFromStoryIDsToMaybeDelete(message.story_id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'removeFromStoryIDsToMaybeDelete':
|
case 'removeFromStoryIDsToMaybeDelete':
|
||||||
storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1);
|
removeFromStoryIDsToMaybeDelete(message.story_id);
|
||||||
print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete));
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
print('Unknown message action received by snapshot.js!');
|
print('Unknown message action received by snapshot.js!');
|
||||||
|
|
Loading…
Reference in a new issue