Merge branch 'master' of https://github.com/highfidelity/hifi into sessionDisplayNames

This commit is contained in:
howard-stearns 2016-12-19 09:19:14 -08:00
commit 9d9345cb65
74 changed files with 1200 additions and 523 deletions
assignment-client/src
domain-server/src
interface
libraries
plugins
scripts/system/controllers
tests
render-perf/src
shared/src
tutorial

View file

@ -38,6 +38,7 @@
#include "AssignmentClientLogging.h"
#include "avatars/ScriptableAvatar.h"
#include <Trace.h>
#include <StatTracker.h>
const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client";
const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000;
@ -50,6 +51,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
LogUtils::init();
DependencyManager::set<tracing::Tracer>();
DependencyManager::set<StatTracker>();
DependencyManager::set<AccountManager>();
auto scriptableAvatar = DependencyManager::set<ScriptableAvatar>();

View file

@ -44,6 +44,7 @@
#include "DomainServerNodeData.h"
#include "NodeConnectionData.h"
#include <Trace.h>
#include <StatTracker.h>
int const DomainServer::EXIT_CODE_REBOOT = 234923;
@ -75,6 +76,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
parseCommandLine();
DependencyManager::set<tracing::Tracer>();
DependencyManager::set<StatTracker>();
LogUtils::init();
Setting::init();

View file

