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

This commit is contained in:
Wayne Chen 2018-12-07 17:39:51 -08:00
commit a787b39cae
50 changed files with 873 additions and 815 deletions

View file

@ -19,6 +19,9 @@
#include <AnimUtil.h> #include <AnimUtil.h>
#include <ClientTraitsHandler.h> #include <ClientTraitsHandler.h>
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <ResourceRequestObserver.h>
#include <AvatarLogging.h>
ScriptableAvatar::ScriptableAvatar() { ScriptableAvatar::ScriptableAvatar() {
_clientTraitsHandler.reset(new ClientTraitsHandler(this)); _clientTraitsHandler.reset(new ClientTraitsHandler(this));
@ -62,11 +65,28 @@ AnimationDetails ScriptableAvatar::getAnimationDetails() {
return _animationDetails; 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) { void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_bind.reset(); _bind.reset();
_animSkeleton.reset(); _animSkeleton.reset();
AvatarData::setSkeletonModelURL(skeletonModelURL); AvatarData::setSkeletonModelURL(skeletonModelURL);
updateJointMappings();
} }
static AnimPose composeAnimPose(const HFMJoint& joint, const glm::quat rotation, const glm::vec3 translation) { 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) { 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 // Run animation
if (_animation && _animation->isLoaded() && _animation->getFrames().size() > 0 && !_bind.isNull() && _bind->isLoaded()) { if (_animation && _animation->isLoaded() && _animation->getFrames().size() > 0 && !_bind.isNull() && _bind->isLoaded()) {
if (!_animSkeleton) { if (!_animSkeleton) {
@ -146,6 +162,82 @@ void ScriptableAvatar::update(float deltatime) {
_clientTraitsHandler->sendChangedTraitsToMixer(); _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) { void ScriptableAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) {
_headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement); _headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement);
} }

View file

@ -153,6 +153,27 @@ public:
*/ */
Q_INVOKABLE AnimationDetails getAnimationDetails(); 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 void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override; virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override;
@ -167,12 +188,23 @@ public:
public slots: public slots:
void update(float deltatime); void update(float deltatime);
/**jsdoc
* @function MyAvatar.setJointMappingsFromNetworkReply
*/
void setJointMappingsFromNetworkReply();
private: private:
AnimationPointer _animation; AnimationPointer _animation;
AnimationDetails _animationDetails; AnimationDetails _animationDetails;
QStringList _maskedJoints; QStringList _maskedJoints;
AnimationPointer _bind; // a sleazy way to get the skeleton, given the various library/cmake dependencies AnimationPointer _bind; // a sleazy way to get the skeleton, given the various library/cmake dependencies
std::shared_ptr<AnimSkeleton> _animSkeleton; 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 #endif // hifi_ScriptableAvatar_h

View file

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

View file

@ -3172,24 +3172,34 @@ void DomainServer::processPathQueryPacket(QSharedPointer<ReceivedMessage> messag
const QString PATH_VIEWPOINT_KEY = "viewpoint"; const QString PATH_VIEWPOINT_KEY = "viewpoint";
const QString INDEX_PATH = "/"; const QString INDEX_PATH = "/";
// check out paths in the _configMap to see if we have a match QString responseViewpoint;
auto keypath = QString(PATHS_SETTINGS_KEYPATH_FORMAT).arg(SETTINGS_PATHS_KEY).arg(pathQuery);
QVariant pathMatch = _settingsManager.valueForKeyPath(keypath);
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 // we got a match, respond with the resulting viewpoint
auto nodeList = DependencyManager::get<LimitedNodeList>(); 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()) { if (!responseViewpoint.isEmpty()) {
QByteArray viewpointUTF8 = responseViewpoint.toUtf8(); QByteArray viewpointUTF8 = responseViewpoint.toUtf8();

View file

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

View file

@ -1,47 +1,32 @@
const vec3 COLOR = vec3(0x00, 0xD8, 0x02) / vec3(0xFF); // Replicate the default skybox texture
const float CUTOFF = 0.65;
const float NOISE_MULT = 8.0;
const float NOISE_POWER = 1.0;
float noise4D(vec4 p) { const int NUM_COLORS = 5;
return fract(sin(dot(p ,vec4(12.9898,78.233,126.7235, 593.2241))) * 43758.5453); 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[](
float worley4D(vec4 p) { GREENISH,
float r = 3.0; GREENISH,
vec4 f = floor(p); WHITISH,
vec4 x = fract(p); WHITISH,
for(int i = -1; i<=1; i++) vec3(0.6, 0.275, 0.706) // purple
{ );
for(int j = -1; j<=1; j++) const float PI = 3.14159265359;
{ const vec3 BLACK = vec3(0.0);
for(int k = -1; k<=1; k++) const vec3 SPACE_BLUE = vec3(0.0, 0.118, 0.392);
{ const float HORIZONTAL_OFFSET = 3.75;
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);
}
vec3 getSkyboxColor() { 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 <trackers/EyeTracker.h>
#include <avatars-renderer/ScriptAvatar.h> #include <avatars-renderer/ScriptAvatar.h>
#include <RenderableEntityItem.h> #include <RenderableEntityItem.h>
#include <procedural/ProceduralSkybox.h>
#include <model-networking/MaterialCache.h> #include <model-networking/MaterialCache.h>
#include "recording/ClipCache.h" #include "recording/ClipCache.h"
@ -2795,8 +2794,6 @@ void Application::initializeGL() {
_graphicsEngine.initializeGPU(_glWidget); _graphicsEngine.initializeGPU(_glWidget);
} }
static const QString SPLASH_SKYBOX{ "{\"ProceduralEntity\":{ \"version\":2, \"shaderUrl\":\"qrc:///shaders/splashSkybox.frag\" } }" };
void Application::initializeDisplayPlugins() { void Application::initializeDisplayPlugins() {
auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
Setting::Handle<QString> activeDisplayPluginSetting{ ACTIVE_DISPLAY_PLUGIN_SETTING_NAME, displayPlugins.at(0)->getName() }; Setting::Handle<QString> activeDisplayPluginSetting{ ACTIVE_DISPLAY_PLUGIN_SETTING_NAME, displayPlugins.at(0)->getName() };
@ -2832,45 +2829,6 @@ void Application::initializeDisplayPlugins() {
// Submit a default frame to render until the engine starts up // Submit a default frame to render until the engine starts up
updateRenderArgs(0.0f); 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() { void Application::initializeRenderEngine() {

View file

@ -73,7 +73,6 @@
#include "workload/GameWorkload.h" #include "workload/GameWorkload.h"
#include "graphics/GraphicsEngine.h" #include "graphics/GraphicsEngine.h"
#include <procedural/ProceduralSkybox.h>
#include <graphics/Skybox.h> #include <graphics/Skybox.h>
#include <ModelScriptingInterface.h> #include <ModelScriptingInterface.h>
@ -787,5 +786,6 @@ private:
bool _showTrackedObjects { false }; bool _showTrackedObjects { false };
bool _prevShowTrackedObjects { false }; bool _prevShowTrackedObjects { false };
}; };
#endif // hifi_Application_h #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" #include "Application.h"
GraphicsEngine::GraphicsEngine() { GraphicsEngine::GraphicsEngine() {
const QString SPLASH_SKYBOX { "{\"ProceduralEntity\":{ \"version\":2, \"shaderUrl\":\"qrc:///shaders/splashSkybox.frag\" } }" };
_splashScreen->parse(SPLASH_SKYBOX);
} }
GraphicsEngine::~GraphicsEngine() { GraphicsEngine::~GraphicsEngine() {
@ -54,6 +56,10 @@ void GraphicsEngine::initializeGPU(GLWidget* glwidget) {
glwidget->makeCurrent(); glwidget->makeCurrent();
_gpuContext = std::make_shared<gpu::Context>(); _gpuContext = std::make_shared<gpu::Context>();
_gpuContext->pushProgramsToSync(shader::allPrograms(), [this] {
_programsCompiled.store(true);
}, 1);
DependencyManager::get<TextureCache>()->setGPUContext(_gpuContext); 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 unsigned int THROTTLED_SIM_FRAMERATE = 15;
static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE; static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE;
bool GraphicsEngine::shouldPaint() const { bool GraphicsEngine::shouldPaint() const {
auto displayPlugin = qApp->getActiveDisplayPlugin(); auto displayPlugin = qApp->getActiveDisplayPlugin();
#ifdef DEBUG_PAINT_DELAY #ifdef DEBUG_PAINT_DELAY
@ -145,7 +147,7 @@ bool GraphicsEngine::shouldPaint() const {
// Throttle if requested // Throttle if requested
//if (displayPlugin->isThrottled() && (_graphicsEngine._renderEventHandler->_lastTimeRendered.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) { //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)) { (static_cast<RenderEventHandler*>(_renderEventHandler)->_lastTimeRendered.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) {
return false; return false;
} }
@ -158,8 +160,6 @@ bool GraphicsEngine::checkPendingRenderEvent() {
return (_renderEventHandler && static_cast<RenderEventHandler*>(_renderEventHandler)->_pendingRenderEvent.compare_exchange_strong(expected, true)); return (_renderEventHandler && static_cast<RenderEventHandler*>(_renderEventHandler)->_pendingRenderEvent.compare_exchange_strong(expected, true));
} }
void GraphicsEngine::render_performFrame() { void GraphicsEngine::render_performFrame() {
// Some plugins process message events, allowing paintGL to be called reentrantly. // Some plugins process message events, allowing paintGL to be called reentrantly.
@ -189,6 +189,7 @@ void GraphicsEngine::render_performFrame() {
glm::mat4 HMDSensorPose; glm::mat4 HMDSensorPose;
glm::mat4 eyeToWorld; glm::mat4 eyeToWorld;
glm::mat4 sensorToWorld; glm::mat4 sensorToWorld;
ViewFrustum viewFrustum;
bool isStereo; bool isStereo;
glm::mat4 stereoEyeOffsets[2]; glm::mat4 stereoEyeOffsets[2];
@ -211,6 +212,7 @@ void GraphicsEngine::render_performFrame() {
stereoEyeOffsets[eye] = _appRenderArgs._eyeOffsets[eye]; stereoEyeOffsets[eye] = _appRenderArgs._eyeOffsets[eye];
stereoEyeProjections[eye] = _appRenderArgs._eyeProjections[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) { gpu::doInBatch("Application_render::gpuContextReset", getGPUContext(), [&](gpu::Batch& batch) {
batch.resetStages(); batch.resetStages();
}); });
}
if (isStereo) {
{ renderArgs._context->enableStereo(true);
PROFILE_RANGE(render, "/renderOverlay"); renderArgs._context->setStereoProjections(stereoEyeProjections);
PerformanceTimer perfTimer("renderOverlay"); renderArgs._context->setStereoViews(stereoEyeOffsets);
// 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);
} }
gpu::FramebufferPointer finalFramebuffer; gpu::FramebufferPointer finalFramebuffer;
@ -245,21 +238,40 @@ void GraphicsEngine::render_performFrame() {
// Primary rendering pass // Primary rendering pass
auto framebufferCache = DependencyManager::get<FramebufferCache>(); auto framebufferCache = DependencyManager::get<FramebufferCache>();
finalFramebufferSize = framebufferCache->getFrameBufferSize(); 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(); finalFramebuffer = framebufferCache->getFramebuffer();
} }
{ if (!_programsCompiled.load()) {
if (isStereo) { gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {
renderArgs._context->enableStereo(true); batch.setFramebuffer(finalFramebuffer);
renderArgs._context->setStereoProjections(stereoEyeProjections); batch.enableSkybox(true);
renderArgs._context->setStereoViews(stereoEyeOffsets); 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(); PROFILE_RANGE(render, "/updateCompositor");
renderArgs._blitFramebuffer = finalFramebuffer; qApp->getApplicationCompositor().setFrameInfo(_renderFrameCount, eyeToWorld, sensorToWorld);
render_runRenderFrame(&renderArgs); }
{
PROFILE_RANGE(render, "/runRenderFrame");
renderArgs._hudOperator = displayPlugin->getHUDOperator();
renderArgs._hudTexture = qApp->getApplicationOverlay().getOverlayTexture();
renderArgs._blitFramebuffer = finalFramebuffer;
render_runRenderFrame(&renderArgs);
}
} }
auto frame = getGPUContext()->endFrame(); auto frame = getGPUContext()->endFrame();
@ -283,18 +295,19 @@ void GraphicsEngine::render_performFrame() {
renderArgs._blitFramebuffer.reset(); renderArgs._blitFramebuffer.reset();
renderArgs._context->enableStereo(false); renderArgs._context->enableStereo(false);
#if !defined(DISABLE_QML)
{ {
auto stats = Stats::getInstance(); auto stats = Stats::getInstance();
if (stats) { if (stats) {
stats->setRenderDetails(renderArgs._details); stats->setRenderDetails(renderArgs._details);
} }
} }
#endif
uint64_t lastPaintDuration = usecTimestampNow() - lastPaintBegin; uint64_t lastPaintDuration = usecTimestampNow() - lastPaintBegin;
_frameTimingsScriptingInterface.addValue(lastPaintDuration); _frameTimingsScriptingInterface.addValue(lastPaintDuration);
} }
void GraphicsEngine::editRenderArgs(RenderArgsEditor editor) { void GraphicsEngine::editRenderArgs(RenderArgsEditor editor) {
QMutexLocker renderLocker(&_renderArgsMutex); QMutexLocker renderLocker(&_renderArgsMutex);
editor(_appRenderArgs); editor(_appRenderArgs);

View file

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

View file

@ -327,7 +327,7 @@ std::vector<int> AnimSkeleton::lookUpJointIndices(const std::vector<QString>& jo
for (auto& name : jointNames) { for (auto& name : jointNames) {
int index = nameToJointIndex(name); int index = nameToJointIndex(name);
if (index == -1) { 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); 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 AvatarData::getJointIndex(const QString& name) const {
int result = getFauxJointIndex(name); int result = getFauxJointIndex(name);
if (result != -1) { return result;
return result;
}
QReadLocker readLock(&_jointDataLock);
return _fstJointIndices.value(name) - 1;
} }
QStringList AvatarData::getJointNames() const { QStringList AvatarData::getJointNames() const {
QReadLocker readLock(&_jointDataLock); return QStringList();
return _fstJointNames;
} }
glm::quat AvatarData::getOrientationOutbound() const { glm::quat AvatarData::getOrientationOutbound() const {
@ -2000,8 +1995,6 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
_skeletonModelURL = expanded; _skeletonModelURL = expanded;
updateJointMappings();
if (_clientTraitsHandler) { if (_clientTraitsHandler) {
_clientTraitsHandler->markTraitUpdated(AvatarTraits::SkeletonModelURL); _clientTraitsHandler->markTraitUpdated(AvatarTraits::SkeletonModelURL);
} }
@ -2097,58 +2090,6 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) {
setAttachmentData(attachmentData); 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) { void AvatarData::sendAvatarDataPacket(bool sendAll) {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
@ -2210,34 +2151,6 @@ void AvatarData::sendIdentityPacket() {
_identityDataChanged = false; _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_URL = QStringLiteral("modelUrl");
static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName"); static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName");
static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform"); static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform");

View file

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

View file

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

View file

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

View file

@ -417,6 +417,19 @@ QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) {
return filepath.mid(filepath.lastIndexOf('/') + 1); 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> getJointRotationOffsets(const QVariantHash& mapping) {
QMap<QString, glm::quat> jointRotationOffsets; QMap<QString, glm::quat> jointRotationOffsets;
static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset"; 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; std::map<QString, HFMLight> lights;
QVariantHash joints = mapping.value("joint").toHash(); QVariantHash joints = mapping.value("joint").toHash();
QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft"))); QString jointEyeLeftName = "EyeLeft";
QString jointEyeRightName = processID(getString(joints.value("jointEyeRight", "jointEyeRight"))); QString jointEyeRightName = "EyeRight";
QString jointNeckName = processID(getString(joints.value("jointNeck", "jointNeck"))); QString jointNeckName = "Neck";
QString jointRootName = processID(getString(joints.value("jointRoot", "jointRoot"))); QString jointRootName = "Hips";
QString jointLeanName = processID(getString(joints.value("jointLean", "jointLean"))); QString jointLeanName = "Spine";
QString jointHeadName = processID(getString(joints.value("jointHead", "jointHead"))); QString jointHeadName = "Head";
QString jointLeftHandName = processID(getString(joints.value("jointLeftHand", "jointLeftHand"))); QString jointLeftHandName = "LeftHand";
QString jointRightHandName = processID(getString(joints.value("jointRightHand", "jointRightHand"))); QString jointRightHandName = "RightHand";
QString jointEyeLeftID; QString jointEyeLeftID;
QString jointEyeRightID; QString jointEyeRightID;
QString jointNeckID; QString jointNeckID;
@ -519,6 +532,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
HFMModel& hfmModel = *hfmModelPtr; HFMModel& hfmModel = *hfmModelPtr;
hfmModel.originalURL = url; hfmModel.originalURL = url;
hfmModel.hfmToHifiJointNameMapping.clear();
hfmModel.hfmToHifiJointNameMapping = getJointNameMapping(mapping);
float unitScaleFactor = 1.0f; float unitScaleFactor = 1.0f;
glm::vec3 ambientColor; glm::vec3 ambientColor;
@ -587,34 +602,34 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
hifiGlobalNodeID = id; 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); 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); 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); jointNeckID = getID(object.properties);
} else if (name == jointRootName) { } else if (name == jointRootName || (hfmModel.hfmToHifiJointNameMapping.contains(jointRootName) && (name == hfmModel.hfmToHifiJointNameMapping[jointRootName]))) {
jointRootID = getID(object.properties); jointRootID = getID(object.properties);
} else if (name == jointLeanName) { } else if (name == jointLeanName || (hfmModel.hfmToHifiJointNameMapping.contains(jointLeanName) && (name == hfmModel.hfmToHifiJointNameMapping[jointLeanName]))) {
jointLeanID = getID(object.properties); jointLeanID = getID(object.properties);
} else if (name == jointHeadName) { } else if ((name == jointHeadName) || (hfmModel.hfmToHifiJointNameMapping.contains(jointHeadName) && (name == hfmModel.hfmToHifiJointNameMapping[jointHeadName]))) {
jointHeadID = getID(object.properties); 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); 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); 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); 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); jointRightToeID = getID(object.properties);
} }
@ -1388,6 +1403,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
} }
joint.inverseBindRotation = joint.inverseDefaultRotation; joint.inverseBindRotation = joint.inverseDefaultRotation;
joint.name = fbxModel.name; 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)) { foreach (const QString& childID, _connectionChildMap.values(modelID)) {
QString type = typeFlags.value(childID); QString type = typeFlags.value(childID);
@ -1400,7 +1418,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
joint.bindTransformFoundInCluster = false; joint.bindTransformFoundInCluster = false;
hfmModel.joints.append(joint); 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); QString rotationID = localRotations.value(modelID);
AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID)); AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID));
@ -1824,6 +1842,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
QString jointName = itr.key(); QString jointName = itr.key();
glm::quat rotationOffset = itr.value(); glm::quat rotationOffset = itr.value();
int jointIndex = hfmModel.getJointIndex(jointName); int jointIndex = hfmModel.getJointIndex(jointName);
if (hfmModel.hfmToHifiJointNameMapping.contains(jointName)) {
jointIndex = hfmModel.getJointIndex(jointName);
}
if (jointIndex != -1) { if (jointIndex != -1) {
hfmModel.jointRotationOffsets.insert(jointIndex, rotationOffset); 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 FREE_JOINT_FIELD = "freeJoint";
static const QString BLENDSHAPE_FIELD = "bs"; static const QString BLENDSHAPE_FIELD = "bs";
static const QString SCRIPT_FIELD = "script"; static const QString SCRIPT_FIELD = "script";
static const QString JOINT_NAME_MAPPING_FIELD = "jointMap";
class FSTReader { class FSTReader {
public: 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->setSubData(0, _transform._correction);
_pipeline._cameraCorrectionBuffer._buffer->flush(); _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! // Let's try to avoid to do that as much as possible!
void syncCache() final override; void syncCache() final override;
void syncProgram(const gpu::ShaderPointer& program) override;
// This is the ugly "download the pixels to sysmem for taking a snapshot" // This is the ugly "download the pixels to sysmem for taking a snapshot"
// Just avoid using it, it's ugly and will break performances // Just avoid using it, it's ugly and will break performances
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer,

View file

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

View file

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

View file

@ -18,8 +18,8 @@
float color_scalar_sRGBToLinear(float value) { float color_scalar_sRGBToLinear(float value) {
const float SRGB_ELBOW = 0.04045; 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) { vec3 color_sRGBToLinear(vec3 srgb) {

View file

@ -51,6 +51,7 @@ Context::~Context() {
delete batch; delete batch;
} }
_batchPool.clear(); _batchPool.clear();
_syncedPrograms.clear();
} }
void Context::shutdown() { void Context::shutdown() {
@ -346,6 +347,40 @@ Size Context::getTextureResourceIdealGPUMemSize() {
return Backend::textureResourceIdealGPUMemSize.getValue(); 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) { BatchPointer Context::acquireBatch(const char* name) {
Batch* rawBatch = nullptr; Batch* rawBatch = nullptr;
{ {

View file

@ -13,6 +13,7 @@
#include <assert.h> #include <assert.h>
#include <mutex> #include <mutex>
#include <queue>
#include <GLMHelpers.h> #include <GLMHelpers.h>
@ -61,6 +62,7 @@ public:
virtual void render(const Batch& batch) = 0; virtual void render(const Batch& batch) = 0;
virtual void syncCache() = 0; virtual void syncCache() = 0;
virtual void syncProgram(const gpu::ShaderPointer& program) = 0;
virtual void recycle() const = 0; virtual void recycle() const = 0;
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0; virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0;
@ -247,6 +249,20 @@ public:
static Size getTextureResourcePopulatedGPUMemSize(); static Size getTextureResourcePopulatedGPUMemSize();
static Size getTextureResourceIdealGPUMemSize(); 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: protected:
Context(const Context& context); Context(const Context& context);
@ -258,6 +274,11 @@ protected:
RangeTimerPointer _frameRangeTimer; RangeTimerPointer _frameRangeTimer;
StereoState _stereo; 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 // Sampled at the end of every frame, the stats of all the counters
mutable ContextStats _frameStats; mutable ContextStats _frameStats;

View file

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

View file

@ -313,6 +313,7 @@ public:
QList<QString> blendshapeChannelNames; QList<QString> blendshapeChannelNames;
QMap<int, glm::quat> jointRotationOffsets; 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) { void main(void) {
vec4 texel = texture(originalTexture, _texCoord0); 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.rgb *= _color.rgb;
packDeferredFragment( packDeferredFragment(

View file

@ -41,9 +41,9 @@ void main(void) {
applyFade(fadeParams, _positionWS.xyz, fadeEmissive); applyFade(fadeParams, _positionWS.xyz, fadeEmissive);
vec4 texel = texture(originalTexture, _texCoord0); 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.rgb *= _color.rgb;
texel.a = abs(_color.a); texel.a *= abs(_color.a);
const float ALPHA_THRESHOLD = 0.999; const float ALPHA_THRESHOLD = 0.999;
if (texel.a < ALPHA_THRESHOLD) { if (texel.a < ALPHA_THRESHOLD) {

View file

@ -29,9 +29,9 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
void main(void) { void main(void) {
vec4 texel = texture(originalTexture, _texCoord0); 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.rgb *= _color.rgb;
texel.a = abs(_color.a); texel.a *= abs(_color.a);
const float ALPHA_THRESHOLD = 0.999; const float ALPHA_THRESHOLD = 0.999;
if (texel.a < ALPHA_THRESHOLD) { if (texel.a < ALPHA_THRESHOLD) {

View file

@ -41,9 +41,9 @@ void main(void) {
applyFade(fadeParams, _positionWS.xyz, fadeEmissive); applyFade(fadeParams, _positionWS.xyz, fadeEmissive);
vec4 texel = texture(originalTexture, _texCoord0); 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.rgb *= _color.rgb;
texel.a = abs(_color.a); texel.a *= abs(_color.a);
const float ALPHA_THRESHOLD = 0.999; const float ALPHA_THRESHOLD = 0.999;
if (texel.a < ALPHA_THRESHOLD) { if (texel.a < ALPHA_THRESHOLD) {

View file

@ -29,7 +29,7 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
void main(void) { void main(void) {
vec4 texel = texture(originalTexture, _texCoord0); 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.rgb *= _color.rgb;
texel.a *= abs(_color.a); texel.a *= abs(_color.a);

View file

@ -47,7 +47,7 @@ void main(void) {
applyFade(fadeParams, _positionWS.xyz, fadeEmissive); applyFade(fadeParams, _positionWS.xyz, fadeEmissive);
vec4 texel = texture(originalTexture, _texCoord0); 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.rgb *= _color.rgb;
texel.a *= abs(_color.a); texel.a *= abs(_color.a);

View file

@ -28,7 +28,7 @@ layout(location=0) out vec4 _fragColor0;
void main(void) { void main(void) {
vec4 texel = texture(originalTexture, _texCoord0); 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.rgb *= _color.rgb;
texel.a *= abs(_color.a); texel.a *= abs(_color.a);

View file

@ -40,7 +40,7 @@ void main(void) {
applyFade(fadeParams, _positionWS.xyz, fadeEmissive); applyFade(fadeParams, _positionWS.xyz, fadeEmissive);
vec4 texel = texture(originalTexture, _texCoord0); 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.rgb *= _color.rgb;
texel.a *= abs(_color.a); texel.a *= abs(_color.a);

View file

@ -2061,68 +2061,6 @@ bool ScriptEngine::hasEntityScriptDetails(const EntityItemID& entityID) const {
return _entityScripts.contains(entityID); 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) { void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "loadEntityScript", QMetaObject::invokeMethod(this, "loadEntityScript",
@ -2147,40 +2085,6 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString&
updateEntityScriptStatus(entityID, EntityScriptStatus::PENDING, "...pending..."); 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 #ifdef DEBUG_ENTITY_STATES
{ {
EntityScriptDetails details; EntityScriptDetails details;
@ -2223,10 +2127,6 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString&
qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting"; qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting";
#endif #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); }, forceRedownload);
} }
@ -2301,13 +2201,6 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
newDetails.errorInfo = errorInfo; newDetails.errorInfo = errorInfo;
newDetails.status = status; newDetails.status = status;
setEntityScriptDetails(entityID, newDetails); 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 // NETWORK / FILESYSTEM ERRORS
@ -2441,9 +2334,6 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
callEntityScriptMethod(entityID, "preload"); callEntityScriptMethod(entityID, "preload");
emit entityScriptPreloadFinished(entityID); emit entityScriptPreloadFinished(entityID);
_occupiedScriptURLs.remove(entityScript);
processDeferredEntityLoads(entityScript, entityID);
} }
/**jsdoc /**jsdoc
@ -2499,10 +2389,6 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR
} }
stopAllTimersForEntityScript(entityID); 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(); _entityScripts.clear();
} }
emit entityScriptDetailsUpdated(); emit entityScriptDetailsUpdated();
_occupiedScriptURLs.clear();
#ifdef DEBUG_ENGINE_STATE #ifdef DEBUG_ENGINE_STATE
_debugDump( _debugDump(

View file

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

View file

@ -138,6 +138,8 @@ static QString outOfRangeDataStrategyToString(ViveControllerManager::OutOfRangeD
return "Freeze"; return "Freeze";
case ViveControllerManager::OutOfRangeDataStrategy::Drop: case ViveControllerManager::OutOfRangeDataStrategy::Drop:
return "Drop"; return "Drop";
case ViveControllerManager::OutOfRangeDataStrategy::DropAfterDelay:
return "DropAfterDelay";
} }
} }
@ -146,6 +148,8 @@ static ViveControllerManager::OutOfRangeDataStrategy stringToOutOfRangeDataStrat
return ViveControllerManager::OutOfRangeDataStrategy::Drop; return ViveControllerManager::OutOfRangeDataStrategy::Drop;
} else if (string == "Freeze") { } else if (string == "Freeze") {
return ViveControllerManager::OutOfRangeDataStrategy::Freeze; return ViveControllerManager::OutOfRangeDataStrategy::Freeze;
} else if (string == "DropAfterDelay") {
return ViveControllerManager::OutOfRangeDataStrategy::DropAfterDelay;
} else { } else {
return ViveControllerManager::OutOfRangeDataStrategy::None; return ViveControllerManager::OutOfRangeDataStrategy::None;
} }
@ -302,7 +306,7 @@ void ViveControllerManager::loadSettings() {
if (_inputDevice) { if (_inputDevice) {
const double DEFAULT_ARM_CIRCUMFERENCE = 0.33; const double DEFAULT_ARM_CIRCUMFERENCE = 0.33;
const double DEFAULT_SHOULDER_WIDTH = 0.48; 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->_armCircumference = settings.value("armCircumference", QVariant(DEFAULT_ARM_CIRCUMFERENCE)).toDouble();
_inputDevice->_shoulderWidth = settings.value("shoulderWidth", QVariant(DEFAULT_SHOULDER_WIDTH)).toDouble(); _inputDevice->_shoulderWidth = settings.value("shoulderWidth", QVariant(DEFAULT_SHOULDER_WIDTH)).toDouble();
_inputDevice->_outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(settings.value("outOfRangeDataStrategy", QVariant(DEFAULT_OUT_OF_RANGE_STRATEGY)).toString()); _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 && _nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid &&
poseIndex <= controller::TRACKED_OBJECT_15) { poseIndex <= controller::TRACKED_OBJECT_15) {
uint64_t now = usecTimestampNow();
controller::Pose pose; controller::Pose pose;
switch (_outOfRangeDataStrategy) { switch (_outOfRangeDataStrategy) {
case OutOfRangeDataStrategy::Drop: case OutOfRangeDataStrategy::Drop:
@ -544,6 +549,22 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde
_nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex]; _nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex];
} }
break; 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) { if (pose.valid) {

View file

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

View file

@ -193,22 +193,52 @@
"emitterShouldTrail": { "emitterShouldTrail": {
"tooltip": "If enabled, then particles are \"left behind\" as the emitter moves, otherwise they are not." "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": { "particleRadius": {
"tooltip": "The size of each particle." "tooltip": "The size of each particle."
}, },
"radiusStart": {
"tooltip": "The start size of each particle."
},
"radiusFinish": {
"tooltip": "The finish size of each particle."
},
"radiusSpread": { "radiusSpread": {
"tooltip": "The spread in size that each particle is given, resulting in a variety of sizes." "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": { "particleColor": {
"tooltip": "The color of each particle.", "tooltip": "The color of each particle.",
"jsPropertyName": "color" "jsPropertyName": "color"
}, },
"colorStart": {
"tooltip": "The start color of each particle."
},
"colorFinish": {
"tooltip": "The finish color of each particle."
},
"colorSpread": { "colorSpread": {
"tooltip": "The spread in color that each particle is given, resulting in a variety of colors." "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": { "alpha": {
"tooltip": "The alpha of each particle." "tooltip": "The alpha of each particle."
}, },
"alphaStart": {
"tooltip": "The start alpha of each particle."
},
"alphaFinish": {
"tooltip": "The finish alpha of each particle."
},
"alphaSpread": { "alphaSpread": {
"tooltip": "The spread in alpha that each particle is given, resulting in a variety of alphas." "tooltip": "The spread in alpha that each particle is given, resulting in a variety of alphas."
}, },
@ -218,20 +248,44 @@
"accelerationSpread": { "accelerationSpread": {
"tooltip": "The spread in accelerations that each particle is given, resulting in a variety of accelerations." "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": { "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": { "spinSpread": {
"tooltip": "The spread in spin that each particle is given, resulting in a variety of spins." "tooltip": "The spread in spin that each particle is given, resulting in a variety of spins."
}, },
"rotateWithEntity": { "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": { "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": { "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": { "lightColor": {
"tooltip": "The color of the light emitted.", "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." "tooltip": "The URL of a sound to play when the entity collides with something else."
}, },
"grab.grabbable": { "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": { "grab.triggerable": {
"tooltip": "If enabled, the collider on this entity is used for triggering events." "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-transform: uppercase;
text-align: center; text-align: center;
padding: 6px 0; padding: 6px 0;
cursor: default;
} }
.triple-item { .triple-item {
@ -1390,6 +1391,10 @@ input[type=button]#export {
cursor: col-resize; cursor: col-resize;
} }
#entity-table .dragging {
background-color: #b3ecff;
}
#entity-table td { #entity-table td {
box-sizing: border-box; box-sizing: border-box;
} }
@ -1517,6 +1522,7 @@ input.rename-entity {
} }
.create-app-tooltip { .create-app-tooltip {
z-index: 100;
position: absolute; position: absolute;
background: #6a6a6a; background: #6a6a6a;
border: 1px solid black; border: 1px solid black;
@ -1651,6 +1657,7 @@ input.number-slider {
font-family: Raleway-Light; font-family: Raleway-Light;
font-size: 14px; font-size: 14px;
margin: 6px 0; margin: 6px 0;
cursor: default;
} }
#property-name, #property-id { #property-name, #property-id {
@ -1663,9 +1670,38 @@ input.number-slider {
} }
#placeholder-property-type { #placeholder-property-type {
min-width: 0px; min-width: 0;
} }
.collapse-icon { .collapse-icon {
cursor: pointer; 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/spinButtons.js"></script>
<script type="text/javascript" src="js/listView.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/entityListContextMenu.js"></script>
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/entityList.js"></script> <script type="text/javascript" src="js/entityList.js"></script>
</head> </head>
<body onload='loaded();' id="entity-list-body"> <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/underscore-min.js"></script>
<script type="text/javascript" src="js/createAppTooltip.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/draggableNumber.js"></script>
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/entityProperties.js"></script> <script type="text/javascript" src="js/entityProperties.js"></script>
<script src="js/jsoneditor.min.js"></script> <script src="js/jsoneditor.min.js"></script>
</head> </head>

View file

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

View file

@ -21,6 +21,9 @@ const MAX_LENGTH_RADIUS = 9;
const MINIMUM_COLUMN_WIDTH = 24; const MINIMUM_COLUMN_WIDTH = 24;
const SCROLLBAR_WIDTH = 20; const SCROLLBAR_WIDTH = 20;
const RESIZER_WIDTH = 10; const RESIZER_WIDTH = 10;
const DELTA_X_MOVE_COLUMNS_THRESHOLD = 2;
const DELTA_X_COLUMN_SWAP_POSITION = 5;
const CERTIFIED_PLACEHOLDER = "** Certified **";
const COLUMNS = { const COLUMNS = {
type: { type: {
@ -108,8 +111,8 @@ const COLUMNS = {
}; };
const COMPARE_ASCENDING = function(a, b) { const COMPARE_ASCENDING = function(a, b) {
let va = a[currentSortColumn]; let va = a[currentSortColumnID];
let vb = b[currentSortColumn]; let vb = b[currentSortColumnID];
if (va < vb) { if (va < vb) {
return -1; return -1;
@ -172,7 +175,7 @@ let entityList = null; // The ListView
*/ */
let entityListContextMenu = null; let entityListContextMenu = null;
let currentSortColumn = 'type'; let currentSortColumnID = 'type';
let currentSortOrder = ASCENDING_SORT; let currentSortOrder = ASCENDING_SORT;
let elSortOrders = {}; let elSortOrders = {};
let typeFilters = []; let typeFilters = [];
@ -180,10 +183,13 @@ let isFilterInView = false;
let columns = []; let columns = [];
let columnsByID = {}; let columnsByID = {};
let currentResizeEl = null; let lastResizeEvent = null;
let startResizeEvent = null;
let resizeColumnIndex = 0; 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 renameTimeout = null;
let renameLastBlur = null; let renameLastBlur = null;
let renameLastEntityID = 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"); console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms");
}; };
debugPrint = function (message) {
console.log(message);
};
function loaded() { function loaded() {
openEventBridge(function() { openEventBridge(function() {
elEntityTable = document.getElementById("entity-table"); elEntityTable = document.getElementById("entity-table");
@ -324,10 +326,11 @@ function loaded() {
for (let columnID in COLUMNS) { for (let columnID in COLUMNS) {
let columnData = COLUMNS[columnID]; let columnData = COLUMNS[columnID];
let thID = "entity-" + columnID;
let elTh = document.createElement("th"); let elTh = document.createElement("th");
let thID = "entity-" + columnID;
elTh.setAttribute("id", thID); elTh.setAttribute("id", thID);
elTh.setAttribute("data-resizable-column-id", thID); elTh.setAttribute("columnIndex", columnIndex);
elTh.setAttribute("columnID", columnID);
if (columnData.glyph) { if (columnData.glyph) {
let elGlyph = document.createElement("span"); let elGlyph = document.createElement("span");
elGlyph.className = "glyph"; elGlyph.className = "glyph";
@ -336,20 +339,20 @@ function loaded() {
} else { } else {
elTh.innerText = columnData.columnHeader; elTh.innerText = columnData.columnHeader;
} }
elTh.onmousedown = function() { elTh.onmousedown = function(event) {
startThClick = this; if (event.target.nodeName === 'TH') {
}; elTargetTh = event.target;
elTh.onmouseup = function() { targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex"));
if (startThClick === this) { lastColumnSwapPosition = event.clientX;
setSortColumn(columnID); } else if (event.target.nodeName === 'SPAN') {
elTargetSpan = event.target;
} }
startThClick = null; initialThEvent = event;
}; };
let elResizer = document.createElement("span"); let elResizer = document.createElement("span");
elResizer.className = "resizer"; elResizer.className = "resizer";
elResizer.innerHTML = "&nbsp;"; elResizer.innerHTML = "&nbsp;";
elResizer.setAttribute("columnIndex", columnIndex);
elResizer.onmousedown = onStartResize; elResizer.onmousedown = onStartResize;
elTh.appendChild(elResizer); elTh.appendChild(elResizer);
@ -633,10 +636,11 @@ function loaded() {
id: entity.id, id: entity.id,
name: entity.name, name: entity.name,
type: type, type: type,
url: filename, url: entity.certificateID === "" ? filename : "<i>" + CERTIFIED_PLACEHOLDER + "</i>",
fullUrl: entity.url, fullUrl: entity.certificateID === "" ? filename : CERTIFIED_PLACEHOLDER,
locked: entity.locked, locked: entity.locked,
visible: entity.visible, visible: entity.visible,
certificateID: entity.certificateID,
verticesCount: displayIfNonZero(entity.verticesCount), verticesCount: displayIfNonZero(entity.verticesCount),
texturesCount: displayIfNonZero(entity.texturesCount), texturesCount: displayIfNonZero(entity.texturesCount),
texturesSize: decimalMegabytes(entity.texturesSize), texturesSize: decimalMegabytes(entity.texturesSize),
@ -762,13 +766,13 @@ function loaded() {
refreshNoEntitiesMessage(); refreshNoEntitiesMessage();
} }
function setSortColumn(column) { function setSortColumn(columnID) {
PROFILE("set-sort-column", function() { PROFILE("set-sort-column", function() {
if (currentSortColumn === column) { if (currentSortColumnID === columnID) {
currentSortOrder *= -1; currentSortOrder *= -1;
} else { } else {
elSortOrders[currentSortColumn].innerHTML = ""; elSortOrders[currentSortColumnID].innerHTML = "";
currentSortColumn = column; currentSortColumnID = columnID;
currentSortOrder = ASCENDING_SORT; currentSortOrder = ASCENDING_SORT;
} }
refreshSortOrder(); refreshSortOrder();
@ -777,7 +781,7 @@ function loaded() {
} }
function refreshSortOrder() { function refreshSortOrder() {
elSortOrders[currentSortColumn].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING; elSortOrders[currentSortColumnID].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING;
} }
function refreshEntities() { function refreshEntities() {
@ -874,7 +878,7 @@ function loaded() {
if (column.data.glyph) { if (column.data.glyph) {
elCell.innerHTML = itemData[column.data.propertyID] ? column.data.columnHeader : null; elCell.innerHTML = itemData[column.data.propertyID] ? column.data.columnHeader : null;
} else { } 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.style = "min-width:" + column.widthPx + "px;" + "max-width:" + column.widthPx + "px;";
elCell.className = createColumnClassName(column.columnID); elCell.className = createColumnClassName(column.columnID);
@ -1093,8 +1097,8 @@ function loaded() {
} }
function onStartResize(event) { function onStartResize(event) {
startResizeEvent = event; lastResizeEvent = event;
resizeColumnIndex = parseInt(this.getAttribute("columnIndex")); resizeColumnIndex = parseInt(this.parentNode.getAttribute("columnIndex"));
event.stopPropagation(); event.stopPropagation();
} }
@ -1137,8 +1141,37 @@ function loaded() {
entityList.refresh(); entityList.refresh();
} }
document.onmousemove = function(ev) { function swapColumns(columnAIndex, columnBIndex) {
if (startResizeEvent) { 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; startTh = null;
let column = columns[resizeColumnIndex]; let column = columns[resizeColumnIndex];
@ -1150,7 +1183,7 @@ function loaded() {
} }
let fullWidth = elEntityTableBody.offsetWidth; let fullWidth = elEntityTableBody.offsetWidth;
let dx = ev.clientX - startResizeEvent.clientX; let dx = event.clientX - lastResizeEvent.clientX;
let dPct = dx / fullWidth; let dPct = dx / fullWidth;
let newColWidth = column.width + dPct; let newColWidth = column.width + dPct;
@ -1160,14 +1193,60 @@ function loaded() {
column.width += dPct; column.width += dPct;
nextColumn.width -= dPct; nextColumn.width -= dPct;
updateColumnWidths(); 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) { document.onmouseup = function(event) {
startResizeEvent = null; if (elTargetTh) {
ev.stopPropagation(); 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) { function setSpaceMode(spaceMode) {
@ -1287,8 +1366,9 @@ function loaded() {
}); });
augmentSpinButtons(); augmentSpinButtons();
disableDragDrop();
document.addEventListener("contextmenu", function (event) { document.addEventListener("contextmenu", function(event) {
entityListContextMenu.close(); entityListContextMenu.close();
// Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked // 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", label: "Parent Joint Index",
type: "number", type: "number-draggable",
propertyID: "parentJointIndex", propertyID: "parentJointIndex",
}, },
{ {
@ -135,7 +135,7 @@ const GROUPS = [
}, },
{ {
label: "Line Height", label: "Line Height",
type: "number", type: "number-draggable",
min: 0, min: 0,
step: 0.005, step: 0.005,
decimals: 4, decimals: 4,
@ -183,7 +183,7 @@ const GROUPS = [
}, },
{ {
label: "Light Intensity", label: "Light Intensity",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 10, max: 10,
step: 0.1, step: 0.1,
@ -193,7 +193,7 @@ const GROUPS = [
}, },
{ {
label: "Light Horizontal Angle", label: "Light Horizontal Angle",
type: "number", type: "number-draggable",
multiplier: DEGREES_TO_RADIANS, multiplier: DEGREES_TO_RADIANS,
decimals: 2, decimals: 2,
unit: "deg", unit: "deg",
@ -202,7 +202,7 @@ const GROUPS = [
}, },
{ {
label: "Light Vertical Angle", label: "Light Vertical Angle",
type: "number", type: "number-draggable",
multiplier: DEGREES_TO_RADIANS, multiplier: DEGREES_TO_RADIANS,
decimals: 2, decimals: 2,
unit: "deg", unit: "deg",
@ -241,7 +241,7 @@ const GROUPS = [
}, },
{ {
label: "Ambient Intensity", label: "Ambient Intensity",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 10, max: 10,
step: 0.1, step: 0.1,
@ -270,7 +270,7 @@ const GROUPS = [
}, },
{ {
label: "Range", label: "Range",
type: "number", type: "number-draggable",
min: 5, min: 5,
max: 10000, max: 10000,
step: 5, step: 5,
@ -287,7 +287,7 @@ const GROUPS = [
}, },
{ {
label: "Base", label: "Base",
type: "number", type: "number-draggable",
min: -1000, min: -1000,
max: 1000, max: 1000,
step: 10, step: 10,
@ -298,7 +298,7 @@ const GROUPS = [
}, },
{ {
label: "Ceiling", label: "Ceiling",
type: "number", type: "number-draggable",
min: -1000, min: -1000,
max: 5000, max: 5000,
step: 10, step: 10,
@ -315,7 +315,7 @@ const GROUPS = [
}, },
{ {
label: "Background Blend", label: "Background Blend",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 1, max: 1,
step: 0.01, step: 0.01,
@ -337,7 +337,7 @@ const GROUPS = [
}, },
{ {
label: "Glare Angle", label: "Glare Angle",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 180, max: 180,
step: 1, step: 1,
@ -353,7 +353,7 @@ const GROUPS = [
}, },
{ {
label: "Bloom Intensity", label: "Bloom Intensity",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 1, max: 1,
step: 0.01, step: 0.01,
@ -363,7 +363,7 @@ const GROUPS = [
}, },
{ {
label: "Bloom Threshold", label: "Bloom Threshold",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 1, max: 1,
step: 0.01, step: 0.01,
@ -373,7 +373,7 @@ const GROUPS = [
}, },
{ {
label: "Bloom Size", label: "Bloom Size",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 2, max: 2,
step: 0.01, step: 0.01,
@ -390,7 +390,9 @@ const GROUPS = [
{ {
label: "Model", label: "Model",
type: "string", type: "string",
placeholder: "URL",
propertyID: "modelURL", propertyID: "modelURL",
hideIfCertified: true,
}, },
{ {
label: "Collision Shape", label: "Collision Shape",
@ -404,11 +406,13 @@ const GROUPS = [
label: "Compound Shape", label: "Compound Shape",
type: "string", type: "string",
propertyID: "compoundShapeURL", propertyID: "compoundShapeURL",
hideIfCertified: true,
}, },
{ {
label: "Animation", label: "Animation",
type: "string", type: "string",
propertyID: "animation.url", propertyID: "animation.url",
hideIfCertified: true,
}, },
{ {
label: "Play Automatically", label: "Play Automatically",
@ -432,22 +436,22 @@ const GROUPS = [
}, },
{ {
label: "Animation Frame", label: "Animation Frame",
type: "number", type: "number-draggable",
propertyID: "animation.currentFrame", propertyID: "animation.currentFrame",
}, },
{ {
label: "First Frame", label: "First Frame",
type: "number", type: "number-draggable",
propertyID: "animation.firstFrame", propertyID: "animation.firstFrame",
}, },
{ {
label: "Last Frame", label: "Last Frame",
type: "number", type: "number-draggable",
propertyID: "animation.lastFrame", propertyID: "animation.lastFrame",
}, },
{ {
label: "Animation FPS", label: "Animation FPS",
type: "number", type: "number-draggable",
propertyID: "animation.fps", propertyID: "animation.fps",
}, },
{ {
@ -460,6 +464,7 @@ const GROUPS = [
type: "textarea", type: "textarea",
propertyID: "originalTextures", propertyID: "originalTextures",
readOnly: true, readOnly: true,
hideIfCertified: true,
}, },
] ]
}, },
@ -486,7 +491,7 @@ const GROUPS = [
}, },
{ {
label: "Source Resolution", label: "Source Resolution",
type: "number", type: "number-draggable",
propertyID: "dpi", propertyID: "dpi",
}, },
] ]
@ -503,7 +508,7 @@ const GROUPS = [
}, },
{ {
label: "Intensity", label: "Intensity",
type: "number", type: "number-draggable",
min: 0, min: 0,
step: 0.1, step: 0.1,
decimals: 1, decimals: 1,
@ -511,7 +516,7 @@ const GROUPS = [
}, },
{ {
label: "Fall-Off Radius", label: "Fall-Off Radius",
type: "number", type: "number-draggable",
min: 0, min: 0,
step: 0.1, step: 0.1,
decimals: 1, decimals: 1,
@ -525,14 +530,14 @@ const GROUPS = [
}, },
{ {
label: "Spotlight Exponent", label: "Spotlight Exponent",
type: "number", type: "number-draggable",
step: 0.01, step: 0.01,
decimals: 2, decimals: 2,
propertyID: "exponent", propertyID: "exponent",
}, },
{ {
label: "Spotlight Cut-Off", label: "Spotlight Cut-Off",
type: "number", type: "number-draggable",
step: 0.01, step: 0.01,
decimals: 2, decimals: 2,
propertyID: "cutoff", propertyID: "cutoff",
@ -563,7 +568,7 @@ const GROUPS = [
}, },
{ {
label: "Submesh to Replace", label: "Submesh to Replace",
type: "number", type: "number-draggable",
min: 0, min: 0,
step: 1, step: 1,
propertyID: "submeshToReplace", propertyID: "submeshToReplace",
@ -577,7 +582,7 @@ const GROUPS = [
}, },
{ {
label: "Priority", label: "Priority",
type: "number", type: "number-draggable",
min: 0, min: 0,
propertyID: "priority", propertyID: "priority",
}, },
@ -612,7 +617,7 @@ const GROUPS = [
}, },
{ {
label: "Material Rotation", label: "Material Rotation",
type: "number", type: "number-draggable",
step: 0.1, step: 0.1,
decimals: 2, decimals: 2,
unit: "deg", unit: "deg",
@ -636,7 +641,7 @@ const GROUPS = [
}, },
{ {
label: "Lifespan", label: "Lifespan",
type: "number", type: "number-draggable",
unit: "s", unit: "s",
min: 0.01, min: 0.01,
max: 10, max: 10,
@ -646,7 +651,7 @@ const GROUPS = [
}, },
{ {
label: "Max Particles", label: "Max Particles",
type: "number", type: "number-draggable",
min: 1, min: 1,
max: 10000, max: 10000,
step: 1, step: 1,
@ -667,7 +672,7 @@ const GROUPS = [
properties: [ properties: [
{ {
label: "Emit Rate", label: "Emit Rate",
type: "number", type: "number-draggable",
min: 1, min: 1,
max: 1000, max: 1000,
step: 1, step: 1,
@ -675,7 +680,7 @@ const GROUPS = [
}, },
{ {
label: "Emit Speed", label: "Emit Speed",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 5, max: 5,
step: 0.01, step: 0.01,
@ -684,7 +689,7 @@ const GROUPS = [
}, },
{ {
label: "Speed Spread", label: "Speed Spread",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 5, max: 5,
step: 0.01, step: 0.01,
@ -703,7 +708,7 @@ const GROUPS = [
}, },
{ {
label: "Emit Radius Start", label: "Emit Radius Start",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 1, max: 1,
step: 0.01, step: 0.01,
@ -735,10 +740,11 @@ const GROUPS = [
{ {
type: "triple", type: "triple",
label: "Size", label: "Size",
propertyID: "particleRadiusTriple",
properties: [ properties: [
{ {
label: "Start", label: "Start",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 4, max: 4,
step: 0.01, step: 0.01,
@ -748,7 +754,7 @@ const GROUPS = [
}, },
{ {
label: "Middle", label: "Middle",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 4, max: 4,
step: 0.01, step: 0.01,
@ -757,7 +763,7 @@ const GROUPS = [
}, },
{ {
label: "Finish", label: "Finish",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 4, max: 4,
step: 0.01, step: 0.01,
@ -769,7 +775,7 @@ const GROUPS = [
}, },
{ {
label: "Size Spread", label: "Size Spread",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 4, max: 4,
step: 0.01, step: 0.01,
@ -786,6 +792,7 @@ const GROUPS = [
{ {
type: "triple", type: "triple",
label: "Color", label: "Color",
propertyID: "particleColorTriple",
properties: [ properties: [
{ {
label: "Start", label: "Start",
@ -822,10 +829,11 @@ const GROUPS = [
{ {
type: "triple", type: "triple",
label: "Alpha", label: "Alpha",
propertyID: "particleAlphaTriple",
properties: [ properties: [
{ {
label: "Start", label: "Start",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 1, max: 1,
step: 0.01, step: 0.01,
@ -835,7 +843,7 @@ const GROUPS = [
}, },
{ {
label: "Middle", label: "Middle",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 1, max: 1,
step: 0.01, step: 0.01,
@ -844,7 +852,7 @@ const GROUPS = [
}, },
{ {
label: "Finish", label: "Finish",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 1, max: 1,
step: 0.01, step: 0.01,
@ -856,7 +864,7 @@ const GROUPS = [
}, },
{ {
label: "Alpha Spread", label: "Alpha Spread",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 1, max: 1,
step: 0.01, step: 0.01,
@ -898,10 +906,11 @@ const GROUPS = [
{ {
type: "triple", type: "triple",
label: "Spin", label: "Spin",
propertyID: "particleSpinTriple",
properties: [ properties: [
{ {
label: "Start", label: "Start",
type: "number", type: "number-draggable",
min: -360, min: -360,
max: 360, max: 360,
step: 1, step: 1,
@ -913,7 +922,7 @@ const GROUPS = [
}, },
{ {
label: "Middle", label: "Middle",
type: "number", type: "number-draggable",
min: -360, min: -360,
max: 360, max: 360,
step: 1, step: 1,
@ -924,7 +933,7 @@ const GROUPS = [
}, },
{ {
label: "Finish", label: "Finish",
type: "number", type: "number-draggable",
min: -360, min: -360,
max: 360, max: 360,
step: 1, step: 1,
@ -938,7 +947,7 @@ const GROUPS = [
}, },
{ {
label: "Spin Spread", label: "Spin Spread",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 360, max: 360,
step: 1, step: 1,
@ -962,10 +971,11 @@ const GROUPS = [
{ {
type: "triple", type: "triple",
label: "Horizontal Angle", label: "Horizontal Angle",
propertyID: "particlePolarTriple",
properties: [ properties: [
{ {
label: "Start", label: "Start",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 180, max: 180,
step: 1, step: 1,
@ -976,7 +986,7 @@ const GROUPS = [
}, },
{ {
label: "Finish", label: "Finish",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 180, max: 180,
step: 1, step: 1,
@ -990,10 +1000,11 @@ const GROUPS = [
{ {
type: "triple", type: "triple",
label: "Vertical Angle", label: "Vertical Angle",
propertyID: "particleAzimuthTriple",
properties: [ properties: [
{ {
label: "Start", label: "Start",
type: "number", type: "number-draggable",
min: -180, min: -180,
max: 180, max: 180,
step: 1, step: 1,
@ -1004,7 +1015,7 @@ const GROUPS = [
}, },
{ {
label: "Finish", label: "Finish",
type: "number", type: "number-draggable",
min: -180, min: -180,
max: 180, max: 180,
step: 1, step: 1,
@ -1089,7 +1100,7 @@ const GROUPS = [
}, },
{ {
label: "Scale", label: "Scale",
type: "number", type: "number-draggable",
defaultValue: 100, defaultValue: 100,
unit: "%", unit: "%",
buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions },
@ -1131,14 +1142,14 @@ const GROUPS = [
}, },
{ {
label: "Clone Lifetime", label: "Clone Lifetime",
type: "number", type: "number-draggable",
unit: "s", unit: "s",
propertyID: "cloneLifetime", propertyID: "cloneLifetime",
showPropertyRule: { "cloneable": "true" }, showPropertyRule: { "cloneable": "true" },
}, },
{ {
label: "Clone Limit", label: "Clone Limit",
type: "number", type: "number-draggable",
propertyID: "cloneLimit", propertyID: "cloneLimit",
showPropertyRule: { "cloneable": "true" }, showPropertyRule: { "cloneable": "true" },
}, },
@ -1181,6 +1192,7 @@ const GROUPS = [
buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ],
propertyID: "script", propertyID: "script",
placeholder: "URL", placeholder: "URL",
hideIfCertified: true,
}, },
{ {
label: "Server Script", label: "Server Script",
@ -1197,7 +1209,7 @@ const GROUPS = [
}, },
{ {
label: "Lifetime", label: "Lifetime",
type: "string", type: "number",
unit: "s", unit: "s",
propertyID: "lifetime", propertyID: "lifetime",
}, },
@ -1267,6 +1279,7 @@ const GROUPS = [
placeholder: "URL", placeholder: "URL",
propertyID: "collisionSoundURL", propertyID: "collisionSoundURL",
showPropertyRule: { "collisionless": "false" }, showPropertyRule: { "collisionless": "false" },
hideIfCertified: true,
}, },
{ {
label: "Dynamic", label: "Dynamic",
@ -1291,7 +1304,7 @@ const GROUPS = [
}, },
{ {
label: "Linear Damping", label: "Linear Damping",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 1, max: 1,
step: 0.01, step: 0.01,
@ -1310,7 +1323,7 @@ const GROUPS = [
}, },
{ {
label: "Angular Damping", label: "Angular Damping",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 1, max: 1,
step: 0.01, step: 0.01,
@ -1319,7 +1332,7 @@ const GROUPS = [
}, },
{ {
label: "Bounciness", label: "Bounciness",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 1, max: 1,
step: 0.01, step: 0.01,
@ -1328,7 +1341,7 @@ const GROUPS = [
}, },
{ {
label: "Friction", label: "Friction",
type: "number", type: "number-draggable",
min: 0, min: 0,
max: 10, max: 10,
step: 0.1, step: 0.1,
@ -1337,7 +1350,7 @@ const GROUPS = [
}, },
{ {
label: "Density", label: "Density",
type: "number", type: "number-draggable",
min: 100, min: 100,
max: 10000, max: 10000,
step: 1, step: 1,
@ -1431,13 +1444,13 @@ const TEXTURE_ELEMENTS = {
const JSON_EDITOR_ROW_DIV_INDEX = 2; const JSON_EDITOR_ROW_DIV_INDEX = 2;
var elGroups = {}; let elGroups = {};
var properties = {}; let properties = {};
var colorPickers = {}; let colorPickers = {};
var particlePropertyUpdates = {}; let particlePropertyUpdates = {};
var selectedEntityProperties; let selectedEntityProperties;
var lastEntityID = null; let lastEntityID = null;
var createAppTooltip = new CreateAppTooltip(); let createAppTooltip = new CreateAppTooltip();
let currentSpaceMode = PROPERTY_SPACE_MODE.LOCAL; let currentSpaceMode = PROPERTY_SPACE_MODE.LOCAL;
function createElementFromHTML(htmlString) { function createElementFromHTML(htmlString) {
@ -1454,12 +1467,13 @@ function getPropertyInputElement(propertyID) {
let property = properties[propertyID]; let property = properties[propertyID];
switch (property.data.type) { switch (property.data.type) {
case 'string': case 'string':
case 'number':
case 'bool': case 'bool':
case 'dropdown': case 'dropdown':
case 'textarea': case 'textarea':
case 'texture': case 'texture':
return property.elInput; return property.elInput;
case 'number': case 'number-draggable':
return property.elNumber.elInput; return property.elNumber.elInput;
case 'vec3': case 'vec3':
case 'vec2': case 'vec2':
@ -1529,15 +1543,20 @@ function resetProperties() {
let propertyData = property.data; let propertyData = property.data;
switch (propertyData.type) { switch (propertyData.type) {
case 'number':
case 'string': { case 'string': {
property.elInput.value = ""; if (propertyData.defaultValue !== undefined) {
property.elInput.value = propertyData.defaultValue;
} else {
property.elInput.value = "";
}
break; break;
} }
case 'bool': { case 'bool': {
property.elInput.checked = false; property.elInput.checked = false;
break; break;
} }
case 'number': { case 'number-draggable': {
if (propertyData.defaultValue !== undefined) { if (propertyData.defaultValue !== undefined) {
property.elNumber.setValue(propertyData.defaultValue); property.elNumber.setValue(propertyData.defaultValue);
} else { } else {
@ -1691,7 +1710,7 @@ function updateProperty(originalPropertyName, propertyValue, isParticleProperty)
} }
} }
var particleSyncDebounce = _.debounce(function () { let particleSyncDebounce = _.debounce(function () {
updateProperties(particlePropertyUpdates); updateProperties(particlePropertyUpdates);
particlePropertyUpdates = {}; particlePropertyUpdates = {};
}, DEBOUNCE_TIMEOUT); }, DEBOUNCE_TIMEOUT);
@ -1825,7 +1844,7 @@ function createStringProperty(property, elProperty) {
type="text" type="text"
${propertyData.placeholder ? 'placeholder="' + propertyData.placeholder + '"' : ''} ${propertyData.placeholder ? 'placeholder="' + propertyData.placeholder + '"' : ''}
${propertyData.readOnly ? 'readonly' : ''}></input> ${propertyData.readOnly ? 'readonly' : ''}></input>
`) `);
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
@ -1873,7 +1892,45 @@ function createBoolProperty(property, elProperty) {
return elInput; 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 elementID = property.elementID;
let propertyData = property.data; let propertyData = property.data;
@ -2217,7 +2274,11 @@ function createProperty(propertyData, propertyElementID, propertyName, propertyI
break; break;
} }
case 'number': { case 'number': {
property.elNumber = createNumberProperty(property, elProperty); property.elInput = createNumberProperty(property, elProperty);
break;
}
case 'number-draggable': {
property.elNumber = createNumberDraggableProperty(property, elProperty);
break; break;
} }
case 'vec3': { case 'vec3': {
@ -2362,31 +2423,41 @@ function saveUserData() {
saveJSONUserData(true); 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) { function setUserDataFromEditor(noUpdate) {
let json = null; let json = null;
let errorFound = false;
try { try {
json = editor.get(); json = editor.get();
} catch (e) { } 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; return;
}
let text = editor.getText();
if (noUpdate) {
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "saveUserData",
properties: {
userData: text
}
})
);
} else { } else {
let text = editor.getText(); updateProperty('userData', text, false);
if (noUpdate === true) {
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "saveUserData",
properties: {
userData: text
}
})
);
return;
} else {
updateProperty('userData', text, false);
}
} }
} }
@ -2445,7 +2516,7 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r
updateProperties(propertyUpdate, false); updateProperties(propertyUpdate, false);
} }
var editor = null; let editor = null;
function createJSONEditor() { function createJSONEditor() {
let container = document.getElementById("property-userData-editor"); let container = document.getElementById("property-userData-editor");
@ -2508,9 +2579,10 @@ function hideUserDataSaved() {
function showStaticUserData() { function showStaticUserData() {
if (editor !== null) { if (editor !== null) {
$('#property-userData-static').show(); let $propertyUserDataStatic = $('#property-userData-static');
$('#property-userData-static').css('height', $('#property-userData-editor').height()); $propertyUserDataStatic.show();
$('#property-userData-static').text(editor.getText()); $propertyUserDataStatic.css('height', $('#property-userData-editor').height());
$propertyUserDataStatic.text(editor.getText());
} }
} }
@ -2531,12 +2603,13 @@ function getEditorJSON() {
function deleteJSONEditor() { function deleteJSONEditor() {
if (editor !== null) { if (editor !== null) {
setJSONError('userData', false);
editor.destroy(); editor.destroy();
editor = null; editor = null;
} }
} }
var savedJSONTimer = null; let savedJSONTimer = null;
function saveJSONUserData(noUpdate) { function saveJSONUserData(noUpdate) {
setUserDataFromEditor(noUpdate); setUserDataFromEditor(noUpdate);
@ -2581,33 +2654,35 @@ function saveMaterialData() {
function setMaterialDataFromEditor(noUpdate) { function setMaterialDataFromEditor(noUpdate) {
let json = null; let json = null;
let errorFound = false;
try { try {
json = materialEditor.get(); json = materialEditor.get();
} catch (e) { } 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; return;
}
let text = materialEditor.getText();
if (noUpdate) {
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "saveMaterialData",
properties: {
materialData: text
}
})
);
} else { } else {
let text = materialEditor.getText(); updateProperty('materialData', text, false);
if (noUpdate === true) {
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "saveMaterialData",
properties: {
materialData: text
}
})
);
return;
} else {
updateProperty('materialData', text, false);
}
} }
} }
var materialEditor = null; let materialEditor = null;
function createJSONMaterialEditor() { function createJSONMaterialEditor() {
let container = document.getElementById("property-materialData-editor"); let container = document.getElementById("property-materialData-editor");
@ -2670,9 +2745,10 @@ function hideMaterialDataSaved() {
function showStaticMaterialData() { function showStaticMaterialData() {
if (materialEditor !== null) { if (materialEditor !== null) {
$('#property-materialData-static').show(); let $propertyMaterialDataStatic = $('#property-materialData-static');
$('#property-materialData-static').css('height', $('#property-materialData-editor').height()); $propertyMaterialDataStatic.show();
$('#property-materialData-static').text(materialEditor.getText()); $propertyMaterialDataStatic.css('height', $('#property-materialData-editor').height());
$propertyMaterialDataStatic.text(materialEditor.getText());
} }
} }
@ -2693,12 +2769,13 @@ function getMaterialEditorJSON() {
function deleteJSONMaterialEditor() { function deleteJSONMaterialEditor() {
if (materialEditor !== null) { if (materialEditor !== null) {
setJSONError('materialData', false);
materialEditor.destroy(); materialEditor.destroy();
materialEditor = null; materialEditor = null;
} }
} }
var savedMaterialJSONTimer = null; let savedMaterialJSONTimer = null;
function saveJSONMaterialData(noUpdate) { function saveJSONMaterialData(noUpdate) {
setMaterialDataFromEditor(noUpdate); setMaterialDataFromEditor(noUpdate);
@ -2872,23 +2949,20 @@ function loaded() {
elGroup.appendChild(elContainer); elGroup.appendChild(elContainer);
} }
elLabel = document.createElement('label'); let labelText = propertyData.label !== undefined ? propertyData.label : "";
elLabel.setAttribute("for", propertyElementID); let className = '';
if (propertyData.indentedLabel || propertyData.showPropertyRule !== undefined) { if (propertyData.indentedLabel || propertyData.showPropertyRule !== undefined) {
let elSpan = document.createElement('span'); className = 'indented';
elSpan.className = 'indented';
elSpan.innerText = propertyData.label !== undefined ? propertyData.label : "";
elLabel.appendChild(elSpan);
} else {
elLabel.innerText = propertyData.label !== undefined ? propertyData.label : "";
} }
elLabel = createElementFromHTML(
`<label><span class="${className}">${labelText}</span></label>`);
elContainer.appendChild(elLabel); elContainer.appendChild(elLabel);
} else { } else {
elContainer = document.getElementById(propertyData.replaceID); elContainer = document.getElementById(propertyData.replaceID);
} }
if (elLabel) { if (elLabel) {
createAppTooltip.registerTooltipElement(elLabel, propertyID); createAppTooltip.registerTooltipElement(elLabel.childNodes[0], propertyID);
} }
let elProperty = createElementFromHTML('<div style="width: 100%;"></div>'); let elProperty = createElementFromHTML('<div style="width: 100%;"></div>');
@ -2912,7 +2986,10 @@ function loaded() {
property.elContainer = elContainer; property.elContainer = elContainer;
property.spaceMode = propertySpaceMode; 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') { if (property.type !== 'placeholder') {
properties[propertyID] = property; properties[propertyID] = property;
@ -2958,7 +3035,7 @@ function loaded() {
let elServerScriptError = document.getElementById("property-serverScripts-error"); let elServerScriptError = document.getElementById("property-serverScripts-error");
let elServerScriptStatus = document.getElementById("property-serverScripts-status"); let elServerScriptStatus = document.getElementById("property-serverScripts-status");
elServerScriptError.value = data.errorInfo; 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. // it's parent contributing 21px bottom padding even when elServerScriptError is display:none.
// So set it's parent to block or none // So set it's parent to block or none
elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none"; elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none";
@ -3068,6 +3145,15 @@ function loaded() {
showGroupsForType(selectedEntityProperties.type); showGroupsForType(selectedEntityProperties.type);
if (selectedEntityProperties.locked) {
disableProperties();
getPropertyInputElement("locked").removeAttribute('disabled');
} else {
enableProperties();
disableSaveUserDataButton();
disableSaveMaterialDataButton()
}
for (let propertyID in properties) { for (let propertyID in properties) {
let property = properties[propertyID]; let property = properties[propertyID];
let propertyData = property.data; let propertyData = property.data;
@ -3078,10 +3164,19 @@ function loaded() {
if (propertyValue === undefined && !isSubProperty) { if (propertyValue === undefined && !isSubProperty) {
continue; continue;
} }
if (propertyData.hideIfCertified) {
let shouldHide = selectedEntityProperties.certificateID !== "";
if (shouldHide) {
propertyValue = "** Certified **";
}
property.elInput.disabled = shouldHide;
}
let isPropertyNotNumber = false; let isPropertyNotNumber = false;
switch (propertyData.type) { switch (propertyData.type) {
case 'number': case 'number':
case 'number-draggable':
isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null; isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null;
break; break;
case 'vec3': case 'vec3':
@ -3114,6 +3209,10 @@ function loaded() {
break; break;
} }
case 'number': { case 'number': {
property.elInput.value = propertyValue;
break;
}
case 'number-draggable': {
let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1;
let value = propertyValue / multiplier; let value = propertyValue / multiplier;
if (propertyData.round !== undefined) { if (propertyData.round !== undefined) {
@ -3256,15 +3355,6 @@ function loaded() {
hideMaterialDataSaved(); hideMaterialDataSaved();
} }
if (selectedEntityProperties.locked) {
disableProperties();
getPropertyInputElement("locked").removeAttribute('disabled');
} else {
enableProperties();
disableSaveUserDataButton();
disableSaveMaterialDataButton()
}
let activeElement = document.activeElement; let activeElement = document.activeElement;
if (doSelectElement && typeof activeElement.select !== "undefined") { if (doSelectElement && typeof activeElement.select !== "undefined") {
activeElement.select(); activeElement.select();
@ -3317,12 +3407,15 @@ function loaded() {
elStaticUserData.setAttribute("id", userDataElementID + "-static"); elStaticUserData.setAttribute("id", userDataElementID + "-static");
let elUserDataEditor = document.createElement('div'); let elUserDataEditor = document.createElement('div');
elUserDataEditor.setAttribute("id", userDataElementID + "-editor"); elUserDataEditor.setAttribute("id", userDataElementID + "-editor");
let elUserDataEditorStatus = document.createElement('div');
elUserDataEditorStatus.setAttribute("id", userDataElementID + "-editorStatus");
let elUserDataSaved = document.createElement('span'); let elUserDataSaved = document.createElement('span');
elUserDataSaved.setAttribute("id", userDataElementID + "-saved"); elUserDataSaved.setAttribute("id", userDataElementID + "-saved");
elUserDataSaved.innerText = "Saved!"; elUserDataSaved.innerText = "Saved!";
elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved); elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved);
elDiv.insertBefore(elStaticUserData, elUserData); elDiv.insertBefore(elStaticUserData, elUserData);
elDiv.insertBefore(elUserDataEditor, elUserData); elDiv.insertBefore(elUserDataEditor, elUserData);
elDiv.insertBefore(elUserDataEditorStatus, elUserData);
// Material Data // Material Data
let materialDataProperty = properties["materialData"]; let materialDataProperty = properties["materialData"];
@ -3333,12 +3426,15 @@ function loaded() {
elStaticMaterialData.setAttribute("id", materialDataElementID + "-static"); elStaticMaterialData.setAttribute("id", materialDataElementID + "-static");
let elMaterialDataEditor = document.createElement('div'); let elMaterialDataEditor = document.createElement('div');
elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor"); elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor");
let elMaterialDataEditorStatus = document.createElement('div');
elMaterialDataEditorStatus.setAttribute("id", materialDataElementID + "-editorStatus");
let elMaterialDataSaved = document.createElement('span'); let elMaterialDataSaved = document.createElement('span');
elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved"); elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved");
elMaterialDataSaved.innerText = "Saved!"; elMaterialDataSaved.innerText = "Saved!";
elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved); elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved);
elDiv.insertBefore(elStaticMaterialData, elMaterialData); elDiv.insertBefore(elStaticMaterialData, elMaterialData);
elDiv.insertBefore(elMaterialDataEditor, elMaterialData); elDiv.insertBefore(elMaterialDataEditor, elMaterialData);
elDiv.insertBefore(elMaterialDataEditorStatus, elMaterialData);
// Special Property Callbacks // Special Property Callbacks
let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace");
@ -3529,6 +3625,7 @@ function loaded() {
}); });
augmentSpinButtons(); augmentSpinButtons();
disableDragDrop();
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked // 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) { document.addEventListener("contextmenu", function(event) {

View file

@ -108,6 +108,7 @@ function loaded() {
}); });
augmentSpinButtons(); augmentSpinButtons();
disableDragDrop();
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' })); 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 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 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, function ListView(elTableBody, elTableScroll, elTableHeaderRow, createRowFunction, updateRowFunction, clearRowFunction,
preRefreshFunction, postRefreshFunction, preResizeFunction, WINDOW_NONVARIABLE_HEIGHT) { preRefreshFunction, postRefreshFunction, preResizeFunction, WINDOW_NONVARIABLE_HEIGHT) {
this.elTableBody = elTableBody; this.elTableBody = elTableBody;
@ -246,7 +242,7 @@ ListView.prototype = {
resize: function() { resize: function() {
if (!this.elTableBody || !this.elTableScroll) { 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; return;
} }
this.preResizeFunction(); this.preResizeFunction();
@ -288,7 +284,7 @@ ListView.prototype = {
initialize: function() { initialize: function() {
if (!this.elTableBody || !this.elTableScroll) { 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; 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; var cameraPosition = Camera.position;
PROFILE("getMultipleProperties", function () { PROFILE("getMultipleProperties", function () {
var multipleProperties = Entities.getMultipleEntityProperties(ids, ['name', 'type', 'locked', 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++) { for (var i = 0; i < multipleProperties.length; i++) {
var properties = multipleProperties[i]; var properties = multipleProperties[i];
@ -182,6 +182,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
url: url, url: url,
locked: properties.locked, locked: properties.locked,
visible: properties.visible, visible: properties.visible,
certificateID: properties.certificateID,
verticesCount: (properties.renderInfo !== undefined ? verticesCount: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.verticesCount) : ""), valueIfDefined(properties.renderInfo.verticesCount) : ""),
texturesCount: (properties.renderInfo !== undefined ? texturesCount: (properties.renderInfo !== undefined ?