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

This commit is contained in:
Sam Gateau 2018-12-08 19:14:21 -08:00
commit 79cad36c58
50 changed files with 873 additions and 815 deletions

View file

@ -19,6 +19,9 @@
#include <AnimUtil.h>
#include <ClientTraitsHandler.h>
#include <GLMHelpers.h>
#include <ResourceRequestObserver.h>
#include <AvatarLogging.h>
ScriptableAvatar::ScriptableAvatar() {
_clientTraitsHandler.reset(new ClientTraitsHandler(this));
@ -62,11 +65,28 @@ AnimationDetails ScriptableAvatar::getAnimationDetails() {
return _animationDetails;
}
int ScriptableAvatar::getJointIndex(const QString& name) const {
// Faux joints:
int result = AvatarData::getJointIndex(name);
if (result != -1) {
return result;
}
QReadLocker readLock(&_jointDataLock);
return _fstJointIndices.value(name) - 1;
}
QStringList ScriptableAvatar::getJointNames() const {
QReadLocker readLock(&_jointDataLock);
return _fstJointNames;
return QStringList();
}
void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_bind.reset();
_animSkeleton.reset();
AvatarData::setSkeletonModelURL(skeletonModelURL);
updateJointMappings();
}
static AnimPose composeAnimPose(const HFMJoint& joint, const glm::quat rotation, const glm::vec3 translation) {
@ -77,10 +97,6 @@ static AnimPose composeAnimPose(const HFMJoint& joint, const glm::quat rotation,
}
void ScriptableAvatar::update(float deltatime) {
if (_bind.isNull() && !_skeletonFBXURL.isEmpty()) { // AvatarData will parse the .fst, but not get the .fbx skeleton.
_bind = DependencyManager::get<AnimationCache>()->getAnimation(_skeletonFBXURL);
}
// Run animation
if (_animation && _animation->isLoaded() && _animation->getFrames().size() > 0 && !_bind.isNull() && _bind->isLoaded()) {
if (!_animSkeleton) {
@ -146,6 +162,82 @@ void ScriptableAvatar::update(float deltatime) {
_clientTraitsHandler->sendChangedTraitsToMixer();
}
void ScriptableAvatar::updateJointMappings() {
{
QWriteLocker writeLock(&_jointDataLock);
_fstJointIndices.clear();
_fstJointNames.clear();
_jointData.clear();
}
if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) {
////
// TODO: Should we rely upon HTTPResourceRequest for ResourceRequestObserver instead?
// HTTPResourceRequest::doSend() covers all of the following and
// then some. It doesn't cover the connect() call, so we may
// want to add a HTTPResourceRequest::doSend() method that does
// connects.
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL);
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
DependencyManager::get<ResourceRequestObserver>()->update(
_skeletonModelURL, -1, "AvatarData::updateJointMappings");
QNetworkReply* networkReply = networkAccessManager.get(networkRequest);
//
////
connect(networkReply, &QNetworkReply::finished, this, &ScriptableAvatar::setJointMappingsFromNetworkReply);
}
}
void ScriptableAvatar::setJointMappingsFromNetworkReply() {
QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
// before we process this update, make sure that the skeleton model URL hasn't changed
// since we made the FST request
if (networkReply->url() != _skeletonModelURL) {
qCDebug(avatars) << "Refusing to set joint mappings for FST URL that does not match the current URL";
networkReply->deleteLater();
return;
}
{
QWriteLocker writeLock(&_jointDataLock);
QByteArray line;
while (!(line = networkReply->readLine()).isEmpty()) {
line = line.trimmed();
if (line.startsWith("filename")) {
int filenameIndex = line.indexOf('=') + 1;
if (filenameIndex > 0) {
_skeletonFBXURL = _skeletonModelURL.resolved(QString(line.mid(filenameIndex).trimmed()));
}
}
if (!line.startsWith("jointIndex")) {
continue;
}
int jointNameIndex = line.indexOf('=') + 1;
if (jointNameIndex == 0) {
continue;
}
int secondSeparatorIndex = line.indexOf('=', jointNameIndex);
if (secondSeparatorIndex == -1) {
continue;
}
QString jointName = line.mid(jointNameIndex, secondSeparatorIndex - jointNameIndex).trimmed();
bool ok;
int jointIndex = line.mid(secondSeparatorIndex + 1).trimmed().toInt(&ok);
if (ok) {
while (_fstJointNames.size() < jointIndex + 1) {
_fstJointNames.append(QString());
}
_fstJointNames[jointIndex] = jointName;
}
}
for (int i = 0; i < _fstJointNames.size(); i++) {
_fstJointIndices.insert(_fstJointNames.at(i), i + 1);
}
}
networkReply->deleteLater();
}
void ScriptableAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) {
_headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement);
}

View file

@ -153,6 +153,27 @@ public:
*/
Q_INVOKABLE AnimationDetails getAnimationDetails();
/**jsdoc
* Get the names of all the joints in the current avatar.
* @function MyAvatar.getJointNames
* @returns {string[]} The joint names.
* @example <caption>Report the names of all the joints in your current avatar.</caption>
* print(JSON.stringify(MyAvatar.getJointNames()));
*/
Q_INVOKABLE virtual QStringList getJointNames() const override;
/**jsdoc
* Get the joint index for a named joint. The joint index value is the position of the joint in the array returned by
* {@link MyAvatar.getJointNames} or {@link Avatar.getJointNames}.
* @function MyAvatar.getJointIndex
* @param {string} name - The name of the joint.
* @returns {number} The index of the joint.
* @example <caption>Report the index of your avatar's left arm joint.</caption>
* print(JSON.stringify(MyAvatar.getJointIndex("LeftArm"));
*/
/// Returns the index of the joint with the specified name, or -1 if not found/unknown.
Q_INVOKABLE virtual int getJointIndex(const QString& name) const override;
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override;
@ -167,12 +188,23 @@ public:
public slots:
void update(float deltatime);
/**jsdoc
* @function MyAvatar.setJointMappingsFromNetworkReply
*/
void setJointMappingsFromNetworkReply();
private:
AnimationPointer _animation;
AnimationDetails _animationDetails;
QStringList _maskedJoints;
AnimationPointer _bind; // a sleazy way to get the skeleton, given the various library/cmake dependencies
std::shared_ptr<AnimSkeleton> _animSkeleton;
QHash<QString, int> _fstJointIndices; ///< 1-based, since zero is returned for missing keys
QStringList _fstJointNames; ///< in order of depth-first traversal
QUrl _skeletonFBXURL;
/// Loads the joint indices, names from the FST file (if any)
void updateJointMappings();
};
#endif // hifi_ScriptableAvatar_h

View file