@ -179,6 +179,8 @@ extern "C" {
}
#endif
Q_LOGGING_CATEGORY(trace_app_input_mouse, "trace.app.input.mouse")
using namespace std;
static QTimer locationUpdateTimer;
@ -1389,16 +1391,31 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
}
qCDebug(interfaceapp) << "Server content version: " << contentVersion;
bool hasTutorialContent = contentVersion >= 1;
static const int MIN_VIVE_CONTENT_VERSION = 1;
static const int MIN_OCULUS_TOUCH_CONTENT_VERSION = 27;
bool hasSufficientTutorialContent = false;
bool hasHandControllers = false;
// Only specific hand controllers are currently supported, so only send users to the tutorial
// if they have one of those hand controllers.
if (PluginUtils::isViveControllerAvailable()) {
hasHandControllers = true;
hasSufficientTutorialContent = contentVersion >= MIN_VIVE_CONTENT_VERSION;
} else if (PluginUtils::isOculusTouchControllerAvailable()) {
hasHandControllers = true;
hasSufficientTutorialContent = contentVersion >= MIN_OCULUS_TOUCH_CONTENT_VERSION;
}
Setting::Handle<bool> firstRun { Settings::firstRun, true };
bool hasHMDAndHandControllers = PluginUtils::isHMDAvailable("OpenVR (Vive)") && PluginUtils::isHandControllerAvailable();
bool hasHMDAndHandControllers = PluginUtils::isHMDAvailable() && hasHandControllers;
Setting::Handle<bool> tutorialComplete { "tutorialComplete", false };
bool shouldGoToTutorial = hasHMDAndHandControllers && hasTutorialContent && !tutorialComplete.get();
bool shouldGoToTutorial = hasHMDAndHandControllers && hasSufficientTutorialContent && !tutorialComplete.get();
qCDebug(interfaceapp) << "Has HMD + Hand Controllers: " << hasHMDAndHandControllers << ", current plugin: " << _displayPlugin->getName();
qCDebug(interfaceapp) << "Has tutorial content: " << hasTutorialContent;
qCDebug(interfaceapp) << "Has sufficient tutorial content (" << contentVersion << ") : " << hasSufficientTutorialContent;
qCDebug(interfaceapp) << "Tutorial complete: " << tutorialComplete.get();
qCDebug(interfaceapp) << "Should go to tutorial: " << shouldGoToTutorial;
@ -1763,7 +1780,7 @@ void Application::initializeGL() {
// Set up the render engine
render::CullFunctor cullFunctor = LODManager::shouldRender;
_renderEngine->addJob<RenderShadowTask>("RenderShadowTask", cullFunctor);
static const QString RENDER_FORWARD = "RENDER_FORWARD";
static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD";
if (QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD)) {
_renderEngine->addJob<RenderForwardTask>("RenderForwardTask", cullFunctor);
} else {
@ -1945,16 +1962,6 @@ void Application::initializeUi() {
}
void Application::paintGL() {
PROFILE_COUNTER(interfaceapp, "fps", { { "fps", _frameCounter.rate() } });
PROFILE_COUNTER(interfaceapp, "downloads", {
{ "current", ResourceCache::getLoadingRequests().length() },
{ "pending", ResourceCache::getPendingRequestCount() }
});
PROFILE_COUNTER(interfaceapp, "processing", {
{ "current", DependencyManager::get<StatTracker>()->getStat("Processing") },
{ "pending", DependencyManager::get<StatTracker>()->getStat("PendingProcessing") }
});
// Some plugins process message events, allowing paintGL to be called reentrantly.
if (_inPaint || _aboutToQuit) {
return;
@ -1966,7 +1973,7 @@ void Application::paintGL() {
_frameCount++;
auto lastPaintBegin = usecTimestampNow();
PROFILE_RANGE_EX(interfaceapp, __FUNCTION__, 0xff0000ff, (uint64_t)_frameCount);
PROFILE_RANGE_EX(render, __FUNCTION__, 0xff0000ff, (uint64_t)_frameCount);
PerformanceTimer perfTimer("paintGL");
if (nullptr == _displayPlugin) {
@ -2143,7 +2150,7 @@ void Application::paintGL() {
auto finalFramebuffer = framebufferCache->getFramebuffer();
{
PROFILE_RANGE(interfaceapp, "/mainRender");
PROFILE_RANGE(render, "/mainRender");
PerformanceTimer perfTimer("mainRender");
renderArgs._boomOffset = boomOffset;
// Viewport is assigned to the size of the framebuffer
@ -2198,7 +2205,7 @@ void Application::paintGL() {
frame->overlay = _applicationOverlay.getOverlayTexture();
// deliver final scene rendering commands to the display plugin
{
PROFILE_RANGE(interfaceapp, "/pluginOutput");
PROFILE_RANGE(render, "/pluginOutput");
PerformanceTimer perfTimer("pluginOutput");
_frameCounter.increment();
displayPlugin->submitFrame(frame);
@ -2286,7 +2293,7 @@ void Application::resizeEvent(QResizeEvent* event) {
}
void Application::resizeGL() {
PROFILE_RANGE(interfaceapp, __FUNCTION__);
PROFILE_RANGE(render, __FUNCTION__);
if (nullptr == _displayPlugin) {
return;
}
@ -2931,7 +2938,7 @@ void Application::maybeToggleMenuVisible(QMouseEvent* event) const {
}
void Application::mouseMoveEvent(QMouseEvent* event) {
PROFILE_RANGE(interfaceapp, __FUNCTION__);
PROFILE_RANGE(app_input_mouse, __FUNCTION__);
if (_aboutToQuit) {
return;
@ -3225,7 +3232,6 @@ bool Application::shouldPaint(float nsecsElapsed) {
}
void Application::idle(float nsecsElapsed) {
PROFILE_RANGE(interfaceapp, __FUNCTION__);
PerformanceTimer perfTimer("idle");
// Update the deadlock watchdog
@ -3241,6 +3247,18 @@ void Application::idle(float nsecsElapsed) {
connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
}
PROFILE_COUNTER(app, "fps", { { "fps", _frameCounter.rate() } });
PROFILE_COUNTER(app, "downloads", {
{ "current", ResourceCache::getLoadingRequests().length() },
{ "pending", ResourceCache::getPendingRequestCount() }
});
PROFILE_COUNTER(app, "processing", {
{ "current", DependencyManager::get<StatTracker>()->getStat("Processing") },
{ "pending", DependencyManager::get<StatTracker>()->getStat("PendingProcessing") }
});
PROFILE_RANGE(app, __FUNCTION__);
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
steamClient->runCallbacks();
}
@ -3958,7 +3976,7 @@ static bool domainLoadingInProgress = false;
void Application::update(float deltaTime) {
PROFILE_RANGE_EX(interfaceapp, __FUNCTION__, 0xffff0000, (uint64_t)_frameCount + 1);
PROFILE_RANGE_EX(app, __FUNCTION__, 0xffff0000, (uint64_t)_frameCount + 1);
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::update()");
@ -3967,7 +3985,7 @@ void Application::update(float deltaTime) {
if (!_physicsEnabled) {
if (!domainLoadingInProgress) {
PROFILE_ASYNC_BEGIN(interfaceapp, "Scene Loading", "");
PROFILE_ASYNC_BEGIN(app, "Scene Loading", "");
domainLoadingInProgress = true;
}
@ -4002,7 +4020,7 @@ void Application::update(float deltaTime) {
}
} else if (domainLoadingInProgress) {
domainLoadingInProgress = false;
PROFILE_ASYNC_END(interfaceapp, "Scene Loading", "");
PROFILE_ASYNC_END(app, "Scene Loading", "");
}
{
@ -4096,12 +4114,12 @@ void Application::update(float deltaTime) {
QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
if (_physicsEnabled) {
PROFILE_RANGE_EX(interfaceapp, "Physics", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
PROFILE_RANGE_EX(simulation_physics, "Physics", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("physics");
{
PROFILE_RANGE_EX(interfaceapp, "UpdateStats", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
PROFILE_RANGE_EX(simulation_physics, "UpdateStats", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("updateStates)");
static VectorOfMotionStates motionStates;
@ -4135,14 +4153,14 @@ void Application::update(float deltaTime) {
});
}
{
PROFILE_RANGE_EX(interfaceapp, "StepSimulation", 0xffff8000, (uint64_t)getActiveDisplayPlugin()->presentCount());
PROFILE_RANGE_EX(simulation_physics, "StepSimulation", 0xffff8000, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("stepSimulation");
getEntities()->getTree()->withWriteLock([&] {
_physicsEngine->stepSimulation();
});
}
{
PROFILE_RANGE_EX(interfaceapp, "HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
PROFILE_RANGE_EX(simulation_physics, "HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("harvestChanges");
if (_physicsEngine->hasOutgoingChanges()) {
getEntities()->getTree()->withWriteLock([&] {
@ -4184,20 +4202,20 @@ void Application::update(float deltaTime) {
_avatarSimCounter.increment();
{
PROFILE_RANGE_EX(interfaceapp, "OtherAvatars", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount());
PROFILE_RANGE_EX(simulation, "OtherAvatars", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount());
avatarManager->updateOtherAvatars(deltaTime);
}
qApp->updateMyAvatarLookAtPosition();
{
PROFILE_RANGE_EX(interfaceapp, "MyAvatar", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount());
PROFILE_RANGE_EX(simulation, "MyAvatar", 0xffff00ff, (uint64_t)getActiveDisplayPlugin()->presentCount());
avatarManager->updateMyAvatar(deltaTime);
}
}
{
PROFILE_RANGE_EX(interfaceapp, "Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
PROFILE_RANGE_EX(app, "Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
PerformanceTimer perfTimer("overlays");
_overlays.update(deltaTime);
}
@ -4217,7 +4235,7 @@ void Application::update(float deltaTime) {
// Update my voxel servers with my current voxel query...
{
PROFILE_RANGE_EX(interfaceapp, "QueryOctree", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
PROFILE_RANGE_EX(app, "QueryOctree", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
QMutexLocker viewLocker(&_viewMutex);
PerformanceTimer perfTimer("queryOctree");
quint64 sinceLastQuery = now - _lastQueriedTime;
@ -4257,7 +4275,7 @@ void Application::update(float deltaTime) {
avatarManager->postUpdate(deltaTime);
{
PROFILE_RANGE_EX(interfaceapp, "PreRenderLambdas", 0xffff0000, (uint64_t)0);
PROFILE_RANGE_EX(app, "PreRenderLambdas", 0xffff0000, (uint64_t)0);
std::unique_lock<std::mutex> guard(_postUpdateLambdasLock);
for (auto& iter : _postUpdateLambdas) {
@ -4534,8 +4552,6 @@ QRect Application::getDesirableApplicationGeometry() const {
// or the "myCamera".
//
void Application::loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum) {
PROFILE_RANGE(interfaceapp, __FUNCTION__);
PerformanceTimer perfTimer("loadViewFrustum");
// We will use these below, from either the camera or head vectors calculated above
viewFrustum.setProjection(camera.getProjection());
@ -4711,7 +4727,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
myAvatar->preDisplaySide(renderArgs);
activeRenderingThread = QThread::currentThread();
PROFILE_RANGE(interfaceapp, __FUNCTION__);
PROFILE_RANGE(render, __FUNCTION__);
PerformanceTimer perfTimer("display");
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()");

View file

@ -49,12 +49,17 @@ glm::vec2 HMDScriptingInterface::overlayToSpherical(const glm::vec2 & position)
return qApp->getApplicationCompositor().overlayToSpherical(position);
}
bool HMDScriptingInterface::isHMDAvailable() {
return PluginUtils::isHMDAvailable();
bool HMDScriptingInterface::isHMDAvailable(const QString& name) {
return PluginUtils::isHMDAvailable(name);
}
bool HMDScriptingInterface::isHandControllerAvailable() {
return PluginUtils::isHandControllerAvailable();
bool HMDScriptingInterface::isHandControllerAvailable(const QString& name) {
return PluginUtils::isHandControllerAvailable(name);
}
bool HMDScriptingInterface::isSubdeviceContainingNameAvailable(const QString& name) {
return PluginUtils::isSubdeviceContainingNameAvailable(name);
}
void HMDScriptingInterface::requestShowHandControllers() {

View file

@ -38,8 +38,9 @@ public:
Q_INVOKABLE QString preferredAudioInput() const;
Q_INVOKABLE QString preferredAudioOutput() const;
Q_INVOKABLE bool isHMDAvailable();
Q_INVOKABLE bool isHandControllerAvailable();
Q_INVOKABLE bool isHMDAvailable(const QString& name = "");
Q_INVOKABLE bool isHandControllerAvailable(const QString& name = "");
Q_INVOKABLE bool isSubdeviceContainingNameAvailable(const QString& name);
Q_INVOKABLE void requestShowHandControllers();
Q_INVOKABLE void requestHideHandControllers();

View file

@ -57,7 +57,7 @@ ApplicationOverlay::~ApplicationOverlay() {
// Renders the overlays either to a texture or to the screen
void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
PROFILE_RANGE(interfaceapp, __FUNCTION__);
PROFILE_RANGE(render, __FUNCTION__);
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()");
buildFramebufferObject();
@ -96,7 +96,7 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
}
void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
PROFILE_RANGE(interfaceapp, __FUNCTION__);
PROFILE_RANGE(app, __FUNCTION__);
if (!_uiTexture) {
_uiTexture = gpu::TexturePointer(gpu::Texture::createExternal2D(OffscreenQmlSurface::getDiscardLambda()));
@ -124,7 +124,7 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
}
void ApplicationOverlay::renderAudioScope(RenderArgs* renderArgs) {
PROFILE_RANGE(interfaceapp, __FUNCTION__);
PROFILE_RANGE(app, __FUNCTION__);
gpu::Batch& batch = *renderArgs->_batch;
auto geometryCache = DependencyManager::get<GeometryCache>();
@ -143,7 +143,7 @@ void ApplicationOverlay::renderAudioScope(RenderArgs* renderArgs) {
}
void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) {
PROFILE_RANGE(interfaceapp, __FUNCTION__);
PROFILE_RANGE(app, __FUNCTION__);
gpu::Batch& batch = *renderArgs->_batch;
auto geometryCache = DependencyManager::get<GeometryCache>();
@ -262,7 +262,7 @@ static const auto DEFAULT_SAMPLER = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LI
static const auto DEPTH_FORMAT = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH);
void ApplicationOverlay::buildFramebufferObject() {
PROFILE_RANGE(interfaceapp, __FUNCTION__);
PROFILE_RANGE(app, __FUNCTION__);
auto uiSize = qApp->getUiSize();
if (!_overlayFramebuffer || uiSize != _overlayFramebuffer->getSize()) {

View file

@ -37,6 +37,7 @@
#include "Web3DOverlay.h"
#include <QtQuick/QQuickWindow>
Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays")
Overlays::Overlays() :
_nextOverlayID(1) {}
@ -102,7 +103,7 @@ void Overlays::cleanupOverlaysToDelete() {
}
void Overlays::renderHUD(RenderArgs* renderArgs) {
PROFILE_RANGE(interfaceapp, __FUNCTION__);
PROFILE_RANGE(render_overlays, __FUNCTION__);
QReadLocker lock(&_lock);
gpu::Batch& batch = *renderArgs->_batch;

View file

@ -394,7 +394,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
loadPoses(underPoses);
} else {
PROFILE_RANGE_EX(animation, "ik/relax", 0xffff00ff, 0);
PROFILE_RANGE_EX(simulation_animation, "ik/relax", 0xffff00ff, 0);
// relax toward underPoses
// HACK: this relaxation needs to be constant per-frame rather than per-realtime
@ -433,7 +433,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
// build a list of targets from _targetVarVec
std::vector<IKTarget> targets;
{
PROFILE_RANGE_EX(animation, "ik/computeTargets", 0xffff00ff, 0);
PROFILE_RANGE_EX(simulation_animation, "ik/computeTargets", 0xffff00ff, 0);
computeTargets(animVars, targets, underPoses);
}
@ -450,7 +450,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
} else {
{
PROFILE_RANGE_EX(animation, "ik/shiftHips", 0xffff00ff, 0);
PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0);
// shift hips according to the _hipsOffset from the previous frame
float offsetLength = glm::length(_hipsOffset);
@ -476,12 +476,12 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
}
{
PROFILE_RANGE_EX(animation, "ik/ccd", 0xffff00ff, 0);
PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0);
solveWithCyclicCoordinateDescent(targets);
}
{
PROFILE_RANGE_EX(animation, "ik/measureHipsOffset", 0xffff00ff, 0);
PROFILE_RANGE_EX(simulation_animation, "ik/measureHipsOffset", 0xffff00ff, 0);
// measure new _hipsOffset for next frame
// by looking for discrepancies between where a targeted endEffector is

View file

@ -55,7 +55,7 @@ void AnimationReader::run() {
DependencyManager::get<StatTracker>()->decrementStat("PendingProcessing");
CounterStat counter("Processing");
PROFILE_RANGE_EX(animation, __FUNCTION__, 0xFF00FF00, 0, { { "url", _url.toString() } });
PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xFF00FF00, 0, { { "url", _url.toString() } });
auto originalPriority = QThread::currentThread()->priority();
if (originalPriority == QThread::InheritPriority) {
originalPriority = QThread::NormalPriority;

View file

@ -883,7 +883,7 @@ void Rig::updateAnimationStateHandlers() { // called on avatar update thread (wh
void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) {
PROFILE_RANGE_EX(animation, __FUNCTION__, 0xffff00ff, 0);
PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xffff00ff, 0);
setModelOffset(rootTransform);

View file

@ -1135,8 +1135,9 @@ void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
} else if (injector->isStereo()) {
// stereo gets directly mixed into mixBuffer
float gain = injector->getVolume();
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) {
mixBuffer[i] += (float)_scratchBuffer[i] * (1/32768.0f);
mixBuffer[i] += (float)_scratchBuffer[i] * (1/32768.0f) * gain;
}
} else {

View file

@ -166,7 +166,6 @@ bool AudioInjector::injectLocally() {
_localBuffer->open(QIODevice::ReadOnly);
_localBuffer->setShouldLoop(_options.loop);
_localBuffer->setVolume(_options.volume);
// give our current send position to the local buffer
_localBuffer->setCurrentOffset(_currentSendOffset);

View file

@ -63,7 +63,7 @@ public:
AudioFOA& getLocalFOA() { return _localFOA; }
bool isLocalOnly() const { return _options.localOnly; }
float getVolume() const { return _options.volume; }
float getVolume() const { return glm::clamp(_options.volume, 0.0f, 1.0f); }
glm::vec3 getPosition() const { return _options.position; }
glm::quat getOrientation() const { return _options.orientation; }
bool isStereo() const { return _options.stereo; }

View file

@ -16,8 +16,7 @@ AudioInjectorLocalBuffer::AudioInjectorLocalBuffer(const QByteArray& rawAudioArr
_rawAudioArray(rawAudioArray),
_shouldLoop(false),
_isStopped(false),
_currentOffset(0),
_volume(1.0f)
_currentOffset(0)
{
}
@ -36,17 +35,6 @@ bool AudioInjectorLocalBuffer::seek(qint64 pos) {
}
}
void copy(char* to, char* from, int size, qreal factor) {
int16_t* toArray = (int16_t*) to;
int16_t* fromArray = (int16_t*) from;
int sampleSize = size / sizeof(int16_t);
for (int i = 0; i < sampleSize; i++) {
*toArray = factor * (*fromArray);
toArray++;
fromArray++;
}
}
qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) {
if (!_isStopped) {
@ -60,7 +48,7 @@ qint64 AudioInjectorLocalBuffer::readData(char* data, qint64 maxSize) {
bytesRead = bytesToEnd;
}
copy(data, _rawAudioArray.data() + _currentOffset, bytesRead, _volume);
memcpy(data, _rawAudioArray.data() + _currentOffset, bytesRead);
// now check if we are supposed to loop and if we can copy more from the beginning
if (_shouldLoop && maxSize != bytesRead) {
@ -88,7 +76,7 @@ qint64 AudioInjectorLocalBuffer::recursiveReadFromFront(char* data, qint64 maxSi
}
// copy that amount
copy(data, _rawAudioArray.data(), bytesRead, _volume);
memcpy(data, _rawAudioArray.data(), bytesRead);
// check if we need to call ourselves again and pull from the front again
if (bytesRead < maxSize) {
@ -97,4 +85,4 @@ qint64 AudioInjectorLocalBuffer::recursiveReadFromFront(char* data, qint64 maxSi
_currentOffset = bytesRead;
return bytesRead;
}
}
}

View file

@ -30,7 +30,6 @@ public:
void setShouldLoop(bool shouldLoop) { _shouldLoop = shouldLoop; }
void setCurrentOffset(int currentOffset) { _currentOffset = currentOffset; }
void setVolume(float volume) { _volume = glm::clamp(volume, 0.0f, 1.0f); }
private:
qint64 recursiveReadFromFront(char* data, qint64 maxSize);
@ -40,7 +39,6 @@ private:
bool _isStopped;
int _currentOffset;
float _volume;
};
#endif // hifi_AudioInjectorLocalBuffer_h

View file

@ -129,7 +129,7 @@ public:
_context->makeCurrent();
while (!_shutdown) {
if (_pendingMainThreadOperation) {
PROFILE_RANGE(displayPlugins, "MainThreadOp")
PROFILE_RANGE(render, "MainThreadOp")
{
Lock lock(_mutex);
_context->doneCurrent();
@ -203,7 +203,7 @@ public:
// Execute the frame and present it to the display device.
_context->makeCurrent();
{
PROFILE_RANGE(displayPlugins, "PluginPresent")
PROFILE_RANGE(render, "PluginPresent")
currentPlugin->present();
CHECK_GL_ERROR();
}
@ -560,22 +560,22 @@ void OpenGLDisplayPlugin::compositeLayers() {
updateCompositeFramebuffer();
{
PROFILE_RANGE_EX(displayPlugins, "compositeScene", 0xff0077ff, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, "compositeScene", 0xff0077ff, (uint64_t)presentCount())
compositeScene();
}
{
PROFILE_RANGE_EX(displayPlugins, "compositeOverlay", 0xff0077ff, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, "compositeOverlay", 0xff0077ff, (uint64_t)presentCount())
compositeOverlay();
}
auto compositorHelper = DependencyManager::get<CompositorHelper>();
if (compositorHelper->getReticleVisible()) {
PROFILE_RANGE_EX(displayPlugins, "compositePointer", 0xff0077ff, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, "compositePointer", 0xff0077ff, (uint64_t)presentCount())
compositePointer();
}
{
PROFILE_RANGE_EX(displayPlugins, "compositeExtra", 0xff0077ff, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, "compositeExtra", 0xff0077ff, (uint64_t)presentCount())
compositeExtra();
}
}
@ -595,12 +595,12 @@ void OpenGLDisplayPlugin::internalPresent() {
}
void OpenGLDisplayPlugin::present() {
PROFILE_RANGE_EX(displayPlugins, __FUNCTION__, 0xffffff00, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, __FUNCTION__, 0xffffff00, (uint64_t)presentCount())
updateFrameData();
incrementPresentCount();
{
PROFILE_RANGE_EX(displayPlugins, "recycle", 0xff00ff00, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, "recycle", 0xff00ff00, (uint64_t)presentCount())
_gpuContext->recycle();
}
@ -614,19 +614,19 @@ void OpenGLDisplayPlugin::present() {
_lastFrame = _currentFrame.get();
});
// Execute the frame rendering commands
PROFILE_RANGE_EX(displayPlugins, "execute", 0xff00ff00, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, "execute", 0xff00ff00, (uint64_t)presentCount())
_gpuContext->executeFrame(_currentFrame);
}
// Write all layers to a local framebuffer
{
PROFILE_RANGE_EX(displayPlugins, "composite", 0xff00ffff, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, "composite", 0xff00ffff, (uint64_t)presentCount())
compositeLayers();
}
// Take the composite framebuffer and send it to the output device
{
PROFILE_RANGE_EX(displayPlugins, "internalPresent", 0xff00ffff, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, (uint64_t)presentCount())
internalPresent();
}

View file

@ -206,7 +206,7 @@ float HmdDisplayPlugin::getLeftCenterPixel() const {
}
void HmdDisplayPlugin::internalPresent() {
PROFILE_RANGE_EX(displayPlugins, __FUNCTION__, 0xff00ff00, (uint64_t)presentCount())
PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)presentCount())
// Composite together the scene, overlay and mouse cursor
hmdPresent();

View file

@ -211,7 +211,7 @@ namespace render {
template <> void payloadRender(const RenderableModelEntityItemMeta::Pointer& payload, RenderArgs* args) {
if (args) {
if (payload && payload->entity) {
PROFILE_RANGE(renderlogging, "MetaModelRender");
PROFILE_RANGE(render, "MetaModelRender");
payload->entity->render(args);
}
}

View file

@ -321,7 +321,7 @@ FBXNode parseTextFBXNode(Tokenizer& tokenizer) {
}
FBXNode FBXReader::parseFBX(QIODevice* device) {
PROFILE_RANGE_EX(modelformat, __FUNCTION__, 0xff0000ff, device);
PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xff0000ff, device);
// verify the prolog
const QByteArray BINARY_PROLOG = "Kaydara FBX Binary ";
if (device->peek(BINARY_PROLOG.size()) != BINARY_PROLOG) {

View file

@ -420,7 +420,7 @@ done:
FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, const QUrl& url) {
PROFILE_RANGE_EX(modelformat, __FUNCTION__, 0xffff0000, nullptr);
PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr);
QBuffer buffer { &model };
buffer.open(QIODevice::ReadOnly);

View file

@ -38,6 +38,9 @@
#include "GLLogging.h"
#include "Context.h"
Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml")
Q_LOGGING_CATEGORY(trace_render_qml_gl, "trace.render.qml.gl")
struct TextureSet {
// The number of surfaces with this size
size_t count { 0 };
@ -276,6 +279,7 @@ void OffscreenQmlSurface::render() {
return;
}
PROFILE_RANGE(render_qml_gl, __FUNCTION__)
_canvas->makeCurrent();
_renderControl->sync();
@ -284,7 +288,6 @@ void OffscreenQmlSurface::render() {
GLuint texture = offscreenTextures.getNextTexture(_size);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0);
PROFILE_RANGE(glLogging, "qml_render->rendercontrol")
_renderControl->render();
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, texture);
@ -617,12 +620,12 @@ void OffscreenQmlSurface::updateQuick() {
}
if (_polish) {
PROFILE_RANGE(render_qml, "OffscreenQML polish")
_renderControl->polishItems();
_polish = false;
}
if (_render) {
PROFILE_RANGE(glLogging, __FUNCTION__);
render();
_render = false;
}

View file

@ -200,7 +200,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) {
_inRenderTransferPass = true;
{ // Sync all the buffers
PROFILE_RANGE(gpugllogging, "syncGPUBuffer");
PROFILE_RANGE(render_gpu_gl, "syncGPUBuffer");
for (auto& cached : batch._buffers._items) {
if (cached._data) {
@ -210,7 +210,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) {
}
{ // Sync all the buffers
PROFILE_RANGE(gpugllogging, "syncCPUTransform");
PROFILE_RANGE(render_gpu_gl, "syncCPUTransform");
_transform._cameras.clear();
_transform._cameraOffsets.clear();
@ -242,7 +242,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) {
}
{ // Sync the transform buffers
PROFILE_RANGE(gpugllogging, "syncGPUTransform");
PROFILE_RANGE(render_gpu_gl, "syncGPUTransform");
transferTransformState(batch);
}
@ -304,7 +304,7 @@ void GLBackend::render(const Batch& batch) {
}
{
PROFILE_RANGE(gpugllogging, "Transfer");
PROFILE_RANGE(render_gpu_gl, "Transfer");
renderPassTransfer(batch);
}
@ -314,7 +314,7 @@ void GLBackend::render(const Batch& batch) {
}
#endif
{
PROFILE_RANGE(gpugllogging, _stereo._enable ? "Render Stereo" : "Render");
PROFILE_RANGE(render_gpu_gl, _stereo._enable ? "Render Stereo" : "Render");
renderPassDraw(batch);
}
#ifdef GPU_STEREO_DRAWCALL_INSTANCED

View file

@ -28,7 +28,7 @@ void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) {
auto query = batch._queries.get(batch._params[paramOffset]._uint);
GLQuery* glquery = syncGPUObject(*query);
if (glquery) {
PROFILE_RANGE_BEGIN(gpugllogging, glquery->_profileRangeId, query->getName().c_str(), 0xFFFF7F00);
PROFILE_RANGE_BEGIN(render_gpu_gl, glquery->_profileRangeId, query->getName().c_str(), 0xFFFF7F00);
++_queryStage._rangeQueryDepth;
glGetInteger64v(GL_TIMESTAMP, (GLint64*)&glquery->_batchElapsedTime);
@ -62,7 +62,7 @@ void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) {
glGetInteger64v(GL_TIMESTAMP, &now);
glquery->_batchElapsedTime = now - glquery->_batchElapsedTime;
PROFILE_RANGE_END(gpugllogging, glquery->_profileRangeId);
PROFILE_RANGE_END(render_gpu_gl, glquery->_profileRangeId);
(void)CHECK_GL_ERROR();
}

View file

@ -16,6 +16,7 @@
#include <fstream>
Q_LOGGING_CATEGORY(gpugllogging, "hifi.gpu.gl")
Q_LOGGING_CATEGORY(trace_render_gpu_gl, "trace.render.gpu.gl")
namespace gpu { namespace gl {

View file

@ -15,6 +15,7 @@
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(gpugllogging)
Q_DECLARE_LOGGING_CATEGORY(trace_render_gpu_gl)
namespace gpu { namespace gl {

View file

@ -122,7 +122,7 @@ void GL41Texture::transferMip(uint16_t mipLevel, uint8_t face) const {
}
void GL41Texture::startTransfer() {
PROFILE_RANGE(gpugllogging, __FUNCTION__);
PROFILE_RANGE(render_gpu_gl, __FUNCTION__);
Parent::startTransfer();
glBindTexture(_target, _id);

View file

@ -759,7 +759,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
return false;
}
PROFILE_RANGE(gpulogging, "sphericalHarmonicsFromTexture");
PROFILE_RANGE(render_gpu, "sphericalHarmonicsFromTexture");
const uint sqOrder = order*order;
@ -792,7 +792,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
// for each face of cube texture
for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) {
PROFILE_RANGE(gpulogging, "ProcessFace");
PROFILE_RANGE(render_gpu, "ProcessFace");
auto numComponents = cubeTexture.accessStoredMipFace(0,face)->getFormat().getScalarCount();
auto data = cubeTexture.accessStoredMipFace(0,face)->readData();

View file

@ -24,6 +24,8 @@
#include <Trace.h>
#include <StatTracker.h>
Q_LOGGING_CATEGORY(trace_resource_parse_geometry, "trace.resource.parse.geometry")
class GeometryReader;
class GeometryExtra {
@ -54,7 +56,7 @@ private:
};
void GeometryMappingResource::downloadFinished(const QByteArray& data) {
PROFILE_ASYNC_BEGIN(modelnetworking, "GeometryMappingResource::downloadFinished", _url.toString(),
PROFILE_ASYNC_BEGIN(resource_parse_geometry, "GeometryMappingResource::downloadFinished", _url.toString(),
{ { "url", _url.toString() } });
auto mapping = FSTReader::readMapping(data);
@ -120,7 +122,7 @@ void GeometryMappingResource::onGeometryMappingLoaded(bool success) {
disconnect(_connection); // FIXME Should not have to do this
}
PROFILE_ASYNC_END(modelnetworking, "GeometryMappingResource::downloadFinished", _url.toString());
PROFILE_ASYNC_END(resource_parse_geometry, "GeometryMappingResource::downloadFinished", _url.toString());
finishedLoading(success);
}
@ -145,7 +147,7 @@ private:
void GeometryReader::run() {
DependencyManager::get<StatTracker>()->decrementStat("PendingProcessing");
CounterStat counter("Processing");
PROFILE_RANGE_EX(modelnetworking, "GeometryReader::run", 0xFF00FF00, 0, { { "url", _url.toString() } });
PROFILE_RANGE_EX(resource_parse_geometry, "GeometryReader::run", 0xFF00FF00, 0, { { "url", _url.toString() } });
auto originalPriority = QThread::currentThread()->priority();
if (originalPriority == QThread::InheritPriority) {
originalPriority = QThread::NormalPriority;

View file

@ -36,6 +36,8 @@
#include <Trace.h>
#include <StatTracker.h>
Q_LOGGING_CATEGORY(trace_resource_parse_image, "trace.resource.parse.image")
TextureCache::TextureCache() {
setUnusedResourceCacheSize(0);
setObjectName("TextureCache");
@ -349,7 +351,7 @@ void ImageReader::run() {
CounterStat counter("Processing");
PROFILE_RANGE_EX(modelnetworking, __FUNCTION__, 0xffff0000, 0, { { "url", _url.toString() } });
PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffff0000, 0, { { "url", _url.toString() } });
auto originalPriority = QThread::currentThread()->priority();
if (originalPriority == QThread::InheritPriority) {
originalPriority = QThread::NormalPriority;
@ -395,7 +397,7 @@ void ImageReader::run() {
auto url = _url.toString().toStdString();
PROFILE_RANGE_EX(modelnetworking, __FUNCTION__, 0xffffff00, 0);
PROFILE_RANGE_EX(resource_parse_image, __FUNCTION__, 0xffffff00, 0);
texture.reset(resource.dynamicCast<NetworkTexture>()->getTextureLoader()(image, url));
}

View file

@ -746,7 +746,7 @@ const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = {
const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout);
gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips, bool generateIrradiance) {
PROFILE_RANGE(modelLog, "processCubeTextureColorFromImage");
PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage");
gpu::Texture* theTexture = nullptr;
if ((srcImage.width() > 0) && (srcImage.height() > 0)) {
@ -805,13 +805,13 @@ gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcIm
}
if (generateMips) {
PROFILE_RANGE(modelLog, "generateMips");
PROFILE_RANGE(resource_parse, "generateMips");
theTexture->autoGenerateMips(-1);
}
// Generate irradiance while we are at it
if (generateIrradiance) {
PROFILE_RANGE(modelLog, "generateIrradiance");
PROFILE_RANGE(resource_parse, "generateIrradiance");
theTexture->generateIrradiance();
}
}

View file

@ -650,12 +650,12 @@ void Resource::reinsert() {
void Resource::makeRequest() {
if (_request) {
PROFILE_ASYNC_END(resourceLog, "Resource:" + getType(), QString::number(_requestID));
PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID));
_request->disconnect();
_request->deleteLater();
}
PROFILE_ASYNC_BEGIN(resourceLog, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } });
PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } });
_request = ResourceManager::createResourceRequest(this, _activeUrl);
@ -663,7 +663,7 @@ void Resource::makeRequest() {
qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
ResourceCache::requestCompleted(_self);
finishedLoading(false);
PROFILE_ASYNC_END(resourceLog, "Resource:" + getType(), QString::number(_requestID));
PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID));
return;
}
@ -688,7 +688,7 @@ void Resource::handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTota
void Resource::handleReplyFinished() {
Q_ASSERT_X(_request, "Resource::handleReplyFinished", "Request should not be null while in handleReplyFinished");
PROFILE_ASYNC_END(resourceLog, "Resource:" + getType(), QString::number(_requestID), {
PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID), {
{ "from_cache", _request->loadedFromCache() },
{ "size_mb", _bytesTotal / 1000000.0 }
});

View file

@ -25,14 +25,15 @@ void UserActivityLoggerScriptingInterface::toggledAway(bool isAway) {
}
void UserActivityLoggerScriptingInterface::tutorialProgress( QString stepName, int stepNumber, float secondsToComplete,
float tutorialElapsedTime, QString tutorialRunID, int tutorialVersion) {
float tutorialElapsedTime, QString tutorialRunID, int tutorialVersion, QString controllerType) {
logAction("tutorial_progress", {
{ "tutorial_run_id", tutorialRunID },
{ "tutorial_version", tutorialVersion },
{ "step", stepName },
{ "step_number", stepNumber },
{ "seconds_to_complete", secondsToComplete },
{ "tutorial_elapsed_seconds", tutorialElapsedTime }
{ "tutorial_elapsed_seconds", tutorialElapsedTime },
{ "controller_type", controllerType }
});
}

View file

@ -24,7 +24,7 @@ public:
Q_INVOKABLE void openedMarketplace();
Q_INVOKABLE void toggledAway(bool isAway);
Q_INVOKABLE void tutorialProgress(QString stepName, int stepNumber, float secondsToComplete,
float tutorialElapsedTime, QString tutorialRunID = "", int tutorialVersion = 0);
float tutorialElapsedTime, QString tutorialRunID = "", int tutorialVersion = 0, QString controllerType = "");
private:
void logAction(QString action, QJsonObject details = {});

View file

@ -86,7 +86,7 @@ SendQueue::SendQueue(Socket* socket, HifiSockAddr dest) :
_socket(socket),
_destination(dest)
{
PROFILE_ASYNC_BEGIN(networking, "SendQueue", _destination.toString());
PROFILE_ASYNC_BEGIN(network, "SendQueue", _destination.toString());
// setup psuedo-random number generation for all instances of SendQueue
static std::random_device rd;
@ -106,7 +106,7 @@ SendQueue::SendQueue(Socket* socket, HifiSockAddr dest) :
}
SendQueue::~SendQueue() {
PROFILE_ASYNC_END(networking, "SendQueue", _destination.toString());
PROFILE_ASYNC_END(network, "SendQueue", _destination.toString());
}
void SendQueue::queuePacket(std::unique_ptr<Packet> packet) {
@ -227,7 +227,7 @@ void SendQueue::sendHandshake() {
if (!_hasReceivedHandshakeACK) {
// we haven't received a handshake ACK from the client, send another now
auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, sizeof(SequenceNumber));
PROFILE_ASYNC_BEGIN(networking, "SendQueue:Handshake", _destination.toString());
PROFILE_ASYNC_BEGIN(network, "SendQueue:Handshake", _destination.toString());
handshakePacket->writePrimitive(_initialSequenceNumber);
_socket->writeBasePacket(*handshakePacket, _destination);
@ -244,7 +244,7 @@ void SendQueue::handshakeACK(SequenceNumber initialSequenceNumber) {
std::lock_guard<std::mutex> locker { _handshakeMutex };
_hasReceivedHandshakeACK = true;
}
PROFILE_ASYNC_END(networking, "SendQueue:Handshake", _destination.toString());
PROFILE_ASYNC_END(network, "SendQueue:Handshake", _destination.toString());
// Notify on the handshake ACK condition
_handshakeACKCondition.notify_one();

View file

@ -392,7 +392,6 @@ OctreeElementPointer OctreeElement::addChildAtIndex(int childIndex) {
_isDirty = true;
markWithChangedTime();
PROFILE_INSTANT(octree, "EntityAdd", "g");
}
return childAt;
}

View file

@ -24,16 +24,16 @@ bool PluginUtils::isHMDAvailable(const QString& pluginName) {
return false;
}
bool PluginUtils::isHandControllerAvailable() {
bool PluginUtils::isHandControllerAvailable(const QString& pluginName) {
for (auto& inputPlugin : PluginManager::getInstance()->getInputPlugins()) {
if (inputPlugin->isHandController()) {
if (inputPlugin->isHandController() && (pluginName.isEmpty() || inputPlugin->getName() == pluginName)) {
return true;
}
}
return false;
};
bool isSubdeviceContainingNameAvailable(QString name) {
bool PluginUtils::isSubdeviceContainingNameAvailable(QString name) {
for (auto& inputPlugin : PluginManager::getInstance()->getInputPlugins()) {
if (inputPlugin->isActive()) {
auto subdeviceNames = inputPlugin->getSubdeviceNames();

View file

@ -15,7 +15,8 @@
class PluginUtils {
public:
static bool isHMDAvailable(const QString& pluginName = "");
static bool isHandControllerAvailable();
static bool isHandControllerAvailable(const QString& pluginName = "");
static bool isSubdeviceContainingNameAvailable(QString name);
static bool isViveControllerAvailable();
static bool isOculusTouchControllerAvailable();
static bool isXboxControllerAvailable();

View file

@ -793,7 +793,7 @@ void RenderDeferred::configure(const Config& config) {
}
void RenderDeferred::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const Inputs& inputs) {
PROFILE_RANGE(renderlogging, "DeferredLighting");
PROFILE_RANGE(render, "DeferredLighting");
auto deferredTransform = inputs.get0();
auto deferredFramebuffer = inputs.get1();

View file

@ -281,7 +281,7 @@ void Model::reset() {
}
bool Model::updateGeometry() {
PROFILE_RANGE(renderutils, __FUNCTION__);
PROFILE_RANGE(render, __FUNCTION__);
PerformanceTimer perfTimer("Model::updateGeometry");
bool needFullUpdate = false;
@ -476,7 +476,7 @@ bool Model::convexHullContains(glm::vec3 point) {
// entity-scripts to call. I think it would be best to do the picking once-per-frame (in cpu, or gpu if possible)
// and then the calls use the most recent such result.
void Model::recalculateMeshBoxes(bool pickAgainstTriangles) {
PROFILE_RANGE(renderutils, __FUNCTION__);
PROFILE_RANGE(render, __FUNCTION__);
bool calculatedMeshTrianglesNeeded = pickAgainstTriangles && !_calculatedMeshTrianglesValid;
if (!_calculatedMeshBoxesValid || calculatedMeshTrianglesNeeded || (!_calculatedMeshPartBoxesValid && pickAgainstTriangles) ) {
@ -850,6 +850,12 @@ void Model::setTextures(const QVariantMap& textures) {
_needsUpdateTextures = true;
_needsFixupInScene = true;
_renderGeometry->setTextures(textures);
} else {
// FIXME(Huffman): Disconnect previously connected lambdas so we don't set textures multiple
// after the geometry has finished loading.
connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, [this, textures]() {
_renderGeometry->setTextures(textures);
});
}
}
@ -969,7 +975,7 @@ Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointe
}
void Blender::run() {
PROFILE_RANGE_EX(renderutils, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } });
PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } });
QVector<glm::vec3> vertices, normals;
if (_model) {
int offset = 0;
@ -1090,7 +1096,7 @@ void Model::snapToRegistrationPoint() {
}
void Model::simulate(float deltaTime, bool fullUpdate) {
PROFILE_RANGE(renderutils, __FUNCTION__);
PROFILE_RANGE(simulation, __FUNCTION__);
PerformanceTimer perfTimer("Model::simulate");
fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit)
|| (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint);

View file

@ -21,29 +21,14 @@
#include <render/CullTask.h>
#include <render/SortTask.h>
#include <render/DrawTask.h>
#include <render/DrawStatus.h>
#include <render/DrawSceneOctree.h>
#include <render/BlurTask.h>
#include "LightingModel.h"
#include "DebugDeferredBuffer.h"
#include "DeferredFramebuffer.h"
#include "DeferredLightingEffect.h"
#include "SurfaceGeometryPass.h"
#include "FramebufferCache.h"
#include "HitEffect.h"
#include "TextureCache.h"
#include "AmbientOcclusionEffect.h"
#include "AntialiasingEffect.h"
#include "ToneMappingEffect.h"
#include "SubsurfaceScattering.h"
#include <gpu/StandardShaderLib.h>
#include "drawOpaqueStencil_frag.h"
#include <render/drawItemBounds_vert.h>
#include <render/drawItemBounds_frag.h>
using namespace render;
extern void initOverlay3DPipelines(render::ShapePlumber& plumber);
@ -95,6 +80,8 @@ RenderForwardTask::RenderForwardTask(CullFunctor cullFunctor) {
const auto framebuffer = addJob<PrepareFramebuffer>("PrepareFramebuffer");
addJob<DrawBounds>("DrawBounds", opaques);
// Blit!
addJob<Blit>("Blit", framebuffer);
}
@ -151,13 +138,62 @@ void PrepareFramebuffer::run(const SceneContextPointer& sceneContext, const Rend
batch.setFramebuffer(_framebuffer);
batch.clearFramebuffer(
gpu::Framebuffer::BUFFER_COLOR0 |
gpu::Framebuffer::BUFFER_COLOR1 |
gpu::Framebuffer::BUFFER_COLOR2 |
gpu::Framebuffer::BUFFER_COLOR3 |
gpu::Framebuffer::BUFFER_DEPTH |
gpu::Framebuffer::BUFFER_STENCIL,
vec4(vec3(0), 0), 1.0, 0.0, true);
vec4(vec3(0), 1), 1.0, 0.0, true);
});
framebuffer = _framebuffer;
}
const gpu::PipelinePointer DrawBounds::getPipeline() {
if (!_boundsPipeline) {
auto vs = gpu::Shader::createVertex(std::string(drawItemBounds_vert));
auto ps = gpu::Shader::createPixel(std::string(drawItemBounds_frag));
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
gpu::Shader::makeProgram(*program, slotBindings);
_cornerLocation = program->getUniforms().findLocation("inBoundPos");
_scaleLocation = program->getUniforms().findLocation("inBoundDim");
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, false, gpu::LESS_EQUAL);
state->setBlendFunction(true,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO);
_boundsPipeline = gpu::Pipeline::create(program, state);
}
return _boundsPipeline;
}
void DrawBounds::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const Inputs& items) {
RenderArgs* args = renderContext->args;
gpu::doInBatch(args->_context, [&](gpu::Batch& batch) {
// Setup projection
glm::mat4 projMat;
Transform viewMat;
args->getViewFrustum().evalProjectionMatrix(projMat);
args->getViewFrustum().evalViewTransform(viewMat);
batch.setProjectionTransform(projMat);
batch.setViewTransform(viewMat);
batch.setModelTransform(Transform());
// Bind program
batch.setPipeline(getPipeline());
assert(_cornerLocation >= 0);
assert(_scaleLocation >= 0);
// Render bounds
for (const auto& item : items) {
batch._glUniform3fv(_cornerLocation, 1, (const float*)(&item.bound.getCorner()));
batch._glUniform3fv(_scaleLocation, 1, (const float*)(&item.bound.getScale()));
static const int NUM_VERTICES_PER_CUBE = 24;
batch.draw(gpu::LINES, NUM_VERTICES_PER_CUBE, 0);
}
});
}

View file

@ -39,4 +39,18 @@ private:
gpu::FramebufferPointer _framebuffer;
};
#endif // hifi_RenderForwardTask_h
class DrawBounds {
public:
using Inputs = render::ItemBounds;
using JobModel = render::Job::ModelI<DrawBounds, Inputs>;
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& items);
private:
const gpu::PipelinePointer getPipeline();
gpu::PipelinePointer _boundsPipeline;
int _cornerLocation { -1 };
int _scaleLocation { -1 };
};
#endif // hifi_RenderForwardTask_h

View file

@ -78,13 +78,16 @@ void consolidateChangeQueue(PendingChangesQueue& queue, PendingChanges& singleBa
}
void Scene::processPendingChangesQueue() {
PROFILE_RANGE(renderlogging, __FUNCTION__);
_changeQueueMutex.lock();
PROFILE_RANGE(render, __FUNCTION__);
PendingChanges consolidatedPendingChanges;
consolidateChangeQueue(_changeQueue, consolidatedPendingChanges);
_changeQueueMutex.unlock();
{
std::unique_lock<std::mutex> lock(_changeQueueMutex);
consolidateChangeQueue(_changeQueue, consolidatedPendingChanges);
}
_itemsMutex.lock();
{
std::unique_lock<std::mutex> lock(_itemsMutex);
// Here we should be able to check the value of last ItemID allocated
// and allocate new items accordingly
ItemID maxID = _IDAllocator.load();
@ -108,9 +111,7 @@ void Scene::processPendingChangesQueue() {
// Update the numItemsAtomic counter AFTER the pending changes went through
_numAllocatedItems.exchange(maxID);
// ready to go back to rendering activities
_itemsMutex.unlock();
}
}
void Scene::resetItems(const ItemIDs& ids, Payloads& payloads) {

View file

@ -572,7 +572,7 @@ public:
void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) {
PerformanceTimer perfTimer(_name.c_str());
PROFILE_RANGE(renderlogging, _name.c_str());
PROFILE_RANGE(render, _name.c_str());
auto start = usecTimestampNow();
_concept->run(sceneContext, renderContext);

View file

@ -8,6 +8,17 @@
#include "Profile.h"
Q_LOGGING_CATEGORY(trace_app, "trace.app")
Q_LOGGING_CATEGORY(trace_network, "trace.network")
Q_LOGGING_CATEGORY(trace_parse, "trace.parse")
Q_LOGGING_CATEGORY(trace_render, "trace.render")
Q_LOGGING_CATEGORY(trace_render_gpu, "trace.render.gpu")
Q_LOGGING_CATEGORY(trace_resource, "trace.resource")
Q_LOGGING_CATEGORY(trace_resource_network, "trace.resource.network")
Q_LOGGING_CATEGORY(trace_resource_parse, "trace.resource.parse")
Q_LOGGING_CATEGORY(trace_simulation, "trace.simulation")
Q_LOGGING_CATEGORY(trace_simulation_animation, "trace.simulation.animation")
Q_LOGGING_CATEGORY(trace_simulation_physics, "trace.simulation.physics")
#if defined(NSIGHT_FOUND)
#include "nvToolsExt.h"

View file

@ -11,6 +11,18 @@
#define HIFI_PROFILE_
#include "Trace.h"
#include "SharedUtil.h"
Q_DECLARE_LOGGING_CATEGORY(trace_app)
Q_DECLARE_LOGGING_CATEGORY(trace_network)
Q_DECLARE_LOGGING_CATEGORY(trace_render)
Q_DECLARE_LOGGING_CATEGORY(trace_render_gpu)
Q_DECLARE_LOGGING_CATEGORY(trace_resource)
Q_DECLARE_LOGGING_CATEGORY(trace_resource_parse)
Q_DECLARE_LOGGING_CATEGORY(trace_resource_network)
Q_DECLARE_LOGGING_CATEGORY(trace_simulation)
Q_DECLARE_LOGGING_CATEGORY(trace_simulation_animation)
Q_DECLARE_LOGGING_CATEGORY(trace_simulation_physics)
class Duration {
public:
@ -51,13 +63,18 @@ inline void counter(const QLoggingCategory& category, const QString& name, const
}
}
#define PROFILE_RANGE(category, name) Duration profileRangeThis(category(), name);
#define PROFILE_RANGE_EX(category, name, argbColor, payload, ...) Duration profileRangeThis(category(), name, argbColor, (uint64_t)payload, ##__VA_ARGS__);
#define PROFILE_RANGE_BEGIN(category, rangeId, name, argbColor) rangeId = Duration::beginRange(category(), name, argbColor)
#define PROFILE_RANGE_END(category, rangeId) Duration::endRange(category(), rangeId)
#define PROFILE_ASYNC_BEGIN(category, name, id, ...) asyncBegin(category(), name, id, ##__VA_ARGS__);
#define PROFILE_ASYNC_END(category, name, id, ...) asyncEnd(category(), name, id, ##__VA_ARGS__);
#define PROFILE_COUNTER(category, name, ...) counter(category(), name, ##__VA_ARGS__);
#define PROFILE_INSTANT(category, name, ...) instant(category(), name, ##__VA_ARGS__);
#define PROFILE_RANGE(category, name) Duration profileRangeThis(trace_##category(), name);
#define PROFILE_RANGE_EX(category, name, argbColor, payload, ...) Duration profileRangeThis(trace_##category(), name, argbColor, (uint64_t)payload, ##__VA_ARGS__);
#define PROFILE_RANGE_BEGIN(category, rangeId, name, argbColor) rangeId = Duration::beginRange(trace_##category(), name, argbColor)
#define PROFILE_RANGE_END(category, rangeId) Duration::endRange(trace_##category(), rangeId)
#define PROFILE_ASYNC_BEGIN(category, name, id, ...) asyncBegin(trace_##category(), name, id, ##__VA_ARGS__);
#define PROFILE_ASYNC_END(category, name, id, ...) asyncEnd(trace_##category(), name, id, ##__VA_ARGS__);
#define PROFILE_COUNTER(category, name, ...) counter(trace_##category(), name, ##__VA_ARGS__);
#define PROFILE_INSTANT(category, name, ...) instant(trace_##category(), name, ##__VA_ARGS__);
#define SAMPLE_PROFILE_RANGE(chance, category, name, ...) if (randFloat() <= chance) { PROFILE_RANGE(category, name); }
#define SAMPLE_PROFILE_RANGE_EX(chance, category, name, ...) if (randFloat() <= chance) { PROFILE_RANGE_EX(category, name, argbColor, payload, ##__VA_ARGS__); }
#define SAMPLE_PROFILE_COUNTER(chance, category, name, ...) if (randFloat() <= chance) { PROFILE_COUNTER(category, name, ##__VA_ARGS__); }
#define SAMPLE_PROFILE_INSTANT(chance, category, name, ...) if (randFloat() <= chance) { PROFILE_INSTANT(category, name, ##__VA_ARGS__); }
#endif

View file

@ -13,6 +13,9 @@
#include <QtCore/QDebug>
#include <QtCore/QCoreApplication>
#include <QtCore/QThread>
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <QtCore/QStandardPaths>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
@ -100,7 +103,24 @@ void TraceEvent::writeJson(QTextStream& out) const {
#endif
}
void Tracer::serialize(const QString& path) {
void Tracer::serialize(const QString& originalPath) {
QString path = originalPath;
// If the filename is relative, turn it into an absolute path relative to the document directory.
QFileInfo originalFileInfo(path);
if (originalFileInfo.isRelative()) {
QString docsLocation = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
path = docsLocation + "/" + path;
QFileInfo info(path);
if (!info.absoluteDir().exists()) {
QString originalRelativePath = originalFileInfo.path();
QDir(docsLocation).mkpath(originalRelativePath);
}
}
std::list<TraceEvent> currentEvents;
{
std::lock_guard<std::mutex> guard(_eventsMutex);

View file

@ -21,8 +21,6 @@
#include "DependencyManager.h"
#define TRACE_ENABLED
namespace tracing {
bool enabled();

View file

@ -110,7 +110,7 @@ void OculusDisplayPlugin::hmdPresent() {
return;
}
PROFILE_RANGE_EX(displayplugins, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
int curIndex;
ovr_GetTextureSwapChainCurrentIndex(_session, _textureSwapChain, &curIndex);

View file

@ -529,7 +529,7 @@ static bool isBadPose(vr::HmdMatrix34_t* mat) {
}
bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
PROFILE_RANGE_EX(displayplugins, __FUNCTION__, 0xff7fff00, frameIndex)
PROFILE_RANGE_EX(render, __FUNCTION__, 0xff7fff00, frameIndex)
handleOpenVrEvents();
if (openVrQuitRequested()) {
QMetaObject::invokeMethod(qApp, "quit");
@ -633,7 +633,7 @@ void OpenVrDisplayPlugin::compositeLayers() {
}
void OpenVrDisplayPlugin::hmdPresent() {
PROFILE_RANGE_EX(displayplugins, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
if (_threadedSubmit) {
_submitThread->waitForPresent();
@ -654,7 +654,7 @@ void OpenVrDisplayPlugin::hmdPresent() {
}
void OpenVrDisplayPlugin::postPreview() {
PROFILE_RANGE_EX(displayplugins, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
PoseData nextRender, nextSim;
nextRender.frameIndex = presentCount();

View file

@ -21,14 +21,17 @@ function clamp(value, min, max) {
}
function resolveHardware(path) {
var parts = path.split(".");
function resolveInner(base, path, i) {
if (i >= path.length) {
return base;
if (typeof path === 'string') {
var parts = path.split(".");
function resolveInner(base, path, i) {
if (i >= path.length) {
return base;
}
return resolveInner(base[path[i]], path, ++i);
}
return resolveInner(base[path[i]], path, ++i);
return resolveInner(Controller.Hardware, parts, 0);
}
return resolveInner(Controller.Hardware, parts, 0);
return path;
}
var DEBUG = true;
@ -132,7 +135,9 @@ createControllerDisplay = function(config) {
overlayID = Overlays.addOverlay("model", properties);
if (part.type === "rotational") {
mapping.from([part.input]).peek().to(function(controller, overlayID, part) {
var input = resolveHardware(part.input);
print("Mapping to: ", part.input, input);
mapping.from([input]).peek().to(function(controller, overlayID, part) {
return function(value) {
value = clamp(value, part.minValue, part.maxValue);
@ -157,18 +162,85 @@ createControllerDisplay = function(config) {
}(controller, overlayID, part));
} else if (part.type === "touchpad") {
var visibleInput = resolveHardware(part.visibleInput);
var xinput = resolveHardware(part.xInput);
var yinput = resolveHardware(part.yInput);
var xInput = resolveHardware(part.xInput);
var yInput = resolveHardware(part.yInput);
// TODO: Touchpad inputs are currently only working for half
// of the touchpad. When that is fixed, it would be useful
// to update these to display the current finger position.
mapping.from([visibleInput]).peek().to(function(value) {
});
mapping.from([xinput]).peek().to(function(value) {
mapping.from([xInput]).peek().to(function(value) {
});
mapping.from([yinput]).peek().invert().to(function(value) {
mapping.from([yInput]).peek().invert().to(function(value) {
});
} else if (part.type === "joystick") {
(function(controller, overlayID, part) {
const xInput = resolveHardware(part.xInput);
const yInput = resolveHardware(part.yInput);
var xvalue = 0;
var yvalue = 0;
function calculatePositionAndRotation(xValue, yValue) {
var rotation = Quat.fromPitchYawRollDegrees(yValue * part.xHalfAngle, 0, xValue * part.yHalfAngle);
var offset = { x: 0, y: 0, z: 0 };
if (part.originOffset) {
offset = Vec3.multiplyQbyV(rotation, part.originOffset);
offset = Vec3.subtract(part.originOffset, offset);
}
var partPosition = Vec3.sum(controller.position,
Vec3.multiplyQbyV(controller.rotation, Vec3.sum(offset, part.naturalPosition)));
var partRotation = Quat.multiply(controller.rotation, rotation)
return {
position: partPosition,
rotation: partRotation
}
}
mapping.from([xInput]).peek().to(function(value) {
xvalue = value;
//print(overlayID, xvalue.toFixed(3), yvalue.toFixed(3));
var posRot = calculatePositionAndRotation(xvalue, yvalue);
Overlays.editOverlay(overlayID, {
localPosition: posRot.position,
localRotation: posRot.rotation
});
});
mapping.from([yInput]).peek().to(function(value) {
yvalue = value;
var posRot = calculatePositionAndRotation(xvalue, yvalue);
Overlays.editOverlay(overlayID, {
localPosition: posRot.position,
localRotation: posRot.rotation
});
});
})(controller, overlayID, part);
} else if (part.type === "linear") {
(function(controller, overlayID, part) {
const input = resolveHardware(part.input);
mapping.from([input]).peek().to(function(value) {
//print(value);
var axis = Vec3.multiplyQbyV(controller.rotation, part.axis);
var offset = Vec3.multiply(part.maxTranslation * value, axis);
var partPosition = Vec3.sum(controller.position, Vec3.multiplyQbyV(controller.rotation, part.naturalPosition));
var position = Vec3.sum(partPosition, offset);
Overlays.editOverlay(overlayID, {
localPosition: position
});
});
})(controller, overlayID, part);
} else if (part.type === "static") {
// do nothing
} else {

View file

@ -15,6 +15,7 @@
Script.include("controllerDisplay.js");
Script.include("viveControllerConfiguration.js");
Script.include("touchControllerConfiguration.js");
var HIDE_CONTROLLERS_ON_EQUIP = false;
@ -41,12 +42,28 @@ ControllerDisplayManager = function() {
function updateControllers() {
if (HMD.active && HMD.shouldShowHandControllers()) {
var leftConfig = null;
var rightConfig = null;
if ("Vive" in Controller.Hardware) {
if (!controllerLeft) {
controllerLeft = createControllerDisplay(VIVE_CONTROLLER_CONFIGURATION_LEFT);
leftConfig = VIVE_CONTROLLER_CONFIGURATION_LEFT;
rightConfig = VIVE_CONTROLLER_CONFIGURATION_RIGHT;
}
if ("OculusTouch" in Controller.Hardware) {
leftConfig = TOUCH_CONTROLLER_CONFIGURATION_LEFT;
rightConfig = TOUCH_CONTROLLER_CONFIGURATION_RIGHT;
}
if (leftConfig !== null && rightConfig !== null) {
print("Loading controllers");
if (controllerLeft === null) {
controllerLeft = createControllerDisplay(leftConfig);
controllerLeft.setVisible(true);
}
if (!controllerRight) {
controllerRight = createControllerDisplay(VIVE_CONTROLLER_CONFIGURATION_RIGHT);
if (controllerRight === null) {
controllerRight = createControllerDisplay(rightConfig);
controllerRight.setVisible(true);
}
// We've found the controllers, we no longer need to look for active controllers
if (controllerCheckerIntervalID) {

View file

@ -0,0 +1,353 @@
//
// touchControllerConfiguration.js
//
// Created by Ryan Huffman on 12/06/16
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* globals TOUCH_CONTROLLER_CONFIGURATION_LEFT:true TOUCH_CONTROLLER_CONFIGURATION_RIGHT:true */
/* eslint camelcase: ["error", { "properties": "never" }] */
var leftBaseRotation = Quat.multiply(
Quat.fromPitchYawRollDegrees(-90, 0, 0),
Quat.fromPitchYawRollDegrees(0, 0, 90)
);
var rightBaseRotation = Quat.multiply(
Quat.fromPitchYawRollDegrees(-90, 0, 0),
Quat.fromPitchYawRollDegrees(0, 0, -90)
);
// keep these in sync with the values from OculusHelpers.cpp
var CONTROLLER_LENGTH_OFFSET = 0.0762;
var CONTROLLER_LATERAL_OFFSET = 0.0381;
var CONTROLLER_VERTICAL_OFFSET = 0.0381;
var CONTROLLER_FORWARD_OFFSET = 0.1524;
var leftBasePosition = Vec3.multiplyQbyV(leftBaseRotation, {
x: -CONTROLLER_LENGTH_OFFSET / 2.0,
y: CONTROLLER_LENGTH_OFFSET / 2.0,
z: CONTROLLER_LENGTH_OFFSET * 1.5
});
var rightBasePosition = Vec3.multiplyQbyV(rightBaseRotation, {
x: CONTROLLER_LENGTH_OFFSET / 2.0,
y: CONTROLLER_LENGTH_OFFSET / 2.0,
z: CONTROLLER_LENGTH_OFFSET * 1.5
});
var BASE_URL = Script.resourcesPath() + "meshes/controller/touch/";
TOUCH_CONTROLLER_CONFIGURATION_LEFT = {
name: "Touch",
controllers: [
{
modelURL: BASE_URL + "touch_l_body.fbx",
jointIndex: MyAvatar.getJointIndex("_CONTROLLER_LEFTHAND"),
naturalPosition: { x: 0.01648625358939171, y: -0.03551870584487915, z: -0.018527675420045853 },
dimensions: { x: 0.11053799837827682, y: 0.0995776429772377, z: 0.10139888525009155 },
rotation: leftBaseRotation,
position: leftBasePosition,
parts: {
tips: {
type: "static",
modelURL: BASE_URL + "Oculus-Labels-L.fbx",
naturalPosition: { x: -0.022335469722747803, y: 0.00022516027092933655, z: 0.020340695977211 },
textureName: "blank",
defaultTextureLayer: "blank",
textureLayers: {
blank: {
defaultTextureURL: BASE_URL + "Oculus-Labels-L.fbx/Oculus-Labels-L.fbm/Blank.png"
},
trigger: {
defaultTextureURL: BASE_URL + "Oculus-Labels-L.fbx/Oculus-Labels-L.fbm/Trigger.png"
},
arrows: {
defaultTextureURL: BASE_URL + "Oculus-Labels-L.fbx/Oculus-Labels-L.fbm/Rotate.png"
},
grip: {
defaultTextureURL: BASE_URL + "Oculus-Labels-L.fbx/Oculus-Labels-L.fbm/Grip-oculus.png"
},
teleport: {
defaultTextureURL: BASE_URL + "Oculus-Labels-L.fbx/Oculus-Labels-L.fbm/Teleport.png"
},
}
},
trigger: {
type: "rotational",
modelURL: BASE_URL + "touch_l_trigger.fbx",
naturalPosition: { x: 0.0008544912561774254, y: -0.019867943599820137, z: 0.018800459802150726 },
// rotational
input: Controller.Standard.LT,
origin: { x: 0, y: -0.015, z: -0.00 },
minValue: 0.0,
maxValue: 1.0,
axis: { x: 1, y: 0, z: 0 },
maxAngle: 17,
textureName: "tex-highlight",
defaultTextureLayer: "normal",
textureLayers: {
normal: {
defaultTextureURL: BASE_URL + "touch_l_trigger.fbx/touch_l_trigger.fbm/L_controller_DIF.jpg",
},
highlight: {
defaultTextureURL: BASE_URL + "touch_l_trigger.fbx/touch_l_trigger.fbm/L_controller-highlight_DIF.jpg",
}
}
},
grip: {
type: "linear",
modelURL: BASE_URL + "touch_l_bumper.fbx",
naturalPosition: { x: 0.00008066371083259583, y: -0.02715788595378399, z: -0.02448512241244316 },
// linear properties
// Offset from origin = 0.36470, 0.11048, 0.11066
input: "OculusTouch.LeftGrip",
axis: { x: 1, y: 0.302933918, z: 0.302933918 },
maxTranslation: 0.003967,
textureName: "tex-highlight",
defaultTextureLayer: "normal",
textureLayers: {
normal: {
defaultTextureURL: BASE_URL + "touch_l_bumper.fbx/touch_l_bumper.fbm/L_controller_DIF.jpg",
},
highlight: {
defaultTextureURL: BASE_URL + "touch_l_bumper.fbx/touch_l_bumper.fbm/L_controller-highlight_DIF.jpg",
}
}
},
joystick: {
type: "joystick",
modelURL: BASE_URL + "touch_l_joystick.fbx",
naturalPosition: { x: 0.0075613949447870255, y: -0.008225866593420506, z: 0.004792703315615654 },
// joystick
xInput: "OculusTouch.LX",
yInput: "OculusTouch.LY",
originOffset: { x: 0, y: -0.0028564, z: -0.00 },
xHalfAngle: 20,
yHalfAngle: 20,
textureName: "tex-highlight",
defaultTextureLayer: "normal",
textureLayers: {
normal: {
defaultTextureURL: BASE_URL + "touch_l_joystick.fbx/touch_l_joystick.fbm/L_controller_DIF.jpg",
},
highlight: {
defaultTextureURL: BASE_URL + "touch_l_joystick.fbx/touch_l_joystick.fbm/L_controller-highlight_DIF.jpg",
}
}
},
button_a: {
type: "linear",
modelURL: BASE_URL + "touch_l_button_x.fbx",
naturalPosition: { x: -0.009307309985160828, y: -0.00005015172064304352, z: -0.012594521045684814 },
input: "OculusTouch.X",
axis: { x: 0, y: -1, z: 0 },
maxTranslation: 0.001,
textureName: "tex-highlight",
defaultTextureLayer: "normal",
textureLayers: {
normal: {
defaultTextureURL: BASE_URL + "touch_l_button_x.fbx/touch_l_button_x.fbm/L_controller_DIF.jpg",
},
highlight: {
defaultTextureURL: BASE_URL + "touch_l_button_x.fbx/touch_l_button_x.fbm/L_controller-highlight_DIF.jpg",
}
}
},
button_b: {
type: "linear",
modelURL: BASE_URL + "touch_l_button_y.fbx",
naturalPosition: { x: -0.01616849936544895, y: -0.000050364527851343155, z: 0.0017703399062156677 },
input: "OculusTouch.Y",
axis: { x: 0, y: -1, z: 0 },
maxTranslation: 0.001,
textureName: "tex-highlight",
defaultTextureLayer: "normal",
textureLayers: {
normal: {
defaultTextureURL: BASE_URL + "touch_l_button_y.fbx/touch_l_button_y.fbm/L_controller_DIF.jpg",
},
highlight: {
defaultTextureURL: BASE_URL + "touch_l_button_y.fbx/touch_l_button_y.fbm/L_controller-highlight_DIF.jpg",
}
}
},
}
}
]
};
TOUCH_CONTROLLER_CONFIGURATION_RIGHT = {
name: "Touch",
controllers: [
{
modelURL: BASE_URL + "touch_r_body.fbx",
jointIndex: MyAvatar.getJointIndex("_CONTROLLER_RIGHTHAND"),
naturalPosition: { x: -0.016486231237649918, y: -0.03551865369081497, z: -0.018527653068304062 },
dimensions: { x: 0.11053784191608429, y: 0.09957750141620636, z: 0.10139875113964081 },
rotation: rightBaseRotation,
position: rightBasePosition,
parts: {
tips: {
type: "static",
modelURL: BASE_URL + "Oculus-Labels-R.fbx",
naturalPosition: { x: 0.009739525616168976, y: -0.0017818436026573181, z: 0.016794726252555847 },
textureName: "Texture",
defaultTextureLayer: "blank",
textureLayers: {
blank: {
defaultTextureURL: BASE_URL + "Oculus-Labels-R.fbx/Oculus-Labels-R.fbm/Blank.png"
},
trigger: {
defaultTextureURL: BASE_URL + "Oculus-Labels-R.fbx/Oculus-Labels-R.fbm/Trigger.png"
},
arrows: {
defaultTextureURL: BASE_URL + "Oculus-Labels-R.fbx/Oculus-Labels-R.fbm/Rotate.png"
},
grip: {
defaultTextureURL: BASE_URL + "Oculus-Labels-R.fbx/Oculus-Labels-R.fbm/Grip-oculus.png"
},
teleport: {
defaultTextureURL: BASE_URL + "Oculus-Labels-R.fbx/Oculus-Labels-R.fbm/Teleport.png"
},
}
},
trigger: {
type: "rotational",
modelURL: BASE_URL + "touch_r_trigger.fbx",
naturalPosition: { x: -0.0008544912561774254, y: -0.019867943599820137, z: 0.018800459802150726 },
// rotational
input: "OculusTouch.RT",
origin: { x: 0, y: -0.015, z: 0 },
minValue: 0.0,
maxValue: 1.0,
axis: { x: 1, y: 0, z: 0 },
maxAngle: 17,
textureName: "tex-highlight",
defaultTextureLayer: "normal",
textureLayers: {
normal: {
defaultTextureURL: BASE_URL + "touch_r_trigger.fbx/touch_r_trigger.fbm/R_controller_DIF.jpg",
},
highlight: {
defaultTextureURL: BASE_URL + "touch_r_trigger.fbx/touch_r_trigger.fbm/R_controller-highlight_DIF.jpg",
}
}
},
grip: {
type: "linear",
modelURL: BASE_URL + "touch_r_bumper.fbx",
naturalPosition: { x: -0.0000806618481874466, y: -0.027157839387655258, z: -0.024485092610120773 },
// linear properties
// Offset from origin = 0.36470, 0.11048, 0.11066
input: "OculusTouch.RightGrip",
axis: { x: -1, y: 0.302933918, z: 0.302933918 },
maxTranslation: 0.003967,
textureName: "tex-highlight",
defaultTextureLayer: "normal",
textureLayers: {
normal: {
defaultTextureURL: BASE_URL + "touch_r_bumper.fbx/touch_r_bumper.fbm/R_controller_DIF.jpg",
},
highlight: {
defaultTextureURL: BASE_URL + "touch_r_bumper.fbx/touch_r_bumper.fbm/R_controller-highlight_DIF.jpg",
}
}
},
joystick: {
type: "joystick",
modelURL: BASE_URL + "touch_r_joystick.fbx",
naturalPosition: { x: -0.007561382371932268, y: -0.008225853554904461, z: 0.00479268841445446 },
// joystick
xInput: "OculusTouch.RX",
yInput: "OculusTouch.RY",
originOffset: { x: 0, y: -0.0028564, z: 0 },
xHalfAngle: 20,
yHalfAngle: 20,
textureName: "tex-highlight",
defaultTextureLayer: "normal",
textureLayers: {
normal: {
defaultTextureURL: BASE_URL + "touch_r_joystick.fbx/touch_r_joystick.fbm/R_controller_DIF.jpg",
},
highlight: {
defaultTextureURL: BASE_URL + "touch_r_joystick.fbx/touch_r_joystick.fbm/R_controller-highlight_DIF.jpg",
}
}
},
button_a: {
type: "linear",
modelURL: BASE_URL + "touch_r_button_a.fbx",
naturalPosition: { x: 0.009307296946644783, y: -0.00005015172064304352, z: -0.012594504281878471 },
input: "OculusTouch.A",
axis: { x: 0, y: -1, z: 0 },
maxTranslation: 0.001,
textureName: "tex-highlight",
defaultTextureLayer: "normal",
textureLayers: {
normal: {
defaultTextureURL: BASE_URL + "touch_r_button_a.fbx/touch_r_button_a.fbm/R_controller_DIF.jpg",
},
highlight: {
defaultTextureURL: BASE_URL + "touch_r_button_a.fbx/touch_r_button_a.fbm/R_controller-highlight_DIF.jpg",
}
}
},
button_b: {
type: "linear",
modelURL: BASE_URL + "touch_r_button_b.fbx",
naturalPosition: { x: 0.01616847701370716, y: -0.000050364527851343155, z: 0.0017703361809253693 },
input: "OculusTouch.B",
axis: { x: 0, y: -1, z: 0 },
maxTranslation: 0.001,
textureName: "tex-highlight",
defaultTextureLayer: "normal",
textureLayers: {
normal: {
defaultTextureURL: BASE_URL + "touch_r_button_b.fbx/touch_r_button_b.fbm/R_controller_DIF.jpg",
},
highlight: {
defaultTextureURL: BASE_URL + "touch_r_button_b.fbx/touch_r_button_b.fbm/R_controller-highlight_DIF.jpg",
}
}
},
}
}
]
};

View file

@ -83,7 +83,6 @@ VIVE_CONTROLLER_CONFIGURATION_LEFT = {
naturalPosition: {"x":-0.004377640783786774,"y":-0.034371938556432724,"z":0.06769277155399323},
textureName: "Tex.Blank",
defaultTextureLayer: "blank",
textureLayers: {
blank: {

View file

@ -900,7 +900,7 @@ private:
gpu::doInBatch(gpuContext, [&](gpu::Batch& batch) {
batch.resetStages();
});
PROFILE_RANGE(renderperflogging, __FUNCTION__);
PROFILE_RANGE(render, __FUNCTION__);
PerformanceTimer perfTimer("draw");
// The pending changes collecting the changes here
render::PendingChanges pendingChanges;

View file

@ -9,33 +9,35 @@
#include "TraceTests.h"
#include <QtTest/QtTest>
#include <Trace.h>
#include <QtGui/QDesktopServices>
#include <Profile.h>
#include <SharedUtil.h>
#include <NumericalConstants.h>
#include <../QTestExtensions.h>
QTEST_MAIN(TraceTests)
Q_LOGGING_CATEGORY(tracertestlogging, "hifi.tracer.test")
Q_LOGGING_CATEGORY(trace_test, "trace.test")
const QString OUTPUT_FILE = "traces/testTrace.json.gz";
void TraceTests::testTraceSerialization() {
auto tracer = DependencyManager::set<tracing::Tracer>();
tracer->startTracing();
tracer->traceEvent(tracertestlogging(), "TestEvent", tracing::DurationBegin);
{
auto start = usecTimestampNow();
PROFILE_RANGE(test, "TestEvent")
for (size_t i = 0; i < 10000; ++i) {
tracer->traceEvent(tracertestlogging(), "TestCounter", tracing::Counter, "", { { "i", i } });
SAMPLE_PROFILE_COUNTER(0.1f, test, "TestCounter", { { "i", i } })
}
auto duration = usecTimestampNow() - start;
duration /= USECS_PER_MSEC;
qDebug() << "Recording took " << duration << "ms";
}
tracer->traceEvent(tracertestlogging(), "TestEvent", tracing::DurationEnd);
tracer->stopTracing();
{
auto start = usecTimestampNow();
tracer->serialize("testTrace.json.gz");
tracer->serialize(OUTPUT_FILE);
auto duration = usecTimestampNow() - start;
duration /= USECS_PER_MSEC;
qDebug() << "Serialization took " << duration << "ms";

View file

@ -58,11 +58,6 @@ function info() {
}
}
// Return a number between min (inclusive) and max (exclusive)
function randomInt(min, max) {
return min + Math.floor(Math.random() * (max - min))
}
var NEAR_BOX_SPAWN_NAME = "tutorial/nearGrab/box_spawn";
var FAR_BOX_SPAWN_NAME = "tutorial/farGrab/box_spawn";
var GUN_SPAWN_NAME = "tutorial/gun_spawn";
@ -78,10 +73,6 @@ function setAwayEnabled(value) {
Messages.sendLocalMessage(CHANNEL_AWAY_ENABLE, message);
}
function beginsWithFilter(value, key) {
return value.indexOf(properties[key]) == 0;
}
findEntity = function(properties, searchRadius, filterFn) {
var entities = findEntities(properties, searchRadius, filterFn);
return entities.length > 0 ? entities[0] : null;
@ -116,32 +107,30 @@ findEntities = function(properties, searchRadius, filterFn) {
return matchedEntities;
}
function setControllerPartsVisible(parts) {
Messages.sendLocalMessage('Controller-Display-Parts', JSON.stringify(parts));
function findEntitiesWithTag(tag) {
return findEntities({ userData: "" }, 10000, function(properties, key, value) {
data = parseJSON(value);
return data.tag === tag;
});
}
/**
* A controller in made up of parts, and each part can have multiple "layers,"
* which are really just different texures. For example, the "trigger" part
* has "normal" and "highlight" layers.
*/
function setControllerPartLayer(part, layer) {
data = {};
data[part] = layer;
Messages.sendLocalMessage('Controller-Set-Part-Layer', JSON.stringify(data));
}
function triggerHapticPulse() {
function scheduleHaptics(delay, strength, duration) {
Script.setTimeout(function() {
Controller.triggerHapticPulse(strength, duration, 0);
Controller.triggerHapticPulse(strength, duration, 1);
}, delay);
}
scheduleHaptics(0, 0.8, 100);
scheduleHaptics(300, 0.5, 100);
scheduleHaptics(600, 0.3, 100);
scheduleHaptics(900, 0.2, 100);
scheduleHaptics(1200, 0.1, 100);
}
function spawn(entityData, transform, modifyFn) {
debug("Creating: ", entityData);
/**
* Spawn entities and return the newly created entity's ids.
* @param {object[]} entityPropertiesList A list of properties of the entities
* to spawn.
*/
function spawn(entityPropertiesList, transform, modifyFn) {
if (!transform) {
transform = {
position: { x: 0, y: 0, z: 0 },
@ -149,9 +138,8 @@ function spawn(entityData, transform, modifyFn) {
}
}
var ids = [];
for (var i = 0; i < entityData.length; ++i) {
var data = entityData[i];
debug("Creating: ", data.name);
for (var i = 0; i < entityPropertiesList.length; ++i) {
var data = entityPropertiesList[i];
data.position = Vec3.sum(transform.position, data.position);
data.rotation = Quat.multiply(data.rotation, transform.rotation);
if (modifyFn) {
@ -159,11 +147,16 @@ function spawn(entityData, transform, modifyFn) {
}
var id = Entities.addEntity(data);
ids.push(id);
debug(id, "data:", JSON.stringify(data));
}
return ids;
}
/**
* @function parseJSON
* @param {string} jsonString The string to parse.
* @return {object} Return an empty if the string was not valid JSON, otherwise
* the parsed object is returned.
*/
function parseJSON(jsonString) {
var data;
try {
@ -174,6 +167,10 @@ function parseJSON(jsonString) {
return data;
}
/**
* Spawn entities with `tag` in the userData.
* @function spawnWithTag
*/
function spawnWithTag(entityData, transform, tag) {
function modifyFn(data) {
var userData = parseJSON(data.userData);
@ -185,6 +182,10 @@ function spawnWithTag(entityData, transform, tag) {
return spawn(entityData, transform, modifyFn);
}
/**
* Delete all entities with the tag `tag` in their userData.
* @function deleteEntitiesWithTag
*/
function deleteEntitiesWithTag(tag) {
debug("searching for...:", tag);
var entityIDs = findEntitiesWithTag(tag);
@ -192,6 +193,7 @@ function deleteEntitiesWithTag(tag) {
Entities.deleteEntity(entityIDs[i]);
}
}
function editEntitiesWithTag(tag, propertiesOrFn) {
var entities = TUTORIAL_TAG_TO_ENTITY_IDS_MAP[tag];
@ -208,19 +210,130 @@ function editEntitiesWithTag(tag, propertiesOrFn) {
}
}
function findEntitiesWithTag(tag) {
return findEntities({ userData: "" }, 10000, function(properties, key, value) {
data = parseJSON(value);
return data.tag == tag;
});
}
// From http://stackoverflow.com/questions/5999998/how-can-i-check-if-a-javascript-variable-is-function-type
function isFunction(functionToCheck) {
var getType = {};
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
}
/**
* Return `true` if `entityID` can be found in the local entity tree, otherwise `false`.
*/
function isEntityInLocalTree(entityID) {
return Entities.getEntityProperties(entityID, 'visible').visible !== undefined;
}
/**
*
*/
function showEntitiesWithTags(tags) {
for (var i = 0; i < tags.length; ++i) {
showEntitiesWithTag(tags[i]);
}
}
function showEntitiesWithTag(tag) {
var entities = TUTORIAL_TAG_TO_ENTITY_IDS_MAP[tag];
if (entities) {
for (entityID in entities) {
var data = entities[entityID];
var collisionless = data.visible === false ? true : false;
if (data.collidable !== undefined) {
collisionless = data.collidable === true ? false : true;
}
if (data.soundKey) {
data.soundKey.playing = true;
}
var newProperties = {
visible: data.visible == false ? false : true,
collisionless: collisionless,
userData: JSON.stringify(data),
};
debug("Showing: ", entityID, ", Is in local tree: ", isEntityInLocalTree(entityID));
Entities.editEntity(entityID, newProperties);
}
} else {
debug("ERROR | No entities for tag: ", tag);
}
return;
// Dynamic method, suppressed for now
//editEntitiesWithTag(tag, function(entityID) {
// var userData = Entities.getEntityProperties(entityID, "userData").userData;
// var data = parseJSON(userData);
// var collisionless = data.visible === false ? true : false;
// if (data.collidable !== undefined) {
// collisionless = data.collidable === true ? false : true;
// }
// if (data.soundKey) {
// data.soundKey.playing = true;
// }
// var newProperties = {
// visible: data.visible == false ? false : true,
// collisionless: collisionless,
// userData: JSON.stringify(data),
// };
// Entities.editEntity(entityID, newProperties);
//});
}
function hideEntitiesWithTags(tags) {
for (var i = 0; i < tags.length; ++i) {
hideEntitiesWithTag(tags[i]);
}
}
function hideEntitiesWithTag(tag) {
var entities = TUTORIAL_TAG_TO_ENTITY_IDS_MAP[tag];
if (entities) {
for (entityID in entities) {
var data = entities[entityID];
if (data.soundKey) {
data.soundKey.playing = false;
}
var newProperties = {
visible: false,
collisionless: 1,
ignoreForCollisions: 1,
userData: JSON.stringify(data),
};
debug("Hiding: ", entityID, ", Is in local tree: ", isEntityInLocalTree(entityID));
Entities.editEntity(entityID, newProperties);
}
}
return;
// Dynamic method, suppressed for now
//editEntitiesWithTag(tag, function(entityID) {
// var userData = Entities.getEntityProperties(entityID, "userData").userData;
// var data = parseJSON(userData);
// if (data.soundKey) {
// data.soundKey.playing = false;
// }
// var newProperties = {
// visible: false,
// collisionless: 1,
// ignoreForCollisions: 1,
// userData: JSON.stringify(data),
// };
// Entities.editEntity(entityID, newProperties);
//});
}
/**
* Return the entity properties for an entity with a given name if it is in our
* cached list of entities. Otherwise, return undefined.
*/
function getEntityWithName(name) {
debug("Getting entity with name:", name);
var entityID = TUTORIAL_NAME_TO_ENTITY_PROPERTIES_MAP[name];
debug("Entity id: ", entityID, ", Is in local tree: ", isEntityInLocalTree(entityID));
return entityID;
}
function playSuccessSound() {
Audio.playSound(successSound, {
position: MyAvatar.position,
@ -229,7 +342,6 @@ function playSuccessSound() {
});
}
function playFirecrackerSound(position) {
Audio.playSound(firecrackerSound, {
position: position,
@ -238,27 +350,17 @@ function playFirecrackerSound(position) {
});
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// //
// STEP: DISABLE CONTROLLERS //
// //
///////////////////////////////////////////////////////////////////////////////
var stepStart = function(name) {
this.tag = name;
}
stepStart.prototype = {
start: function(onFinish) {
disableEverything();
HMD.requestShowHandControllers();
onFinish();
},
cleanup: function() {
}
};
/**
* This disables everything, including:
*
* - The door to leave the tutorial
* - Overlays
* - Hand controlelrs
* - Teleportation
* - Advanced movement
* - Equip and far grab
* - Away mode
*/
function disableEverything() {
editEntitiesWithTag('door', { visible: true, collisionless: false });
Menu.setIsOptionChecked("Overlays", false);
@ -271,6 +373,11 @@ function disableEverything() {
farGrabEnabled: false,
}));
setControllerPartLayer('touchpad', 'blank');
setControllerPartLayer('trigger', 'blank');
setControllerPartLayer('joystick', 'blank');
setControllerPartLayer('grip', 'blank');
setControllerPartLayer('button_a', 'blank');
setControllerPartLayer('button_b', 'blank');
setControllerPartLayer('tips', 'blank');
hideEntitiesWithTag('finish');
@ -278,6 +385,10 @@ function disableEverything() {
setAwayEnabled(false);
}
/**
* This reenables everything that disableEverything() disables. This can be
* used when leaving the tutorial to ensure that nothing is left disabled.
*/
function reenableEverything() {
editEntitiesWithTag('door', { visible: false, collisionless: true });
Menu.setIsOptionChecked("Overlays", true);
@ -290,11 +401,39 @@ function reenableEverything() {
farGrabEnabled: true,
}));
setControllerPartLayer('touchpad', 'blank');
setControllerPartLayer('trigger', 'blank');
setControllerPartLayer('joystick', 'blank');
setControllerPartLayer('grip', 'blank');
setControllerPartLayer('button_a', 'blank');
setControllerPartLayer('button_b', 'blank');
setControllerPartLayer('tips', 'blank');
MyAvatar.shouldRenderLocally = true;
setAwayEnabled(true);
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// //
// STEP: DISABLE CONTROLLERS //
// //
///////////////////////////////////////////////////////////////////////////////
var stepStart = function() {
this.name = 'start';
};
stepStart.prototype = {
start: function(onFinish) {
disableEverything();
HMD.requestShowHandControllers();
onFinish();
},
cleanup: function() {
}
};
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// //
@ -302,10 +441,9 @@ function reenableEverything() {
// //
///////////////////////////////////////////////////////////////////////////////
var stepEnableControllers = function(name) {
this.tag = name;
var stepEnableControllers = function() {
this.shouldLog = false;
}
};
stepEnableControllers.prototype = {
start: function(onFinish) {
reenableEverything();
@ -316,28 +454,6 @@ stepEnableControllers.prototype = {
}
};
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// //
// STEP: Welcome //
// //
///////////////////////////////////////////////////////////////////////////////
var stepWelcome = function(name) {
this.tag = name;
}
stepWelcome.prototype = {
start: function(onFinish) {
this.timerID = Script.setTimeout(onFinish, 8000);
showEntitiesWithTag(this.tag);
},
cleanup: function() {
if (this.timerID) {
Script.clearTimeout(this.timerID);
this.timerID = null;
}
hideEntitiesWithTag(this.tag);
}
};
///////////////////////////////////////////////////////////////////////////////
@ -346,9 +462,9 @@ stepWelcome.prototype = {
// STEP: Orient and raise hands above head //
// //
///////////////////////////////////////////////////////////////////////////////
var stepOrient = function(name) {
this.tag = name;
this.tempTag = name + "-temporary";
var stepOrient = function(tutorialManager) {
this.name = 'orient';
this.tags = ["orient", "orient-" + tutorialManager.controllerName];
}
stepOrient.prototype = {
start: function(onFinish) {
@ -357,7 +473,8 @@ stepOrient.prototype = {
var tag = this.tag;
// Spawn content set
editEntitiesWithTag(this.tag, { visible: true });
//editEntitiesWithTag(this.tag, { visible: true });
showEntitiesWithTags(this.tags);
this.checkIntervalID = null;
function checkForHandsAboveHead() {
@ -386,8 +503,8 @@ stepOrient.prototype = {
Script.clearInterval(this.checkIntervalID);
this.checkIntervalID = null;
}
editEntitiesWithTag(this.tag, { visible: false, collisionless: 1 });
deleteEntitiesWithTag(this.tempTag);
//editEntitiesWithTag(this.tag, { visible: false, collisionless: 1 });
hideEntitiesWithTags(this.tags);
}
};
@ -399,9 +516,10 @@ stepOrient.prototype = {
// STEP: Near Grab //
// //
///////////////////////////////////////////////////////////////////////////////
var stepNearGrab = function(name) {
this.tag = name;
this.tempTag = name + "-temporary";
var stepNearGrab = function() {
this.name = 'nearGrab';
this.tag = "nearGrab";
this.tempTag = "nearGrab-temporary";
this.birdIDs = [];
Messages.subscribe("Entity-Exploded");
@ -414,7 +532,6 @@ stepNearGrab.prototype = {
setControllerPartLayer('tips', 'trigger');
setControllerPartLayer('trigger', 'highlight');
var tag = this.tag;
// Spawn content set
showEntitiesWithTag(this.tag, { visible: true });
@ -457,6 +574,7 @@ stepNearGrab.prototype = {
setControllerPartLayer('tips', 'blank');
setControllerPartLayer('trigger', 'normal');
hideEntitiesWithTag(this.tag, { visible: false});
hideEntitiesWithTag('bothGrab', { visible: false});
deleteEntitiesWithTag(this.tempTag);
if (this.positionWatcher) {
this.positionWatcher.destroy();
@ -473,9 +591,10 @@ stepNearGrab.prototype = {
// STEP: Far Grab //
// //
///////////////////////////////////////////////////////////////////////////////
var stepFarGrab = function(name) {
this.tag = name;
this.tempTag = name + "-temporary";
var stepFarGrab = function() {
this.name = 'farGrab';
this.tag = "farGrab";
this.tempTag = "farGrab-temporary";
this.finished = true;
this.birdIDs = [];
@ -535,7 +654,6 @@ stepFarGrab.prototype = {
setControllerPartLayer('tips', 'blank');
setControllerPartLayer('trigger', 'normal');
hideEntitiesWithTag(this.tag, { visible: false});
hideEntitiesWithTag('bothGrab', { visible: false});
deleteEntitiesWithTag(this.tempTag);
if (this.positionWatcher) {
this.positionWatcher.destroy();
@ -576,11 +694,16 @@ PositionWatcher.prototype = {
// STEP: Equip //
// //
///////////////////////////////////////////////////////////////////////////////
var stepEquip = function(name) {
this.tag = name;
this.tagPart1 = name + "-part1";
this.tagPart2 = name + "-part2";
this.tempTag = name + "-temporary";
var stepEquip = function(tutorialManager) {
const controllerName = tutorialManager.controllerName;
this.name = 'equip';
this.tags = ["equip", "equip-" + controllerName];
this.tagsPart1 = ["equip-part1", "equip-part1-" + controllerName];
this.tagsPart2 = ["equip-part2", "equip-part2-" + controllerName];
this.tempTag = "equip-temporary";
this.PART1 = 0;
this.PART2 = 1;
this.PART3 = 2;
@ -593,6 +716,7 @@ stepEquip.prototype = {
start: function(onFinish) {
setControllerPartLayer('tips', 'trigger');
setControllerPartLayer('trigger', 'highlight');
Messages.sendLocalMessage('Hifi-Grab-Disable', JSON.stringify({
holdEnabled: true,
}));
@ -600,8 +724,8 @@ stepEquip.prototype = {
var tag = this.tag;
// Spawn content set
showEntitiesWithTag(this.tag);
showEntitiesWithTag(this.tagPart1);
showEntitiesWithTags(this.tags);
showEntitiesWithTags(this.tagsPart1);
this.currentPart = this.PART1;
@ -658,9 +782,10 @@ stepEquip.prototype = {
Script.setTimeout(function() {
debug("Equip | Starting part 3");
this.currentPart = this.PART3;
hideEntitiesWithTag(this.tagPart1);
showEntitiesWithTag(this.tagPart2);
hideEntitiesWithTags(this.tagsPart1);
showEntitiesWithTags(this.tagsPart2);
setControllerPartLayer('trigger', 'normal');
setControllerPartLayer('grip', 'highlight');
setControllerPartLayer('tips', 'grip');
Messages.subscribe('Hifi-Object-Manipulation');
debug("Equip | Finished starting part 3");
@ -687,16 +812,19 @@ stepEquip.prototype = {
}
setControllerPartLayer('tips', 'blank');
setControllerPartLayer('grip', 'normal');
setControllerPartLayer('trigger', 'normal');
this.stopWatchingGun();
this.currentPart = this.COMPLETE;
if (this.checkCollidesTimer) {
Script.clearInterval(this.checkCollidesTimer);
this.checkColllidesTimer = null;
}
hideEntitiesWithTag(this.tagPart1);
hideEntitiesWithTag(this.tagPart2);
hideEntitiesWithTag(this.tag);
hideEntitiesWithTags(this.tagsPart1);
hideEntitiesWithTags(this.tagsPart2);
hideEntitiesWithTags(this.tags);
deleteEntitiesWithTag(this.tempTag);
}
};
@ -710,9 +838,11 @@ stepEquip.prototype = {
// STEP: Turn Around //
// //
///////////////////////////////////////////////////////////////////////////////
var stepTurnAround = function(name) {
this.tag = name;
this.tempTag = name + "-temporary";
var stepTurnAround = function(tutorialManager) {
this.name = 'turnAround';
this.tags = ["turnAround", "turnAround-" + tutorialManager.controllerName];
this.tempTag = "turnAround-temporary";
this.onActionBound = this.onAction.bind(this);
this.numTimesSnapTurnPressed = 0;
@ -720,10 +850,11 @@ var stepTurnAround = function(name) {
}
stepTurnAround.prototype = {
start: function(onFinish) {
setControllerPartLayer('joystick', 'highlight');
setControllerPartLayer('touchpad', 'arrows');
setControllerPartLayer('tips', 'arrows');
showEntitiesWithTag(this.tag);
showEntitiesWithTags(this.tags);
this.numTimesSnapTurnPressed = 0;
this.numTimesSmoothTurnPressed = 0;
@ -776,13 +907,14 @@ stepTurnAround.prototype = {
} catch (e) {
}
setControllerPartLayer('joystick', 'normal');
setControllerPartLayer('touchpad', 'blank');
setControllerPartLayer('tips', 'blank');
if (this.interval) {
Script.clearInterval(this.interval);
}
hideEntitiesWithTag(this.tag);
hideEntitiesWithTags(this.tags);
deleteEntitiesWithTag(this.tempTag);
}
};
@ -796,12 +928,15 @@ stepTurnAround.prototype = {
// STEP: Teleport //
// //
///////////////////////////////////////////////////////////////////////////////
var stepTeleport = function(name) {
this.tag = name;
this.tempTag = name + "-temporary";
var stepTeleport = function(tutorialManager) {
this.name = 'teleport';
this.tags = ["teleport", "teleport-" + tutorialManager.controllerName];
this.tempTag = "teleport-temporary";
}
stepTeleport.prototype = {
start: function(onFinish) {
setControllerPartLayer('button_a', 'highlight');
setControllerPartLayer('touchpad', 'teleport');
setControllerPartLayer('tips', 'teleport');
@ -831,17 +966,18 @@ stepTeleport.prototype = {
}
this.checkCollidesTimer = Script.setInterval(checkCollides.bind(this), 500);
showEntitiesWithTag(this.tag);
showEntitiesWithTags(this.tags);
},
cleanup: function() {
debug("Teleport | Cleanup");
setControllerPartLayer('button_a', 'normal');
setControllerPartLayer('touchpad', 'blank');
setControllerPartLayer('tips', 'blank');
if (this.checkCollidesTimer) {
Script.clearInterval(this.checkCollidesTimer);
}
hideEntitiesWithTag(this.tag);
hideEntitiesWithTags(this.tags);
deleteEntitiesWithTag(this.tempTag);
}
};
@ -856,9 +992,11 @@ stepTeleport.prototype = {
// STEP: Finish //
// //
///////////////////////////////////////////////////////////////////////////////
var stepFinish = function(name) {
this.tag = name;
this.tempTag = name + "-temporary";
var stepFinish = function() {
this.name = 'finish';
this.tag = "finish";
this.tempTag = "finish-temporary";
}
stepFinish.prototype = {
start: function(onFinish) {
@ -872,6 +1010,8 @@ stepFinish.prototype = {
};
var stepCleanupFinish = function() {
this.name = 'cleanup';
this.shouldLog = false;
}
stepCleanupFinish.prototype = {
@ -885,101 +1025,6 @@ stepCleanupFinish.prototype = {
function isEntityInLocalTree(entityID) {
return Entities.getEntityProperties(entityID, 'visible').visible !== undefined;
}
function showEntitiesWithTag(tag) {
var entities = TUTORIAL_TAG_TO_ENTITY_IDS_MAP[tag];
if (entities) {
for (entityID in entities) {
var data = entities[entityID];
var collisionless = data.visible === false ? true : false;
if (data.collidable !== undefined) {
collisionless = data.collidable === true ? false : true;
}
if (data.soundKey) {
data.soundKey.playing = true;
}
var newProperties = {
visible: data.visible == false ? false : true,
collisionless: collisionless,
userData: JSON.stringify(data),
};
debug("Showing: ", entityID, ", Is in local tree: ", isEntityInLocalTree(entityID));
Entities.editEntity(entityID, newProperties);
}
}
// Dynamic method, suppressed for now
return;
editEntitiesWithTag(tag, function(entityID) {
var userData = Entities.getEntityProperties(entityID, "userData").userData;
var data = parseJSON(userData);
var collisionless = data.visible === false ? true : false;
if (data.collidable !== undefined) {
collisionless = data.collidable === true ? false : true;
}
if (data.soundKey) {
data.soundKey.playing = true;
}
var newProperties = {
visible: data.visible == false ? false : true,
collisionless: collisionless,
userData: JSON.stringify(data),
};
Entities.editEntity(entityID, newProperties);
});
}
function hideEntitiesWithTag(tag) {
var entities = TUTORIAL_TAG_TO_ENTITY_IDS_MAP[tag];
if (entities) {
for (entityID in entities) {
var data = entities[entityID];
if (data.soundKey) {
data.soundKey.playing = false;
}
var newProperties = {
visible: false,
collisionless: 1,
ignoreForCollisions: 1,
userData: JSON.stringify(data),
};
debug("Hiding: ", entityID, ", Is in local tree: ", isEntityInLocalTree(entityID));
Entities.editEntity(entityID, newProperties);
}
}
// Dynamic method, suppressed for now
return;
editEntitiesWithTag(tag, function(entityID) {
var userData = Entities.getEntityProperties(entityID, "userData").userData;
var data = parseJSON(userData);
if (data.soundKey) {
data.soundKey.playing = false;
}
var newProperties = {
visible: false,
collisionless: 1,
ignoreForCollisions: 1,
userData: JSON.stringify(data),
};
Entities.editEntity(entityID, newProperties);
});
}
// Return the entity properties for an entity with a given name if it is in our
// cached list of entities. Otherwise, return undefined.
function getEntityWithName(name) {
debug("Getting entity with name:", name);
var entityID = TUTORIAL_NAME_TO_ENTITY_PROPERTIES_MAP[name];
debug("Entity id: ", entityID, ", Is in local tree: ", isEntityInLocalTree(entityID));
return entityID;
}
TutorialManager = function() {
@ -992,11 +1037,25 @@ TutorialManager = function() {
var didFinishTutorial = false;
var wentToEntryStepNum;
var VERSION = 1;
var VERSION = 2;
var tutorialID;
var self = this;
// The real controller name is the actual detected controller name, or 'unknown'
// if one is not found.
if (HMD.isSubdeviceContainingNameAvailable("OculusTouch")) {
this.controllerName = "touch";
this.realControllerName = "touch";
} else if (HMD.isHandControllerAvailable("OpenVR")) {
this.controllerName = "vive";
this.realControllerName = "vive";
} else {
info("ERROR, no known hand controller found, defaulting to Vive");
this.controllerName = "vive";
this.realControllerName = "unknown";
}
this.startTutorial = function() {
currentStepNum = -1;
currentStep = null;
@ -1006,15 +1065,15 @@ TutorialManager = function() {
// If Script.generateUUID is not available, default to an empty string.
tutorialID = Script.generateUUID ? Script.generateUUID() : "";
STEPS = [
new stepStart("start"),
new stepOrient("orient"),
new stepNearGrab("nearGrab"),
new stepFarGrab("farGrab"),
new stepEquip("equip"),
new stepTurnAround("turnAround"),
new stepTeleport("teleport"),
new stepFinish("finish"),
new stepEnableControllers("enableControllers"),
new stepStart(this),
new stepOrient(this),
new stepFarGrab(this),
new stepNearGrab(this),
new stepEquip(this),
new stepTurnAround(this),
new stepTeleport(this),
new stepFinish(this),
new stepEnableControllers(this),
];
wentToEntryStepNum = STEPS.length;
for (var i = 0; i < STEPS.length; ++i) {
@ -1027,7 +1086,7 @@ TutorialManager = function() {
this.onFinish = function() {
debug("onFinish", currentStepNum);
if (currentStep && currentStep.shouldLog !== false) {
self.trackStep(currentStep.tag, currentStepNum);
self.trackStep(currentStep.name, currentStepNum);
}
self.startNextStep();
@ -1083,7 +1142,7 @@ TutorialManager = function() {
var tutorialTimeElapsed = (Date.now() - startedTutorialAt) / 1000;
UserActivityLogger.tutorialProgress(
name, stepNum, timeToFinishStep, tutorialTimeElapsed,
tutorialID, VERSION);
tutorialID, VERSION, this.realControllerName);
}
// This is a message sent from the "entry" portal in the courtyard,
@ -1099,6 +1158,25 @@ TutorialManager = function() {
// To run the tutorial:
//
// var tutorialManager = new TutorialManager();
// tutorialManager.startTutorial();
//var tutorialManager = new TutorialManager();
//tutorialManager.startTutorial();
//
//
//var keyReleaseHandler = function(event) {
// if (event.isShifted && event.isAlt) {
// print('here', event.text);
// if (event.text == "F12") {
// if (!tutorialManager.startNextStep()) {
// tutorialManager.startTutorial();
// }
// } else if (event.text == "F11") {
// tutorialManager.restartStep();
// } else if (event.text == "F10") {
// MyAvatar.shouldRenderLocally = !MyAvatar.shouldRenderLocally;
// } else if (event.text == "r") {
// tutorialManager.stopTutorial();
// tutorialManager.startTutorial();
// }
// }
//};
//Controller.keyReleaseEvent.connect(keyReleaseHandler);

View file

@ -1,8 +1,25 @@
TUTORIAL_TAG_TO_ENTITY_IDS_MAP = {
"teleport": {
"teleport-vive": {
"{7df1abc4-1b7c-4352-985c-f3f6ad8d65b7}": {
"tag": "teleport-vive"
}
},
"teleport-touch": {
"{ff064b9e-7fa4-4693-a386-a67b9f92a948}": {
"tag": "teleport"
},
"tag": "teleport-touch"
}
},
"turnAround-vive": {
"{9b14f224-b2f6-447f-bb86-f5d875cf4c33}": {
"tag": "turnAround-vive"
}
},
"turnAround-touch": {
"{ce74b3ca-d1c7-4980-bd98-2d488095a39e}": {
"tag": "turnAround-touch"
}
},
"teleport": {
"{4478f7b5-d3ac-4213-9a7b-ad8cd69575b8}": {
"tag": "teleport"
}
@ -82,17 +99,19 @@ TUTORIAL_TAG_TO_ENTITY_IDS_MAP = {
"tag": "equip-part1"
}
},
"equip-part2": {
"equip-part2-vive": {
"{b5d17eda-90ab-40cf-b973-efcecb2e992e}": {
"tag": "equip-part2"
},
"{6307cd16-dd1d-4988-a339-578178436b45}": {
"tag": "equip-part2"
"tag": "equip-part2-vive"
}
},
"turnAround": {
"{ce74b3ca-d1c7-4980-bd98-2d488095a39e}": {
"tag": "turnAround"
"equip-part2-touch": {
"{69195139-e020-4739-bb2c-50faebc6860a}": {
"tag": "equip-part2-touch"
}
},
"equip-part2": {
"{6307cd16-dd1d-4988-a339-578178436b45}": {
"tag": "equip-part2"
}
},
"bothGrab": {
@ -143,133 +162,144 @@ TUTORIAL_TAG_TO_ENTITY_IDS_MAP = {
"tag": "equip"
}
},
"orient": {
"orient-vive": {
"{95d233ab-ed0a-46e1-b047-1c542688ef3f}": {
"tag": "orient"
"tag": "orient-vive"
}
},
"orient-touch": {
"{1c95f945-ec46-4aac-b0f1-e64e073dbfaa}": {
"tag": "orient-touch"
}
}
};
TUTORIAL_NAME_TO_ENTITY_PROPERTIES_MAP = {
"tutorial/gun_spawn": {
"userData": "{\"tag\":\"equip\",\"visible\":false}",
"dimensions": {
"y": 0.0649842768907547,
"x": 0.0649842768907547,
"z": 0.0649842768907547
},
"clientOnly": 0,
"collisionless": 1,
"created": "2016-09-08T18:38:24Z",
"color": {
"blue": 0,
"green": 0,
"red": 255
},
"queryAACube": {
"y": 0.6283726096153259,
"x": 0.6865367293357849,
"scale": 0.11255607008934021,
"z": 0.3359576463699341
"created": "2016-09-08T18:38:24Z",
"dimensions": {
"x": 0.0649842768907547,
"y": 0.0649842768907547,
"z": 0.0649842768907547
},
"visible": 0,
"shape": "Cube",
"clientOnly": 0,
"id": "{9df518da-9e65-4b76-8a79-eeefdb0b7310}",
"ignoreForCollisions": 1,
"lastEdited": 1481926907366120,
"lastEditedBy": "{b80185ea-0936-4397-a5a4-3a64004f545f}",
"name": "tutorial/gun_spawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"position": {
"y": 0.6846506595611572,
"x": 0.7428147792816162,
"z": 0.3922356963157654
"x": 0.60231781005859375,
"y": 0.68465065956115723,
"z": 0.39223569631576538
},
"queryAACube": {
"scale": 0.11255607008934021,
"x": 0.54603976011276245,
"y": 0.62837260961532593,
"z": 0.33595764636993408
},
"rotation": {
"y": 0.7066605091094971,
"x": 0.7066605091094971,
"z": -0.025131583213806152,
"w": -0.025101065635681152
"w": -0.025101065635681152,
"x": 0.70666050910949707,
"y": 0.70666050910949707,
"z": -0.025131583213806152
},
"ignoreForCollisions": 1,
"shape": "Cube",
"type": "Box",
"id": "{9df518da-9e65-4b76-8a79-eeefdb0b7310}",
"name": "tutorial/gun_spawn"
"userData": "{\"visible\":false,\"tag\":\"equip\"}",
"visible": 0
},
"tutorial/nearGrab/box_spawn": {
"userData": "{\"tag\":\"nearGrab\",\"visible\":false}",
"dimensions": {
"y": 0.08225371688604355,
"x": 0.08225371688604355,
"z": 0.08225371688604355
},
"clientOnly": 0,
"collisionless": 1,
"created": "2016-09-08T18:38:24Z",
"color": {
"blue": 255,
"green": 0,
"red": 255
},
"queryAACube": {
"y": 0.738319456577301,
"x": 0.8985498547554016,
"scale": 0.14246761798858643,
"z": 0.29067665338516235
"created": "2016-09-08T18:38:24Z",
"dimensions": {
"x": 0.082253716886043549,
"y": 0.082253716886043549,
"z": 0.082253716886043549
},
"visible": 0,
"shape": "Cube",
"clientOnly": 0,
"id": "{5cf22b9c-fb22-4854-8821-554422980b24}",
"ignoreForCollisions": 1,
"lastEdited": 1481926907334206,
"lastEditedBy": "{b80185ea-0936-4397-a5a4-3a64004f545f}",
"name": "tutorial/nearGrab/box_spawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"position": {
"y": 0.8095532655715942,
"x": 0.9697836637496948,
"x": 0.61857688426971436,
"y": 0.80955326557159424,
"z": 0.36191046237945557
},
"rotation": {
"y": -1.52587890625e-05,
"x": -1.52587890625e-05,
"z": -1.52587890625e-05,
"w": 1
"queryAACube": {
"scale": 0.14246761798858643,
"x": 0.54734307527542114,
"y": 0.73831945657730103,
"z": 0.29067665338516235
},
"ignoreForCollisions": 1,
"rotation": {
"w": 1,
"x": -1.52587890625e-05,
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"shape": "Cube",
"type": "Box",
"id": "{5cf22b9c-fb22-4854-8821-554422980b24}",
"name": "tutorial/nearGrab/box_spawn"
"userData": "{\"visible\":false,\"tag\":\"nearGrab\"}",
"visible": 0
},
"tutorial/farGrab/box_spawn": {
"userData": "{\"tag\":\"farGrab\",\"visible\":false}",
"dimensions": {
"y": 0.37358683347702026,
"x": 0.37358683347702026,
"z": 0.37358683347702026
},
"clientOnly": 0,
"collisionless": 1,
"created": "2016-09-08T18:38:24Z",
"color": {
"blue": 255,
"green": 0,
"red": 255
},
"queryAACube": {
"y": 0.3304251432418823,
"x": 3.0951309204101562,
"scale": 0.647071361541748,
"z": 0.18027013540267944
"created": "2016-09-08T18:38:24Z",
"dimensions": {
"x": 0.16850528120994568,
"y": 0.16850528120994568,
"z": 0.16850528120994568
},
"visible": 0,
"shape": "Cube",
"clientOnly": 0,
"id": "{70fcd96c-cd59-4f23-9ca5-a167f2f85680}",
"ignoreForCollisions": 1,
"lastEdited": 1481926908795578,
"lastEditedBy": "{b80185ea-0936-4397-a5a4-3a64004f545f}",
"name": "tutorial/farGrab/box_spawn",
"owningAvatarID": "{00000000-0000-0000-0000-000000000000}",
"position": {
x: 3.4866,
y: 0.6716,
z: 0.4789
"x": 3.4866282939910889,
"y": 0.67159509658813477,
"z": 0.47892442345619202
},
"queryAACube": {
"scale": 0.64707136154174805,
"x": 3.2037394046783447,
"y": 0.33042514324188232,
"z": 0.14542555809020996
},
"rotation": {
"y": -1.52587890625e-05,
"w": 1,
"x": -1.52587890625e-05,
"z": -1.52587890625e-05,
"w": 1
"y": -1.52587890625e-05,
"z": -1.52587890625e-05
},
"ignoreForCollisions": 1,
"shape": "Cube",
"type": "Box",
"id": "{70fcd96c-cd59-4f23-9ca5-a167f2f85680}",
"name": "tutorial/farGrab/box_spawn"
"userData": "{\"visible\":false,\"tag\":\"farGrab\"}",
"visible": 0
},
"tutorial/teleport/pad": {
"userData": "{\"tag\":\"teleport\"}",

View file

@ -45,7 +45,7 @@ if (!Function.prototype.bind) {
keyReleaseHandler: function(event) {
print(event.text);
if (event.isShifted && event.isAlt) {
if (event.text == ",") {
if (event.text == "F12") {
if (!this.tutorialManager.startNextStep()) {
this.tutorialManager.startTutorial();
}