mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 04:44:11 +02:00
Merge remote-tracking branch 'upstream/master' into NOverlays6
This commit is contained in:
commit
fd8245e258
81 changed files with 1478 additions and 591 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -29,7 +29,7 @@ ScriptableAvatar::ScriptableAvatar() {
|
|||
|
||||
QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) {
|
||||
_globalPosition = getWorldPosition();
|
||||
return AvatarData::toByteArrayStateful(dataDetail);
|
||||
return AvatarData::toByteArrayStateful(dataDetail, dropFaceTracking);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
// }
|
||||
//}
|
||||
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <Profile.h>
|
||||
|
||||
#include "AnimationLogging.h"
|
||||
#include <FBXSerializer.h>
|
||||
|
||||
int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include <QtScript/QScriptValue>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <FBXSerializer.h>
|
||||
#include <hfm/HFM.h>
|
||||
#include <ResourceCache.h>
|
||||
|
||||
class Animation;
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -515,7 +515,6 @@ QVector<bool> ModelEntityItem::getJointTranslationsSet() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool ModelEntityItem::hasModel() const {
|
||||
return resultWithReadLock<bool>([&] {
|
||||
return !_modelURL.isEmpty();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -760,4 +760,4 @@ void Batch::flush() {
|
|||
}
|
||||
buffer->flush();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 { }
|
||||
|
|
|
@ -313,6 +313,7 @@ public:
|
|||
QList<QString> blendshapeChannelNames;
|
||||
|
||||
QMap<int, glm::quat> jointRotationOffsets;
|
||||
QMap<QString, QString> hfmToHifiJointNameMapping;
|
||||
};
|
||||
|
||||
};
|
||||
|
|
65
libraries/hfm/src/hfm/HFMFormatRegistry.cpp
Normal file
65
libraries/hfm/src/hfm/HFMFormatRegistry.cpp
Normal 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);
|
||||
}
|
||||
|
||||
};
|
50
libraries/hfm/src/hfm/HFMFormatRegistry.h
Normal file
50
libraries/hfm/src/hfm/HFMFormatRegistry.h
Normal 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
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
20
libraries/hfm/src/hfm/ModelFormatRegistry.cpp
Normal file
20
libraries/hfm/src/hfm/ModelFormatRegistry.cpp
Normal 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);
|
||||
}
|
28
libraries/hfm/src/hfm/ModelFormatRegistry.h
Normal file
28
libraries/hfm/src/hfm/ModelFormatRegistry.h
Normal 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
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
85
libraries/shared/src/shared/MediaTypeLibrary.cpp
Normal file
85
libraries/shared/src/shared/MediaTypeLibrary.cpp
Normal 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;
|
||||
}
|
90
libraries/shared/src/shared/MediaTypeLibrary.h
Normal file
90
libraries/shared/src/shared/MediaTypeLibrary.h
Normal 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
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}());
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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, [], []);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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, [], []);
|
||||
|
|
|
@ -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, [], []);
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
8
tools/unity-avatar-exporter/Assets/Editor.meta
Normal file
8
tools/unity-avatar-exporter/Assets/Editor.meta
Normal file
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 51b3237a2992bd449a58ade16e52d0e0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
207
tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs
Normal file
207
tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c7a34be82b3ae554ea097963914b083f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
BIN
tools/unity-avatar-exporter/avatarExporter.unitypackage
Normal file
BIN
tools/unity-avatar-exporter/avatarExporter.unitypackage
Normal file
Binary file not shown.
1
tools/unity-avatar-exporter/packager.bat
Normal file
1
tools/unity-avatar-exporter/packager.bat
Normal file
|
@ -0,0 +1 @@
|
|||
"C:\Program Files\Unity\Editor\Unity.exe" -quit -batchmode -projectPath %CD% -exportPackage "Assets/Editor" "avatarExporter.unitypackage"
|
Loading…
Reference in a new issue