@ -364,7 +364,7 @@ function validateInputs() {
if (keyVal.length === 0) {
empty = true
markParentRowInvalid(input);
markParentRowInvalid(input)
return;
}
@ -373,11 +373,13 @@ function validateInputs() {
_.each(otherKeys, function(otherKeyCell) {
var keyInput = $(otherKeyCell).children('input');
var lowerNewValue = keyVal.toLowerCase();
if (keyInput.length) {
if ($(keyInput).val() == keyVal) {
if ($(keyInput).val().toLowerCase() == lowerNewValue) {
duplicateKey = true;
}
} else if ($(otherKeyCell).html() == keyVal) {
} else if ($(otherKeyCell).html().toLowerCase() == lowerNewValue) {
duplicateKey = true;
}

View file

@ -3172,24 +3172,34 @@ void DomainServer::processPathQueryPacket(QSharedPointer<ReceivedMessage> messag
const QString PATH_VIEWPOINT_KEY = "viewpoint";
const QString INDEX_PATH = "/";
// check out paths in the _configMap to see if we have a match
auto keypath = QString(PATHS_SETTINGS_KEYPATH_FORMAT).arg(SETTINGS_PATHS_KEY).arg(pathQuery);
QVariant pathMatch = _settingsManager.valueForKeyPath(keypath);
QString responseViewpoint;
if (pathMatch.isValid() || pathQuery == INDEX_PATH) {
// check out paths in the _configMap to see if we have a match
auto pathsVariant = _settingsManager.valueForKeyPath(SETTINGS_PATHS_KEY);
auto lowerPathQuery = pathQuery.toLower();
if (pathsVariant.canConvert<QVariantMap>()) {
auto pathsMap = pathsVariant.toMap();
// enumerate the paths and look case-insensitively for a matching one
for (auto it = pathsMap.constKeyValueBegin(); it != pathsMap.constKeyValueEnd(); ++it) {
if ((*it).first.toLower() == lowerPathQuery) {
responseViewpoint = (*it).second.toMap()[PATH_VIEWPOINT_KEY].toString().toLower();
break;
}
}
}
if (responseViewpoint.isEmpty() && pathQuery == INDEX_PATH) {
const QString DEFAULT_INDEX_PATH = "/0,0,0/0,0,0,1";
responseViewpoint = DEFAULT_INDEX_PATH;
}
if (!responseViewpoint.isEmpty()) {
// we got a match, respond with the resulting viewpoint
auto nodeList = DependencyManager::get<LimitedNodeList>();
QString responseViewpoint;
// if we didn't match the path BUT this is for the index path then send back our default
if (pathMatch.isValid()) {
responseViewpoint = pathMatch.toMap()[PATH_VIEWPOINT_KEY].toString();
} else {
const QString DEFAULT_INDEX_PATH = "/0,0,0/0,0,0,1";
responseViewpoint = DEFAULT_INDEX_PATH;
}
if (!responseViewpoint.isEmpty()) {
QByteArray viewpointUTF8 = responseViewpoint.toUtf8();

View file

@ -873,7 +873,7 @@ Flickable {
editable: true
colorScheme: hifi.colorSchemes.dark
model: ["None", "Freeze", "Drop"]
model: ["None", "Freeze", "Drop", "DropAfterDelay"]
label: ""
onCurrentIndexChanged: {

View file

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

View file

@ -150,7 +150,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"
@ -2819,8 +2818,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() };
@ -2856,45 +2853,6 @@ void Application::initializeDisplayPlugins() {
// Submit a default frame to render until the engine starts up
updateRenderArgs(0.0f);
#define ENABLE_SPLASH_FRAME 0
#if ENABLE_SPLASH_FRAME
{
QMutexLocker viewLocker(&_renderArgsMutex);
if (_appRenderArgs._isStereo) {
_gpuContext->enableStereo(true);
_gpuContext->setStereoProjections(_appRenderArgs._eyeProjections);
_gpuContext->setStereoViews(_appRenderArgs._eyeOffsets);
}
// Frame resources
auto framebufferCache = DependencyManager::get<FramebufferCache>();
gpu::FramebufferPointer finalFramebuffer = framebufferCache->getFramebuffer();
std::shared_ptr<ProceduralSkybox> procedural = std::make_shared<ProceduralSkybox>();
procedural->parse(SPLASH_SKYBOX);
_gpuContext->beginFrame(_appRenderArgs._view, _appRenderArgs._headPose);
gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {
batch.resetStages();
batch.enableStereo(false);
batch.setFramebuffer(finalFramebuffer);
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, { 0, 0, 0, 1 });
batch.enableSkybox(true);
batch.enableStereo(_appRenderArgs._isStereo);
batch.setViewportTransform({ 0, 0, finalFramebuffer->getSize() });
procedural->render(batch, _appRenderArgs._renderArgs.getViewFrustum());
});
auto frame = _gpuContext->endFrame();
frame->frameIndex = 0;
frame->framebuffer = finalFramebuffer;
frame->pose = _appRenderArgs._headPose;
frame->framebufferRecycler = [framebufferCache, procedural](const gpu::FramebufferPointer& framebuffer) {
framebufferCache->releaseFramebuffer(framebuffer);
};
_displayPlugin->submitFrame(frame);
}
#endif
}
void Application::initializeRenderEngine() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1776,16 +1776,11 @@ int AvatarData::getFauxJointIndex(const QString& name) const {
int AvatarData::getJointIndex(const QString& name) const {
int result = getFauxJointIndex(name);
if (result != -1) {
return result;
}
QReadLocker readLock(&_jointDataLock);
return _fstJointIndices.value(name) - 1;
return result;
}
QStringList AvatarData::getJointNames() const {
QReadLocker readLock(&_jointDataLock);
return _fstJointNames;
return QStringList();
}
glm::quat AvatarData::getOrientationOutbound() const {
@ -2000,8 +1995,6 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_skeletonModelURL = expanded;
updateJointMappings();
if (_clientTraitsHandler) {
_clientTraitsHandler->markTraitUpdated(AvatarTraits::SkeletonModelURL);
}
@ -2097,58 +2090,6 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) {
setAttachmentData(attachmentData);
}
void AvatarData::setJointMappingsFromNetworkReply() {
QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
// before we process this update, make sure that the skeleton model URL hasn't changed
// since we made the FST request
if (networkReply->error() != QNetworkReply::NoError || networkReply->url() != _skeletonModelURL) {
qCDebug(avatars) << "Refusing to set joint mappings for FST URL that does not match the current URL";
networkReply->deleteLater();
return;
}
{
QWriteLocker writeLock(&_jointDataLock);
QByteArray line;
while (!(line = networkReply->readLine()).isEmpty()) {
line = line.trimmed();
if (line.startsWith("filename")) {
int filenameIndex = line.indexOf('=') + 1;
if (filenameIndex > 0) {
_skeletonFBXURL = _skeletonModelURL.resolved(QString(line.mid(filenameIndex).trimmed()));
}
}
if (!line.startsWith("jointIndex")) {
continue;
}
int jointNameIndex = line.indexOf('=') + 1;
if (jointNameIndex == 0) {
continue;
}
int secondSeparatorIndex = line.indexOf('=', jointNameIndex);
if (secondSeparatorIndex == -1) {
continue;
}
QString jointName = line.mid(jointNameIndex, secondSeparatorIndex - jointNameIndex).trimmed();
bool ok;
int jointIndex = line.mid(secondSeparatorIndex + 1).trimmed().toInt(&ok);
if (ok) {
while (_fstJointNames.size() < jointIndex + 1) {
_fstJointNames.append(QString());
}
_fstJointNames[jointIndex] = jointName;
}
}
for (int i = 0; i < _fstJointNames.size(); i++) {
_fstJointIndices.insert(_fstJointNames.at(i), i + 1);
}
}
networkReply->deleteLater();
}
void AvatarData::sendAvatarDataPacket(bool sendAll) {
auto nodeList = DependencyManager::get<NodeList>();
@ -2210,34 +2151,6 @@ void AvatarData::sendIdentityPacket() {
_identityDataChanged = false;
}
void AvatarData::updateJointMappings() {
{
QWriteLocker writeLock(&_jointDataLock);
_fstJointIndices.clear();
_fstJointNames.clear();
_jointData.clear();
}
if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) {
////
// TODO: Should we rely upon HTTPResourceRequest for ResourceRequestObserver instead?
// HTTPResourceRequest::doSend() covers all of the following and
// then some. It doesn't cover the connect() call, so we may
// want to add a HTTPResourceRequest::doSend() method that does
// connects.
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL);
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
DependencyManager::get<ResourceRequestObserver>()->update(
_skeletonModelURL, -1, "AvatarData::updateJointMappings");
QNetworkReply* networkReply = networkAccessManager.get(networkRequest);
//
////
connect(networkReply, &QNetworkReply::finished, this, &AvatarData::setJointMappingsFromNetworkReply);
}
}
static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl");
static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName");
static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform");

View file

@ -1269,11 +1269,6 @@ public slots:
*/
void sendIdentityPacket();
/**jsdoc
* @function MyAvatar.setJointMappingsFromNetworkReply
*/
void setJointMappingsFromNetworkReply();
/**jsdoc
* @function MyAvatar.setSessionUUID
* @param {Uuid} sessionUUID
@ -1376,23 +1371,16 @@ protected:
mutable HeadData* _headData { nullptr };
QUrl _skeletonModelURL;
QUrl _skeletonFBXURL;
QVector<AttachmentData> _attachmentData;
QVector<AttachmentData> _oldAttachmentData;
QString _displayName;
QString _sessionDisplayName { };
bool _lookAtSnappingEnabled { true };
QHash<QString, int> _fstJointIndices; ///< 1-based, since zero is returned for missing keys
QStringList _fstJointNames; ///< in order of depth-first traversal
quint64 _errorLogExpiry; ///< time in future when to log an error
QWeakPointer<Node> _owningAvatarMixer;
/// Loads the joint indices, names from the FST file (if any)
virtual void updateJointMappings();
glm::vec3 _targetVelocity;
SimpleMovingAverage _averageBytesReceived;
@ -1496,11 +1484,8 @@ protected:
T readLockWithNamedJointIndex(const QString& name, const T& defaultValue, F f) const {
int index = getFauxJointIndex(name);
QReadLocker readLock(&_jointDataLock);
if (index == -1) {
index = _fstJointIndices.value(name) - 1;
}
// The first conditional is superfluous, but illsutrative
// The first conditional is superfluous, but illustrative
if (index == -1 || index < _jointData.size()) {
return defaultValue;
}
@ -1517,9 +1502,6 @@ protected:
void writeLockWithNamedJointIndex(const QString& name, F f) {
int index = getFauxJointIndex(name);
QWriteLocker writeLock(&_jointDataLock);
if (index == -1) {
index = _fstJointIndices.value(name) - 1;
}
if (index == -1) {
return;
}

View file

@ -104,7 +104,7 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
// we double check that it is a simple iterator here
auto traitType = static_cast<AvatarTraits::TraitType>(std::distance(traitStatusesCopy.simpleCBegin(), simpleIt));
if (_shouldPerformInitialSend || *simpleIt == Updated) {
if (initialSend || *simpleIt == Updated) {
if (traitType == AvatarTraits::SkeletonModelURL) {
_owningAvatar->packTrait(traitType, *traitsPacketList);

View file

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

View file

@ -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);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2061,68 +2061,6 @@ bool ScriptEngine::hasEntityScriptDetails(const EntityItemID& entityID) const {
return _entityScripts.contains(entityID);
}
const static EntityItemID BAD_SCRIPT_UUID_PLACEHOLDER { "{20170224-dead-face-0000-cee000021114}" };
void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const EntityItemID& leaderID) {
QList<DeferredLoadEntity> retryLoads;
QMutableListIterator<DeferredLoadEntity> i(_deferredEntityLoads);
while (i.hasNext()) {
auto retry = i.next();
if (retry.entityScript == entityScript) {
retryLoads << retry;
i.remove();
}
}
foreach(DeferredLoadEntity retry, retryLoads) {
// check whether entity was since been deleted
EntityScriptDetails details;
if (!getEntityScriptDetails(retry.entityID, details)) {
qCDebug(scriptengine) << "processDeferredEntityLoads -- entity details gone (entity deleted?)"
<< retry.entityID;
continue;
}
// check whether entity has since been unloaded or otherwise errored-out
if (details.status != EntityScriptStatus::PENDING) {
qCDebug(scriptengine) << "processDeferredEntityLoads -- entity status no longer PENDING; "
<< retry.entityID << details.status;
continue;
}
// propagate leader's failure reasons to the pending entity
EntityScriptDetails leaderDetails;
{
QWriteLocker locker { &_entityScriptsLock };
leaderDetails = _entityScripts[leaderID];
}
if (leaderDetails.status != EntityScriptStatus::RUNNING) {
qCDebug(scriptengine) << QString("... pending load of %1 cancelled (leader: %2 status: %3)")
.arg(retry.entityID.toString()).arg(leaderID.toString()).arg(leaderDetails.status);
auto extraDetail = QString("\n(propagated from %1)").arg(leaderID.toString());
if (leaderDetails.status == EntityScriptStatus::ERROR_LOADING_SCRIPT ||
leaderDetails.status == EntityScriptStatus::ERROR_RUNNING_SCRIPT) {
// propagate same error so User doesn't have to hunt down stampede's leader
updateEntityScriptStatus(retry.entityID, leaderDetails.status, leaderDetails.errorInfo + extraDetail);
} else {
// the leader Entity somehow ended up in some other state (rapid-fire delete or unload could cause)
updateEntityScriptStatus(retry.entityID, EntityScriptStatus::ERROR_LOADING_SCRIPT,
"A previous Entity failed to load using this script URL; reload to try again." + extraDetail);
}
continue;
}
if (_occupiedScriptURLs.contains(retry.entityScript)) {
qCWarning(scriptengine) << "--- SHOULD NOT HAPPEN -- recursive call into processDeferredEntityLoads" << retry.entityScript;
continue;
}
// if we made it here then the leading entity was successful so proceed with normal load
loadEntityScript(retry.entityID, retry.entityScript, false);
}
}
void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "loadEntityScript",
@ -2147,40 +2085,6 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString&
updateEntityScriptStatus(entityID, EntityScriptStatus::PENDING, "...pending...");
}
// This "occupied" approach allows multiple Entities to boot from the same script URL while still taking
// full advantage of cacheable require modules. This only affects Entities literally coming in back-to-back
// before the first one has time to finish loading.
if (_occupiedScriptURLs.contains(entityScript)) {
auto currentEntityID = _occupiedScriptURLs[entityScript];
if (currentEntityID == BAD_SCRIPT_UUID_PLACEHOLDER) {
if (forceRedownload) {
// script was previously marked unusable, but we're reloading so reset it
_occupiedScriptURLs.remove(entityScript);
} else {
// since not reloading, assume that the exact same input would produce the exact same output again
// note: this state gets reset with "reload all scripts," leaving/returning to a Domain, clear cache, etc.
#ifdef DEBUG_ENTITY_STATES
qCDebug(scriptengine) << QString("loadEntityScript.cancelled entity: %1 (previous script failure)")
.arg(entityID.toString());
#endif
updateEntityScriptStatus(entityID, EntityScriptStatus::ERROR_LOADING_SCRIPT,
"A previous Entity failed to load using this script URL; reload to try again.");
return;
}
} else {
// another entity is busy loading from this script URL so wait for them to finish
#ifdef DEBUG_ENTITY_STATES
qCDebug(scriptengine) << QString("loadEntityScript.deferring[%0] entity: %1 (waiting on %2 )")
.arg(_deferredEntityLoads.size()).arg(entityID.toString()).arg(currentEntityID.toString());
#endif
_deferredEntityLoads.push_back({ entityID, entityScript });
return;
}
}
// the scriptURL slot is available; flag as in-use
_occupiedScriptURLs[entityScript] = entityID;
#ifdef DEBUG_ENTITY_STATES
{
EntityScriptDetails details;
@ -2223,10 +2127,6 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString&
qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting";
#endif
}
// recheck whether us since may have been set to BAD_SCRIPT_UUID_PLACEHOLDER in entityScriptContentAvailable
if (_occupiedScriptURLs.contains(entityScript) && _occupiedScriptURLs[entityScript] == entityID) {
_occupiedScriptURLs.remove(entityScript);
}
});
}, forceRedownload);
}
@ -2301,13 +2201,6 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
newDetails.errorInfo = errorInfo;
newDetails.status = status;
setEntityScriptDetails(entityID, newDetails);
#ifdef DEBUG_ENTITY_STATES
qCDebug(scriptengine) << "entityScriptContentAvailable -- flagging as BAD_SCRIPT_UUID_PLACEHOLDER";
#endif
// flag the original entityScript as unusuable
_occupiedScriptURLs[entityScript] = BAD_SCRIPT_UUID_PLACEHOLDER;
processDeferredEntityLoads(entityScript, entityID);
};
// NETWORK / FILESYSTEM ERRORS
@ -2441,9 +2334,6 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
callEntityScriptMethod(entityID, "preload");
emit entityScriptPreloadFinished(entityID);
_occupiedScriptURLs.remove(entityScript);
processDeferredEntityLoads(entityScript, entityID);
}
/**jsdoc
@ -2499,10 +2389,6 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR
}
stopAllTimersForEntityScript(entityID);
{
// FIXME: shouldn't have to do this here, but currently something seems to be firing unloads moments after firing initial load requests
processDeferredEntityLoads(scriptText, entityID);
}
}
}
@ -2532,7 +2418,6 @@ void ScriptEngine::unloadAllEntityScripts() {
_entityScripts.clear();
}
emit entityScriptDetailsUpdated();
_occupiedScriptURLs.clear();
#ifdef DEBUG_ENGINE_STATE
_debugDump(

View file

@ -747,7 +747,6 @@ protected:
void updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus& status, const QString& errorInfo = QString());
void setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details);
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
void processDeferredEntityLoads(const QString& entityScript, const EntityItemID& leaderID);
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
void stopTimer(QTimer* timer);
@ -783,8 +782,6 @@ protected:
QSet<QUrl> _includedURLs;
mutable QReadWriteLock _entityScriptsLock { QReadWriteLock::Recursive };
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
QHash<QString, EntityItemID> _occupiedScriptURLs;
QList<DeferredLoadEntity> _deferredEntityLoads;
EntityScriptContentAvailableMap _contentAvailableQueue;
bool _isThreaded { false };

View file

@ -138,6 +138,8 @@ static QString outOfRangeDataStrategyToString(ViveControllerManager::OutOfRangeD
return "Freeze";
case ViveControllerManager::OutOfRangeDataStrategy::Drop:
return "Drop";
case ViveControllerManager::OutOfRangeDataStrategy::DropAfterDelay:
return "DropAfterDelay";
}
}
@ -146,6 +148,8 @@ static ViveControllerManager::OutOfRangeDataStrategy stringToOutOfRangeDataStrat
return ViveControllerManager::OutOfRangeDataStrategy::Drop;
} else if (string == "Freeze") {
return ViveControllerManager::OutOfRangeDataStrategy::Freeze;
} else if (string == "DropAfterDelay") {
return ViveControllerManager::OutOfRangeDataStrategy::DropAfterDelay;
} else {
return ViveControllerManager::OutOfRangeDataStrategy::None;
}
@ -302,7 +306,7 @@ void ViveControllerManager::loadSettings() {
if (_inputDevice) {
const double DEFAULT_ARM_CIRCUMFERENCE = 0.33;
const double DEFAULT_SHOULDER_WIDTH = 0.48;
const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "Drop";
const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "DropAfterDelay";
_inputDevice->_armCircumference = settings.value("armCircumference", QVariant(DEFAULT_ARM_CIRCUMFERENCE)).toDouble();
_inputDevice->_shoulderWidth = settings.value("shoulderWidth", QVariant(DEFAULT_SHOULDER_WIDTH)).toDouble();
_inputDevice->_outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(settings.value("outOfRangeDataStrategy", QVariant(DEFAULT_OUT_OF_RANGE_STRATEGY)).toString());
@ -516,6 +520,7 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde
_nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid &&
poseIndex <= controller::TRACKED_OBJECT_15) {
uint64_t now = usecTimestampNow();
controller::Pose pose;
switch (_outOfRangeDataStrategy) {
case OutOfRangeDataStrategy::Drop:
@ -544,6 +549,22 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde
_nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex];
}
break;
case OutOfRangeDataStrategy::DropAfterDelay:
const uint64_t DROP_DELAY_TIME = 500 * USECS_PER_MSEC;
// All Running_OK results are valid.
if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult == vr::TrackingResult_Running_OK) {
pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]);
// update the timer
_simDataRunningOkTimestampMap[deviceIndex] = now;
} else if (now - _simDataRunningOkTimestampMap[deviceIndex] < DROP_DELAY_TIME) {
// report the pose, even though pose is out-of-range
pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]);
} else {
// this pose has been out-of-range for too long.
pose.valid = false;
}
break;
}
if (pose.valid) {

View file

@ -63,7 +63,8 @@ public:
enum class OutOfRangeDataStrategy {
None,
Freeze,
Drop
Drop,
DropAfterDelay
};
private:
@ -205,6 +206,8 @@ private:
bool _hmdTrackingEnabled { true };
std::map<uint32_t, uint64_t> _simDataRunningOkTimestampMap;
QString configToString(Config config);
friend class ViveControllerManager;
};

View file

@ -193,22 +193,52 @@
"emitterShouldTrail": {
"tooltip": "If enabled, then particles are \"left behind\" as the emitter moves, otherwise they are not."
},
"particleRadiusTriple": {
"tooltip": "The size of each particle.",
"jsPropertyName": "particleRadius"
},
"particleRadius": {
"tooltip": "The size of each particle."
},
"radiusStart": {
"tooltip": "The start size of each particle."
},
"radiusFinish": {
"tooltip": "The finish size of each particle."
},
"radiusSpread": {
"tooltip": "The spread in size that each particle is given, resulting in a variety of sizes."
},
"particleColorTriple": {
"tooltip": "The color of each particle.",
"jsPropertyName": "color"
},
"particleColor": {
"tooltip": "The color of each particle.",
"jsPropertyName": "color"
},
"colorStart": {
"tooltip": "The start color of each particle."
},
"colorFinish": {
"tooltip": "The finish color of each particle."
},
"colorSpread": {
"tooltip": "The spread in color that each particle is given, resulting in a variety of colors."
},
"particleAlphaTriple": {
"tooltip": "The alpha of each particle.",
"jsPropertyName": "alpha"
},
"alpha": {
"tooltip": "The alpha of each particle."
},
"alphaStart": {
"tooltip": "The start alpha of each particle."
},
"alphaFinish": {
"tooltip": "The finish alpha of each particle."
},
"alphaSpread": {
"tooltip": "The spread in alpha that each particle is given, resulting in a variety of alphas."
},
@ -218,20 +248,44 @@
"accelerationSpread": {
"tooltip": "The spread in accelerations that each particle is given, resulting in a variety of accelerations."
},
"particleSpinTriple": {
"tooltip": "The spin of each particle.",
"jsPropertyName": "particleSpin"
},
"particleSpin": {
"tooltip": "The spin of each particle in the system."
"tooltip": "The spin of each particle."
},
"spinStart": {
"tooltip": "The start spin of each particle."
},
"spinFinish": {
"tooltip": "The finish spin of each particle."
},
"spinSpread": {
"tooltip": "The spread in spin that each particle is given, resulting in a variety of spins."
},
"rotateWithEntity": {
"tooltip": "If enabled, each particle will spin relative to the roation of the entity as a whole."
"tooltip": "If enabled, each particle will spin relative to the rotation of the entity as a whole."
},
"particlePolarTriple": {
"tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis.",
"skipJSProperty": true
},
"polarStart": {
"tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis."
"tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis."
},
"polarFinish": {
"tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis."
},
"particleAzimuthTriple": {
"tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis.",
"skipJSProperty": true
},
"azimuthStart": {
"tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis."
"tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis."
},
"azimuthFinish": {
"tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis."
},
"lightColor": {
"tooltip": "The color of the light emitted.",
@ -352,7 +406,7 @@
"tooltip": "The URL of a sound to play when the entity collides with something else."
},
"grab.grabbable": {
"tooltip": "If enabled, this entity will allow grabbing input and will be moveable."
"tooltip": "If enabled, this entity will allow grabbing input and will be movable."
},
"grab.triggerable": {
"tooltip": "If enabled, the collider on this entity is used for triggering events."

View file

@ -600,6 +600,7 @@ div.section[collapsed="true"], div.section[collapsed="true"] > .section-header {
text-transform: uppercase;
text-align: center;
padding: 6px 0;
cursor: default;
}
.triple-item {
@ -1390,6 +1391,10 @@ input[type=button]#export {
cursor: col-resize;
}
#entity-table .dragging {
background-color: #b3ecff;
}
#entity-table td {
box-sizing: border-box;
}
@ -1517,6 +1522,7 @@ input.rename-entity {
}
.create-app-tooltip {
z-index: 100;
position: absolute;
background: #6a6a6a;
border: 1px solid black;
@ -1651,6 +1657,7 @@ input.number-slider {
font-family: Raleway-Light;
font-size: 14px;
margin: 6px 0;
cursor: default;
}
#property-name, #property-id {
@ -1663,9 +1670,38 @@ input.number-slider {
}
#placeholder-property-type {
min-width: 0px;
min-width: 0;
}
.collapse-icon {
cursor: pointer;
}
#property-userData-editor.error {
border: 2px solid red;
}
#property-userData-editorStatus {
color: white;
background-color: red;
padding: 5px;
display: none;
cursor: pointer;
}
#property-materialData-editor.error {
border: 2px solid red;
}
#property-materialData-editorStatus {
color: white;
background-color: red;
padding: 5px;
display: none;
cursor: pointer;
}
input[type=number].hide-spinner::-webkit-inner-spin-button {
-webkit-appearance: none;
visibility: hidden;
}

View file

@ -18,6 +18,7 @@
<script type="text/javascript" src="js/spinButtons.js"></script>
<script type="text/javascript" src="js/listView.js"></script>
<script type="text/javascript" src="js/entityListContextMenu.js"></script>
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/entityList.js"></script>
</head>
<body onload='loaded();' id="entity-list-body">

View file

@ -23,6 +23,7 @@
<script type="text/javascript" src="js/underscore-min.js"></script>
<script type="text/javascript" src="js/createAppTooltip.js"></script>
<script type="text/javascript" src="js/draggableNumber.js"></script>
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/entityProperties.js"></script>
<script src="js/jsoneditor.min.js"></script>
</head>

View file

@ -16,6 +16,7 @@
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
<script type="text/javascript" src="js/spinButtons.js"></script>
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/gridControls.js"></script>
</head>
<body onload='loaded();'>

View file

@ -21,6 +21,9 @@ const MAX_LENGTH_RADIUS = 9;
const MINIMUM_COLUMN_WIDTH = 24;
const SCROLLBAR_WIDTH = 20;
const RESIZER_WIDTH = 10;
const DELTA_X_MOVE_COLUMNS_THRESHOLD = 2;
const DELTA_X_COLUMN_SWAP_POSITION = 5;
const CERTIFIED_PLACEHOLDER = "** Certified **";
const COLUMNS = {
type: {
@ -108,8 +111,8 @@ const COLUMNS = {
};
const COMPARE_ASCENDING = function(a, b) {
let va = a[currentSortColumn];
let vb = b[currentSortColumn];
let va = a[currentSortColumnID];
let vb = b[currentSortColumnID];
if (va < vb) {
return -1;
@ -172,7 +175,7 @@ let entityList = null; // The ListView
*/
let entityListContextMenu = null;
let currentSortColumn = 'type';
let currentSortColumnID = 'type';
let currentSortOrder = ASCENDING_SORT;
let elSortOrders = {};
let typeFilters = [];
@ -180,10 +183,13 @@ let isFilterInView = false;
let columns = [];
let columnsByID = {};
let currentResizeEl = null;
let startResizeEvent = null;
let lastResizeEvent = null;
let resizeColumnIndex = 0;
let startThClick = null;
let elTargetTh = null;
let elTargetSpan = null;
let targetColumnIndex = 0;
let lastColumnSwapPosition = -1;
let initialThEvent = null;
let renameTimeout = null;
let renameLastBlur = null;
let renameLastEntityID = null;
@ -230,10 +236,6 @@ const PROFILE = !ENABLE_PROFILING ? PROFILE_NOOP : function(name, fn, args) {
console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms");
};
debugPrint = function (message) {
console.log(message);
};
function loaded() {
openEventBridge(function() {
elEntityTable = document.getElementById("entity-table");
@ -324,10 +326,11 @@ function loaded() {
for (let columnID in COLUMNS) {
let columnData = COLUMNS[columnID];
let thID = "entity-" + columnID;
let elTh = document.createElement("th");
let thID = "entity-" + columnID;
elTh.setAttribute("id", thID);
elTh.setAttribute("data-resizable-column-id", thID);
elTh.setAttribute("columnIndex", columnIndex);
elTh.setAttribute("columnID", columnID);
if (columnData.glyph) {
let elGlyph = document.createElement("span");
elGlyph.className = "glyph";
@ -336,20 +339,20 @@ function loaded() {
} else {
elTh.innerText = columnData.columnHeader;
}
elTh.onmousedown = function() {
startThClick = this;
};
elTh.onmouseup = function() {
if (startThClick === this) {
setSortColumn(columnID);
elTh.onmousedown = function(event) {
if (event.target.nodeName === 'TH') {
elTargetTh = event.target;
targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex"));
lastColumnSwapPosition = event.clientX;
} else if (event.target.nodeName === 'SPAN') {
elTargetSpan = event.target;
}
startThClick = null;
initialThEvent = event;
};
let elResizer = document.createElement("span");
elResizer.className = "resizer";
elResizer.innerHTML = "&nbsp;";
elResizer.setAttribute("columnIndex", columnIndex);
elResizer.onmousedown = onStartResize;
elTh.appendChild(elResizer);
@ -633,10 +636,11 @@ function loaded() {
id: entity.id,
name: entity.name,
type: type,
url: filename,
fullUrl: entity.url,
url: entity.certificateID === "" ? filename : "<i>" + CERTIFIED_PLACEHOLDER + "</i>",
fullUrl: entity.certificateID === "" ? filename : CERTIFIED_PLACEHOLDER,
locked: entity.locked,
visible: entity.visible,
certificateID: entity.certificateID,
verticesCount: displayIfNonZero(entity.verticesCount),
texturesCount: displayIfNonZero(entity.texturesCount),
texturesSize: decimalMegabytes(entity.texturesSize),
@ -762,13 +766,13 @@ function loaded() {
refreshNoEntitiesMessage();
}
function setSortColumn(column) {
function setSortColumn(columnID) {
PROFILE("set-sort-column", function() {
if (currentSortColumn === column) {
if (currentSortColumnID === columnID) {
currentSortOrder *= -1;
} else {
elSortOrders[currentSortColumn].innerHTML = "";
currentSortColumn = column;
elSortOrders[currentSortColumnID].innerHTML = "";
currentSortColumnID = columnID;
currentSortOrder = ASCENDING_SORT;
}
refreshSortOrder();
@ -777,7 +781,7 @@ function loaded() {
}
function refreshSortOrder() {
elSortOrders[currentSortColumn].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING;
elSortOrders[currentSortColumnID].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING;
}
function refreshEntities() {
@ -874,7 +878,7 @@ function loaded() {
if (column.data.glyph) {
elCell.innerHTML = itemData[column.data.propertyID] ? column.data.columnHeader : null;
} else {
elCell.innerText = itemData[column.data.propertyID];
elCell.innerHTML = itemData[column.data.propertyID];
}
elCell.style = "min-width:" + column.widthPx + "px;" + "max-width:" + column.widthPx + "px;";
elCell.className = createColumnClassName(column.columnID);
@ -1093,8 +1097,8 @@ function loaded() {
}
function onStartResize(event) {
startResizeEvent = event;
resizeColumnIndex = parseInt(this.getAttribute("columnIndex"));
lastResizeEvent = event;
resizeColumnIndex = parseInt(this.parentNode.getAttribute("columnIndex"));
event.stopPropagation();
}
@ -1137,8 +1141,37 @@ function loaded() {
entityList.refresh();
}
document.onmousemove = function(ev) {
if (startResizeEvent) {
function swapColumns(columnAIndex, columnBIndex) {
let columnA = columns[columnAIndex];
let columnB = columns[columnBIndex];
let columnATh = columns[columnAIndex].elTh;
let columnBTh = columns[columnBIndex].elTh;
let columnThParent = columnATh.parentNode;
columnThParent.removeChild(columnBTh);
columnThParent.insertBefore(columnBTh, columnATh);
columnATh.setAttribute("columnIndex", columnBIndex);
columnBTh.setAttribute("columnIndex", columnAIndex);
columnA.elResizer.setAttribute("columnIndex", columnBIndex);
columnB.elResizer.setAttribute("columnIndex", columnAIndex);
for (let i = 0; i < visibleEntities.length; ++i) {
let elRow = visibleEntities[i].elRow;
if (elRow) {
let columnACell = elRow.childNodes[columnAIndex];
let columnBCell = elRow.childNodes[columnBIndex];
elRow.removeChild(columnBCell);
elRow.insertBefore(columnBCell, columnACell);
}
}
columns[columnAIndex] = columnB;
columns[columnBIndex] = columnA;
updateColumnWidths();
}
document.onmousemove = function(event) {
if (lastResizeEvent) {
startTh = null;
let column = columns[resizeColumnIndex];
@ -1150,7 +1183,7 @@ function loaded() {
}
let fullWidth = elEntityTableBody.offsetWidth;
let dx = ev.clientX - startResizeEvent.clientX;
let dx = event.clientX - lastResizeEvent.clientX;
let dPct = dx / fullWidth;
let newColWidth = column.width + dPct;
@ -1160,14 +1193,60 @@ function loaded() {
column.width += dPct;
nextColumn.width -= dPct;
updateColumnWidths();
startResizeEvent = ev;
lastResizeEvent = event;
}
} else if (elTargetTh) {
let dxFromInitial = event.clientX - initialThEvent.clientX;
if (Math.abs(dxFromInitial) >= DELTA_X_MOVE_COLUMNS_THRESHOLD) {
elTargetTh.className = "dragging";
}
if (targetColumnIndex < columns.length - 1) {
let nextColumnIndex = targetColumnIndex + 1;
let nextColumnTh = columns[nextColumnIndex].elTh;
let nextColumnStartX = nextColumnTh.getBoundingClientRect().left;
if (event.clientX >= nextColumnStartX && event.clientX - lastColumnSwapPosition >= DELTA_X_COLUMN_SWAP_POSITION) {
swapColumns(targetColumnIndex, nextColumnIndex);
targetColumnIndex = nextColumnIndex;
lastColumnSwapPosition = event.clientX;
}
}
if (targetColumnIndex >= 1) {
let prevColumnIndex = targetColumnIndex - 1;
let prevColumnTh = columns[prevColumnIndex].elTh;
let prevColumnEndX = prevColumnTh.getBoundingClientRect().right;
if (event.clientX <= prevColumnEndX && lastColumnSwapPosition - event.clientX >= DELTA_X_COLUMN_SWAP_POSITION) {
swapColumns(prevColumnIndex, targetColumnIndex);
targetColumnIndex = prevColumnIndex;
lastColumnSwapPosition = event.clientX;
}
}
} else if (elTargetSpan) {
let dxFromInitial = event.clientX - initialThEvent.clientX;
if (Math.abs(dxFromInitial) >= DELTA_X_MOVE_COLUMNS_THRESHOLD) {
elTargetTh = elTargetSpan.parentNode;
elTargetTh.className = "dragging";
targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex"));
lastColumnSwapPosition = event.clientX;
elTargetSpan = null;
}
}
};
document.onmouseup = function(ev) {
startResizeEvent = null;
ev.stopPropagation();
document.onmouseup = function(event) {
if (elTargetTh) {
if (elTargetTh.className !== "dragging" && elTargetTh === event.target) {
let columnID = elTargetTh.getAttribute("columnID");
setSortColumn(columnID);
}
elTargetTh.className = "";
} else if (elTargetSpan) {
let columnID = elTargetSpan.parentNode.getAttribute("columnID");
setSortColumn(columnID);
}
lastResizeEvent = null;
elTargetTh = null;
elTargetSpan = null;
initialThEvent = null;
};
function setSpaceMode(spaceMode) {
@ -1287,8 +1366,9 @@ function loaded() {
});
augmentSpinButtons();
disableDragDrop();
document.addEventListener("contextmenu", function (event) {
document.addEventListener("contextmenu", function(event) {
entityListContextMenu.close();
// Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked

View file

@ -75,7 +75,7 @@ const GROUPS = [
},
{
label: "Parent Joint Index",
type: "number",
type: "number-draggable",
propertyID: "parentJointIndex",
},
{
@ -135,7 +135,7 @@ const GROUPS = [
},
{
label: "Line Height",
type: "number",
type: "number-draggable",
min: 0,
step: 0.005,
decimals: 4,
@ -183,7 +183,7 @@ const GROUPS = [
},
{
label: "Light Intensity",
type: "number",
type: "number-draggable",
min: 0,
max: 10,
step: 0.1,
@ -193,7 +193,7 @@ const GROUPS = [
},
{
label: "Light Horizontal Angle",
type: "number",
type: "number-draggable",
multiplier: DEGREES_TO_RADIANS,
decimals: 2,
unit: "deg",
@ -202,7 +202,7 @@ const GROUPS = [
},
{
label: "Light Vertical Angle",
type: "number",
type: "number-draggable",
multiplier: DEGREES_TO_RADIANS,
decimals: 2,
unit: "deg",
@ -241,7 +241,7 @@ const GROUPS = [
},
{
label: "Ambient Intensity",
type: "number",
type: "number-draggable",
min: 0,
max: 10,
step: 0.1,
@ -270,7 +270,7 @@ const GROUPS = [
},
{
label: "Range",
type: "number",
type: "number-draggable",
min: 5,
max: 10000,
step: 5,
@ -287,7 +287,7 @@ const GROUPS = [
},
{
label: "Base",
type: "number",
type: "number-draggable",
min: -1000,
max: 1000,
step: 10,
@ -298,7 +298,7 @@ const GROUPS = [
},
{
label: "Ceiling",
type: "number",
type: "number-draggable",
min: -1000,
max: 5000,
step: 10,
@ -315,7 +315,7 @@ const GROUPS = [
},
{
label: "Background Blend",
type: "number",
type: "number-draggable",
min: 0,
max: 1,
step: 0.01,
@ -337,7 +337,7 @@ const GROUPS = [
},
{
label: "Glare Angle",
type: "number",
type: "number-draggable",
min: 0,
max: 180,
step: 1,
@ -353,7 +353,7 @@ const GROUPS = [
},
{
label: "Bloom Intensity",
type: "number",
type: "number-draggable",
min: 0,
max: 1,
step: 0.01,
@ -363,7 +363,7 @@ const GROUPS = [
},
{
label: "Bloom Threshold",
type: "number",
type: "number-draggable",
min: 0,
max: 1,
step: 0.01,
@ -373,7 +373,7 @@ const GROUPS = [
},
{
label: "Bloom Size",
type: "number",
type: "number-draggable",
min: 0,
max: 2,
step: 0.01,
@ -390,7 +390,9 @@ const GROUPS = [
{
label: "Model",
type: "string",
placeholder: "URL",
propertyID: "modelURL",
hideIfCertified: true,
},
{
label: "Collision Shape",
@ -404,11 +406,13 @@ const GROUPS = [
label: "Compound Shape",
type: "string",
propertyID: "compoundShapeURL",
hideIfCertified: true,
},
{
label: "Animation",
type: "string",
propertyID: "animation.url",
hideIfCertified: true,
},
{
label: "Play Automatically",
@ -432,22 +436,22 @@ const GROUPS = [
},
{
label: "Animation Frame",
type: "number",
type: "number-draggable",
propertyID: "animation.currentFrame",
},
{
label: "First Frame",
type: "number",
type: "number-draggable",
propertyID: "animation.firstFrame",
},
{
label: "Last Frame",
type: "number",
type: "number-draggable",
propertyID: "animation.lastFrame",
},
{
label: "Animation FPS",
type: "number",
type: "number-draggable",
propertyID: "animation.fps",
},
{
@ -460,6 +464,7 @@ const GROUPS = [
type: "textarea",
propertyID: "originalTextures",
readOnly: true,
hideIfCertified: true,
},
]
},
@ -486,7 +491,7 @@ const GROUPS = [
},
{
label: "Source Resolution",
type: "number",
type: "number-draggable",
propertyID: "dpi",
},
]
@ -503,7 +508,7 @@ const GROUPS = [
},
{
label: "Intensity",
type: "number",
type: "number-draggable",
min: 0,
step: 0.1,
decimals: 1,
@ -511,7 +516,7 @@ const GROUPS = [
},
{
label: "Fall-Off Radius",
type: "number",
type: "number-draggable",
min: 0,
step: 0.1,
decimals: 1,
@ -525,14 +530,14 @@ const GROUPS = [
},
{
label: "Spotlight Exponent",
type: "number",
type: "number-draggable",
step: 0.01,
decimals: 2,
propertyID: "exponent",
},
{
label: "Spotlight Cut-Off",
type: "number",
type: "number-draggable",
step: 0.01,
decimals: 2,
propertyID: "cutoff",
@ -563,7 +568,7 @@ const GROUPS = [
},
{
label: "Submesh to Replace",
type: "number",
type: "number-draggable",
min: 0,
step: 1,
propertyID: "submeshToReplace",
@ -577,7 +582,7 @@ const GROUPS = [
},
{
label: "Priority",
type: "number",
type: "number-draggable",
min: 0,
propertyID: "priority",
},
@ -612,7 +617,7 @@ const GROUPS = [
},
{
label: "Material Rotation",
type: "number",
type: "number-draggable",
step: 0.1,
decimals: 2,
unit: "deg",
@ -636,7 +641,7 @@ const GROUPS = [
},
{
label: "Lifespan",
type: "number",
type: "number-draggable",
unit: "s",
min: 0.01,
max: 10,
@ -646,7 +651,7 @@ const GROUPS = [
},
{
label: "Max Particles",
type: "number",
type: "number-draggable",
min: 1,
max: 10000,
step: 1,
@ -667,7 +672,7 @@ const GROUPS = [
properties: [
{
label: "Emit Rate",
type: "number",
type: "number-draggable",
min: 1,
max: 1000,
step: 1,
@ -675,7 +680,7 @@ const GROUPS = [
},
{
label: "Emit Speed",
type: "number",
type: "number-draggable",
min: 0,
max: 5,
step: 0.01,
@ -684,7 +689,7 @@ const GROUPS = [
},
{
label: "Speed Spread",
type: "number",
type: "number-draggable",
min: 0,
max: 5,
step: 0.01,
@ -703,7 +708,7 @@ const GROUPS = [
},
{
label: "Emit Radius Start",
type: "number",
type: "number-draggable",
min: 0,
max: 1,
step: 0.01,
@ -735,10 +740,11 @@ const GROUPS = [
{
type: "triple",
label: "Size",
propertyID: "particleRadiusTriple",
properties: [
{
label: "Start",
type: "number",
type: "number-draggable",
min: 0,
max: 4,
step: 0.01,
@ -748,7 +754,7 @@ const GROUPS = [
},
{
label: "Middle",
type: "number",
type: "number-draggable",
min: 0,
max: 4,
step: 0.01,
@ -757,7 +763,7 @@ const GROUPS = [
},
{
label: "Finish",
type: "number",
type: "number-draggable",
min: 0,
max: 4,
step: 0.01,
@ -769,7 +775,7 @@ const GROUPS = [
},
{
label: "Size Spread",
type: "number",
type: "number-draggable",
min: 0,
max: 4,
step: 0.01,
@ -786,6 +792,7 @@ const GROUPS = [
{
type: "triple",
label: "Color",
propertyID: "particleColorTriple",
properties: [
{
label: "Start",
@ -822,10 +829,11 @@ const GROUPS = [
{
type: "triple",
label: "Alpha",
propertyID: "particleAlphaTriple",
properties: [
{
label: "Start",
type: "number",
type: "number-draggable",
min: 0,
max: 1,
step: 0.01,
@ -835,7 +843,7 @@ const GROUPS = [
},
{
label: "Middle",
type: "number",
type: "number-draggable",
min: 0,
max: 1,
step: 0.01,
@ -844,7 +852,7 @@ const GROUPS = [
},
{
label: "Finish",
type: "number",
type: "number-draggable",
min: 0,
max: 1,
step: 0.01,
@ -856,7 +864,7 @@ const GROUPS = [
},
{
label: "Alpha Spread",
type: "number",
type: "number-draggable",
min: 0,
max: 1,
step: 0.01,
@ -898,10 +906,11 @@ const GROUPS = [
{
type: "triple",
label: "Spin",
propertyID: "particleSpinTriple",
properties: [
{
label: "Start",
type: "number",
type: "number-draggable",
min: -360,
max: 360,
step: 1,
@ -913,7 +922,7 @@ const GROUPS = [
},
{
label: "Middle",
type: "number",
type: "number-draggable",
min: -360,
max: 360,
step: 1,
@ -924,7 +933,7 @@ const GROUPS = [
},
{
label: "Finish",
type: "number",
type: "number-draggable",
min: -360,
max: 360,
step: 1,
@ -938,7 +947,7 @@ const GROUPS = [
},
{
label: "Spin Spread",
type: "number",
type: "number-draggable",
min: 0,
max: 360,
step: 1,
@ -962,10 +971,11 @@ const GROUPS = [
{
type: "triple",
label: "Horizontal Angle",
propertyID: "particlePolarTriple",
properties: [
{
label: "Start",
type: "number",
type: "number-draggable",
min: 0,
max: 180,
step: 1,
@ -976,7 +986,7 @@ const GROUPS = [
},
{
label: "Finish",
type: "number",
type: "number-draggable",
min: 0,
max: 180,
step: 1,
@ -990,10 +1000,11 @@ const GROUPS = [
{
type: "triple",
label: "Vertical Angle",
propertyID: "particleAzimuthTriple",
properties: [
{
label: "Start",
type: "number",
type: "number-draggable",
min: -180,
max: 180,
step: 1,
@ -1004,7 +1015,7 @@ const GROUPS = [
},
{
label: "Finish",
type: "number",
type: "number-draggable",
min: -180,
max: 180,
step: 1,
@ -1089,7 +1100,7 @@ const GROUPS = [
},
{
label: "Scale",
type: "number",
type: "number-draggable",
defaultValue: 100,
unit: "%",
buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions },
@ -1131,14 +1142,14 @@ const GROUPS = [
},
{
label: "Clone Lifetime",
type: "number",
type: "number-draggable",
unit: "s",
propertyID: "cloneLifetime",
showPropertyRule: { "cloneable": "true" },
},
{
label: "Clone Limit",
type: "number",
type: "number-draggable",
propertyID: "cloneLimit",
showPropertyRule: { "cloneable": "true" },
},
@ -1181,6 +1192,7 @@ const GROUPS = [
buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ],
propertyID: "script",
placeholder: "URL",
hideIfCertified: true,
},
{
label: "Server Script",
@ -1197,7 +1209,7 @@ const GROUPS = [
},
{
label: "Lifetime",
type: "string",
type: "number",
unit: "s",
propertyID: "lifetime",
},
@ -1267,6 +1279,7 @@ const GROUPS = [
placeholder: "URL",
propertyID: "collisionSoundURL",
showPropertyRule: { "collisionless": "false" },
hideIfCertified: true,
},
{
label: "Dynamic",
@ -1291,7 +1304,7 @@ const GROUPS = [
},
{
label: "Linear Damping",
type: "number",
type: "number-draggable",
min: 0,
max: 1,
step: 0.01,
@ -1310,7 +1323,7 @@ const GROUPS = [
},
{
label: "Angular Damping",
type: "number",
type: "number-draggable",
min: 0,
max: 1,
step: 0.01,
@ -1319,7 +1332,7 @@ const GROUPS = [
},
{
label: "Bounciness",
type: "number",
type: "number-draggable",
min: 0,
max: 1,
step: 0.01,
@ -1328,7 +1341,7 @@ const GROUPS = [
},
{
label: "Friction",
type: "number",
type: "number-draggable",
min: 0,
max: 10,
step: 0.1,
@ -1337,7 +1350,7 @@ const GROUPS = [
},
{
label: "Density",
type: "number",
type: "number-draggable",
min: 100,
max: 10000,
step: 1,
@ -1431,13 +1444,13 @@ const TEXTURE_ELEMENTS = {
const JSON_EDITOR_ROW_DIV_INDEX = 2;
var elGroups = {};
var properties = {};
var colorPickers = {};
var particlePropertyUpdates = {};
var selectedEntityProperties;
var lastEntityID = null;
var createAppTooltip = new CreateAppTooltip();
let elGroups = {};
let properties = {};
let colorPickers = {};
let particlePropertyUpdates = {};
let selectedEntityProperties;
let lastEntityID = null;
let createAppTooltip = new CreateAppTooltip();
let currentSpaceMode = PROPERTY_SPACE_MODE.LOCAL;
function createElementFromHTML(htmlString) {
@ -1454,12 +1467,13 @@ function getPropertyInputElement(propertyID) {
let property = properties[propertyID];
switch (property.data.type) {
case 'string':
case 'number':
case 'bool':
case 'dropdown':
case 'textarea':
case 'texture':
return property.elInput;
case 'number':
case 'number-draggable':
return property.elNumber.elInput;
case 'vec3':
case 'vec2':
@ -1529,15 +1543,20 @@ function resetProperties() {
let propertyData = property.data;
switch (propertyData.type) {
case 'number':
case 'string': {
property.elInput.value = "";
if (propertyData.defaultValue !== undefined) {
property.elInput.value = propertyData.defaultValue;
} else {
property.elInput.value = "";
}
break;
}
case 'bool': {
property.elInput.checked = false;
break;
}
case 'number': {
case 'number-draggable': {
if (propertyData.defaultValue !== undefined) {
property.elNumber.setValue(propertyData.defaultValue);
} else {
@ -1691,7 +1710,7 @@ function updateProperty(originalPropertyName, propertyValue, isParticleProperty)
}
}
var particleSyncDebounce = _.debounce(function () {
let particleSyncDebounce = _.debounce(function () {
updateProperties(particlePropertyUpdates);
particlePropertyUpdates = {};
}, DEBOUNCE_TIMEOUT);
@ -1825,7 +1844,7 @@ function createStringProperty(property, elProperty) {
type="text"
${propertyData.placeholder ? 'placeholder="' + propertyData.placeholder + '"' : ''}
${propertyData.readOnly ? 'readonly' : ''}></input>
`)
`);
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
@ -1873,7 +1892,45 @@ function createBoolProperty(property, elProperty) {
return elInput;
}
function createNumberProperty(property, elProperty) {
function createNumberProperty(property, elProperty) {
let elementID = property.elementID;
let propertyData = property.data;
elProperty.className = "text";
let elInput = createElementFromHTML(`
<input id="${elementID}"
class='hide-spinner'
type="number"
${propertyData.placeholder ? 'placeholder="' + propertyData.placeholder + '"' : ''}
${propertyData.readOnly ? 'readonly' : ''}></input>
`)
if (propertyData.min !== undefined) {
elInput.setAttribute("min", propertyData.min);
}
if (propertyData.max !== undefined) {
elInput.setAttribute("max", propertyData.max);
}
if (propertyData.step !== undefined) {
elInput.setAttribute("step", propertyData.step);
}
if (propertyData.defaultValue !== undefined) {
elInput.value = propertyData.defaultValue;
}
elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(property));
elProperty.appendChild(elInput);
if (propertyData.buttons !== undefined) {
addButtons(elProperty, elementID, propertyData.buttons, false);
}
return elInput;
}
function createNumberDraggableProperty(property, elProperty) {
let elementID = property.elementID;
let propertyData = property.data;
@ -2217,7 +2274,11 @@ function createProperty(propertyData, propertyElementID, propertyName, propertyI
break;
}
case 'number': {
property.elNumber = createNumberProperty(property, elProperty);
property.elInput = createNumberProperty(property, elProperty);
break;
}
case 'number-draggable': {
property.elNumber = createNumberDraggableProperty(property, elProperty);
break;
}
case 'vec3': {
@ -2362,31 +2423,41 @@ function saveUserData() {
saveJSONUserData(true);
}
function setJSONError(property, isError) {
$("#property-"+ property + "-editor").toggleClass('error', isError);
let $propertyUserDataEditorStatus = $("#property-"+ property + "-editorStatus");
$propertyUserDataEditorStatus.css('display', isError ? 'block' : 'none');
$propertyUserDataEditorStatus.text(isError ? 'Invalid JSON code - look for red X in your code' : '');
}
function setUserDataFromEditor(noUpdate) {
let json = null;
let errorFound = false;
try {
json = editor.get();
} catch (e) {
alert('Invalid JSON code - look for red X in your code ', +e);
errorFound = true;
}
if (json === null) {
setJSONError('userData', errorFound);
if (errorFound) {
return;
}
let text = editor.getText();
if (noUpdate) {
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "saveUserData",
properties: {
userData: text
}
})
);
} else {
let text = editor.getText();
if (noUpdate === true) {
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "saveUserData",
properties: {
userData: text
}
})
);
return;
} else {
updateProperty('userData', text, false);
}
updateProperty('userData', text, false);
}
}
@ -2445,7 +2516,7 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r
updateProperties(propertyUpdate, false);
}
var editor = null;
let editor = null;
function createJSONEditor() {
let container = document.getElementById("property-userData-editor");
@ -2508,9 +2579,10 @@ function hideUserDataSaved() {
function showStaticUserData() {
if (editor !== null) {
$('#property-userData-static').show();
$('#property-userData-static').css('height', $('#property-userData-editor').height());
$('#property-userData-static').text(editor.getText());
let $propertyUserDataStatic = $('#property-userData-static');
$propertyUserDataStatic.show();
$propertyUserDataStatic.css('height', $('#property-userData-editor').height());
$propertyUserDataStatic.text(editor.getText());
}
}
@ -2531,12 +2603,13 @@ function getEditorJSON() {
function deleteJSONEditor() {
if (editor !== null) {
setJSONError('userData', false);
editor.destroy();
editor = null;
}
}
var savedJSONTimer = null;
let savedJSONTimer = null;
function saveJSONUserData(noUpdate) {
setUserDataFromEditor(noUpdate);
@ -2581,33 +2654,35 @@ function saveMaterialData() {
function setMaterialDataFromEditor(noUpdate) {
let json = null;
let errorFound = false;
try {
json = materialEditor.get();
} catch (e) {
alert('Invalid JSON code - look for red X in your code ', +e);
errorFound = true;
}
if (json === null) {
setJSONError('materialData', errorFound);
if (errorFound) {
return;
}
let text = materialEditor.getText();
if (noUpdate) {
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "saveMaterialData",
properties: {
materialData: text
}
})
);
} else {
let text = materialEditor.getText();
if (noUpdate === true) {
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "saveMaterialData",
properties: {
materialData: text
}
})
);
return;
} else {
updateProperty('materialData', text, false);
}
updateProperty('materialData', text, false);
}
}
var materialEditor = null;
let materialEditor = null;
function createJSONMaterialEditor() {
let container = document.getElementById("property-materialData-editor");
@ -2670,9 +2745,10 @@ function hideMaterialDataSaved() {
function showStaticMaterialData() {
if (materialEditor !== null) {
$('#property-materialData-static').show();
$('#property-materialData-static').css('height', $('#property-materialData-editor').height());
$('#property-materialData-static').text(materialEditor.getText());
let $propertyMaterialDataStatic = $('#property-materialData-static');
$propertyMaterialDataStatic.show();
$propertyMaterialDataStatic.css('height', $('#property-materialData-editor').height());
$propertyMaterialDataStatic.text(materialEditor.getText());
}
}
@ -2693,12 +2769,13 @@ function getMaterialEditorJSON() {
function deleteJSONMaterialEditor() {
if (materialEditor !== null) {
setJSONError('materialData', false);
materialEditor.destroy();
materialEditor = null;
}
}
var savedMaterialJSONTimer = null;
let savedMaterialJSONTimer = null;
function saveJSONMaterialData(noUpdate) {
setMaterialDataFromEditor(noUpdate);
@ -2872,23 +2949,20 @@ function loaded() {
elGroup.appendChild(elContainer);
}
elLabel = document.createElement('label');
elLabel.setAttribute("for", propertyElementID);
let labelText = propertyData.label !== undefined ? propertyData.label : "";
let className = '';
if (propertyData.indentedLabel || propertyData.showPropertyRule !== undefined) {
let elSpan = document.createElement('span');
elSpan.className = 'indented';
elSpan.innerText = propertyData.label !== undefined ? propertyData.label : "";
elLabel.appendChild(elSpan);
} else {
elLabel.innerText = propertyData.label !== undefined ? propertyData.label : "";
className = 'indented';
}
elLabel = createElementFromHTML(
`<label><span class="${className}">${labelText}</span></label>`);
elContainer.appendChild(elLabel);
} else {
elContainer = document.getElementById(propertyData.replaceID);
}
if (elLabel) {
createAppTooltip.registerTooltipElement(elLabel, propertyID);
createAppTooltip.registerTooltipElement(elLabel.childNodes[0], propertyID);
}
let elProperty = createElementFromHTML('<div style="width: 100%;"></div>');
@ -2912,7 +2986,10 @@ function loaded() {
property.elContainer = elContainer;
property.spaceMode = propertySpaceMode;
elWrapper.appendChild(createElementFromHTML(`<div class="triple-label">${innerPropertyData.label}</div>`));
let elLabel = createElementFromHTML(`<div class="triple-label">${innerPropertyData.label}</div>`);
createAppTooltip.registerTooltipElement(elLabel, propertyID);
elWrapper.appendChild(elLabel);
if (property.type !== 'placeholder') {
properties[propertyID] = property;
@ -2958,7 +3035,7 @@ function loaded() {
let elServerScriptError = document.getElementById("property-serverScripts-error");
let elServerScriptStatus = document.getElementById("property-serverScripts-status");
elServerScriptError.value = data.errorInfo;
// If we just set elServerScriptError's diplay to block or none, we still end up with
// If we just set elServerScriptError's display to block or none, we still end up with
// it's parent contributing 21px bottom padding even when elServerScriptError is display:none.
// So set it's parent to block or none
elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none";
@ -3068,6 +3145,15 @@ function loaded() {
showGroupsForType(selectedEntityProperties.type);
if (selectedEntityProperties.locked) {
disableProperties();
getPropertyInputElement("locked").removeAttribute('disabled');
} else {
enableProperties();
disableSaveUserDataButton();
disableSaveMaterialDataButton()
}
for (let propertyID in properties) {
let property = properties[propertyID];
let propertyData = property.data;
@ -3078,10 +3164,19 @@ function loaded() {
if (propertyValue === undefined && !isSubProperty) {
continue;
}
if (propertyData.hideIfCertified) {
let shouldHide = selectedEntityProperties.certificateID !== "";
if (shouldHide) {
propertyValue = "** Certified **";
}
property.elInput.disabled = shouldHide;
}
let isPropertyNotNumber = false;
switch (propertyData.type) {
case 'number':
case 'number-draggable':
isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null;
break;
case 'vec3':
@ -3114,6 +3209,10 @@ function loaded() {
break;
}
case 'number': {
property.elInput.value = propertyValue;
break;
}
case 'number-draggable': {
let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1;
let value = propertyValue / multiplier;
if (propertyData.round !== undefined) {
@ -3256,15 +3355,6 @@ function loaded() {
hideMaterialDataSaved();
}
if (selectedEntityProperties.locked) {
disableProperties();
getPropertyInputElement("locked").removeAttribute('disabled');
} else {
enableProperties();
disableSaveUserDataButton();
disableSaveMaterialDataButton()
}
let activeElement = document.activeElement;
if (doSelectElement && typeof activeElement.select !== "undefined") {
activeElement.select();
@ -3317,12 +3407,15 @@ function loaded() {
elStaticUserData.setAttribute("id", userDataElementID + "-static");
let elUserDataEditor = document.createElement('div');
elUserDataEditor.setAttribute("id", userDataElementID + "-editor");
let elUserDataEditorStatus = document.createElement('div');
elUserDataEditorStatus.setAttribute("id", userDataElementID + "-editorStatus");
let elUserDataSaved = document.createElement('span');
elUserDataSaved.setAttribute("id", userDataElementID + "-saved");
elUserDataSaved.innerText = "Saved!";
elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved);
elDiv.insertBefore(elStaticUserData, elUserData);
elDiv.insertBefore(elUserDataEditor, elUserData);
elDiv.insertBefore(elUserDataEditorStatus, elUserData);
// Material Data
let materialDataProperty = properties["materialData"];
@ -3333,12 +3426,15 @@ function loaded() {
elStaticMaterialData.setAttribute("id", materialDataElementID + "-static");
let elMaterialDataEditor = document.createElement('div');
elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor");
let elMaterialDataEditorStatus = document.createElement('div');
elMaterialDataEditorStatus.setAttribute("id", materialDataElementID + "-editorStatus");
let elMaterialDataSaved = document.createElement('span');
elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved");
elMaterialDataSaved.innerText = "Saved!";
elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved);
elDiv.insertBefore(elStaticMaterialData, elMaterialData);
elDiv.insertBefore(elMaterialDataEditor, elMaterialData);
elDiv.insertBefore(elMaterialDataEditorStatus, elMaterialData);
// Special Property Callbacks
let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace");
@ -3529,6 +3625,7 @@ function loaded() {
});
augmentSpinButtons();
disableDragDrop();
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
document.addEventListener("contextmenu", function(event) {

View file

@ -108,6 +108,7 @@ function loaded() {
});
augmentSpinButtons();
disableDragDrop();
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' }));
});

View file

@ -9,10 +9,6 @@
const SCROLL_ROWS = 2; // number of rows used as scrolling buffer, each time we pass this number of rows we scroll
const FIRST_ROW_INDEX = 2; // the first elRow element's index in the child nodes of the table body
debugPrint = function (message) {
console.log(message);
};
function ListView(elTableBody, elTableScroll, elTableHeaderRow, createRowFunction, updateRowFunction, clearRowFunction,
preRefreshFunction, postRefreshFunction, preResizeFunction, WINDOW_NONVARIABLE_HEIGHT) {
this.elTableBody = elTableBody;
@ -246,7 +242,7 @@ ListView.prototype = {
resize: function() {
if (!this.elTableBody || !this.elTableScroll) {
debugPrint("ListView.resize - no valid table body or table scroll element");
console.log("ListView.resize - no valid table body or table scroll element");
return;
}
this.preResizeFunction();
@ -288,7 +284,7 @@ ListView.prototype = {
initialize: function() {
if (!this.elTableBody || !this.elTableScroll) {
debugPrint("ListView.initialize - no valid table body or table scroll element");
console.log("ListView.initialize - no valid table body or table scroll element");
return;
}

View file

@ -0,0 +1,27 @@
//
// utils.js
//
// Created by David Back on 19 Nov 2018
// Copyright 2016 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
//
function disableDragDrop() {
document.addEventListener("drop", function(event) {
event.preventDefault();
});
document.addEventListener("dragover", function(event) {
event.dataTransfer.effectAllowed = "none";
event.dataTransfer.dropEffect = "none";
event.preventDefault();
});
document.addEventListener("dragenter", function(event) {
event.dataTransfer.effectAllowed = "none";
event.dataTransfer.dropEffect = "none";
event.preventDefault();
}, false);
}

View file

@ -164,7 +164,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
var cameraPosition = Camera.position;
PROFILE("getMultipleProperties", function () {
var multipleProperties = Entities.getMultipleEntityProperties(ids, ['name', 'type', 'locked',
'visible', 'renderInfo', 'modelURL', 'materialURL', 'script']);
'visible', 'renderInfo', 'modelURL', 'materialURL', 'script', 'certificateID']);
for (var i = 0; i < multipleProperties.length; i++) {
var properties = multipleProperties[i];
@ -182,6 +182,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
url: url,
locked: properties.locked,
visible: properties.visible,
certificateID: properties.certificateID,
verticesCount: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.verticesCount) : ""),
texturesCount: (properties.renderInfo !== undefined ?