Merge remote-tracking branch 'upstream/master' into NOverlays6

This commit is contained in:
SamGondelman 2018-12-11 10:46:39 -08:00
commit fd8245e258
81 changed files with 1478 additions and 591 deletions

5
.gitignore vendored
View file

@ -94,3 +94,8 @@ interface/resources/GPUCache/*
# package lock file for JSDoc tool
tools/jsdoc/package-lock.json
# ignore unneeded unity project files for avatar exporter
tools/unity-avatar-exporter/Library
tools/unity-avatar-exporter/Packages
tools/unity-avatar-exporter/ProjectSettings

View file

@ -91,19 +91,73 @@ In a server, it does not make sense to compile interface
### Running the software
#### Domain server
Running domain server:
```bash
./domain-server/domain-server
```
#### Assignment clients
Running assignment client:
```bash
./assignment-client/assignment-client -n 6
```
#### Interface
Running interface:
```bash
./interface/interface
```
Go to localhost in the running interface.
##### Ubuntu 18.04 only
In Ubuntu 18.04 there is a problem related with NVidia driver library version.
It can be workarounded following these steps:
Uninstall incompatible nvtt libraries:
```bash
sudo apt-get remove libnvtt2 libnvtt-dev
```
Install libssl1.0-dev:
```bash
sudo apt-get -y install libssl1.0-dev
```
Clone castano nvidia-texture-tools:
```
git clone https://github.com/castano/nvidia-texture-tools
cd nvidia-texture-tools/
```
Make these changes in repo:
* In file **VERSION** set `2.2.1`
* In file **configure**:
* set `build="release"`
* set `-DNVTT_SHARED=1`
Configure, build and install:
```
./configure
make
sudo make install
```
Link compiled files:
```
sudo ln -s /usr/local/lib/libnvcore.so /usr/lib/libnvcore.so
sudo ln -s /usr/local/lib/libnvimage.so /usr/lib/libnvimage.so
sudo ln -s /usr/local/lib/libnvmath.so /usr/lib/libnvmath.so
sudo ln -s /usr/local/lib/libnvtt.so /usr/lib/libnvtt.so
```
After running this steps you can run interface:
```
interface/interface
```

View file

@ -29,7 +29,7 @@ ScriptableAvatar::ScriptableAvatar() {
QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) {
_globalPosition = getWorldPosition();
return AvatarData::toByteArrayStateful(dataDetail);
return AvatarData::toByteArrayStateful(dataDetail, dropFaceTracking);
}

View file

@ -328,7 +328,7 @@ Rectangle {
HifiStylesUit.RalewayRegular {
text: "Your wallet is not set up.\n" +
"Open the ASSETS app to get started.";
"Open the INVENTORY app to get started.";
// Anchors
anchors.fill: parent;
// Text size

View file

@ -1,47 +1,32 @@
const vec3 COLOR = vec3(0x00, 0xD8, 0x02) / vec3(0xFF);
const float CUTOFF = 0.65;
const float NOISE_MULT = 8.0;
const float NOISE_POWER = 1.0;
// Replicate the default skybox texture
float noise4D(vec4 p) {
return fract(sin(dot(p ,vec4(12.9898,78.233,126.7235, 593.2241))) * 43758.5453);
}
float worley4D(vec4 p) {
float r = 3.0;
vec4 f = floor(p);
vec4 x = fract(p);
for(int i = -1; i<=1; i++)
{
for(int j = -1; j<=1; j++)
{
for(int k = -1; k<=1; k++)
{
for (int l = -1; l <= 1; l++) {
vec4 q = vec4(float(i),float(j),float(k), float(l));
vec4 v = q + vec4(noise4D((q+f)*1.11), noise4D((q+f)*1.14), noise4D((q+f)*1.17), noise4D((q+f)*1.20)) - x;
float d = dot(v, v);
r = min(r, d);
}
}
}
}
return sqrt(r);
}
vec3 mainColor(vec3 direction) {
float n = worley4D(vec4(direction * NOISE_MULT, iGlobalTime / 3.0));
n = 1.0 - n;
n = pow(n, NOISE_POWER);
if (n < CUTOFF) {
return vec3(0.0);
}
n = (n - CUTOFF) / (1.0 - CUTOFF);
return COLOR * (1.0 - n);
}
const int NUM_COLORS = 5;
const vec3 WHITISH = vec3(0.471, 0.725, 0.825);
const vec3 GREENISH = vec3(0.157, 0.529, 0.588);
const vec3 COLORS[NUM_COLORS] = vec3[](
GREENISH,
GREENISH,
WHITISH,
WHITISH,
vec3(0.6, 0.275, 0.706) // purple
);
const float PI = 3.14159265359;
const vec3 BLACK = vec3(0.0);
const vec3 SPACE_BLUE = vec3(0.0, 0.118, 0.392);
const float HORIZONTAL_OFFSET = 3.75;
vec3 getSkyboxColor() {
return mainColor(normalize(_normal));
vec2 horizontal = vec2(_normal.x, _normal.z);
horizontal = normalize(horizontal);
float theta = atan(horizontal.y, horizontal.x);
theta = 0.5 * (theta / PI + 1.0);
float index = theta * NUM_COLORS;
index = mod(index + HORIZONTAL_OFFSET, NUM_COLORS);
int index1 = int(index) % NUM_COLORS;
int index2 = (index1 + 1) % NUM_COLORS;
vec3 horizonColor = mix(COLORS[index1], COLORS[index2], index - index1);
horizonColor = mix(horizonColor, SPACE_BLUE, smoothstep(0.0, 0.08, _normal.y));
horizonColor = mix(horizonColor, BLACK, smoothstep(0.04, 0.15, _normal.y));
horizonColor = mix(BLACK, horizonColor, smoothstep(-0.01, 0.0, _normal.y));
return pow(horizonColor, vec3(0.4545));;
}

View file

@ -100,6 +100,7 @@
#include <MainWindow.h>
#include <MappingRequest.h>
#include <MessagesClient.h>
#include <hfm/ModelFormatRegistry.h>
#include <model-networking/ModelCacheScriptingInterface.h>
#include <model-networking/TextureCacheScriptingInterface.h>
#include <ModelEntityItem.h>
@ -150,7 +151,6 @@
#include <trackers/EyeTracker.h>
#include <avatars-renderer/ScriptAvatar.h>
#include <RenderableEntityItem.h>
#include <procedural/ProceduralSkybox.h>
#include <model-networking/MaterialCache.h>
#include "recording/ClipCache.h"
@ -831,6 +831,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
DependencyManager::set<recording::ClipCache>();
DependencyManager::set<GeometryCache>();
DependencyManager::set<ModelFormatRegistry>(); // ModelFormatRegistry must be defined before ModelCache. See the ModelCache constructor.
DependencyManager::set<ModelCache>();
DependencyManager::set<ModelCacheScriptingInterface>();
DependencyManager::set<ScriptCache>();
@ -2695,6 +2696,7 @@ Application::~Application() {
DependencyManager::destroy<TextureCache>();
DependencyManager::destroy<ModelCacheScriptingInterface>();
DependencyManager::destroy<ModelCache>();
DependencyManager::destroy<ModelFormatRegistry>();
DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<SoundCacheScriptingInterface>();
DependencyManager::destroy<SoundCache>();
@ -2823,8 +2825,6 @@ void Application::initializeGL() {
_graphicsEngine.initializeGPU(_glWidget);
}
static const QString SPLASH_SKYBOX{ "{\"ProceduralEntity\":{ \"version\":2, \"shaderUrl\":\"qrc:///shaders/splashSkybox.frag\" } }" };
void Application::initializeDisplayPlugins() {
auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
Setting::Handle<QString> activeDisplayPluginSetting{ ACTIVE_DISPLAY_PLUGIN_SETTING_NAME, displayPlugins.at(0)->getName() };
@ -2860,45 +2860,6 @@ void Application::initializeDisplayPlugins() {
// Submit a default frame to render until the engine starts up
updateRenderArgs(0.0f);
#define ENABLE_SPLASH_FRAME 0
#if ENABLE_SPLASH_FRAME
{
QMutexLocker viewLocker(&_renderArgsMutex);
if (_appRenderArgs._isStereo) {
_gpuContext->enableStereo(true);
_gpuContext->setStereoProjections(_appRenderArgs._eyeProjections);
_gpuContext->setStereoViews(_appRenderArgs._eyeOffsets);
}
// Frame resources
auto framebufferCache = DependencyManager::get<FramebufferCache>();
gpu::FramebufferPointer finalFramebuffer = framebufferCache->getFramebuffer();
std::shared_ptr<ProceduralSkybox> procedural = std::make_shared<ProceduralSkybox>();
procedural->parse(SPLASH_SKYBOX);
_gpuContext->beginFrame(_appRenderArgs._view, _appRenderArgs._headPose);
gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {
batch.resetStages();
batch.enableStereo(false);
batch.setFramebuffer(finalFramebuffer);
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, { 0, 0, 0, 1 });
batch.enableSkybox(true);
batch.enableStereo(_appRenderArgs._isStereo);
batch.setViewportTransform({ 0, 0, finalFramebuffer->getSize() });
procedural->render(batch, _appRenderArgs._renderArgs.getViewFrustum());
});
auto frame = _gpuContext->endFrame();
frame->frameIndex = 0;
frame->framebuffer = finalFramebuffer;
frame->pose = _appRenderArgs._headPose;
frame->framebufferRecycler = [framebufferCache, procedural](const gpu::FramebufferPointer& framebuffer) {
framebufferCache->releaseFramebuffer(framebuffer);
};
_displayPlugin->submitFrame(frame);
}
#endif
}
void Application::initializeRenderEngine() {

View file

@ -72,7 +72,6 @@
#include "workload/GameWorkload.h"
#include "graphics/GraphicsEngine.h"
#include <procedural/ProceduralSkybox.h>
#include <graphics/Skybox.h>
#include <ModelScriptingInterface.h>
@ -761,5 +760,6 @@ private:
bool _showTrackedObjects { false };
bool _prevShowTrackedObjects { false };
};
#endif // hifi_Application_h

View file

@ -1,225 +0,0 @@
//
// Application_render.cpp
// interface/src
//
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Application.h"
#include <MainWindow.h>
#include <display-plugins/CompositorHelper.h>
#include <FramebufferCache.h>
#include <plugins/PluginManager.h>
#include <SceneScriptingInterface.h>
#include "ui/Stats.h"
#include "Util.h"
//void Application::paintGL() {
// // Some plugins process message events, allowing paintGL to be called reentrantly.
//
// _renderFrameCount++;
// // SG: Moved into the RenderEventHandler
// //_lastTimeRendered.start();
//
// auto lastPaintBegin = usecTimestampNow();
// PROFILE_RANGE_EX(render, __FUNCTION__, 0xff0000ff, (uint64_t)_renderFrameCount);
// PerformanceTimer perfTimer("paintGL");
//
// if (nullptr == _displayPlugin) {
// return;
// }
//
// DisplayPluginPointer displayPlugin;
// {
// PROFILE_RANGE(render, "/getActiveDisplayPlugin");
// displayPlugin = getActiveDisplayPlugin();
// }
//
// {
// PROFILE_RANGE(render, "/pluginBeginFrameRender");
// // If a display plugin loses it's underlying support, it
// // needs to be able to signal us to not use it
// if (!displayPlugin->beginFrameRender(_renderFrameCount)) {
// QMetaObject::invokeMethod(this, "updateDisplayMode");
// return;
// }
// }
//
// RenderArgs renderArgs;
// glm::mat4 HMDSensorPose;
// glm::mat4 eyeToWorld;
// glm::mat4 sensorToWorld;
//
// bool isStereo;
// glm::mat4 stereoEyeOffsets[2];
// glm::mat4 stereoEyeProjections[2];
//
// {
// QMutexLocker viewLocker(&_renderArgsMutex);
// renderArgs = _appRenderArgs._renderArgs;
//
// // don't render if there is no context.
// if (!_appRenderArgs._renderArgs._context) {
// return;
// }
//
// HMDSensorPose = _appRenderArgs._headPose;
// eyeToWorld = _appRenderArgs._eyeToWorld;
// sensorToWorld = _appRenderArgs._sensorToWorld;
// isStereo = _appRenderArgs._isStereo;
// for_each_eye([&](Eye eye) {
// stereoEyeOffsets[eye] = _appRenderArgs._eyeOffsets[eye];
// stereoEyeProjections[eye] = _appRenderArgs._eyeProjections[eye];
// });
// }
//
// {
// PROFILE_RANGE(render, "/gpuContextReset");
// _graphicsEngine.getGPUContext()->beginFrame(_appRenderArgs._view, HMDSensorPose);
// // Reset the gpu::Context Stages
// // Back to the default framebuffer;
// gpu::doInBatch("Application_render::gpuContextReset", _graphicsEngine.getGPUContext(), [&](gpu::Batch& batch) {
// batch.resetStages();
// });
// }
//
//
// {
// PROFILE_RANGE(render, "/renderOverlay");
// PerformanceTimer perfTimer("renderOverlay");
// // NOTE: There is no batch associated with this renderArgs
// // the ApplicationOverlay class assumes it's viewport is setup to be the device size
// renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize() * getRenderResolutionScale());
// _applicationOverlay.renderOverlay(&renderArgs);
// }
//
// {
// PROFILE_RANGE(render, "/updateCompositor");
// getApplicationCompositor().setFrameInfo(_renderFrameCount, eyeToWorld, sensorToWorld);
// }
//
// gpu::FramebufferPointer finalFramebuffer;
// QSize finalFramebufferSize;
// {
// PROFILE_RANGE(render, "/getOutputFramebuffer");
// // Primary rendering pass
// auto framebufferCache = DependencyManager::get<FramebufferCache>();
// finalFramebufferSize = framebufferCache->getFrameBufferSize();
// // Final framebuffer that will be handled to the display-plugin
// finalFramebuffer = framebufferCache->getFramebuffer();
// }
//
// {
// if (isStereo) {
// renderArgs._context->enableStereo(true);
// renderArgs._context->setStereoProjections(stereoEyeProjections);
// renderArgs._context->setStereoViews(stereoEyeOffsets);
// }
//
// renderArgs._hudOperator = displayPlugin->getHUDOperator();
// renderArgs._hudTexture = _applicationOverlay.getOverlayTexture();
// renderArgs._blitFramebuffer = finalFramebuffer;
// _graphicsEngine.render_runRenderFrame(&renderArgs);
// }
//
// auto frame = _graphicsEngine.getGPUContext()->endFrame();
// frame->frameIndex = _renderFrameCount;
// frame->framebuffer = finalFramebuffer;
// frame->framebufferRecycler = [](const gpu::FramebufferPointer& framebuffer) {
// auto frameBufferCache = DependencyManager::get<FramebufferCache>();
// if (frameBufferCache) {
// frameBufferCache->releaseFramebuffer(framebuffer);
// }
// };
// // deliver final scene rendering commands to the display plugin
// {
// PROFILE_RANGE(render, "/pluginOutput");
// PerformanceTimer perfTimer("pluginOutput");
// _renderLoopCounter.increment();
// displayPlugin->submitFrame(frame);
// }
//
// // Reset the framebuffer and stereo state
// renderArgs._blitFramebuffer.reset();
// renderArgs._context->enableStereo(false);
//
// {
// Stats::getInstance()->setRenderDetails(renderArgs._details);
// }
//
// uint64_t lastPaintDuration = usecTimestampNow() - lastPaintBegin;
// _frameTimingsScriptingInterface.addValue(lastPaintDuration);
//}
// WorldBox Render Data & rendering functions
//
//class WorldBoxRenderData {
//public:
// typedef render::Payload<WorldBoxRenderData> Payload;
// typedef Payload::DataPointer Pointer;
//
// int _val = 0;
// static render::ItemID _item; // unique WorldBoxRenderData
//};
//
//render::ItemID WorldBoxRenderData::_item{ render::Item::INVALID_ITEM_ID };
//
//namespace render {
// template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1); }
// template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); }
// template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) {
// if (Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) {
// PerformanceTimer perfTimer("worldBox");
//
// auto& batch = *args->_batch;
// DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch);
// renderWorldBox(args, batch);
// }
// }
//}
//
//void Application::runRenderFrame(RenderArgs* renderArgs) {
// PROFILE_RANGE(render, __FUNCTION__);
// PerformanceTimer perfTimer("display");
// PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::runRenderFrame()");
//
// // The pending changes collecting the changes here
// render::Transaction transaction;
//
// if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) {
// // render models...
// PerformanceTimer perfTimer("entities");
// PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
// "Application::runRenderFrame() ... entities...");
//
// RenderArgs::DebugFlags renderDebugFlags = RenderArgs::RENDER_DEBUG_NONE;
//
// renderArgs->_debugFlags = renderDebugFlags;
// }
//
// // Make sure the WorldBox is in the scene
// // For the record, this one RenderItem is the first one we created and added to the scene.
// // We could move that code elsewhere but you know...
// if (!render::Item::isValidID(WorldBoxRenderData::_item)) {
// auto worldBoxRenderData = std::make_shared<WorldBoxRenderData>();
// auto worldBoxRenderPayload = std::make_shared<WorldBoxRenderData::Payload>(worldBoxRenderData);
//
// WorldBoxRenderData::_item = _main3DScene->allocateID();
//
// transaction.resetItem(WorldBoxRenderData::_item, worldBoxRenderPayload);
// _main3DScene->enqueueTransaction(transaction);
// }
//
// {
// PerformanceTimer perfTimer("EngineRun");
// _renderEngine->getRenderContext()->args = renderArgs;
// _renderEngine->run();
// }
//}

View file

@ -34,6 +34,8 @@
#include "Application.h"
GraphicsEngine::GraphicsEngine() {
const QString SPLASH_SKYBOX { "{\"ProceduralEntity\":{ \"version\":2, \"shaderUrl\":\"qrc:///shaders/splashSkybox.frag\" } }" };
_splashScreen->parse(SPLASH_SKYBOX);
}
GraphicsEngine::~GraphicsEngine() {
@ -54,6 +56,10 @@ void GraphicsEngine::initializeGPU(GLWidget* glwidget) {
glwidget->makeCurrent();
_gpuContext = std::make_shared<gpu::Context>();
_gpuContext->pushProgramsToSync(shader::allPrograms(), [this] {
_programsCompiled.store(true);
}, 1);
DependencyManager::get<TextureCache>()->setGPUContext(_gpuContext);
}
@ -122,11 +128,7 @@ void GraphicsEngine::render_runRenderFrame(RenderArgs* renderArgs) {
static const unsigned int THROTTLED_SIM_FRAMERATE = 15;
static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE;
bool GraphicsEngine::shouldPaint() const {
auto displayPlugin = qApp->getActiveDisplayPlugin();
#ifdef DEBUG_PAINT_DELAY
@ -145,7 +147,7 @@ bool GraphicsEngine::shouldPaint() const {
// Throttle if requested
//if (displayPlugin->isThrottled() && (_graphicsEngine._renderEventHandler->_lastTimeRendered.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) {
if ( displayPlugin->isThrottled() &&
if (displayPlugin->isThrottled() &&
(static_cast<RenderEventHandler*>(_renderEventHandler)->_lastTimeRendered.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) {
return false;
}
@ -158,8 +160,6 @@ bool GraphicsEngine::checkPendingRenderEvent() {
return (_renderEventHandler && static_cast<RenderEventHandler*>(_renderEventHandler)->_pendingRenderEvent.compare_exchange_strong(expected, true));
}
void GraphicsEngine::render_performFrame() {
// Some plugins process message events, allowing paintGL to be called reentrantly.
@ -189,6 +189,7 @@ void GraphicsEngine::render_performFrame() {
glm::mat4 HMDSensorPose;
glm::mat4 eyeToWorld;
glm::mat4 sensorToWorld;
ViewFrustum viewFrustum;
bool isStereo;
glm::mat4 stereoEyeOffsets[2];
@ -211,6 +212,7 @@ void GraphicsEngine::render_performFrame() {
stereoEyeOffsets[eye] = _appRenderArgs._eyeOffsets[eye];
stereoEyeProjections[eye] = _appRenderArgs._eyeProjections[eye];
});
viewFrustum = _appRenderArgs._renderArgs.getViewFrustum();
}
{
@ -221,21 +223,12 @@ void GraphicsEngine::render_performFrame() {
gpu::doInBatch("Application_render::gpuContextReset", getGPUContext(), [&](gpu::Batch& batch) {
batch.resetStages();
});
}
{
PROFILE_RANGE(render, "/renderOverlay");
PerformanceTimer perfTimer("renderOverlay");
// NOTE: There is no batch associated with this renderArgs
// the ApplicationOverlay class assumes it's viewport is setup to be the device size
renderArgs._viewport = glm::ivec4(0, 0, qApp->getDeviceSize());
qApp->getApplicationOverlay().renderOverlay(&renderArgs);
}
{
PROFILE_RANGE(render, "/updateCompositor");
qApp->getApplicationCompositor().setFrameInfo(_renderFrameCount, eyeToWorld, sensorToWorld);
if (isStereo) {
renderArgs._context->enableStereo(true);
renderArgs._context->setStereoProjections(stereoEyeProjections);
renderArgs._context->setStereoViews(stereoEyeOffsets);
}
}
gpu::FramebufferPointer finalFramebuffer;
@ -245,21 +238,40 @@ void GraphicsEngine::render_performFrame() {
// Primary rendering pass
auto framebufferCache = DependencyManager::get<FramebufferCache>();
finalFramebufferSize = framebufferCache->getFrameBufferSize();
// Final framebuffer that will be handled to the display-plugin
// Final framebuffer that will be handed to the display-plugin
finalFramebuffer = framebufferCache->getFramebuffer();
}
{
if (isStereo) {
renderArgs._context->enableStereo(true);
renderArgs._context->setStereoProjections(stereoEyeProjections);
renderArgs._context->setStereoViews(stereoEyeOffsets);
if (!_programsCompiled.load()) {
gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {
batch.setFramebuffer(finalFramebuffer);
batch.enableSkybox(true);
batch.enableStereo(isStereo);
batch.setViewportTransform({ 0, 0, finalFramebuffer->getSize() });
_splashScreen->render(batch, viewFrustum);
});
} else {
{
PROFILE_RANGE(render, "/renderOverlay");
PerformanceTimer perfTimer("renderOverlay");
// NOTE: There is no batch associated with this renderArgs
// the ApplicationOverlay class assumes it's viewport is setup to be the device size
renderArgs._viewport = glm::ivec4(0, 0, qApp->getDeviceSize());
qApp->getApplicationOverlay().renderOverlay(&renderArgs);
}
renderArgs._hudOperator = displayPlugin->getHUDOperator();
renderArgs._hudTexture = qApp->getApplicationOverlay().getOverlayTexture();
renderArgs._blitFramebuffer = finalFramebuffer;
render_runRenderFrame(&renderArgs);
{
PROFILE_RANGE(render, "/updateCompositor");
qApp->getApplicationCompositor().setFrameInfo(_renderFrameCount, eyeToWorld, sensorToWorld);
}
{
PROFILE_RANGE(render, "/runRenderFrame");
renderArgs._hudOperator = displayPlugin->getHUDOperator();
renderArgs._hudTexture = qApp->getApplicationOverlay().getOverlayTexture();
renderArgs._blitFramebuffer = finalFramebuffer;
render_runRenderFrame(&renderArgs);
}
}
auto frame = getGPUContext()->endFrame();
@ -283,18 +295,19 @@ void GraphicsEngine::render_performFrame() {
renderArgs._blitFramebuffer.reset();
renderArgs._context->enableStereo(false);
#if !defined(DISABLE_QML)
{
auto stats = Stats::getInstance();
if (stats) {
stats->setRenderDetails(renderArgs._details);
}
}
#endif
uint64_t lastPaintDuration = usecTimestampNow() - lastPaintBegin;
_frameTimingsScriptingInterface.addValue(lastPaintDuration);
}
void GraphicsEngine::editRenderArgs(RenderArgsEditor editor) {
QMutexLocker renderLocker(&_renderArgsMutex);
editor(_appRenderArgs);

View file

@ -15,6 +15,7 @@
#include <qmutex.h>
#include <render/Engine.h>
#include <procedural/ProceduralSkybox.h>
#include <OctreeConstants.h>
#include <shared/RateCounter.h>
@ -84,6 +85,9 @@ protected:
FrameTimingsScriptingInterface _frameTimingsScriptingInterface;
std::shared_ptr<ProceduralSkybox> _splashScreen { std::make_shared<ProceduralSkybox>() };
std::atomic<bool> _programsCompiled { false };
friend class Application;
};

View file

@ -10,6 +10,7 @@
#ifndef hifi_RenderEventHandler_h
#define hifi_RenderEventHandler_h
#include <functional>
#include <QEvent>
#include <QElapsedTimer>
#include "gl/OffscreenGLCanvas.h"
@ -49,4 +50,4 @@ private:
bool event(QEvent* event) override;
};
#endif // #include hifi_RenderEventHandler_h
#endif // #include hifi_RenderEventHandler_h

View file

@ -327,7 +327,7 @@ std::vector<int> AnimSkeleton::lookUpJointIndices(const std::vector<QString>& jo
for (auto& name : jointNames) {
int index = nameToJointIndex(name);
if (index == -1) {
qWarning(animation) << "AnimSkeleton::lookUpJointIndices(): could not find bone with named " << name;
qWarning(animation) << "AnimSkeleton::lookUpJointIndices(): could not find bone with name " << name;
}
result.push_back(index);
}

View file

@ -20,6 +20,7 @@
#include <Profile.h>
#include "AnimationLogging.h"
#include <FBXSerializer.h>
int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();

View file

@ -17,7 +17,7 @@
#include <QtScript/QScriptValue>
#include <DependencyManager.h>
#include <FBXSerializer.h>
#include <hfm/HFM.h>
#include <ResourceCache.h>
class Animation;

View file

@ -525,6 +525,9 @@ void OpenGLDisplayPlugin::updateFrameData() {
if (_newFrameQueue.size() > 1) {
_droppedFrameRate.increment(_newFrameQueue.size() - 1);
}
_gpuContext->processProgramsToSync();
while (!_newFrameQueue.empty()) {
_currentFrame = _newFrameQueue.front();
_newFrameQueue.pop();
@ -645,6 +648,7 @@ void OpenGLDisplayPlugin::present() {
auto frameId = (uint64_t)presentCount();
PROFILE_RANGE_EX(render, __FUNCTION__, 0xffffff00, frameId)
uint64_t startPresent = usecTimestampNow();
{
PROFILE_RANGE_EX(render, "updateFrameData", 0xff00ff00, frameId)
updateFrameData();
@ -837,7 +841,6 @@ void OpenGLDisplayPlugin::render(std::function<void(gpu::Batch& batch)> f) {
_gpuContext->executeBatch(batch);
}
OpenGLDisplayPlugin::~OpenGLDisplayPlugin() {
}

View file

@ -745,6 +745,14 @@ bool RenderableModelEntityItem::shouldBePhysical() const {
}
}
int RenderableModelEntityItem::getJointParent(int index) const {
auto model = getModel();
if (model) {
return model->getRig().getJointParentIndex(index);
}
return -1;
}
glm::quat RenderableModelEntityItem::getAbsoluteJointRotationInObjectFrame(int index) const {
auto model = getModel();
if (model) {

View file

@ -94,6 +94,7 @@ public:
// these are in the frame of this object (model space)
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
virtual int getJointParent(int index) const override;
virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override;
virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override;

View file

@ -3277,4 +3277,8 @@ void EntityItem::prepareForSimulationOwnershipBid(EntityItemProperties& properti
properties.setEntityHostType(getEntityHostType());
properties.setOwningAvatarID(getOwningAvatarID());
setLastBroadcast(now); // for debug/physics status icons
}
bool EntityItem::isWearable() const {
return isVisible() && (getParentID() == DependencyManager::get<NodeList>()->getSessionUUID() || getParentID() == AVATAR_SELF_ID);
}

View file

@ -467,6 +467,7 @@ public:
// these are in the frame of this object
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override { return glm::quat(); }
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override { return glm::vec3(0.0f); }
virtual int getJointParent(int index) const override { return -1; }
virtual bool setLocalJointRotation(int index, const glm::quat& rotation) override { return false; }
virtual bool setLocalJointTranslation(int index, const glm::vec3& translation) override { return false; }
@ -489,7 +490,7 @@ public:
void scriptHasUnloaded();
void setScriptHasFinishedPreload(bool value);
bool isScriptPreloadFinished();
virtual bool isWearable() const;
bool isDomainEntity() const { return _hostType == entity::HostType::DOMAIN; }
bool isAvatarEntity() const { return _hostType == entity::HostType::AVATAR; }
bool isLocalEntity() const { return _hostType == entity::HostType::LOCAL; }

View file

@ -114,6 +114,8 @@ bool EntityScriptingInterface::canReplaceContent() {
void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) {
if (_entityTree) {
disconnect(_entityTree.get(), &EntityTree::addingEntityPointer, this, &EntityScriptingInterface::onAddingEntity);
disconnect(_entityTree.get(), &EntityTree::deletingEntityPointer, this, &EntityScriptingInterface::onDeletingEntity);
disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);
disconnect(_entityTree.get(), &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity);
disconnect(_entityTree.get(), &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities);
@ -122,6 +124,8 @@ void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) {
_entityTree = elementTree;
if (_entityTree) {
connect(_entityTree.get(), &EntityTree::addingEntityPointer, this, &EntityScriptingInterface::onAddingEntity, Qt::DirectConnection);
connect(_entityTree.get(), &EntityTree::deletingEntityPointer, this, &EntityScriptingInterface::onDeletingEntity, Qt::DirectConnection);
connect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);
connect(_entityTree.get(), &EntityTree::deletingEntity, this, &EntityScriptingInterface::deletingEntity);
connect(_entityTree.get(), &EntityTree::clearingEntities, this, &EntityScriptingInterface::clearingEntities);
@ -1064,7 +1068,17 @@ void EntityScriptingInterface::handleEntityScriptCallMethodPacket(QSharedPointer
}
}
void EntityScriptingInterface::onAddingEntity(EntityItem* entity) {
if (entity->isWearable()) {
emit addingWearable(entity->getEntityItemID());
}
}
void EntityScriptingInterface::onDeletingEntity(EntityItem* entity) {
if (entity->isWearable()) {
emit deletingWearable(entity->getEntityItemID());
}
}
QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float radius) const {
PROFILE_RANGE(script_entities, __FUNCTION__);
@ -1824,6 +1838,15 @@ glm::vec3 EntityScriptingInterface::localCoordsToVoxelCoords(const QUuid& entity
}
}
int EntityScriptingInterface::getJointParent(const QUuid& entityID, int index) {
if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) {
auto modelEntity = std::dynamic_pointer_cast<ModelEntityItem>(entity);
return modelEntity->getJointParent(index);
} else {
return -1;
}
}
glm::vec3 EntityScriptingInterface::getAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex) {
if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) {
auto modelEntity = std::dynamic_pointer_cast<ModelEntityItem>(entity);

View file

@ -1007,7 +1007,16 @@ public slots:
*/
// FIXME move to a renderable entity interface
Q_INVOKABLE glm::vec3 getAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex);
/**jsdoc
* Get the index of the parent joint.
* @function Entities.getJointParent
* @param {Uuid} entityID - The ID of the entity.
* @param {number} index - The integer index of the joint.
* @returns {number} The index of the parent joint.
*/
Q_INVOKABLE int getJointParent(const QUuid& entityID, int index);
/**jsdoc
* Get the translation of a joint in a {@link Entities.EntityType|Model} entity relative to the entity's position and
* orientation.
@ -1908,6 +1917,31 @@ signals:
*/
void addingEntity(const EntityItemID& entityID);
/**jsdoc
* Triggered when an 'wearable' entity is deleted.
* @function Entities.deletingWearable
* @param {Uuid} entityID - The ID of the 'wearable' entity deleted.
* @returns {Signal}
* @example <caption>Report when an 'wearable' entity is deleted.</caption>
* Entities.deletingWearable.connect(function (entityID) {
* print("Deleted wearable: " + entityID);
* });
*/
void deletingWearable(const EntityItemID& entityID);
/**jsdoc
* Triggered when an 'wearable' entity is added to Interface's local in-memory tree of entities it knows about. This may occur when
* 'wearable' entities are added to avatar
* @function Entities.addingWearable
* @param {Uuid} entityID - The ID of the 'wearable' entity added.
* @returns {Signal}
* @example <caption>Report when an 'wearable' entity is added.</caption>
* Entities.addingWearable.connect(function (entityID) {
* print("Added wearable: " + entityID);
* });
*/
void addingWearable(const EntityItemID& entityID);
/**jsdoc
* Triggered when you disconnect from a domain, at which time Interface's local in-memory tree of entities it knows about
* is cleared.
@ -1938,6 +1972,8 @@ protected:
private slots:
void handleEntityScriptCallMethodPacket(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode);
void onAddingEntity(EntityItem* entity);
void onDeletingEntity(EntityItem* entity);
private:
bool actionWorker(const QUuid& entityID, std::function<bool(EntitySimulationPointer, EntityItemPointer)> actor);

View file

@ -305,6 +305,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) {
fixupNeedsParentFixups();
emit addingEntity(entity->getEntityItemID());
emit addingEntityPointer(entity.get());
}
bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) {

View file

@ -309,6 +309,7 @@ signals:
void deletingEntity(const EntityItemID& entityID);
void deletingEntityPointer(EntityItem* entityID);
void addingEntity(const EntityItemID& entityID);
void addingEntityPointer(EntityItem* entityID);
void editingEntityPointer(const EntityItemPointer& entityID);
void entityScriptChanging(const EntityItemID& entityItemID, const bool reload);
void entityServerScriptChanging(const EntityItemID& entityItemID, const bool reload);

View file

@ -515,7 +515,6 @@ QVector<bool> ModelEntityItem::getJointTranslationsSet() const {
return result;
}
bool ModelEntityItem::hasModel() const {
return resultWithReadLock<bool>([&] {
return !_modelURL.isEmpty();

View file

@ -417,6 +417,19 @@ QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) {
return filepath.mid(filepath.lastIndexOf('/') + 1);
}
QMap<QString, QString> getJointNameMapping(const QVariantHash& mapping) {
static const QString JOINT_NAME_MAPPING_FIELD = "jointMap";
QMap<QString, QString> hfmToHifiJointNameMap;
if (!mapping.isEmpty() && mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) {
auto jointNames = mapping[JOINT_NAME_MAPPING_FIELD].toHash();
for (auto itr = jointNames.begin(); itr != jointNames.end(); itr++) {
hfmToHifiJointNameMap.insert(itr.key(), itr.value().toString());
qCDebug(modelformat) << "the mapped key " << itr.key() << " has a value of " << hfmToHifiJointNameMap[itr.key()];
}
}
return hfmToHifiJointNameMap;
}
QMap<QString, glm::quat> getJointRotationOffsets(const QVariantHash& mapping) {
QMap<QString, glm::quat> jointRotationOffsets;
static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset";
@ -465,14 +478,14 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
std::map<QString, HFMLight> lights;
QVariantHash joints = mapping.value("joint").toHash();
QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft")));
QString jointEyeRightName = processID(getString(joints.value("jointEyeRight", "jointEyeRight")));
QString jointNeckName = processID(getString(joints.value("jointNeck", "jointNeck")));
QString jointRootName = processID(getString(joints.value("jointRoot", "jointRoot")));
QString jointLeanName = processID(getString(joints.value("jointLean", "jointLean")));
QString jointHeadName = processID(getString(joints.value("jointHead", "jointHead")));
QString jointLeftHandName = processID(getString(joints.value("jointLeftHand", "jointLeftHand")));
QString jointRightHandName = processID(getString(joints.value("jointRightHand", "jointRightHand")));
QString jointEyeLeftName = "EyeLeft";
QString jointEyeRightName = "EyeRight";
QString jointNeckName = "Neck";
QString jointRootName = "Hips";
QString jointLeanName = "Spine";
QString jointHeadName = "Head";
QString jointLeftHandName = "LeftHand";
QString jointRightHandName = "RightHand";
QString jointEyeLeftID;
QString jointEyeRightID;
QString jointNeckID;
@ -519,6 +532,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
HFMModel& hfmModel = *hfmModelPtr;
hfmModel.originalURL = url;
hfmModel.hfmToHifiJointNameMapping.clear();
hfmModel.hfmToHifiJointNameMapping = getJointNameMapping(mapping);
float unitScaleFactor = 1.0f;
glm::vec3 ambientColor;
@ -587,34 +602,34 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
hifiGlobalNodeID = id;
}
if (name == jointEyeLeftName || name == "EyeL" || name == "joint_Leye") {
if (name == jointEyeLeftName || name == "EyeL" || name == "joint_Leye" || (hfmModel.hfmToHifiJointNameMapping.contains(jointEyeLeftName) && (name == hfmModel.hfmToHifiJointNameMapping[jointEyeLeftName]))) {
jointEyeLeftID = getID(object.properties);
} else if (name == jointEyeRightName || name == "EyeR" || name == "joint_Reye") {
} else if (name == jointEyeRightName || name == "EyeR" || name == "joint_Reye" || (hfmModel.hfmToHifiJointNameMapping.contains(jointEyeRightName) && (name == hfmModel.hfmToHifiJointNameMapping[jointEyeRightName]))) {
jointEyeRightID = getID(object.properties);
} else if (name == jointNeckName || name == "NeckRot" || name == "joint_neck") {
} else if (name == jointNeckName || name == "NeckRot" || name == "joint_neck" || (hfmModel.hfmToHifiJointNameMapping.contains(jointNeckName) && (name == hfmModel.hfmToHifiJointNameMapping[jointNeckName]))) {
jointNeckID = getID(object.properties);
} else if (name == jointRootName) {
} else if (name == jointRootName || (hfmModel.hfmToHifiJointNameMapping.contains(jointRootName) && (name == hfmModel.hfmToHifiJointNameMapping[jointRootName]))) {
jointRootID = getID(object.properties);
} else if (name == jointLeanName) {
} else if (name == jointLeanName || (hfmModel.hfmToHifiJointNameMapping.contains(jointLeanName) && (name == hfmModel.hfmToHifiJointNameMapping[jointLeanName]))) {
jointLeanID = getID(object.properties);
} else if (name == jointHeadName) {
} else if ((name == jointHeadName) || (hfmModel.hfmToHifiJointNameMapping.contains(jointHeadName) && (name == hfmModel.hfmToHifiJointNameMapping[jointHeadName]))) {
jointHeadID = getID(object.properties);
} else if (name == jointLeftHandName || name == "LeftHand" || name == "joint_L_hand") {
} else if (name == jointLeftHandName || name == "LeftHand" || name == "joint_L_hand" || (hfmModel.hfmToHifiJointNameMapping.contains(jointLeftHandName) && (name == hfmModel.hfmToHifiJointNameMapping[jointLeftHandName]))) {
jointLeftHandID = getID(object.properties);
} else if (name == jointRightHandName || name == "RightHand" || name == "joint_R_hand") {
} else if (name == jointRightHandName || name == "RightHand" || name == "joint_R_hand" || (hfmModel.hfmToHifiJointNameMapping.contains(jointRightHandName) && (name == hfmModel.hfmToHifiJointNameMapping[jointRightHandName]))) {
jointRightHandID = getID(object.properties);
} else if (name == "LeftToe" || name == "joint_L_toe" || name == "LeftToe_End") {
} else if (name == "LeftToe" || name == "joint_L_toe" || name == "LeftToe_End" || (hfmModel.hfmToHifiJointNameMapping.contains("LeftToe") && (name == hfmModel.hfmToHifiJointNameMapping["LeftToe"]))) {
jointLeftToeID = getID(object.properties);
} else if (name == "RightToe" || name == "joint_R_toe" || name == "RightToe_End") {
} else if (name == "RightToe" || name == "joint_R_toe" || name == "RightToe_End" || (hfmModel.hfmToHifiJointNameMapping.contains("RightToe") && (name == hfmModel.hfmToHifiJointNameMapping["RightToe"]))) {
jointRightToeID = getID(object.properties);
}
@ -1388,6 +1403,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
}
joint.inverseBindRotation = joint.inverseDefaultRotation;
joint.name = fbxModel.name;
if (hfmModel.hfmToHifiJointNameMapping.contains(hfmModel.hfmToHifiJointNameMapping.key(joint.name))) {
joint.name = hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name);
}
foreach (const QString& childID, _connectionChildMap.values(modelID)) {
QString type = typeFlags.value(childID);
@ -1400,7 +1418,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
joint.bindTransformFoundInCluster = false;
hfmModel.joints.append(joint);
hfmModel.jointIndices.insert(fbxModel.name, hfmModel.joints.size());
hfmModel.jointIndices.insert(joint.name, hfmModel.joints.size());
QString rotationID = localRotations.value(modelID);
AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID));
@ -1824,6 +1842,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
QString jointName = itr.key();
glm::quat rotationOffset = itr.value();
int jointIndex = hfmModel.getJointIndex(jointName);
if (hfmModel.hfmToHifiJointNameMapping.contains(jointName)) {
jointIndex = hfmModel.getJointIndex(jointName);
}
if (jointIndex != -1) {
hfmModel.jointRotationOffsets.insert(jointIndex, rotationOffset);
}
@ -1833,6 +1854,17 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
return hfmModelPtr;
}
MediaType FBXSerializer::getMediaType() const {
MediaType mediaType("fbx");
mediaType.extensions.push_back("fbx");
mediaType.fileSignatures.emplace_back("Kaydara FBX Binary \x00", 0);
return mediaType;
}
std::unique_ptr<hfm::Serializer::Factory> FBXSerializer::getFactory() const {
return std::make_unique<hfm::Serializer::SimpleFactory<FBXSerializer>>();
}
HFMModel::Pointer FBXSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
QBuffer buffer(const_cast<QByteArray*>(&data));
buffer.open(QIODevice::ReadOnly);

View file

@ -96,6 +96,9 @@ class ExtractedMesh;
class FBXSerializer : public HFMSerializer {
public:
MediaType getMediaType() const override;
std::unique_ptr<hfm::Serializer::Factory> getFactory() const override;
HFMModel* _hfmModel;
/// Reads HFMModel from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing

View file

@ -29,6 +29,7 @@ static const QString JOINT_FIELD = "joint";
static const QString FREE_JOINT_FIELD = "freeJoint";
static const QString BLENDSHAPE_FIELD = "bs";
static const QString SCRIPT_FIELD = "script";
static const QString JOINT_NAME_MAPPING_FIELD = "jointMap";
class FSTReader {
public:

View file

@ -35,11 +35,6 @@
#include "FBXSerializer.h"
GLTFSerializer::GLTFSerializer() {
}
bool GLTFSerializer::getStringVal(const QJsonObject& object, const QString& fieldname,
QString& value, QMap<QString, bool>& defined) {
bool _defined = (object.contains(fieldname) && object[fieldname].isString());
@ -910,6 +905,17 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) {
return true;
}
MediaType GLTFSerializer::getMediaType() const {
MediaType mediaType("gltf");
mediaType.extensions.push_back("gltf");
mediaType.webMediaTypes.push_back("model/gltf+json");
return mediaType;
}
std::unique_ptr<hfm::Serializer::Factory> GLTFSerializer::getFactory() const {
return std::make_unique<hfm::Serializer::SimpleFactory<GLTFSerializer>>();
}
HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
_url = url;

View file

@ -16,7 +16,6 @@
#include <QtNetwork/QNetworkReply>
#include <hfm/ModelFormatLogging.h>
#include <hfm/HFMSerializer.h>
#include "FBXSerializer.h"
struct GLTFAsset {
@ -703,7 +702,9 @@ struct GLTFFile {
class GLTFSerializer : public QObject, public HFMSerializer {
Q_OBJECT
public:
GLTFSerializer();
MediaType getMediaType() const override;
std::unique_ptr<hfm::Serializer::Factory> getFactory() const override;
HFMModel::Pointer read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url = QUrl()) override;
private:
GLTFFile _file;

View file

@ -651,6 +651,15 @@ done:
return result;
}
MediaType OBJSerializer::getMediaType() const {
MediaType mediaType("obj");
mediaType.extensions.push_back("obj");
return mediaType;
}
std::unique_ptr<hfm::Serializer::Factory> OBJSerializer::getFactory() const {
return std::make_unique<hfm::Serializer::SimpleFactory<OBJSerializer>>();
}
HFMModel::Pointer OBJSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr);

View file

@ -14,7 +14,6 @@
#include <QtNetwork/QNetworkReply>
#include <hfm/HFMSerializer.h>
#include "FBXSerializer.h"
class OBJTokenizer {
public:
@ -92,6 +91,9 @@ public:
class OBJSerializer: public QObject, public HFMSerializer { // QObject so we can make network requests.
Q_OBJECT
public:
MediaType getMediaType() const override;
std::unique_ptr<hfm::Serializer::Factory> getFactory() const override;
typedef QVector<OBJFace> FaceGroup;
QVector<glm::vec3> vertices;
QVector<glm::vec3> vertexColors;

View file

@ -859,3 +859,7 @@ void GLBackend::setCameraCorrection(const Mat4& correction, const Mat4& prevRend
_pipeline._cameraCorrectionBuffer._buffer->setSubData(0, _transform._correction);
_pipeline._cameraCorrectionBuffer._buffer->flush();
}
void GLBackend::syncProgram(const gpu::ShaderPointer& program) {
gpu::gl::GLShader::sync(*this, *program);
}

View file

@ -249,6 +249,8 @@ public:
// Let's try to avoid to do that as much as possible!
void syncCache() final override;
void syncProgram(const gpu::ShaderPointer& program) override;
// This is the ugly "download the pixels to sysmem for taking a snapshot"
// Just avoid using it, it's ugly and will break performances
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer,

View file

@ -37,6 +37,7 @@ GLShader* GLShader::sync(GLBackend& backend, const Shader& shader, const Shader:
if (object) {
return object;
}
PROFILE_RANGE(render, "/GLShader::sync");
// need to have a gpu object?
if (shader.isProgram()) {
GLShader* tempObject = backend.compileBackendProgram(shader, handler);

View file

@ -760,4 +760,4 @@ void Batch::flush() {
}
buffer->flush();
}
}
}

View file

@ -18,8 +18,8 @@
float color_scalar_sRGBToLinear(float value) {
const float SRGB_ELBOW = 0.04045;
return (value <= SRGB_ELBOW) ? value / 12.92 : pow((value + 0.055) / 1.055, 2.4);
return mix(pow((value + 0.055) / 1.055, 2.4), value / 12.92, float(value <= SRGB_ELBOW));
}
vec3 color_sRGBToLinear(vec3 srgb) {

View file

@ -51,6 +51,7 @@ Context::~Context() {
delete batch;
}
_batchPool.clear();
_syncedPrograms.clear();
}
void Context::shutdown() {
@ -346,6 +347,40 @@ Size Context::getTextureResourceIdealGPUMemSize() {
return Backend::textureResourceIdealGPUMemSize.getValue();
}
void Context::pushProgramsToSync(const std::vector<uint32_t>& programIDs, std::function<void()> callback, size_t rate) {
std::vector<gpu::ShaderPointer> programs;
for (auto programID : programIDs) {
programs.push_back(gpu::Shader::createProgram(programID));
}
pushProgramsToSync(programs, callback, rate);
}
void Context::pushProgramsToSync(const std::vector<gpu::ShaderPointer>& programs, std::function<void()> callback, size_t rate) {
Lock lock(_programsToSyncMutex);
_programsToSyncQueue.emplace(programs, callback, rate == 0 ? programs.size() : rate);
}
void Context::processProgramsToSync() {
if (!_programsToSyncQueue.empty()) {
Lock lock(_programsToSyncMutex);
ProgramsToSync programsToSync = _programsToSyncQueue.front();
size_t numSynced = 0;
while (_nextProgramToSyncIndex < programsToSync.programs.size() && numSynced < programsToSync.rate) {
auto nextProgram = programsToSync.programs.at(_nextProgramToSyncIndex);
_backend->syncProgram(nextProgram);
_syncedPrograms.push_back(nextProgram);
_nextProgramToSyncIndex++;
numSynced++;
}
if (_nextProgramToSyncIndex == programsToSync.programs.size()) {
programsToSync.callback();
_nextProgramToSyncIndex = 0;
_programsToSyncQueue.pop();
}
}
}
BatchPointer Context::acquireBatch(const char* name) {
Batch* rawBatch = nullptr;
{

View file

@ -13,6 +13,7 @@
#include <assert.h>
#include <mutex>
#include <queue>
#include <GLMHelpers.h>
@ -61,6 +62,7 @@ public:
virtual void render(const Batch& batch) = 0;
virtual void syncCache() = 0;
virtual void syncProgram(const gpu::ShaderPointer& program) = 0;
virtual void recycle() const = 0;
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0;
@ -247,6 +249,20 @@ public:
static Size getTextureResourcePopulatedGPUMemSize();
static Size getTextureResourceIdealGPUMemSize();
struct ProgramsToSync {
ProgramsToSync(const std::vector<gpu::ShaderPointer>& programs, std::function<void()> callback, size_t rate) :
programs(programs), callback(callback), rate(rate) {}
std::vector<gpu::ShaderPointer> programs;
std::function<void()> callback;
size_t rate;
};
void pushProgramsToSync(const std::vector<uint32_t>& programIDs, std::function<void()> callback, size_t rate = 0);
void pushProgramsToSync(const std::vector<gpu::ShaderPointer>& programs, std::function<void()> callback, size_t rate = 0);
void processProgramsToSync();
protected:
Context(const Context& context);
@ -258,6 +274,11 @@ protected:
RangeTimerPointer _frameRangeTimer;
StereoState _stereo;
std::mutex _programsToSyncMutex;
std::queue<ProgramsToSync> _programsToSyncQueue;
gpu::Shaders _syncedPrograms;
size_t _nextProgramToSyncIndex { 0 };
// Sampled at the end of every frame, the stats of all the counters
mutable ContextStats _frameStats;

View file

@ -43,6 +43,8 @@ public:
// Let's try to avoid to do that as much as possible!
void syncCache() final { }
void syncProgram(const gpu::ShaderPointer& program) final {}
// This is the ugly "download the pixels to sysmem for taking a snapshot"
// Just avoid using it, it's ugly and will break performances
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) final { }

View file

@ -313,6 +313,7 @@ public:
QList<QString> blendshapeChannelNames;
QMap<int, glm::quat> jointRotationOffsets;
QMap<QString, QString> hfmToHifiJointNameMapping;
};
};

View file

@ -0,0 +1,65 @@
//
// HFMFormatRegistry.cpp
// libraries/hfm/src/hfm
//
// Created by Sabrina Shanman on 2018/11/29.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "HFMFormatRegistry.h"
namespace hfm {
FormatRegistry::MediaTypeID FormatRegistry::registerMediaType(const MediaType& mediaType, std::unique_ptr<Serializer::Factory> supportedFactory) {
std::lock_guard<std::mutex> lock(_libraryLock);
MediaTypeID id = _mediaTypeLibrary.registerMediaType(mediaType);
_supportedFormats.emplace_back(id, supportedFactory);
return id;
}
void FormatRegistry::unregisterMediaType(const MediaTypeID& mediaTypeID) {
std::lock_guard<std::mutex> lock(_libraryLock);
for (auto it = _supportedFormats.begin(); it != _supportedFormats.end(); it++) {
if ((*it).mediaTypeID == mediaTypeID) {
_supportedFormats.erase(it);
break;
}
}
_mediaTypeLibrary.unregisterMediaType(mediaTypeID);
}
std::shared_ptr<Serializer> FormatRegistry::getSerializerForMediaTypeID(FormatRegistry::MediaTypeID mediaTypeID) const {
// TODO: shared_lock in C++14
std::lock_guard<std::mutex> lock(*const_cast<std::mutex*>(&_libraryLock));
for (auto it = _supportedFormats.begin(); it != _supportedFormats.end(); it++) {
if ((*it).mediaTypeID == mediaTypeID) {
return (*it).factory->get();
}
}
return std::shared_ptr<Serializer>();
}
std::shared_ptr<Serializer> FormatRegistry::getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const {
MediaTypeID id;
{
// TODO: shared_lock in C++14
std::lock_guard<std::mutex> lock(*const_cast<std::mutex*>(&_libraryLock));
id = _mediaTypeLibrary.findMediaTypeForData(data);
if (id == INVALID_MEDIA_TYPE_ID) {
id = _mediaTypeLibrary.findMediaTypeForURL(url);
if (id == INVALID_MEDIA_TYPE_ID) {
id = _mediaTypeLibrary.findMediaTypeForWebID(webMediaType);
}
}
}
return getSerializerForMediaTypeID(id);
}
};

View file

@ -0,0 +1,50 @@
//
// HFMFormatRegistry.h
// libraries/hfm/src/hfm
//
// Created by Sabrina Shanman on 2018/11/28.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_HFMFormatRegistry_h
#define hifi_HFMFormatRegistry_h
#include "HFMSerializer.h"
#include <shared/MediaTypeLibrary.h>
#include <shared/ReadWriteLockable.h>
namespace hfm {
class FormatRegistry {
public:
using MediaTypeID = MediaTypeLibrary::ID;
static const MediaTypeID INVALID_MEDIA_TYPE_ID { MediaTypeLibrary::INVALID_ID };
MediaTypeID registerMediaType(const MediaType& mediaType, std::unique_ptr<Serializer::Factory> supportedFactory);
void unregisterMediaType(const MediaTypeID& id);
std::shared_ptr<Serializer> getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const;
protected:
std::shared_ptr<Serializer> getSerializerForMediaTypeID(MediaTypeID id) const;
MediaTypeLibrary _mediaTypeLibrary;
std::mutex _libraryLock;
class SupportedFormat {
public:
SupportedFormat(const MediaTypeID& mediaTypeID, std::unique_ptr<Serializer::Factory>& factory) :
mediaTypeID(mediaTypeID),
factory(std::move(factory)) {
}
MediaTypeID mediaTypeID;
std::unique_ptr<Serializer::Factory> factory;
};
std::vector<SupportedFormat> _supportedFormats;
};
};
#endif // hifi_HFMFormatRegistry_h

View file

@ -15,10 +15,27 @@
#include <shared/HifiTypes.h>
#include "HFM.h"
#include <shared/MediaTypeLibrary.h>
namespace hfm {
class Serializer {
public:
class Factory {
public:
virtual std::shared_ptr<Serializer> get() = 0;
};
template<typename T>
class SimpleFactory : public Factory {
std::shared_ptr<Serializer> get() override {
return std::make_shared<T>();
}
};
virtual MediaType getMediaType() const = 0;
virtual std::unique_ptr<Factory> getFactory() const = 0;
virtual Model::Pointer read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url = hifi::URL()) = 0;
};

View file

@ -0,0 +1,20 @@
//
// ModelFormatRegistry.cpp
// libraries/model-networking/src/model-networking
//
// Created by Sabrina Shanman on 2018/11/30.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ModelFormatRegistry.h"
void ModelFormatRegistry::addFormat(const hfm::Serializer& serializer) {
_hfmFormatRegistry.registerMediaType(serializer.getMediaType(), serializer.getFactory());
}
std::shared_ptr<hfm::Serializer> ModelFormatRegistry::getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const {
return _hfmFormatRegistry.getSerializerForMediaType(data, url, webMediaType);
}

View file

@ -0,0 +1,28 @@
//
// ModelFormatRegistry.h
// libraries/hfm/src/hfm
//
// Created by Sabrina Shanman on 2018/11/30.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ModelFormatRegistry_h
#define hifi_ModelFormatRegistry_h
#include "HFMFormatRegistry.h"
#include <DependencyManager.h>
class ModelFormatRegistry : public Dependency {
public:
void addFormat(const hfm::Serializer& serializer);
std::shared_ptr<hfm::Serializer> getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const;
protected:
hfm::FormatRegistry _hfmFormatRegistry;
};
#endif // hifi_ModelFormatRegistry_h

View file

@ -12,9 +12,6 @@
#include "ModelCache.h"
#include <Finally.h>
#include <FSTReader.h>
#include "FBXSerializer.h"
#include "OBJSerializer.h"
#include "GLTFSerializer.h"
#include <gpu/Batch.h>
#include <gpu/Stream.h>
@ -26,6 +23,10 @@
#include "ModelNetworkingLogging.h"
#include <Trace.h>
#include <StatTracker.h>
#include <hfm/ModelFormatRegistry.h>
#include <FBXSerializer.h>
#include <OBJSerializer.h>
#include <GLTFSerializer.h>
Q_LOGGING_CATEGORY(trace_resource_parse_geometry, "trace.resource.parse.geometry")
@ -144,9 +145,9 @@ void GeometryMappingResource::onGeometryMappingLoaded(bool success) {
class GeometryReader : public QRunnable {
public:
GeometryReader(QWeakPointer<Resource>& resource, const QUrl& url, const QVariantHash& mapping,
const QByteArray& data, bool combineParts) :
_resource(resource), _url(url), _mapping(mapping), _data(data), _combineParts(combineParts) {
GeometryReader(const ModelLoader& modelLoader, QWeakPointer<Resource>& resource, const QUrl& url, const QVariantHash& mapping,
const QByteArray& data, bool combineParts, const QString& webMediaType) :
_modelLoader(modelLoader), _resource(resource), _url(url), _mapping(mapping), _data(data), _combineParts(combineParts), _webMediaType(webMediaType) {
DependencyManager::get<StatTracker>()->incrementStat("PendingProcessing");
}
@ -154,11 +155,13 @@ public:
virtual void run() override;
private:
ModelLoader _modelLoader;
QWeakPointer<Resource> _resource;
QUrl _url;
QVariantHash _mapping;
QByteArray _data;
bool _combineParts;
QString _webMediaType;
};
void GeometryReader::run() {
@ -183,62 +186,53 @@ void GeometryReader::run() {
throw QString("reply is NULL");
}
QString urlname = _url.path().toLower();
if (!urlname.isEmpty() && !_url.path().isEmpty() &&
// Ensure the resource has not been deleted
auto resource = _resource.toStrongRef();
if (!resource) {
qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref";
return;
}
(_url.path().toLower().endsWith(".fbx") ||
_url.path().toLower().endsWith(".obj") ||
_url.path().toLower().endsWith(".obj.gz") ||
_url.path().toLower().endsWith(".gltf"))) {
HFMModel::Pointer hfmModel;
QVariantHash serializerMapping = _mapping;
serializerMapping["combineParts"] = _combineParts;
if (_url.path().toLower().endsWith(".fbx")) {
hfmModel = FBXSerializer().read(_data, serializerMapping, _url);
if (hfmModel->meshes.size() == 0 && hfmModel->joints.size() == 0) {
throw QString("empty geometry, possibly due to an unsupported FBX version");
}
} else if (_url.path().toLower().endsWith(".obj")) {
hfmModel = OBJSerializer().read(_data, serializerMapping, _url);
} else if (_url.path().toLower().endsWith(".obj.gz")) {
QByteArray uncompressedData;
if (gunzip(_data, uncompressedData)){
hfmModel = OBJSerializer().read(uncompressedData, serializerMapping, _url);
} else {
throw QString("failed to decompress .obj.gz");
}
} else if (_url.path().toLower().endsWith(".gltf")) {
hfmModel = GLTFSerializer().read(_data, serializerMapping, _url);
if (hfmModel->meshes.size() == 0 && hfmModel->joints.size() == 0) {
throw QString("empty geometry, possibly due to an unsupported GLTF version");
}
} else {
throw QString("unsupported format");
}
// Add scripts to hfmModel
if (!_mapping.value(SCRIPT_FIELD).isNull()) {
QVariantList scripts = _mapping.values(SCRIPT_FIELD);
for (auto &script : scripts) {
hfmModel->scripts.push_back(script.toString());
}
}
// Ensure the resource has not been deleted
auto resource = _resource.toStrongRef();
if (!resource) {
qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref";
} else {
QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition",
Q_ARG(HFMModel::Pointer, hfmModel));
}
} else {
if (_url.path().isEmpty()) {
throw QString("url is invalid");
}
HFMModel::Pointer hfmModel;
QVariantHash serializerMapping = _mapping;
serializerMapping["combineParts"] = _combineParts;
if (_url.path().toLower().endsWith(".gz")) {
QByteArray uncompressedData;
if (!gunzip(_data, uncompressedData)) {
throw QString("failed to decompress .gz model");
}
// Strip the compression extension from the path, so the loader can infer the file type from what remains.
// This is okay because we don't expect the serializer to be able to read the contents of a compressed model file.
auto strippedUrl = _url;
strippedUrl.setPath(_url.path().left(_url.path().size() - 3));
hfmModel = _modelLoader.load(uncompressedData, serializerMapping, strippedUrl, "");
} else {
hfmModel = _modelLoader.load(_data, serializerMapping, _url, _webMediaType.toStdString());
}
if (!hfmModel) {
throw QString("unsupported format");
}
if (hfmModel->meshes.empty() || hfmModel->joints.empty()) {
throw QString("empty geometry, possibly due to an unsupported model version");
}
// Add scripts to hfmModel
if (!_mapping.value(SCRIPT_FIELD).isNull()) {
QVariantList scripts = _mapping.values(SCRIPT_FIELD);
for (auto &script : scripts) {
hfmModel->scripts.push_back(script.toString());
}
}
QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition",
Q_ARG(HFMModel::Pointer, hfmModel));
} catch (const std::exception&) {
auto resource = _resource.toStrongRef();
if (resource) {
@ -258,8 +252,8 @@ void GeometryReader::run() {
class GeometryDefinitionResource : public GeometryResource {
Q_OBJECT
public:
GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl, bool combineParts) :
GeometryResource(url, resolveTextureBaseUrl(url, textureBaseUrl)), _mapping(mapping), _combineParts(combineParts) {}
GeometryDefinitionResource(const ModelLoader& modelLoader, const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl, bool combineParts) :
GeometryResource(url, resolveTextureBaseUrl(url, textureBaseUrl)), _modelLoader(modelLoader), _mapping(mapping), _combineParts(combineParts) {}
QString getType() const override { return "GeometryDefinition"; }
@ -269,6 +263,7 @@ protected:
Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel);
private:
ModelLoader _modelLoader;
QVariantHash _mapping;
bool _combineParts;
};
@ -278,7 +273,7 @@ void GeometryDefinitionResource::downloadFinished(const QByteArray& data) {
_url = _effectiveBaseURL;
_textureBaseUrl = _effectiveBaseURL;
}
QThreadPool::globalInstance()->start(new GeometryReader(_self, _effectiveBaseURL, _mapping, data, _combineParts));
QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, _self, _effectiveBaseURL, _mapping, data, _combineParts, _request->getWebMediaType()));
}
void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel) {
@ -316,6 +311,11 @@ ModelCache::ModelCache() {
const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE;
setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE);
setObjectName("ModelCache");
auto modelFormatRegistry = DependencyManager::get<ModelFormatRegistry>();
modelFormatRegistry->addFormat(FBXSerializer());
modelFormatRegistry->addFormat(OBJSerializer());
modelFormatRegistry->addFormat(GLTFSerializer());
}
QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
@ -328,7 +328,7 @@ QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QShar
auto mapping = geometryExtra ? geometryExtra->mapping : QVariantHash();
auto textureBaseUrl = geometryExtra ? geometryExtra->textureBaseUrl : QUrl();
bool combineParts = geometryExtra ? geometryExtra->combineParts : true;
resource = new GeometryDefinitionResource(url, mapping, textureBaseUrl, combineParts);
resource = new GeometryDefinitionResource(_modelLoader, url, mapping, textureBaseUrl, combineParts);
}
return QSharedPointer<Resource>(resource, &Resource::deleter);

View file

@ -20,6 +20,7 @@
#include "FBXSerializer.h"
#include "TextureCache.h"
#include "ModelLoader.h"
// Alias instead of derive to avoid copying
@ -158,6 +159,7 @@ protected:
private:
ModelCache();
virtual ~ModelCache() = default;
ModelLoader _modelLoader;
};
class NetworkMaterial : public graphics::Material {

View file

@ -0,0 +1,24 @@
//
// ModelLoader.cpp
// libraries/model-networking/src/model-networking
//
// Created by Sabrina Shanman on 2018/11/14.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ModelLoader.h"
#include <DependencyManager.h>
#include <hfm/ModelFormatRegistry.h>
hfm::Model::Pointer ModelLoader::load(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url, const std::string& webMediaType) const {
auto serializer = DependencyManager::get<ModelFormatRegistry>()->getSerializerForMediaType(data, url, webMediaType);
if (!serializer) {
return hfm::Model::Pointer();
}
return serializer->read(data, mapping, url);
}

View file

@ -0,0 +1,26 @@
//
// ModelLoader.h
// libraries/model-networking/src/model-networking
//
// Created by Sabrina Shanman on 2018/11/13.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_ModelLoader_h
#define hifi_ModelLoader_h
#include <shared/HifiTypes.h>
#include <hfm/HFM.h>
class ModelLoader {
public:
// Given the currently stored list of supported file formats, determine how to load a model from the given parameters.
// If successful, return an owned reference to the newly loaded model.
// If failed, return an empty reference.
hfm::Model::Pointer load(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url, const std::string& webMediaType) const;
};
#endif // hifi_ModelLoader_h

View file

@ -94,7 +94,7 @@ void HTTPResourceRequest::onRequestFinished() {
// Content-Range: <unit> <range-start>-<range-end>/*
// Content-Range: <unit> */<size>
//
auto parseContentRangeHeader = [](QString contentRangeHeader) -> std::pair<bool, uint64_t> {
static auto parseContentRangeHeader = [](QString contentRangeHeader) -> std::pair<bool, uint64_t> {
auto unitRangeParts = contentRangeHeader.split(' ');
if (unitRangeParts.size() != 2) {
return { false, 0 };
@ -115,6 +115,15 @@ void HTTPResourceRequest::onRequestFinished() {
}
};
static auto parseMediaType = [](QString contentTypeHeader) -> std::pair<bool, QString> {
auto contentTypeParts = contentTypeHeader.split(';');
if (contentTypeParts.size() < 1) {
return { false, "" };
}
return { true, contentTypeParts[0] };
};
switch(_reply->error()) {
case QNetworkReply::NoError:
_data = _reply->readAll();
@ -141,6 +150,16 @@ void HTTPResourceRequest::onRequestFinished() {
}
}
{
auto contentTypeHeader = _reply->rawHeader("Content-Type");
bool success;
QString mediaType;
std::tie(success, mediaType) = parseMediaType(contentTypeHeader);
if (success) {
_webMediaType = mediaType;
}
}
recordBytesDownloadedInStats(STAT_HTTP_RESOURCE_TOTAL_BYTES, _data.size());
break;

View file

@ -84,6 +84,7 @@ public:
bool loadedFromCache() const { return _loadedFromCache; }
bool getRangeRequestSuccessful() const { return _rangeRequestSuccessful; }
bool getTotalSizeOfResource() const { return _totalSizeOfResource; }
QString getWebMediaType() const { return _webMediaType; }
void setFailOnRedirect(bool failOnRedirect) { _failOnRedirect = failOnRedirect; }
void setCacheEnabled(bool value) { _cacheEnabled = value; }
@ -111,6 +112,7 @@ protected:
ByteRange _byteRange;
bool _rangeRequestSuccessful { false };
uint64_t _totalSizeOfResource { 0 };
QString _webMediaType;
int64_t _lastRecordedBytesDownloaded { 0 };
bool _isObservable;
qint64 _callerId;

View file

@ -29,7 +29,7 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
void main(void) {
vec4 texel = texture(originalTexture, _texCoord0);
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
texel.rgb *= _color.rgb;
packDeferredFragment(

View file

@ -41,9 +41,9 @@ void main(void) {
applyFade(fadeParams, _positionWS.xyz, fadeEmissive);
vec4 texel = texture(originalTexture, _texCoord0);
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
texel.rgb *= _color.rgb;
texel.a = abs(_color.a);
texel.a *= abs(_color.a);
const float ALPHA_THRESHOLD = 0.999;
if (texel.a < ALPHA_THRESHOLD) {

View file

@ -29,9 +29,9 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
void main(void) {
vec4 texel = texture(originalTexture, _texCoord0);
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
texel.rgb *= _color.rgb;
texel.a = abs(_color.a);
texel.a *= abs(_color.a);
const float ALPHA_THRESHOLD = 0.999;
if (texel.a < ALPHA_THRESHOLD) {

View file

@ -41,9 +41,9 @@ void main(void) {
applyFade(fadeParams, _positionWS.xyz, fadeEmissive);
vec4 texel = texture(originalTexture, _texCoord0);
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
texel.rgb *= _color.rgb;
texel.a = abs(_color.a);
texel.a *= abs(_color.a);
const float ALPHA_THRESHOLD = 0.999;
if (texel.a < ALPHA_THRESHOLD) {

View file

@ -29,7 +29,7 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
void main(void) {
vec4 texel = texture(originalTexture, _texCoord0);
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
texel.rgb *= _color.rgb;
texel.a *= abs(_color.a);

View file

@ -47,7 +47,7 @@ void main(void) {
applyFade(fadeParams, _positionWS.xyz, fadeEmissive);
vec4 texel = texture(originalTexture, _texCoord0);
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
texel.rgb *= _color.rgb;
texel.a *= abs(_color.a);

View file

@ -28,7 +28,7 @@ layout(location=0) out vec4 _fragColor0;
void main(void) {
vec4 texel = texture(originalTexture, _texCoord0);
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
texel.rgb *= _color.rgb;
texel.a *= abs(_color.a);

View file

@ -40,7 +40,7 @@ void main(void) {
applyFade(fadeParams, _positionWS.xyz, fadeEmissive);
vec4 texel = texture(originalTexture, _texCoord0);
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
texel.rgb *= _color.rgb;
texel.a *= abs(_color.a);

View file

@ -31,6 +31,8 @@ static const QUrl DEFAULT_SCRIPTS_LOCATION { "file:///~//defaultScripts.js" };
// Using a QVariantList so this is human-readable in the settings file
static Setting::Handle<QVariantList> runningScriptsHandle(SETTINGS_KEY, { QVariant(DEFAULT_SCRIPTS_LOCATION) });
const int RELOAD_ALL_SCRIPTS_TIMEOUT = 1000;
ScriptsModel& getScriptsModel() {
static ScriptsModel scriptsModel;
@ -386,15 +388,10 @@ void ScriptEngines::stopAllScripts(bool restart) {
// queue user scripts if restarting
if (restart && scriptEngine->isUserLoaded()) {
_isReloading = true;
bool lastScript = (it == _scriptEnginesHash.constEnd() - 1);
ScriptEngine::Type type = scriptEngine->getType();
connect(scriptEngine.data(), &ScriptEngine::finished, this, [this, type, lastScript] (QString scriptName) {
connect(scriptEngine.data(), &ScriptEngine::finished, this, [this, type] (QString scriptName) {
reloadScript(scriptName, true)->setType(type);
if (lastScript) {
_isReloading = false;
}
});
}
@ -404,6 +401,9 @@ void ScriptEngines::stopAllScripts(bool restart) {
if (restart) {
qCDebug(scriptengine) << "stopAllScripts -- emitting scriptsReloading";
QTimer::singleShot(RELOAD_ALL_SCRIPTS_TIMEOUT, this, [&] {
_isReloading = false;
});
emit scriptsReloading();
}
}

View file

@ -748,6 +748,18 @@ const Transform SpatiallyNestable::getTransform() const {
return result;
}
void SpatiallyNestable::breakParentingLoop() const {
// someone created a loop. break it...
qCDebug(shared) << "Parenting loop detected: " << getID();
SpatiallyNestablePointer _this = getThisPointer();
_this->setParentID(QUuid());
bool setPositionSuccess;
AACube aaCube = getQueryAACube(setPositionSuccess);
if (setPositionSuccess) {
_this->setWorldPosition(aaCube.calcCenter());
}
}
const Transform SpatiallyNestable::getTransform(int jointIndex, bool& success, int depth) const {
// this returns the world-space transform for this object. It finds its parent's transform (which may
// cause this object's parent to query its parent, etc) and multiplies this object's local transform onto it.
@ -755,15 +767,7 @@ const Transform SpatiallyNestable::getTransform(int jointIndex, bool& success, i
if (depth > MAX_PARENTING_CHAIN_SIZE) {
success = false;
// someone created a loop. break it...
qCDebug(shared) << "Parenting loop detected: " << getID();
SpatiallyNestablePointer _this = getThisPointer();
_this->setParentID(QUuid());
bool setPositionSuccess;
AACube aaCube = getQueryAACube(setPositionSuccess);
if (setPositionSuccess) {
_this->setWorldPosition(aaCube.calcCenter());
}
breakParentingLoop();
return jointInWorldFrame;
}
@ -1208,8 +1212,12 @@ AACube SpatiallyNestable::getQueryAACube() const {
return result;
}
bool SpatiallyNestable::hasAncestorOfType(NestableType nestableType) const {
bool success;
bool SpatiallyNestable::hasAncestorOfType(NestableType nestableType, int depth) const {
if (depth > MAX_PARENTING_CHAIN_SIZE) {
breakParentingLoop();
return false;
}
if (nestableType == NestableType::Avatar) {
QUuid parentID = getParentID();
if (parentID == AVATAR_SELF_ID) {
@ -1217,6 +1225,7 @@ bool SpatiallyNestable::hasAncestorOfType(NestableType nestableType) const {
}
}
bool success;
SpatiallyNestablePointer parent = getParentPointer(success);
if (!success || !parent) {
return false;
@ -1226,11 +1235,14 @@ bool SpatiallyNestable::hasAncestorOfType(NestableType nestableType) const {
return true;
}
return parent->hasAncestorOfType(nestableType);
return parent->hasAncestorOfType(nestableType, depth + 1);
}
const QUuid SpatiallyNestable::findAncestorOfType(NestableType nestableType) const {
bool success;
const QUuid SpatiallyNestable::findAncestorOfType(NestableType nestableType, int depth) const {
if (depth > MAX_PARENTING_CHAIN_SIZE) {
breakParentingLoop();
return QUuid();
}
if (nestableType == NestableType::Avatar) {
QUuid parentID = getParentID();
@ -1239,6 +1251,7 @@ const QUuid SpatiallyNestable::findAncestorOfType(NestableType nestableType) con
}
}
bool success;
SpatiallyNestablePointer parent = getParentPointer(success);
if (!success || !parent) {
return QUuid();
@ -1248,7 +1261,7 @@ const QUuid SpatiallyNestable::findAncestorOfType(NestableType nestableType) con
return parent->getID();
}
return parent->findAncestorOfType(nestableType);
return parent->findAncestorOfType(nestableType, depth + 1);
}
void SpatiallyNestable::getLocalTransformAndVelocities(
@ -1336,7 +1349,12 @@ void SpatiallyNestable::dump(const QString& prefix) const {
}
}
bool SpatiallyNestable::isParentPathComplete() const {
bool SpatiallyNestable::isParentPathComplete(int depth) const {
if (depth > MAX_PARENTING_CHAIN_SIZE) {
breakParentingLoop();
return false;
}
static const QUuid IDENTITY;
QUuid parentID = getParentID();
if (parentID.isNull() || parentID == IDENTITY) {
@ -1349,5 +1367,5 @@ bool SpatiallyNestable::isParentPathComplete() const {
return false;
}
return parent->isParentPathComplete();
return parent->isParentPathComplete(depth + 1);
}

View file

@ -75,7 +75,7 @@ public:
static QString nestableTypeToString(NestableType nestableType);
virtual bool isParentPathComplete() const;
virtual bool isParentPathComplete(int depth = 0) const;
// world frame
@ -163,6 +163,8 @@ public:
virtual glm::vec3 getAbsoluteJointScaleInObjectFrame(int index) const { return glm::vec3(1.0f); }
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const { return glm::quat(); }
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const { return glm::vec3(); }
virtual int getJointParent(int index) const { return -1; }
virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) { return false; }
virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) {return false; }
@ -187,8 +189,8 @@ public:
bool isParentIDValid() const { bool success = false; getParentPointer(success); return success; }
virtual SpatialParentTree* getParentTree() const { return nullptr; }
bool hasAncestorOfType(NestableType nestableType) const;
const QUuid findAncestorOfType(NestableType nestableType) const;
bool hasAncestorOfType(NestableType nestableType, int depth = 0) const;
const QUuid findAncestorOfType(NestableType nestableType, int depth = 0) const;
SpatiallyNestablePointer getParentPointer(bool& success) const;
static SpatiallyNestablePointer findByID(QUuid id, bool& success);
@ -246,6 +248,8 @@ private:
mutable bool _parentKnowsMe { false };
bool _isDead { false };
bool _queryAACubeIsPuffed { false };
void breakParentingLoop() const;
};

View file

@ -0,0 +1,85 @@
//
// MediaTypeLibrary.cpp
// libraries/shared/src/shared
//
// Created by Sabrina Shanman on 2018/11/29.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "MediaTypeLibrary.h"
MediaType MediaType::NONE = MediaType("");
MediaTypeLibrary::ID MediaTypeLibrary::registerMediaType(const MediaType& mediaType) {
ID id = nextID++;
_mediaTypes.emplace_back(id, mediaType);
return id;
}
void MediaTypeLibrary::unregisterMediaType(const MediaTypeLibrary::ID& id) {
for (auto it = _mediaTypes.begin(); it != _mediaTypes.end(); it++) {
if ((*it).id == id) {
_mediaTypes.erase(it);
break;
}
}
}
MediaType MediaTypeLibrary::getMediaType(const MediaTypeLibrary::ID& id) const {
for (auto& supportedFormat : _mediaTypes) {
if (supportedFormat.id == id) {
return supportedFormat.mediaType;
}
}
return MediaType::NONE;
}
MediaTypeLibrary::ID MediaTypeLibrary::findMediaTypeForData(const hifi::ByteArray& data) const {
// Check file contents
for (auto& mediaType : _mediaTypes) {
for (auto& fileSignature : mediaType.mediaType.fileSignatures) {
auto testBytes = data.mid(fileSignature.byteOffset, (int)fileSignature.bytes.size()).toStdString();
if (testBytes == fileSignature.bytes) {
return mediaType.id;
}
}
}
return INVALID_ID;
}
MediaTypeLibrary::ID MediaTypeLibrary::findMediaTypeForURL(const hifi::URL& url) const {
// Check file extension
std::string urlString = url.path().toStdString();
std::size_t extensionSeparator = urlString.rfind('.');
if (extensionSeparator != std::string::npos) {
std::string detectedExtension = urlString.substr(extensionSeparator + 1);
for (auto& supportedFormat : _mediaTypes) {
for (auto& extension : supportedFormat.mediaType.extensions) {
if (extension == detectedExtension) {
return supportedFormat.id;
}
}
}
}
return INVALID_ID;
}
MediaTypeLibrary::ID MediaTypeLibrary::findMediaTypeForWebID(const std::string& webMediaType) const {
// Check web media type
if (webMediaType != "") {
for (auto& supportedFormat : _mediaTypes) {
for (auto& candidateWebMediaType : supportedFormat.mediaType.webMediaTypes) {
if (candidateWebMediaType == webMediaType) {
return supportedFormat.id;
}
}
}
}
return INVALID_ID;
}

View file

@ -0,0 +1,90 @@
//
// MediaTypeLibrary.h
// libraries/shared/src/shared
//
// Created by Sabrina Shanman on 2018/11/28.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_MediaTypeLibrary_h
#define hifi_MediaTypeLibrary_h
#include <vector>
#include <string>
#include <functional>
#include <mutex>
#include "HifiTypes.h"
// A short sequence of bytes, typically at the beginning of the file, which identifies the file format
class FileSignature {
public:
FileSignature(const std::string& bytes, int byteOffset) :
bytes(bytes),
byteOffset(byteOffset) {
}
FileSignature(const FileSignature& fileSignature) :
bytes(fileSignature.bytes),
byteOffset(fileSignature.byteOffset) {
}
std::string bytes;
int byteOffset;
};
// A named file extension with a list of known ways to positively identify the file type
class MediaType {
public:
MediaType(const std::string& name) :
name(name) {
}
MediaType() {};
MediaType(const MediaType& mediaType) :
name(mediaType.name),
extensions(mediaType.extensions),
webMediaTypes(mediaType.webMediaTypes),
fileSignatures(mediaType.fileSignatures) {
}
static MediaType NONE;
std::string name;
std::vector<std::string> extensions;
std::vector<std::string> webMediaTypes;
std::vector<FileSignature> fileSignatures;
};
class MediaTypeLibrary {
public:
using ID = unsigned int;
static const ID INVALID_ID { 0 };
ID registerMediaType(const MediaType& mediaType);
void unregisterMediaType(const ID& id);
MediaType getMediaType(const ID& id) const;
ID findMediaTypeForData(const hifi::ByteArray& data) const;
ID findMediaTypeForURL(const hifi::URL& url) const;
ID findMediaTypeForWebID(const std::string& webMediaType) const;
protected:
ID nextID { 1 };
class Entry {
public:
Entry(const ID& id, const MediaType& mediaType) :
id(id),
mediaType(mediaType) {
}
ID id;
MediaType mediaType;
};
std::vector<Entry> _mediaTypes;
};
#endif // hifi_MeidaTypeLibrary_h

View file

@ -69,12 +69,12 @@ function getMyAvatarSettings() {
}
}
function updateAvatarWearables(avatar, bookmarkAvatarName, callback) {
function updateAvatarWearables(avatar, callback) {
executeLater(function() {
var wearables = getMyAvatarWearables();
avatar[ENTRY_AVATAR_ENTITIES] = wearables;
sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables, 'avatarName' : bookmarkAvatarName})
sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables})
if(callback)
callback();
@ -188,7 +188,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
sendToQml(message)
break;
case 'selectAvatar':
Entities.addingWearable.disconnect(onAddingWearable);
Entities.deletingWearable.disconnect(onDeletingWearable);
AvatarBookmarks.loadBookmark(message.name);
Entities.addingWearable.connect(onAddingWearable);
Entities.deletingWearable.connect(onDeletingWearable);
break;
case 'deleteAvatar':
AvatarBookmarks.removeBookmark(message.name);
@ -223,7 +227,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
// revert changes using snapshot of wearables
if(currentAvatarWearablesBackup !== null) {
AvatarBookmarks.updateAvatarEntities(currentAvatarWearablesBackup);
updateAvatarWearables(currentAvatar, message.avatarName);
updateAvatarWearables(currentAvatar);
}
} else {
sendToQml({'method' : 'updateAvatarInBookmarks'});
@ -256,8 +260,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
parentJointIndex: hipsIndex
};
Entities.addingWearable.disconnect(onAddingWearable);
var entityID = Entities.addEntity(properties, true);
updateAvatarWearables(currentAvatar, message.avatarName, function() {
Entities.addingWearable.connect(onAddingWearable);
updateAvatarWearables(currentAvatar, function() {
onSelectedEntity(entityID);
});
break;
@ -265,8 +272,12 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
ensureWearableSelected(message.entityID);
break;
case 'deleteWearable':
Entities.deletingWearable.disconnect(onDeletingWearable);
Entities.deleteEntity(message.entityID);
updateAvatarWearables(currentAvatar, message.avatarName);
Entities.deletingWearable.connect(onDeletingWearable);
updateAvatarWearables(currentAvatar);
break;
case 'changeDisplayName':
if (MyAvatar.displayName !== message.displayName) {
@ -380,6 +391,18 @@ function onSelectedEntity(entityID, pointerEvent) {
}
}
function onAddingWearable(entityID) {
updateAvatarWearables(currentAvatar, function() {
sendToQml({'method' : 'updateAvatarInBookmarks'});
});
}
function onDeletingWearable(entityID) {
updateAvatarWearables(currentAvatar, function() {
sendToQml({'method' : 'updateAvatarInBookmarks'});
});
}
function handleWearableMessages(channel, message, sender) {
if (channel !== 'Hifi-Object-Manipulation') {
return;
@ -485,6 +508,8 @@ function off() {
AvatarBookmarks.bookmarkDeleted.disconnect(onBookmarkDeleted);
AvatarBookmarks.bookmarkAdded.disconnect(onBookmarkAdded);
Entities.addingWearable.disconnect(onAddingWearable);
Entities.deletingWearable.disconnect(onDeletingWearable);
MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged);
MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged);
MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged);
@ -495,16 +520,23 @@ function off() {
}
function on() {
AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded);
AvatarBookmarks.bookmarkDeleted.connect(onBookmarkDeleted);
AvatarBookmarks.bookmarkAdded.connect(onBookmarkAdded);
MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged);
MyAvatar.dominantHandChanged.connect(onDominantHandChanged);
MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged);
MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl);
MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged);
MyAvatar.targetScaleChanged.connect(onTargetScaleChanged);
if (!isWired) { // It is not ok to connect these twice, hence guard.
isWired = true;
AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded);
AvatarBookmarks.bookmarkDeleted.connect(onBookmarkDeleted);
AvatarBookmarks.bookmarkAdded.connect(onBookmarkAdded);
Entities.addingWearable.connect(onAddingWearable);
Entities.deletingWearable.connect(onDeletingWearable);
MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged);
MyAvatar.dominantHandChanged.connect(onDominantHandChanged);
MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged);
MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl);
MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged);
MyAvatar.targetScaleChanged.connect(onTargetScaleChanged);
}
}
function onTabletButtonClicked() {
@ -514,7 +546,6 @@ function onTabletButtonClicked() {
} else {
ContextOverlay.enabled = false;
tablet.loadQMLSource(AVATARAPP_QML_SOURCE);
isWired = true;
}
}
var hasEventBridge = false;

View file

@ -424,9 +424,19 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
}
};
this.leftBlacklistTabletIDs = [];
this.rightBlacklistTabletIDs = [];
this.setLeftBlacklist = function () {
Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist.concat(_this.leftBlacklistTabletIDs));
};
this.setRightBlacklist = function () {
Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist.concat(_this.rightBlacklistTabletIDs));
};
this.setBlacklist = function() {
Pointers.setIgnoreItems(_this.leftPointer, this.blacklist);
Pointers.setIgnoreItems(_this.rightPointer, this.blacklist);
_this.setLeftBlacklist();
_this.setRightBlacklist();
};
var MAPPING_NAME = "com.highfidelity.controllerDispatcher";
@ -490,7 +500,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE,
enabled: true
});
this.handleHandMessage = function(channel, data, sender) {
this.handleMessage = function (channel, data, sender) {
var message;
if (sender === MyAvatar.sessionUUID) {
try {
@ -511,6 +521,18 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
_this.setBlacklist();
}
}
if (action === "tablet") {
var tabletIDs = message.blacklist
? [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, HMD.homeButtonHighlightID] : [];
if (message.hand === LEFT_HAND) {
_this.leftBlacklistTabletIDs = tabletIDs;
_this.setLeftBlacklist();
} else {
_this.rightBlacklistTabletIDs = tabletIDs;
_this.setRightBlacklist();
}
}
}
} catch (e) {
print("WARNING: handControllerGrab.js -- error parsing message: " + data);
@ -550,7 +572,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
var controllerDispatcher = new ControllerDispatcher();
Messages.subscribe('Hifi-Hand-RayPick-Blacklist');
Messages.messageReceived.connect(controllerDispatcher.handleHandMessage);
Messages.messageReceived.connect(controllerDispatcher.handleMessage);
Script.scriptEnding.connect(controllerDispatcher.cleanup);
Script.setTimeout(controllerDispatcher.update, BASIC_TIMER_INTERVAL_MS);
}());

View file

@ -48,6 +48,7 @@ Script.include("/~/system/libraries/controllers.js");
function FarActionGrabEntity(hand) {
this.hand = hand;
this.grabbing = false;
this.grabbedThingID = null;
this.targetObject = null;
this.actionID = null; // action this script created...
@ -151,6 +152,7 @@ Script.include("/~/system/libraries/controllers.js");
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
this.previousRoomControllerPosition = roomControllerPosition;
this.grabbing = true;
};
this.continueDistanceHolding = function(controllerData) {
@ -246,6 +248,7 @@ Script.include("/~/system/libraries/controllers.js");
this.grabbedThingID = null;
this.targetObject = null;
this.potentialEntityWithContextOverlay = false;
this.grabbing = false;
};
this.updateRecommendedArea = function() {
@ -357,8 +360,7 @@ Script.include("/~/system/libraries/controllers.js");
};
this.run = function (controllerData) {
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE ||
this.notPointingAtEntity(controllerData) || this.targetIsNull()) {
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.targetIsNull()) {
this.endFarGrabAction();
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity",
this.highlightedEntity);
@ -375,10 +377,12 @@ Script.include("/~/system/libraries/controllers.js");
this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar",
this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity",
this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity",
this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity",
this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay",
this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"
this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity"
];
if (!this.grabbing) {
nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay");
nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight");
}
var nearGrabReadiness = [];
for (var i = 0; i < nearGrabNames.length; i++) {

View file

@ -47,6 +47,7 @@ Script.include("/~/system/libraries/controllers.js");
function FarParentGrabEntity(hand) {
this.hand = hand;
this.grabbing = false;
this.targetEntityID = null;
this.targetObject = null;
this.previouslyUnhooked = {};
@ -455,8 +456,7 @@ Script.include("/~/system/libraries/controllers.js");
};
this.run = function (controllerData) {
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE ||
this.notPointingAtEntity(controllerData) || this.targetIsNull()) {
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.targetIsNull()) {
this.endFarParentGrab(controllerData);
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity);
this.highlightedEntity = null;
@ -472,10 +472,12 @@ Script.include("/~/system/libraries/controllers.js");
this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar",
this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity",
this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity",
this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity",
this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay",
this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"
this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity"
];
if (!this.grabbing) {
nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay");
nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight");
}
var nearGrabReadiness = [];
for (var i = 0; i < nearGrabNames.length; i++) {
@ -485,11 +487,10 @@ Script.include("/~/system/libraries/controllers.js");
}
if (this.targetEntityID) {
// if we are doing a distance grab and the object or tablet gets close enough to the controller,
// if we are doing a distance grab and the object gets close enough to the controller,
// stop the far-grab so the near-grab or equip can take over.
for (var k = 0; k < nearGrabReadiness.length; k++) {
if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.targetEntityID ||
HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) {
if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.targetEntityID)) {
this.endFarParentGrab(controllerData);
return makeRunningValues(false, [], []);
}

View file

@ -37,7 +37,7 @@ Script.include("/~/system/libraries/controllers.js");
this.getTargetProps = function (controllerData) {
var targetEntity = controllerData.rayPicks[this.hand].objectID;
if (targetEntity) {
if (targetEntity && controllerData.rayPicks[this.hand].type === RayPick.INTERSECTED_ENTITY) {
var targetProperties = Entities.getEntityProperties(targetEntity, DISPATCHER_PROPERTIES);
if (entityWantsFarTrigger(targetProperties)) {
return targetProperties;

View file

@ -20,6 +20,7 @@ Script.include("/~/system/libraries/utils.js");
var MARGIN = 25;
function InEditMode(hand) {
this.hand = hand;
this.isEditing = false;
this.triggerClicked = false;
this.selectedTarget = null;
this.reticleMinX = MARGIN;
@ -62,25 +63,27 @@ Script.include("/~/system/libraries/utils.js");
return point2d;
};
this.ENTITY_TOOL_UPDATES_CHANNEL = "entityToolUpdates";
this.sendPickData = function(controllerData) {
if (controllerData.triggerClicks[this.hand]) {
var hand = this.hand === RIGHT_HAND ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
if (!this.triggerClicked) {
this.selectedTarget = controllerData.rayPicks[this.hand];
if (!this.selectedTarget.intersects) {
Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({
Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({
method: "clearSelection",
hand: hand
}));
} else {
if (this.selectedTarget.type === Picks.INTERSECTED_ENTITY) {
Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({
Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({
method: "selectEntity",
entityID: this.selectedTarget.objectID,
hand: hand
}));
} else if (this.selectedTarget.type === Picks.INTERSECTED_OVERLAY) {
Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({
Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({
method: "selectOverlay",
overlayID: this.selectedTarget.objectID,
hand: hand
@ -102,7 +105,7 @@ Script.include("/~/system/libraries/utils.js");
var desktopWindow = Window.isPointOnDesktopWindow(point2d);
var tablet = this.pointingAtTablet(rayPick.objectID);
var rightHand = this.hand === RIGHT_HAND;
Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({
Messages.sendLocalMessage(this.ENTITY_TOOL_UPDATES_CHANNEL, JSON.stringify({
method: "pointingAt",
desktopWindow: desktopWindow,
tablet: tablet,
@ -110,6 +113,10 @@ Script.include("/~/system/libraries/utils.js");
}));
};
this.runModule = function() {
return makeRunningValues(true, [], []);
};
this.exitModule = function() {
return makeRunningValues(false, [], []);
};
@ -120,13 +127,15 @@ Script.include("/~/system/libraries/utils.js");
this.triggerClicked = false;
}
Messages.sendLocalMessage('Hifi-unhighlight-all', '');
return makeRunningValues(true, [], []);
return this.runModule();
}
this.triggerClicked = false;
return makeRunningValues(false, [], []);
return this.exitModule();
};
this.run = function(controllerData) {
// Tablet stylus.
var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightTabletStylusInput" : "LeftTabletStylusInput");
if (tabletStylusInput) {
@ -136,6 +145,7 @@ Script.include("/~/system/libraries/utils.js");
}
}
// Tablet surface.
var webLaser = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput");
if (webLaser) {
@ -147,6 +157,7 @@ Script.include("/~/system/libraries/utils.js");
}
}
// HUD overlay.
if (!controllerData.triggerClicks[this.hand]) { // Don't grab if trigger pressed when laser starts intersecting.
var hudLaser = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightHudOverlayPointer" : "LeftHudOverlayPointer");
@ -168,6 +179,7 @@ Script.include("/~/system/libraries/utils.js");
}
}
// Teleport.
var teleport = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightTeleporter" : "LeftTeleporter");
if (teleport) {
var teleportReady = teleport.isReady(controllerData);
@ -176,8 +188,6 @@ Script.include("/~/system/libraries/utils.js");
}
}
var stopRunning = false;
if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0)) {
var stopRunning = false;
controllerData.nearbyOverlayIDs[this.hand].forEach(function(overlayID) {
@ -203,6 +213,37 @@ Script.include("/~/system/libraries/utils.js");
enableDispatcherModule("LeftHandInEditMode", leftHandInEditMode);
enableDispatcherModule("RightHandInEditMode", rightHandInEditMode);
var INEDIT_STATUS_CHANNEL = "Hifi-InEdit-Status";
var HAND_RAYPICK_BLACKLIST_CHANNEL = "Hifi-Hand-RayPick-Blacklist";
this.handleMessage = function (channel, data, sender) {
if (channel === INEDIT_STATUS_CHANNEL && sender === MyAvatar.sessionUUID) {
var message;
try {
message = JSON.parse(data);
} catch (e) {
return;
}
switch (message.method) {
case "editing":
if (message.hand === LEFT_HAND) {
leftHandInEditMode.isEditing = message.editing;
} else {
rightHandInEditMode.isEditing = message.editing;
}
Messages.sendLocalMessage(HAND_RAYPICK_BLACKLIST_CHANNEL, JSON.stringify({
action: "tablet",
hand: message.hand,
blacklist: message.editing
}));
break;
}
}
};
Messages.subscribe(INEDIT_STATUS_CHANNEL);
Messages.messageReceived.connect(this.handleMessage);
function cleanup() {
disableDispatcherModule("LeftHandInEditMode");
disableDispatcherModule("RightHandInEditMode");

View file

@ -18,7 +18,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
function InVREditMode(hand) {
this.hand = hand;
this.disableModules = false;
this.isAppActive = false;
this.isEditing = false;
this.running = false;
var NO_HAND_LASER = -1; // Invalid hand parameter so that standard laser is not displayed.
this.parameters = makeDispatcherModuleParameters(
@ -66,7 +67,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
};
this.isReady = function (controllerData) {
if (this.disableModules) {
if (this.isAppActive) {
return makeRunningValues(true, [], []);
}
return makeRunningValues(false, [], []);
@ -74,14 +75,13 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
this.run = function (controllerData) {
// Default behavior if disabling is not enabled.
if (!this.disableModules) {
if (!this.isAppActive) {
return this.exitModule();
}
// Tablet stylus.
var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND ?
"RightTabletStylusInput" :
"LeftTabletStylusInput");
var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightTabletStylusInput" : "LeftTabletStylusInput");
if (tabletStylusInput) {
var tabletReady = tabletStylusInput.isReady(controllerData);
if (tabletReady.active) {
@ -90,9 +90,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
}
// Tablet surface.
var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND ?
"RightWebSurfaceLaserInput" :
"LeftWebSurfaceLaserInput");
var overlayLaser = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightWebSurfaceLaserInput" : "LeftWebSurfaceLaserInput");
if (overlayLaser) {
var overlayLaserReady = overlayLaser.isReady(controllerData);
var target = controllerData.rayPicks[this.hand].objectID;
@ -114,8 +113,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
// HUD overlay.
if (!controllerData.triggerClicks[this.hand]) {
var hudLaser = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightHudOverlayPointer"
: "LeftHudOverlayPointer");
? "RightHudOverlayPointer" : "LeftHudOverlayPointer");
if (hudLaser) {
var hudLaserReady = hudLaser.isReady(controllerData);
if (hudLaserReady.active) {
@ -125,9 +123,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
}
// Teleport.
var teleporter = getEnabledModuleByName(this.hand === RIGHT_HAND ?
"RightTeleporter" :
"LeftTeleporter");
var teleporter = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightTeleporter" : "LeftTeleporter");
if (teleporter) {
var teleporterReady = teleporter.isReady(controllerData);
if (teleporterReady.active) {
@ -145,19 +142,39 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
enableDispatcherModule("LeftHandInVREditMode", leftHandInVREditMode);
enableDispatcherModule("RightHandInVREditMode", rightHandInVREditMode);
var INVREDIT_DISABLER_MESSAGE_CHANNEL = "Hifi-InVREdit-Disabler";
this.handleMessage = function (channel, message, sender) {
if (sender === MyAvatar.sessionUUID && channel === INVREDIT_DISABLER_MESSAGE_CHANNEL) {
if (message === "both") {
leftHandInVREditMode.disableModules = true;
rightHandInVREditMode.disableModules = true;
} else if (message === "none") {
leftHandInVREditMode.disableModules = false;
rightHandInVREditMode.disableModules = false;
var INVREDIT_STATUS_CHANNEL = "Hifi-InVREdit-Status";
var HAND_RAYPICK_BLACKLIST_CHANNEL = "Hifi-Hand-RayPick-Blacklist";
this.handleMessage = function (channel, data, sender) {
if (channel === INVREDIT_STATUS_CHANNEL && sender === MyAvatar.sessionUUID) {
var message;
try {
message = JSON.parse(data);
} catch (e) {
return;
}
switch (message.method) {
case "active":
leftHandInVREditMode.isAppActive = message.active;
rightHandInVREditMode.isAppActive = message.active;
break;
case "editing":
if (message.hand === LEFT_HAND) {
leftHandInVREditMode.isEditing = message.editing;
} else {
rightHandInVREditMode.isEditing = message.editing;
}
Messages.sendLocalMessage(HAND_RAYPICK_BLACKLIST_CHANNEL, JSON.stringify({
action: "tablet",
hand: message.hand,
blacklist: message.editing
}));
break;
}
}
};
Messages.subscribe(INVREDIT_DISABLER_MESSAGE_CHANNEL);
Messages.subscribe(INVREDIT_STATUS_CHANNEL);
Messages.messageReceived.connect(this.handleMessage);
this.cleanup = function () {

View file

@ -9,7 +9,7 @@
/* global Script, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex,
enableDispatcherModule, disableDispatcherModule, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION,
makeDispatcherModuleParameters, Overlays, makeRunningValues, Vec3, resizeTablet, getTabletWidthFromSettings,
NEAR_GRAB_RADIUS, HMD, Uuid
NEAR_GRAB_RADIUS, HMD, Uuid, getEnabledModuleByName
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -176,10 +176,23 @@ Script.include("/~/system/libraries/utils.js");
return null;
};
this.isEditing = function () {
var inEditModeModule = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightHandInEditMode" : "LeftHandInEditMode");
if (inEditModeModule && inEditModeModule.isEditing) {
return true;
}
var inVREditModeModule = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightHandInVREditMode" : "LeftHandInVREditMode");
if (inVREditModeModule && inVREditModeModule.isEditing) {
return true;
}
return false;
};
this.isReady = function (controllerData) {
if ((controllerData.triggerClicks[this.hand] === 0 &&
controllerData.secondaryValues[this.hand] === 0)) {
if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0)
|| this.isEditing()) {
this.robbed = false;
return makeRunningValues(false, [], []);
}
@ -202,7 +215,8 @@ Script.include("/~/system/libraries/utils.js");
};
this.run = function (controllerData) {
if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0) || !this.isGrabbedThingVisible()) {
if ((controllerData.triggerClicks[this.hand] === 0 && controllerData.secondaryValues[this.hand] === 0)
|| this.isEditing() || !this.isGrabbedThingVisible()) {
this.endNearParentingGrabOverlay();
this.robbed = false;
return makeRunningValues(false, [], []);

View file

@ -11,7 +11,7 @@
//
/* global LEFT_HAND, RIGHT_HAND, makeDispatcherModuleParameters, makeRunningValues, enableDispatcherModule,
* disableDispatcherModule */
* disableDispatcherModule, getEnabledModuleByName */
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
@ -66,12 +66,26 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
100
);
this.isEditing = function () {
var inEditModeModule = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightHandInEditMode" : "LeftHandInEditMode");
if (inEditModeModule && inEditModeModule.isEditing) {
return true;
}
var inVREditModeModule = getEnabledModuleByName(this.hand === RIGHT_HAND
? "RightHandInVREditMode" : "LeftHandInVREditMode");
if (inVREditModeModule && inVREditModeModule.isEditing) {
return true;
}
return false;
};
this.isNearTablet = function (controllerData) {
return HMD.tabletID && controllerData.nearbyOverlayIDs[this.hand].indexOf(HMD.tabletID) !== -1;
};
this.isReady = function (controllerData) {
if (this.isNearTablet(controllerData)) {
if (!this.isEditing() && this.isNearTablet(controllerData)) {
return makeRunningValues(true, [], []);
}
setTabletNearGrabbable(this.hand, false);
@ -79,7 +93,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
};
this.run = function (controllerData) {
if (!this.isNearTablet(controllerData)) {
if (this.isEditing() || !this.isNearTablet(controllerData)) {
setTabletNearGrabbable(this.hand, false);
return makeRunningValues(false, [], []);
}

View file

@ -251,19 +251,39 @@
$(this).closest('.col-xs-3').attr("class", 'col-xs-6');
var priceElement = $(this).find('.price');
priceElement.css({
"padding": "3px 5px",
"height": "40px",
"background": "linear-gradient(#00b4ef, #0093C5)",
"color": "#FFF",
"font-weight": "600",
"line-height": "34px"
});
var available = true;
if (priceElement.text() === 'invalidated' ||
priceElement.text() === 'sold out' ||
priceElement.text() === 'not for sale') {
available = false;
priceElement.css({
"padding": "3px 5px 10px 5px",
"height": "40px",
"background": "linear-gradient(#a2a2a2, #fefefe)",
"color": "#000",
"font-weight": "600",
"line-height": "34px"
});
} else {
priceElement.css({
"padding": "3px 5px",
"height": "40px",
"background": "linear-gradient(#00b4ef, #0093C5)",
"color": "#FFF",
"font-weight": "600",
"line-height": "34px"
});
}
if (parseInt(cost) > 0) {
priceElement.css({ "width": "auto" });
priceElement.html('<span class="hifi-glyph hifi-glyph-hfc" style="filter:invert(1);background-size:20px;' +
'width:20px;height:20px;position:relative;top:5px;"></span> ' + cost);
if (available) {
priceElement.html('<span class="hifi-glyph hifi-glyph-hfc" style="filter:invert(1);background-size:20px;' +
'width:20px;height:20px;position:relative;top:5px;"></span> ' + cost);
}
priceElement.css({ "min-width": priceElement.width() + 30 });
}
});

View file

@ -639,6 +639,8 @@ SelectionDisplay = (function() {
ROLL: 2
};
const INEDIT_STATUS_CHANNEL = "Hifi-InEdit-Status";
/**
* The current space mode, this could have been a forced space mode since we do not support multi selection while in
* local space mode.
@ -985,6 +987,7 @@ SelectionDisplay = (function() {
that.triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press');
that.triggeredHand = NO_HAND;
that.pressedHand = NO_HAND;
that.editingHand = NO_HAND;
that.triggered = function() {
return that.triggeredHand !== NO_HAND;
};
@ -1117,6 +1120,12 @@ SelectionDisplay = (function() {
activeTool = hitTool;
that.clearDebugPickPlane();
if (activeTool.onBegin) {
that.editingHand = that.triggeredHand;
Messages.sendLocalMessage(INEDIT_STATUS_CHANNEL, JSON.stringify({
method: "editing",
hand: that.editingHand === Controller.Standard.LeftHand ? LEFT_HAND : RIGHT_HAND,
editing: true
}));
activeTool.onBegin(event, pickRay, results);
} else {
print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool(" + activeTool.mode + ") missing onBegin");
@ -1265,6 +1274,12 @@ SelectionDisplay = (function() {
if (wantDebug) {
print(" Triggering ActiveTool(" + activeTool.mode + ")'s onEnd");
}
Messages.sendLocalMessage(INEDIT_STATUS_CHANNEL, JSON.stringify({
method: "editing",
hand: that.editingHand === Controller.Standard.LeftHand ? LEFT_HAND : RIGHT_HAND,
editing: false
}));
that.editingHand = NO_HAND;
activeTool.onEnd(event);
} else if (wantDebug) {
print(" ActiveTool(" + activeTool.mode + ")'s missing onEnd");

View file

@ -251,11 +251,8 @@
}
function getUIPositionAndRotation(hand) {
return {
position: MINI_POSITIONS[hand],
rotation: MINI_ROTATIONS[hand]
};
function getUIPosition(hand) {
return MINI_POSITIONS[hand];
}
function getMiniTabletID() {
@ -493,7 +490,7 @@
create();
return {
getUIPositionAndRotation: getUIPositionAndRotation,
getUIPosition: getUIPosition,
getMiniTabletID: getMiniTabletID,
getMiniTabletProperties: getMiniTabletProperties,
isLaserPointingAt: isLaserPointingAt,
@ -552,14 +549,23 @@
// Trigger values.
leftTriggerOn = 0,
rightTriggerOn = 0,
MAX_TRIGGER_ON_TIME = 100,
MAX_TRIGGER_ON_TIME = 400,
// Visibility.
MAX_HAND_CAMERA_ANGLE = 30,
MAX_CAMERA_HAND_ANGLE = 30,
MAX_MEDIAL_FINGER_CAMERA_ANGLE = 25, // From palm normal along palm towards fingers.
MAX_MEDIAL_WRIST_CAMERA_ANGLE = 65, // From palm normal along palm towards wrist.
MAX_LATERAL_THUMB_CAMERA_ANGLE = 25, // From palm normal across palm towards of thumb.
MAX_LATERAL_PINKY_CAMERA_ANGLE = 25, // From palm normal across palm towards pinky.
DEGREES_180 = 180,
MAX_HAND_CAMERA_ANGLE_COS = Math.cos(Math.PI * MAX_HAND_CAMERA_ANGLE / DEGREES_180),
MAX_CAMERA_HAND_ANGLE_COS = Math.cos(Math.PI * MAX_CAMERA_HAND_ANGLE / DEGREES_180),
DEGREES_TO_RADIANS = Math.PI / DEGREES_180,
MAX_MEDIAL_FINGER_CAMERA_ANGLE_RAD = DEGREES_TO_RADIANS * MAX_MEDIAL_FINGER_CAMERA_ANGLE,
MAX_MEDIAL_WRIST_CAMERA_ANGLE_RAD = DEGREES_TO_RADIANS * MAX_MEDIAL_WRIST_CAMERA_ANGLE,
MAX_LATERAL_THUMB_CAMERA_ANGLE_RAD = DEGREES_TO_RADIANS * MAX_LATERAL_THUMB_CAMERA_ANGLE,
MAX_LATERAL_PINKY_CAMERA_ANGLE_RAD = DEGREES_TO_RADIANS * MAX_LATERAL_PINKY_CAMERA_ANGLE,
MAX_CAMERA_MINI_ANGLE = 30,
MAX_CAMERA_MINI_ANGLE_COS = Math.cos(MAX_CAMERA_MINI_ANGLE * DEGREES_TO_RADIANS),
SHOWING_DELAY = 1000, // ms
lastInvisible = [0, 0],
HIDING_DELAY = 1000, // ms
lastVisible = [0, 0];
@ -598,11 +604,18 @@
jointIndex,
handPosition,
handOrientation,
uiPositionAndOrientation,
miniPosition,
miniOrientation,
miniToCameraDirection,
cameraToHand;
normalHandVector,
medialHandVector,
lateralHandVector,
normalDot,
medialDot,
lateralDot,
medialAngle,
lateralAngle,
cameraToMini,
now;
// Shouldn't show mini tablet if hand isn't being controlled.
pose = Controller.getPoseValue(hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand);
@ -647,27 +660,48 @@
Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex)));
handOrientation =
Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex));
uiPositionAndOrientation = ui.getUIPositionAndRotation(hand);
var uiPosition = ui.getUIPosition(hand);
miniPosition = Vec3.sum(handPosition, Vec3.multiply(MyAvatar.sensorToWorldScale,
Vec3.multiplyQbyV(handOrientation, uiPositionAndOrientation.position)));
miniOrientation = Quat.multiply(handOrientation, uiPositionAndOrientation.rotation);
Vec3.multiplyQbyV(handOrientation, uiPosition)));
miniToCameraDirection = Vec3.normalize(Vec3.subtract(Camera.position, miniPosition));
show = Vec3.dot(miniToCameraDirection, Quat.getForward(miniOrientation)) > MAX_HAND_CAMERA_ANGLE_COS;
show = show || (-Vec3.dot(miniToCameraDirection, Quat.getForward(handOrientation)) > MAX_HAND_CAMERA_ANGLE_COS);
cameraToHand = -Vec3.dot(miniToCameraDirection, Quat.getForward(Camera.orientation));
show = show && (cameraToHand > MAX_CAMERA_HAND_ANGLE_COS);
// Mini tablet aimed toward camera?
medialHandVector = Vec3.multiplyQbyV(handOrientation, Vec3.UNIT_Y);
lateralHandVector = Vec3.multiplyQbyV(handOrientation, hand === LEFT_HAND ? Vec3.UNIT_X : Vec3.UNIT_NEG_X);
normalHandVector = Vec3.multiplyQbyV(handOrientation, Vec3.UNIT_Z);
medialDot = Vec3.dot(medialHandVector, miniToCameraDirection);
lateralDot = Vec3.dot(lateralHandVector, miniToCameraDirection);
normalDot = Vec3.dot(normalHandVector, miniToCameraDirection);
medialAngle = Math.atan2(medialDot, normalDot);
lateralAngle = Math.atan2(lateralDot, normalDot);
show = -MAX_MEDIAL_WRIST_CAMERA_ANGLE_RAD <= medialAngle
&& medialAngle <= MAX_MEDIAL_FINGER_CAMERA_ANGLE_RAD
&& -MAX_LATERAL_THUMB_CAMERA_ANGLE_RAD <= lateralAngle
&& lateralAngle <= MAX_LATERAL_PINKY_CAMERA_ANGLE_RAD;
// Camera looking at mini tablet?
cameraToMini = -Vec3.dot(miniToCameraDirection, Quat.getForward(Camera.orientation));
show = show && (cameraToMini > MAX_CAMERA_MINI_ANGLE_COS);
// Delay showing for a while after it would otherwise be shown, unless it was showing on the other hand.
now = Date.now();
if (show) {
show = now - lastInvisible[hand] >= SHOWING_DELAY || now - lastVisible[otherHand(hand)] <= HIDING_DELAY;
} else {
lastInvisible[hand] = now;
}
// Persist showing for a while after it would otherwise be hidden.
if (show) {
lastVisible[hand] = Date.now();
lastVisible[hand] = now;
} else {
show = Date.now() - lastVisible[hand] <= HIDING_DELAY;
show = now - lastVisible[hand] <= HIDING_DELAY;
}
}
return {
show: show,
cameraToHand: cameraToHand
cameraToMini: cameraToMini
};
}
@ -690,7 +724,7 @@
showRight = shouldShowMini(RIGHT_HAND);
if (showLeft.show && showRight.show) {
// Both hands would be pointing at camera; show the one the camera is gazing at.
if (showLeft.cameraToHand > showRight.cameraToHand) {
if (showLeft.cameraToMini > showRight.cameraToMini) {
setState(MINI_SHOWING, LEFT_HAND);
} else {
setState(MINI_SHOWING, RIGHT_HAND);
@ -752,7 +786,7 @@
showLeft = shouldShowMini(LEFT_HAND);
showRight = shouldShowMini(RIGHT_HAND);
if (showLeft.show && showRight.show) {
if (showLeft.cameraToHand > showRight.cameraToHand) {
if (showLeft.cameraToMini > showRight.cameraToMini) {
if (miniHand !== LEFT_HAND) {
setState(MINI_HIDING);
}

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 51b3237a2992bd449a58ade16e52d0e0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,207 @@
// AvatarExporter.cs
//
// Created by David Back on 28 Nov 2018
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Collections.Generic;
public class AvatarExporter : MonoBehaviour {
public static Dictionary<string, string> UNITY_TO_HIFI_JOINT_NAME = new Dictionary<string, string> {
{"Chest", "Spine1"},
{"Head", "Head"},
{"Hips", "Hips"},
{"Left Index Distal", "LeftHandIndex3"},
{"Left Index Intermediate", "LeftHandIndex2"},
{"Left Index Proximal", "LeftHandIndex1"},
{"Left Little Distal", "LeftHandPinky3"},
{"Left Little Intermediate", "LeftHandPinky2"},
{"Left Little Proximal", "LeftHandPinky1"},
{"Left Middle Distal", "LeftHandMiddle3"},
{"Left Middle Intermediate", "LeftHandMiddle2"},
{"Left Middle Proximal", "LeftHandMiddle1"},
{"Left Ring Distal", "LeftHandRing3"},
{"Left Ring Intermediate", "LeftHandRing2"},
{"Left Ring Proximal", "LeftHandRing1"},
{"Left Thumb Distal", "LeftHandThumb3"},
{"Left Thumb Intermediate", "LeftHandThumb2"},
{"Left Thumb Proximal", "LeftHandThumb1"},
{"LeftEye", "LeftEye"},
{"LeftFoot", "LeftFoot"},
{"LeftHand", "LeftHand"},
{"LeftLowerArm", "LeftForeArm"},
{"LeftLowerLeg", "LeftLeg"},
{"LeftShoulder", "LeftShoulder"},
{"LeftToes", "LeftToeBase"},
{"LeftUpperArm", "LeftArm"},
{"LeftUpperLeg", "LeftUpLeg"},
{"Neck", "Neck"},
{"Right Index Distal", "RightHandIndex3"},
{"Right Index Intermediate", "RightHandIndex2"},
{"Right Index Proximal", "RightHandIndex1"},
{"Right Little Distal", "RightHandPinky3"},
{"Right Little Intermediate", "RightHandPinky2"},
{"Right Little Proximal", "RightHandPinky1"},
{"Right Middle Distal", "RightHandMiddle3"},
{"Right Middle Intermediate", "RightHandMiddle2"},
{"Right Middle Proximal", "RightHandMiddle1"},
{"Right Ring Distal", "RightHandRing3"},
{"Right Ring Intermediate", "RightHandRing2"},
{"Right Ring Proximal", "RightHandRing1"},
{"Right Thumb Distal", "RightHandThumb3"},
{"Right Thumb Intermediate", "RightHandThumb2"},
{"Right Thumb Proximal", "RightHandThumb1"},
{"RightEye", "RightEye"},
{"RightFoot", "RightFoot"},
{"RightHand", "RightHand"},
{"RightLowerArm", "RightForeArm"},
{"RightLowerLeg", "RightLeg"},
{"RightShoulder", "RightShoulder"},
{"RightToes", "RightToeBase"},
{"RightUpperArm", "RightArm"},
{"RightUpperLeg", "RightUpLeg"},
{"Spine", "Spine"},
{"UpperChest", "Spine2"},
};
public static string exportedPath = String.Empty;
[MenuItem("High Fidelity/Export New Avatar")]
public static void ExportNewAvatar() {
ExportSelectedAvatar(false);
}
[MenuItem("High Fidelity/Export New Avatar", true)]
private static bool ExportNewAvatarValidator() {
// only enable Export New Avatar option if we have an asset selected
string[] guids = Selection.assetGUIDs;
return guids.Length > 0;
}
[MenuItem("High Fidelity/Update Avatar")]
public static void UpdateAvatar() {
ExportSelectedAvatar(true);
}
[MenuItem("High Fidelity/Update Avatar", true)]
private static bool UpdateAvatarValidation() {
// only enable Update Avatar option if the selected avatar is the last one that was exported
if (exportedPath != String.Empty) {
string[] guids = Selection.assetGUIDs;
if (guids.Length > 0) {
string selectedAssetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
string selectedAsset = Path.GetFileNameWithoutExtension(selectedAssetPath);
string exportedAsset = Path.GetFileNameWithoutExtension(exportedPath);
return exportedAsset == selectedAsset;
}
}
return false;
}
public static void ExportSelectedAvatar(bool usePreviousPath) {
string assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]);
if (assetPath.LastIndexOf(".fbx") == -1) {
EditorUtility.DisplayDialog("Error", "Please select an fbx avatar to export", "Ok");
return;
}
ModelImporter importer = ModelImporter.GetAtPath(assetPath) as ModelImporter;
if (importer == null) {
EditorUtility.DisplayDialog("Error", "Please select a model", "Ok");
return;
}
if (importer.animationType != ModelImporterAnimationType.Human) {
EditorUtility.DisplayDialog("Error", "Please set model's Animation Type to Humanoid", "Ok");
return;
}
// store joint mappings only for joints that exist in hifi and verify missing joints
HumanDescription humanDescription = importer.humanDescription;
HumanBone[] boneMap = humanDescription.human;
Dictionary<string, string> jointMappings = new Dictionary<string, string>();
foreach (HumanBone bone in boneMap) {
string humanBone = bone.humanName;
string hifiJointName;
if (UNITY_TO_HIFI_JOINT_NAME.TryGetValue(humanBone, out hifiJointName)) {
jointMappings.Add(hifiJointName, bone.boneName);
}
}
if (!jointMappings.ContainsKey("Hips")) {
EditorUtility.DisplayDialog("Error", "There is no Hips bone in selected avatar", "Ok");
return;
}
if (!jointMappings.ContainsKey("Spine")) {
EditorUtility.DisplayDialog("Error", "There is no Spine bone in selected avatar", "Ok");
return;
}
if (!jointMappings.ContainsKey("Spine1")) {
EditorUtility.DisplayDialog("Error", "There is no Chest bone in selected avatar", "Ok");
return;
}
if (!jointMappings.ContainsKey("Spine2")) {
// if there is no UpperChest (Spine2) bone then we remap Chest (Spine1) to Spine2 in hifi and skip Spine1
jointMappings["Spine2"] = jointMappings["Spine1"];
jointMappings.Remove("Spine1");
}
// open folder explorer defaulting to user documents folder to select target path if exporting new avatar,
// otherwise use previously exported path if updating avatar
string directoryPath;
string assetName = Path.GetFileNameWithoutExtension(assetPath);
if (!usePreviousPath || exportedPath == String.Empty) {
string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
if (!SelectExportFolder(assetName, documentsFolder, out directoryPath)) {
return;
}
} else {
directoryPath = Path.GetDirectoryName(exportedPath) + "/";
}
Directory.CreateDirectory(directoryPath);
// delete any existing fst since we agreed to overwrite it
string fstPath = directoryPath + assetName + ".fst";
if (File.Exists(fstPath)) {
File.Delete(fstPath);
}
// write out core fields to top of fst file
File.WriteAllText(fstPath, "name = " + assetName + "\ntype = body+head\nscale = 1\nfilename = " +
assetName + ".fbx\n" + "texdir = textures\n");
// write out joint mappings to fst file
foreach (var jointMapping in jointMappings) {
File.AppendAllText(fstPath, "jointMap = " + jointMapping.Key + " = " + jointMapping.Value + "\n");
}
// delete any existing fbx since we agreed to overwrite it, and copy fbx over
string targetAssetPath = directoryPath + assetName + ".fbx";
if (File.Exists(targetAssetPath)) {
File.Delete(targetAssetPath);
}
File.Copy(assetPath, targetAssetPath);
exportedPath = targetAssetPath;
}
public static bool SelectExportFolder(string assetName, string initialPath, out string directoryPath) {
string selectedPath = EditorUtility.OpenFolderPanel("Select export location", initialPath, "");
if (selectedPath.Length == 0) { // folder selection cancelled
directoryPath = "";
return false;
}
directoryPath = selectedPath + "/" + assetName + "/";
if (Directory.Exists(directoryPath)) {
bool overwrite = EditorUtility.DisplayDialog("Error", "Directory " + assetName +
" already exists here, would you like to overwrite it?", "Yes", "No");
if (!overwrite) {
SelectExportFolder(assetName, selectedPath, out directoryPath);
}
}
return true;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c7a34be82b3ae554ea097963914b083f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1 @@
"C:\Program Files\Unity\Editor\Unity.exe" -quit -batchmode -projectPath %CD% -exportPackage "Assets/Editor" "avatarExporter.unitypackage"