mirror of
https://github.com/lubosz/overte.git
synced 2025-04-08 07:22:43 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into punk
This commit is contained in:
commit
79cad36c58
50 changed files with 873 additions and 815 deletions
|
@ -19,6 +19,9 @@
|
|||
#include <AnimUtil.h>
|
||||
#include <ClientTraitsHandler.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <ResourceRequestObserver.h>
|
||||
#include <AvatarLogging.h>
|
||||
|
||||
|
||||
ScriptableAvatar::ScriptableAvatar() {
|
||||
_clientTraitsHandler.reset(new ClientTraitsHandler(this));
|
||||
|
@ -62,11 +65,28 @@ AnimationDetails ScriptableAvatar::getAnimationDetails() {
|
|||
return _animationDetails;
|
||||
}
|
||||
|
||||
int ScriptableAvatar::getJointIndex(const QString& name) const {
|
||||
// Faux joints:
|
||||
int result = AvatarData::getJointIndex(name);
|
||||
if (result != -1) {
|
||||
return result;
|
||||
}
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
return _fstJointIndices.value(name) - 1;
|
||||
}
|
||||
|
||||
QStringList ScriptableAvatar::getJointNames() const {
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
return _fstJointNames;
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||
_bind.reset();
|
||||
_animSkeleton.reset();
|
||||
|
||||
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
||||
updateJointMappings();
|
||||
}
|
||||
|
||||
static AnimPose composeAnimPose(const HFMJoint& joint, const glm::quat rotation, const glm::vec3 translation) {
|
||||
|
@ -77,10 +97,6 @@ static AnimPose composeAnimPose(const HFMJoint& joint, const glm::quat rotation,
|
|||
}
|
||||
|
||||
void ScriptableAvatar::update(float deltatime) {
|
||||
if (_bind.isNull() && !_skeletonFBXURL.isEmpty()) { // AvatarData will parse the .fst, but not get the .fbx skeleton.
|
||||
_bind = DependencyManager::get<AnimationCache>()->getAnimation(_skeletonFBXURL);
|
||||
}
|
||||
|
||||
// Run animation
|
||||
if (_animation && _animation->isLoaded() && _animation->getFrames().size() > 0 && !_bind.isNull() && _bind->isLoaded()) {
|
||||
if (!_animSkeleton) {
|
||||
|
@ -146,6 +162,82 @@ void ScriptableAvatar::update(float deltatime) {
|
|||
_clientTraitsHandler->sendChangedTraitsToMixer();
|
||||
}
|
||||
|
||||
void ScriptableAvatar::updateJointMappings() {
|
||||
{
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
_fstJointIndices.clear();
|
||||
_fstJointNames.clear();
|
||||
_jointData.clear();
|
||||
}
|
||||
|
||||
if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) {
|
||||
////
|
||||
// TODO: Should we rely upon HTTPResourceRequest for ResourceRequestObserver instead?
|
||||
// HTTPResourceRequest::doSend() covers all of the following and
|
||||
// then some. It doesn't cover the connect() call, so we may
|
||||
// want to add a HTTPResourceRequest::doSend() method that does
|
||||
// connects.
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL);
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
DependencyManager::get<ResourceRequestObserver>()->update(
|
||||
_skeletonModelURL, -1, "AvatarData::updateJointMappings");
|
||||
QNetworkReply* networkReply = networkAccessManager.get(networkRequest);
|
||||
//
|
||||
////
|
||||
connect(networkReply, &QNetworkReply::finished, this, &ScriptableAvatar::setJointMappingsFromNetworkReply);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptableAvatar::setJointMappingsFromNetworkReply() {
|
||||
QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
|
||||
// before we process this update, make sure that the skeleton model URL hasn't changed
|
||||
// since we made the FST request
|
||||
if (networkReply->url() != _skeletonModelURL) {
|
||||
qCDebug(avatars) << "Refusing to set joint mappings for FST URL that does not match the current URL";
|
||||
networkReply->deleteLater();
|
||||
return;
|
||||
}
|
||||
{
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
QByteArray line;
|
||||
while (!(line = networkReply->readLine()).isEmpty()) {
|
||||
line = line.trimmed();
|
||||
if (line.startsWith("filename")) {
|
||||
int filenameIndex = line.indexOf('=') + 1;
|
||||
if (filenameIndex > 0) {
|
||||
_skeletonFBXURL = _skeletonModelURL.resolved(QString(line.mid(filenameIndex).trimmed()));
|
||||
}
|
||||
}
|
||||
if (!line.startsWith("jointIndex")) {
|
||||
continue;
|
||||
}
|
||||
int jointNameIndex = line.indexOf('=') + 1;
|
||||
if (jointNameIndex == 0) {
|
||||
continue;
|
||||
}
|
||||
int secondSeparatorIndex = line.indexOf('=', jointNameIndex);
|
||||
if (secondSeparatorIndex == -1) {
|
||||
continue;
|
||||
}
|
||||
QString jointName = line.mid(jointNameIndex, secondSeparatorIndex - jointNameIndex).trimmed();
|
||||
bool ok;
|
||||
int jointIndex = line.mid(secondSeparatorIndex + 1).trimmed().toInt(&ok);
|
||||
if (ok) {
|
||||
while (_fstJointNames.size() < jointIndex + 1) {
|
||||
_fstJointNames.append(QString());
|
||||
}
|
||||
_fstJointNames[jointIndex] = jointName;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < _fstJointNames.size(); i++) {
|
||||
_fstJointIndices.insert(_fstJointNames.at(i), i + 1);
|
||||
}
|
||||
}
|
||||
networkReply->deleteLater();
|
||||
}
|
||||
|
||||
void ScriptableAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) {
|
||||
_headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement);
|
||||
}
|
||||
|
|
|
@ -153,6 +153,27 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE AnimationDetails getAnimationDetails();
|
||||
|
||||
/**jsdoc
|
||||
* Get the names of all the joints in the current avatar.
|
||||
* @function MyAvatar.getJointNames
|
||||
* @returns {string[]} The joint names.
|
||||
* @example <caption>Report the names of all the joints in your current avatar.</caption>
|
||||
* print(JSON.stringify(MyAvatar.getJointNames()));
|
||||
*/
|
||||
Q_INVOKABLE virtual QStringList getJointNames() const override;
|
||||
|
||||
/**jsdoc
|
||||
* Get the joint index for a named joint. The joint index value is the position of the joint in the array returned by
|
||||
* {@link MyAvatar.getJointNames} or {@link Avatar.getJointNames}.
|
||||
* @function MyAvatar.getJointIndex
|
||||
* @param {string} name - The name of the joint.
|
||||
* @returns {number} The index of the joint.
|
||||
* @example <caption>Report the index of your avatar's left arm joint.</caption>
|
||||
* print(JSON.stringify(MyAvatar.getJointIndex("LeftArm"));
|
||||
*/
|
||||
/// Returns the index of the joint with the specified name, or -1 if not found/unknown.
|
||||
Q_INVOKABLE virtual int getJointIndex(const QString& name) const override;
|
||||
|
||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
|
||||
|
||||
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override;
|
||||
|
@ -167,12 +188,23 @@ public:
|
|||
public slots:
|
||||
void update(float deltatime);
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.setJointMappingsFromNetworkReply
|
||||
*/
|
||||
void setJointMappingsFromNetworkReply();
|
||||
|
||||
private:
|
||||
AnimationPointer _animation;
|
||||
AnimationDetails _animationDetails;
|
||||
QStringList _maskedJoints;
|
||||
AnimationPointer _bind; // a sleazy way to get the skeleton, given the various library/cmake dependencies
|
||||
std::shared_ptr<AnimSkeleton> _animSkeleton;
|
||||
QHash<QString, int> _fstJointIndices; ///< 1-based, since zero is returned for missing keys
|
||||
QStringList _fstJointNames; ///< in order of depth-first traversal
|
||||
QUrl _skeletonFBXURL;
|
||||
|
||||
/// Loads the joint indices, names from the FST file (if any)
|
||||
void updateJointMappings();
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptableAvatar_h
|
||||
|
|
|
@ -364,7 +364,7 @@ function validateInputs() {
|
|||
if (keyVal.length === 0) {
|
||||
empty = true
|
||||
|
||||
markParentRowInvalid(input);
|
||||
markParentRowInvalid(input)
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -373,11 +373,13 @@ function validateInputs() {
|
|||
_.each(otherKeys, function(otherKeyCell) {
|
||||
var keyInput = $(otherKeyCell).children('input');
|
||||
|
||||
var lowerNewValue = keyVal.toLowerCase();
|
||||
|
||||
if (keyInput.length) {
|
||||
if ($(keyInput).val() == keyVal) {
|
||||
if ($(keyInput).val().toLowerCase() == lowerNewValue) {
|
||||
duplicateKey = true;
|
||||
}
|
||||
} else if ($(otherKeyCell).html() == keyVal) {
|
||||
} else if ($(otherKeyCell).html().toLowerCase() == lowerNewValue) {
|
||||
duplicateKey = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -3172,24 +3172,34 @@ void DomainServer::processPathQueryPacket(QSharedPointer<ReceivedMessage> messag
|
|||
const QString PATH_VIEWPOINT_KEY = "viewpoint";
|
||||
const QString INDEX_PATH = "/";
|
||||
|
||||
// check out paths in the _configMap to see if we have a match
|
||||
auto keypath = QString(PATHS_SETTINGS_KEYPATH_FORMAT).arg(SETTINGS_PATHS_KEY).arg(pathQuery);
|
||||
QVariant pathMatch = _settingsManager.valueForKeyPath(keypath);
|
||||
QString responseViewpoint;
|
||||
|
||||
if (pathMatch.isValid() || pathQuery == INDEX_PATH) {
|
||||
// check out paths in the _configMap to see if we have a match
|
||||
auto pathsVariant = _settingsManager.valueForKeyPath(SETTINGS_PATHS_KEY);
|
||||
|
||||
auto lowerPathQuery = pathQuery.toLower();
|
||||
|
||||
if (pathsVariant.canConvert<QVariantMap>()) {
|
||||
auto pathsMap = pathsVariant.toMap();
|
||||
|
||||
// enumerate the paths and look case-insensitively for a matching one
|
||||
for (auto it = pathsMap.constKeyValueBegin(); it != pathsMap.constKeyValueEnd(); ++it) {
|
||||
if ((*it).first.toLower() == lowerPathQuery) {
|
||||
responseViewpoint = (*it).second.toMap()[PATH_VIEWPOINT_KEY].toString().toLower();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (responseViewpoint.isEmpty() && pathQuery == INDEX_PATH) {
|
||||
const QString DEFAULT_INDEX_PATH = "/0,0,0/0,0,0,1";
|
||||
responseViewpoint = DEFAULT_INDEX_PATH;
|
||||
}
|
||||
|
||||
if (!responseViewpoint.isEmpty()) {
|
||||
// we got a match, respond with the resulting viewpoint
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
QString responseViewpoint;
|
||||
|
||||
// if we didn't match the path BUT this is for the index path then send back our default
|
||||
if (pathMatch.isValid()) {
|
||||
responseViewpoint = pathMatch.toMap()[PATH_VIEWPOINT_KEY].toString();
|
||||
} else {
|
||||
const QString DEFAULT_INDEX_PATH = "/0,0,0/0,0,0,1";
|
||||
responseViewpoint = DEFAULT_INDEX_PATH;
|
||||
}
|
||||
|
||||
if (!responseViewpoint.isEmpty()) {
|
||||
QByteArray viewpointUTF8 = responseViewpoint.toUtf8();
|
||||
|
||||
|
|
|
@ -873,7 +873,7 @@ Flickable {
|
|||
|
||||
editable: true
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
model: ["None", "Freeze", "Drop"]
|
||||
model: ["None", "Freeze", "Drop", "DropAfterDelay"]
|
||||
label: ""
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
|
|
|
@ -1,47 +1,32 @@
|
|||
const vec3 COLOR = vec3(0x00, 0xD8, 0x02) / vec3(0xFF);
|
||||
const float CUTOFF = 0.65;
|
||||
const float NOISE_MULT = 8.0;
|
||||
const float NOISE_POWER = 1.0;
|
||||
// Replicate the default skybox texture
|
||||
|
||||
float noise4D(vec4 p) {
|
||||
return fract(sin(dot(p ,vec4(12.9898,78.233,126.7235, 593.2241))) * 43758.5453);
|
||||
}
|
||||
|
||||
float worley4D(vec4 p) {
|
||||
float r = 3.0;
|
||||
vec4 f = floor(p);
|
||||
vec4 x = fract(p);
|
||||
for(int i = -1; i<=1; i++)
|
||||
{
|
||||
for(int j = -1; j<=1; j++)
|
||||
{
|
||||
for(int k = -1; k<=1; k++)
|
||||
{
|
||||
for (int l = -1; l <= 1; l++) {
|
||||
vec4 q = vec4(float(i),float(j),float(k), float(l));
|
||||
vec4 v = q + vec4(noise4D((q+f)*1.11), noise4D((q+f)*1.14), noise4D((q+f)*1.17), noise4D((q+f)*1.20)) - x;
|
||||
float d = dot(v, v);
|
||||
r = min(r, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sqrt(r);
|
||||
}
|
||||
|
||||
|
||||
vec3 mainColor(vec3 direction) {
|
||||
float n = worley4D(vec4(direction * NOISE_MULT, iGlobalTime / 3.0));
|
||||
n = 1.0 - n;
|
||||
n = pow(n, NOISE_POWER);
|
||||
if (n < CUTOFF) {
|
||||
return vec3(0.0);
|
||||
}
|
||||
|
||||
n = (n - CUTOFF) / (1.0 - CUTOFF);
|
||||
return COLOR * (1.0 - n);
|
||||
}
|
||||
const int NUM_COLORS = 5;
|
||||
const vec3 WHITISH = vec3(0.471, 0.725, 0.825);
|
||||
const vec3 GREENISH = vec3(0.157, 0.529, 0.588);
|
||||
const vec3 COLORS[NUM_COLORS] = vec3[](
|
||||
GREENISH,
|
||||
GREENISH,
|
||||
WHITISH,
|
||||
WHITISH,
|
||||
vec3(0.6, 0.275, 0.706) // purple
|
||||
);
|
||||
const float PI = 3.14159265359;
|
||||
const vec3 BLACK = vec3(0.0);
|
||||
const vec3 SPACE_BLUE = vec3(0.0, 0.118, 0.392);
|
||||
const float HORIZONTAL_OFFSET = 3.75;
|
||||
|
||||
vec3 getSkyboxColor() {
|
||||
return mainColor(normalize(_normal));
|
||||
vec2 horizontal = vec2(_normal.x, _normal.z);
|
||||
horizontal = normalize(horizontal);
|
||||
float theta = atan(horizontal.y, horizontal.x);
|
||||
theta = 0.5 * (theta / PI + 1.0);
|
||||
float index = theta * NUM_COLORS;
|
||||
index = mod(index + HORIZONTAL_OFFSET, NUM_COLORS);
|
||||
int index1 = int(index) % NUM_COLORS;
|
||||
int index2 = (index1 + 1) % NUM_COLORS;
|
||||
vec3 horizonColor = mix(COLORS[index1], COLORS[index2], index - index1);
|
||||
horizonColor = mix(horizonColor, SPACE_BLUE, smoothstep(0.0, 0.08, _normal.y));
|
||||
horizonColor = mix(horizonColor, BLACK, smoothstep(0.04, 0.15, _normal.y));
|
||||
horizonColor = mix(BLACK, horizonColor, smoothstep(-0.01, 0.0, _normal.y));
|
||||
return pow(horizonColor, vec3(0.4545));;
|
||||
}
|
||||
|
|
|
@ -150,7 +150,6 @@
|
|||
#include <trackers/EyeTracker.h>
|
||||
#include <avatars-renderer/ScriptAvatar.h>
|
||||
#include <RenderableEntityItem.h>
|
||||
#include <procedural/ProceduralSkybox.h>
|
||||
#include <model-networking/MaterialCache.h>
|
||||
#include "recording/ClipCache.h"
|
||||
|
||||
|
@ -2819,8 +2818,6 @@ void Application::initializeGL() {
|
|||
_graphicsEngine.initializeGPU(_glWidget);
|
||||
}
|
||||
|
||||
static const QString SPLASH_SKYBOX{ "{\"ProceduralEntity\":{ \"version\":2, \"shaderUrl\":\"qrc:///shaders/splashSkybox.frag\" } }" };
|
||||
|
||||
void Application::initializeDisplayPlugins() {
|
||||
auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins();
|
||||
Setting::Handle<QString> activeDisplayPluginSetting{ ACTIVE_DISPLAY_PLUGIN_SETTING_NAME, displayPlugins.at(0)->getName() };
|
||||
|
@ -2856,45 +2853,6 @@ void Application::initializeDisplayPlugins() {
|
|||
|
||||
// Submit a default frame to render until the engine starts up
|
||||
updateRenderArgs(0.0f);
|
||||
|
||||
#define ENABLE_SPLASH_FRAME 0
|
||||
#if ENABLE_SPLASH_FRAME
|
||||
{
|
||||
QMutexLocker viewLocker(&_renderArgsMutex);
|
||||
|
||||
if (_appRenderArgs._isStereo) {
|
||||
_gpuContext->enableStereo(true);
|
||||
_gpuContext->setStereoProjections(_appRenderArgs._eyeProjections);
|
||||
_gpuContext->setStereoViews(_appRenderArgs._eyeOffsets);
|
||||
}
|
||||
|
||||
// Frame resources
|
||||
auto framebufferCache = DependencyManager::get<FramebufferCache>();
|
||||
gpu::FramebufferPointer finalFramebuffer = framebufferCache->getFramebuffer();
|
||||
std::shared_ptr<ProceduralSkybox> procedural = std::make_shared<ProceduralSkybox>();
|
||||
procedural->parse(SPLASH_SKYBOX);
|
||||
|
||||
_gpuContext->beginFrame(_appRenderArgs._view, _appRenderArgs._headPose);
|
||||
gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {
|
||||
batch.resetStages();
|
||||
batch.enableStereo(false);
|
||||
batch.setFramebuffer(finalFramebuffer);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, { 0, 0, 0, 1 });
|
||||
batch.enableSkybox(true);
|
||||
batch.enableStereo(_appRenderArgs._isStereo);
|
||||
batch.setViewportTransform({ 0, 0, finalFramebuffer->getSize() });
|
||||
procedural->render(batch, _appRenderArgs._renderArgs.getViewFrustum());
|
||||
});
|
||||
auto frame = _gpuContext->endFrame();
|
||||
frame->frameIndex = 0;
|
||||
frame->framebuffer = finalFramebuffer;
|
||||
frame->pose = _appRenderArgs._headPose;
|
||||
frame->framebufferRecycler = [framebufferCache, procedural](const gpu::FramebufferPointer& framebuffer) {
|
||||
framebufferCache->releaseFramebuffer(framebuffer);
|
||||
};
|
||||
_displayPlugin->submitFrame(frame);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Application::initializeRenderEngine() {
|
||||
|
|
|
@ -72,7 +72,6 @@
|
|||
#include "workload/GameWorkload.h"
|
||||
#include "graphics/GraphicsEngine.h"
|
||||
|
||||
#include <procedural/ProceduralSkybox.h>
|
||||
#include <graphics/Skybox.h>
|
||||
#include <ModelScriptingInterface.h>
|
||||
|
||||
|
@ -761,5 +760,6 @@ private:
|
|||
|
||||
bool _showTrackedObjects { false };
|
||||
bool _prevShowTrackedObjects { false };
|
||||
|
||||
};
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -1,225 +0,0 @@
|
|||
//
|
||||
// Application_render.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "Application.h"
|
||||
#include <MainWindow.h>
|
||||
|
||||
#include <display-plugins/CompositorHelper.h>
|
||||
#include <FramebufferCache.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
#include <SceneScriptingInterface.h>
|
||||
#include "ui/Stats.h"
|
||||
#include "Util.h"
|
||||
|
||||
|
||||
//void Application::paintGL() {
|
||||
// // Some plugins process message events, allowing paintGL to be called reentrantly.
|
||||
//
|
||||
// _renderFrameCount++;
|
||||
// // SG: Moved into the RenderEventHandler
|
||||
// //_lastTimeRendered.start();
|
||||
//
|
||||
// auto lastPaintBegin = usecTimestampNow();
|
||||
// PROFILE_RANGE_EX(render, __FUNCTION__, 0xff0000ff, (uint64_t)_renderFrameCount);
|
||||
// PerformanceTimer perfTimer("paintGL");
|
||||
//
|
||||
// if (nullptr == _displayPlugin) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// DisplayPluginPointer displayPlugin;
|
||||
// {
|
||||
// PROFILE_RANGE(render, "/getActiveDisplayPlugin");
|
||||
// displayPlugin = getActiveDisplayPlugin();
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// PROFILE_RANGE(render, "/pluginBeginFrameRender");
|
||||
// // If a display plugin loses it's underlying support, it
|
||||
// // needs to be able to signal us to not use it
|
||||
// if (!displayPlugin->beginFrameRender(_renderFrameCount)) {
|
||||
// QMetaObject::invokeMethod(this, "updateDisplayMode");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// RenderArgs renderArgs;
|
||||
// glm::mat4 HMDSensorPose;
|
||||
// glm::mat4 eyeToWorld;
|
||||
// glm::mat4 sensorToWorld;
|
||||
//
|
||||
// bool isStereo;
|
||||
// glm::mat4 stereoEyeOffsets[2];
|
||||
// glm::mat4 stereoEyeProjections[2];
|
||||
//
|
||||
// {
|
||||
// QMutexLocker viewLocker(&_renderArgsMutex);
|
||||
// renderArgs = _appRenderArgs._renderArgs;
|
||||
//
|
||||
// // don't render if there is no context.
|
||||
// if (!_appRenderArgs._renderArgs._context) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// HMDSensorPose = _appRenderArgs._headPose;
|
||||
// eyeToWorld = _appRenderArgs._eyeToWorld;
|
||||
// sensorToWorld = _appRenderArgs._sensorToWorld;
|
||||
// isStereo = _appRenderArgs._isStereo;
|
||||
// for_each_eye([&](Eye eye) {
|
||||
// stereoEyeOffsets[eye] = _appRenderArgs._eyeOffsets[eye];
|
||||
// stereoEyeProjections[eye] = _appRenderArgs._eyeProjections[eye];
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// PROFILE_RANGE(render, "/gpuContextReset");
|
||||
// _graphicsEngine.getGPUContext()->beginFrame(_appRenderArgs._view, HMDSensorPose);
|
||||
// // Reset the gpu::Context Stages
|
||||
// // Back to the default framebuffer;
|
||||
// gpu::doInBatch("Application_render::gpuContextReset", _graphicsEngine.getGPUContext(), [&](gpu::Batch& batch) {
|
||||
// batch.resetStages();
|
||||
// });
|
||||
// }
|
||||
//
|
||||
//
|
||||
// {
|
||||
// PROFILE_RANGE(render, "/renderOverlay");
|
||||
// PerformanceTimer perfTimer("renderOverlay");
|
||||
// // NOTE: There is no batch associated with this renderArgs
|
||||
// // the ApplicationOverlay class assumes it's viewport is setup to be the device size
|
||||
// renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize() * getRenderResolutionScale());
|
||||
// _applicationOverlay.renderOverlay(&renderArgs);
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// PROFILE_RANGE(render, "/updateCompositor");
|
||||
// getApplicationCompositor().setFrameInfo(_renderFrameCount, eyeToWorld, sensorToWorld);
|
||||
// }
|
||||
//
|
||||
// gpu::FramebufferPointer finalFramebuffer;
|
||||
// QSize finalFramebufferSize;
|
||||
// {
|
||||
// PROFILE_RANGE(render, "/getOutputFramebuffer");
|
||||
// // Primary rendering pass
|
||||
// auto framebufferCache = DependencyManager::get<FramebufferCache>();
|
||||
// finalFramebufferSize = framebufferCache->getFrameBufferSize();
|
||||
// // Final framebuffer that will be handled to the display-plugin
|
||||
// finalFramebuffer = framebufferCache->getFramebuffer();
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// if (isStereo) {
|
||||
// renderArgs._context->enableStereo(true);
|
||||
// renderArgs._context->setStereoProjections(stereoEyeProjections);
|
||||
// renderArgs._context->setStereoViews(stereoEyeOffsets);
|
||||
// }
|
||||
//
|
||||
// renderArgs._hudOperator = displayPlugin->getHUDOperator();
|
||||
// renderArgs._hudTexture = _applicationOverlay.getOverlayTexture();
|
||||
// renderArgs._blitFramebuffer = finalFramebuffer;
|
||||
// _graphicsEngine.render_runRenderFrame(&renderArgs);
|
||||
// }
|
||||
//
|
||||
// auto frame = _graphicsEngine.getGPUContext()->endFrame();
|
||||
// frame->frameIndex = _renderFrameCount;
|
||||
// frame->framebuffer = finalFramebuffer;
|
||||
// frame->framebufferRecycler = [](const gpu::FramebufferPointer& framebuffer) {
|
||||
// auto frameBufferCache = DependencyManager::get<FramebufferCache>();
|
||||
// if (frameBufferCache) {
|
||||
// frameBufferCache->releaseFramebuffer(framebuffer);
|
||||
// }
|
||||
// };
|
||||
// // deliver final scene rendering commands to the display plugin
|
||||
// {
|
||||
// PROFILE_RANGE(render, "/pluginOutput");
|
||||
// PerformanceTimer perfTimer("pluginOutput");
|
||||
// _renderLoopCounter.increment();
|
||||
// displayPlugin->submitFrame(frame);
|
||||
// }
|
||||
//
|
||||
// // Reset the framebuffer and stereo state
|
||||
// renderArgs._blitFramebuffer.reset();
|
||||
// renderArgs._context->enableStereo(false);
|
||||
//
|
||||
// {
|
||||
// Stats::getInstance()->setRenderDetails(renderArgs._details);
|
||||
// }
|
||||
//
|
||||
// uint64_t lastPaintDuration = usecTimestampNow() - lastPaintBegin;
|
||||
// _frameTimingsScriptingInterface.addValue(lastPaintDuration);
|
||||
//}
|
||||
|
||||
|
||||
// WorldBox Render Data & rendering functions
|
||||
//
|
||||
//class WorldBoxRenderData {
|
||||
//public:
|
||||
// typedef render::Payload<WorldBoxRenderData> Payload;
|
||||
// typedef Payload::DataPointer Pointer;
|
||||
//
|
||||
// int _val = 0;
|
||||
// static render::ItemID _item; // unique WorldBoxRenderData
|
||||
//};
|
||||
//
|
||||
//render::ItemID WorldBoxRenderData::_item{ render::Item::INVALID_ITEM_ID };
|
||||
//
|
||||
//namespace render {
|
||||
// template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1); }
|
||||
// template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); }
|
||||
// template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) {
|
||||
// if (Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) {
|
||||
// PerformanceTimer perfTimer("worldBox");
|
||||
//
|
||||
// auto& batch = *args->_batch;
|
||||
// DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch);
|
||||
// renderWorldBox(args, batch);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//void Application::runRenderFrame(RenderArgs* renderArgs) {
|
||||
// PROFILE_RANGE(render, __FUNCTION__);
|
||||
// PerformanceTimer perfTimer("display");
|
||||
// PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::runRenderFrame()");
|
||||
//
|
||||
// // The pending changes collecting the changes here
|
||||
// render::Transaction transaction;
|
||||
//
|
||||
// if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderEntities()) {
|
||||
// // render models...
|
||||
// PerformanceTimer perfTimer("entities");
|
||||
// PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
// "Application::runRenderFrame() ... entities...");
|
||||
//
|
||||
// RenderArgs::DebugFlags renderDebugFlags = RenderArgs::RENDER_DEBUG_NONE;
|
||||
//
|
||||
// renderArgs->_debugFlags = renderDebugFlags;
|
||||
// }
|
||||
//
|
||||
// // Make sure the WorldBox is in the scene
|
||||
// // For the record, this one RenderItem is the first one we created and added to the scene.
|
||||
// // We could move that code elsewhere but you know...
|
||||
// if (!render::Item::isValidID(WorldBoxRenderData::_item)) {
|
||||
// auto worldBoxRenderData = std::make_shared<WorldBoxRenderData>();
|
||||
// auto worldBoxRenderPayload = std::make_shared<WorldBoxRenderData::Payload>(worldBoxRenderData);
|
||||
//
|
||||
// WorldBoxRenderData::_item = _main3DScene->allocateID();
|
||||
//
|
||||
// transaction.resetItem(WorldBoxRenderData::_item, worldBoxRenderPayload);
|
||||
// _main3DScene->enqueueTransaction(transaction);
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// PerformanceTimer perfTimer("EngineRun");
|
||||
// _renderEngine->getRenderContext()->args = renderArgs;
|
||||
// _renderEngine->run();
|
||||
// }
|
||||
//}
|
||||
|
|
@ -34,6 +34,8 @@
|
|||
#include "Application.h"
|
||||
|
||||
GraphicsEngine::GraphicsEngine() {
|
||||
const QString SPLASH_SKYBOX { "{\"ProceduralEntity\":{ \"version\":2, \"shaderUrl\":\"qrc:///shaders/splashSkybox.frag\" } }" };
|
||||
_splashScreen->parse(SPLASH_SKYBOX);
|
||||
}
|
||||
|
||||
GraphicsEngine::~GraphicsEngine() {
|
||||
|
@ -54,6 +56,10 @@ void GraphicsEngine::initializeGPU(GLWidget* glwidget) {
|
|||
glwidget->makeCurrent();
|
||||
_gpuContext = std::make_shared<gpu::Context>();
|
||||
|
||||
_gpuContext->pushProgramsToSync(shader::allPrograms(), [this] {
|
||||
_programsCompiled.store(true);
|
||||
}, 1);
|
||||
|
||||
DependencyManager::get<TextureCache>()->setGPUContext(_gpuContext);
|
||||
}
|
||||
|
||||
|
@ -122,11 +128,7 @@ void GraphicsEngine::render_runRenderFrame(RenderArgs* renderArgs) {
|
|||
static const unsigned int THROTTLED_SIM_FRAMERATE = 15;
|
||||
static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE;
|
||||
|
||||
|
||||
|
||||
|
||||
bool GraphicsEngine::shouldPaint() const {
|
||||
|
||||
auto displayPlugin = qApp->getActiveDisplayPlugin();
|
||||
|
||||
#ifdef DEBUG_PAINT_DELAY
|
||||
|
@ -145,7 +147,7 @@ bool GraphicsEngine::shouldPaint() const {
|
|||
|
||||
// Throttle if requested
|
||||
//if (displayPlugin->isThrottled() && (_graphicsEngine._renderEventHandler->_lastTimeRendered.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) {
|
||||
if ( displayPlugin->isThrottled() &&
|
||||
if (displayPlugin->isThrottled() &&
|
||||
(static_cast<RenderEventHandler*>(_renderEventHandler)->_lastTimeRendered.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -158,8 +160,6 @@ bool GraphicsEngine::checkPendingRenderEvent() {
|
|||
return (_renderEventHandler && static_cast<RenderEventHandler*>(_renderEventHandler)->_pendingRenderEvent.compare_exchange_strong(expected, true));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GraphicsEngine::render_performFrame() {
|
||||
// Some plugins process message events, allowing paintGL to be called reentrantly.
|
||||
|
||||
|
@ -189,6 +189,7 @@ void GraphicsEngine::render_performFrame() {
|
|||
glm::mat4 HMDSensorPose;
|
||||
glm::mat4 eyeToWorld;
|
||||
glm::mat4 sensorToWorld;
|
||||
ViewFrustum viewFrustum;
|
||||
|
||||
bool isStereo;
|
||||
glm::mat4 stereoEyeOffsets[2];
|
||||
|
@ -211,6 +212,7 @@ void GraphicsEngine::render_performFrame() {
|
|||
stereoEyeOffsets[eye] = _appRenderArgs._eyeOffsets[eye];
|
||||
stereoEyeProjections[eye] = _appRenderArgs._eyeProjections[eye];
|
||||
});
|
||||
viewFrustum = _appRenderArgs._renderArgs.getViewFrustum();
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -221,21 +223,12 @@ void GraphicsEngine::render_performFrame() {
|
|||
gpu::doInBatch("Application_render::gpuContextReset", getGPUContext(), [&](gpu::Batch& batch) {
|
||||
batch.resetStages();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
PROFILE_RANGE(render, "/renderOverlay");
|
||||
PerformanceTimer perfTimer("renderOverlay");
|
||||
// NOTE: There is no batch associated with this renderArgs
|
||||
// the ApplicationOverlay class assumes it's viewport is setup to be the device size
|
||||
renderArgs._viewport = glm::ivec4(0, 0, qApp->getDeviceSize());
|
||||
qApp->getApplicationOverlay().renderOverlay(&renderArgs);
|
||||
}
|
||||
|
||||
{
|
||||
PROFILE_RANGE(render, "/updateCompositor");
|
||||
qApp->getApplicationCompositor().setFrameInfo(_renderFrameCount, eyeToWorld, sensorToWorld);
|
||||
if (isStereo) {
|
||||
renderArgs._context->enableStereo(true);
|
||||
renderArgs._context->setStereoProjections(stereoEyeProjections);
|
||||
renderArgs._context->setStereoViews(stereoEyeOffsets);
|
||||
}
|
||||
}
|
||||
|
||||
gpu::FramebufferPointer finalFramebuffer;
|
||||
|
@ -245,21 +238,40 @@ void GraphicsEngine::render_performFrame() {
|
|||
// Primary rendering pass
|
||||
auto framebufferCache = DependencyManager::get<FramebufferCache>();
|
||||
finalFramebufferSize = framebufferCache->getFrameBufferSize();
|
||||
// Final framebuffer that will be handled to the display-plugin
|
||||
// Final framebuffer that will be handed to the display-plugin
|
||||
finalFramebuffer = framebufferCache->getFramebuffer();
|
||||
}
|
||||
|
||||
{
|
||||
if (isStereo) {
|
||||
renderArgs._context->enableStereo(true);
|
||||
renderArgs._context->setStereoProjections(stereoEyeProjections);
|
||||
renderArgs._context->setStereoViews(stereoEyeOffsets);
|
||||
if (!_programsCompiled.load()) {
|
||||
gpu::doInBatch("splashFrame", _gpuContext, [&](gpu::Batch& batch) {
|
||||
batch.setFramebuffer(finalFramebuffer);
|
||||
batch.enableSkybox(true);
|
||||
batch.enableStereo(isStereo);
|
||||
batch.setViewportTransform({ 0, 0, finalFramebuffer->getSize() });
|
||||
_splashScreen->render(batch, viewFrustum);
|
||||
});
|
||||
} else {
|
||||
{
|
||||
PROFILE_RANGE(render, "/renderOverlay");
|
||||
PerformanceTimer perfTimer("renderOverlay");
|
||||
// NOTE: There is no batch associated with this renderArgs
|
||||
// the ApplicationOverlay class assumes it's viewport is setup to be the device size
|
||||
renderArgs._viewport = glm::ivec4(0, 0, qApp->getDeviceSize());
|
||||
qApp->getApplicationOverlay().renderOverlay(&renderArgs);
|
||||
}
|
||||
|
||||
renderArgs._hudOperator = displayPlugin->getHUDOperator();
|
||||
renderArgs._hudTexture = qApp->getApplicationOverlay().getOverlayTexture();
|
||||
renderArgs._blitFramebuffer = finalFramebuffer;
|
||||
render_runRenderFrame(&renderArgs);
|
||||
{
|
||||
PROFILE_RANGE(render, "/updateCompositor");
|
||||
qApp->getApplicationCompositor().setFrameInfo(_renderFrameCount, eyeToWorld, sensorToWorld);
|
||||
}
|
||||
|
||||
{
|
||||
PROFILE_RANGE(render, "/runRenderFrame");
|
||||
renderArgs._hudOperator = displayPlugin->getHUDOperator();
|
||||
renderArgs._hudTexture = qApp->getApplicationOverlay().getOverlayTexture();
|
||||
renderArgs._blitFramebuffer = finalFramebuffer;
|
||||
render_runRenderFrame(&renderArgs);
|
||||
}
|
||||
}
|
||||
|
||||
auto frame = getGPUContext()->endFrame();
|
||||
|
@ -283,18 +295,19 @@ void GraphicsEngine::render_performFrame() {
|
|||
renderArgs._blitFramebuffer.reset();
|
||||
renderArgs._context->enableStereo(false);
|
||||
|
||||
#if !defined(DISABLE_QML)
|
||||
{
|
||||
auto stats = Stats::getInstance();
|
||||
if (stats) {
|
||||
stats->setRenderDetails(renderArgs._details);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
uint64_t lastPaintDuration = usecTimestampNow() - lastPaintBegin;
|
||||
_frameTimingsScriptingInterface.addValue(lastPaintDuration);
|
||||
}
|
||||
|
||||
|
||||
void GraphicsEngine::editRenderArgs(RenderArgsEditor editor) {
|
||||
QMutexLocker renderLocker(&_renderArgsMutex);
|
||||
editor(_appRenderArgs);
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <qmutex.h>
|
||||
|
||||
#include <render/Engine.h>
|
||||
#include <procedural/ProceduralSkybox.h>
|
||||
|
||||
#include <OctreeConstants.h>
|
||||
#include <shared/RateCounter.h>
|
||||
|
@ -84,6 +85,9 @@ protected:
|
|||
|
||||
FrameTimingsScriptingInterface _frameTimingsScriptingInterface;
|
||||
|
||||
std::shared_ptr<ProceduralSkybox> _splashScreen { std::make_shared<ProceduralSkybox>() };
|
||||
std::atomic<bool> _programsCompiled { false };
|
||||
|
||||
friend class Application;
|
||||
};
|
||||
|
||||
|
|
|
@ -327,7 +327,7 @@ std::vector<int> AnimSkeleton::lookUpJointIndices(const std::vector<QString>& jo
|
|||
for (auto& name : jointNames) {
|
||||
int index = nameToJointIndex(name);
|
||||
if (index == -1) {
|
||||
qWarning(animation) << "AnimSkeleton::lookUpJointIndices(): could not find bone with named " << name;
|
||||
qWarning(animation) << "AnimSkeleton::lookUpJointIndices(): could not find bone with name " << name;
|
||||
}
|
||||
result.push_back(index);
|
||||
}
|
||||
|
|
|
@ -1776,16 +1776,11 @@ int AvatarData::getFauxJointIndex(const QString& name) const {
|
|||
|
||||
int AvatarData::getJointIndex(const QString& name) const {
|
||||
int result = getFauxJointIndex(name);
|
||||
if (result != -1) {
|
||||
return result;
|
||||
}
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
return _fstJointIndices.value(name) - 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList AvatarData::getJointNames() const {
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
return _fstJointNames;
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
glm::quat AvatarData::getOrientationOutbound() const {
|
||||
|
@ -2000,8 +1995,6 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
|
||||
_skeletonModelURL = expanded;
|
||||
|
||||
updateJointMappings();
|
||||
|
||||
if (_clientTraitsHandler) {
|
||||
_clientTraitsHandler->markTraitUpdated(AvatarTraits::SkeletonModelURL);
|
||||
}
|
||||
|
@ -2097,58 +2090,6 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) {
|
|||
setAttachmentData(attachmentData);
|
||||
}
|
||||
|
||||
void AvatarData::setJointMappingsFromNetworkReply() {
|
||||
|
||||
QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
|
||||
|
||||
// before we process this update, make sure that the skeleton model URL hasn't changed
|
||||
// since we made the FST request
|
||||
if (networkReply->error() != QNetworkReply::NoError || networkReply->url() != _skeletonModelURL) {
|
||||
qCDebug(avatars) << "Refusing to set joint mappings for FST URL that does not match the current URL";
|
||||
networkReply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
QByteArray line;
|
||||
while (!(line = networkReply->readLine()).isEmpty()) {
|
||||
line = line.trimmed();
|
||||
if (line.startsWith("filename")) {
|
||||
int filenameIndex = line.indexOf('=') + 1;
|
||||
if (filenameIndex > 0) {
|
||||
_skeletonFBXURL = _skeletonModelURL.resolved(QString(line.mid(filenameIndex).trimmed()));
|
||||
}
|
||||
}
|
||||
if (!line.startsWith("jointIndex")) {
|
||||
continue;
|
||||
}
|
||||
int jointNameIndex = line.indexOf('=') + 1;
|
||||
if (jointNameIndex == 0) {
|
||||
continue;
|
||||
}
|
||||
int secondSeparatorIndex = line.indexOf('=', jointNameIndex);
|
||||
if (secondSeparatorIndex == -1) {
|
||||
continue;
|
||||
}
|
||||
QString jointName = line.mid(jointNameIndex, secondSeparatorIndex - jointNameIndex).trimmed();
|
||||
bool ok;
|
||||
int jointIndex = line.mid(secondSeparatorIndex + 1).trimmed().toInt(&ok);
|
||||
if (ok) {
|
||||
while (_fstJointNames.size() < jointIndex + 1) {
|
||||
_fstJointNames.append(QString());
|
||||
}
|
||||
_fstJointNames[jointIndex] = jointName;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < _fstJointNames.size(); i++) {
|
||||
_fstJointIndices.insert(_fstJointNames.at(i), i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
networkReply->deleteLater();
|
||||
}
|
||||
|
||||
void AvatarData::sendAvatarDataPacket(bool sendAll) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
@ -2210,34 +2151,6 @@ void AvatarData::sendIdentityPacket() {
|
|||
_identityDataChanged = false;
|
||||
}
|
||||
|
||||
void AvatarData::updateJointMappings() {
|
||||
{
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
_fstJointIndices.clear();
|
||||
_fstJointNames.clear();
|
||||
_jointData.clear();
|
||||
}
|
||||
|
||||
if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) {
|
||||
////
|
||||
// TODO: Should we rely upon HTTPResourceRequest for ResourceRequestObserver instead?
|
||||
// HTTPResourceRequest::doSend() covers all of the following and
|
||||
// then some. It doesn't cover the connect() call, so we may
|
||||
// want to add a HTTPResourceRequest::doSend() method that does
|
||||
// connects.
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL);
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
DependencyManager::get<ResourceRequestObserver>()->update(
|
||||
_skeletonModelURL, -1, "AvatarData::updateJointMappings");
|
||||
QNetworkReply* networkReply = networkAccessManager.get(networkRequest);
|
||||
//
|
||||
////
|
||||
connect(networkReply, &QNetworkReply::finished, this, &AvatarData::setJointMappingsFromNetworkReply);
|
||||
}
|
||||
}
|
||||
|
||||
static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl");
|
||||
static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName");
|
||||
static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform");
|
||||
|
|
|
@ -1269,11 +1269,6 @@ public slots:
|
|||
*/
|
||||
void sendIdentityPacket();
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.setJointMappingsFromNetworkReply
|
||||
*/
|
||||
void setJointMappingsFromNetworkReply();
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.setSessionUUID
|
||||
* @param {Uuid} sessionUUID
|
||||
|
@ -1376,23 +1371,16 @@ protected:
|
|||
mutable HeadData* _headData { nullptr };
|
||||
|
||||
QUrl _skeletonModelURL;
|
||||
QUrl _skeletonFBXURL;
|
||||
QVector<AttachmentData> _attachmentData;
|
||||
QVector<AttachmentData> _oldAttachmentData;
|
||||
QString _displayName;
|
||||
QString _sessionDisplayName { };
|
||||
bool _lookAtSnappingEnabled { true };
|
||||
|
||||
QHash<QString, int> _fstJointIndices; ///< 1-based, since zero is returned for missing keys
|
||||
QStringList _fstJointNames; ///< in order of depth-first traversal
|
||||
|
||||
quint64 _errorLogExpiry; ///< time in future when to log an error
|
||||
|
||||
QWeakPointer<Node> _owningAvatarMixer;
|
||||
|
||||
/// Loads the joint indices, names from the FST file (if any)
|
||||
virtual void updateJointMappings();
|
||||
|
||||
glm::vec3 _targetVelocity;
|
||||
|
||||
SimpleMovingAverage _averageBytesReceived;
|
||||
|
@ -1496,11 +1484,8 @@ protected:
|
|||
T readLockWithNamedJointIndex(const QString& name, const T& defaultValue, F f) const {
|
||||
int index = getFauxJointIndex(name);
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
if (index == -1) {
|
||||
index = _fstJointIndices.value(name) - 1;
|
||||
}
|
||||
|
||||
// The first conditional is superfluous, but illsutrative
|
||||
// The first conditional is superfluous, but illustrative
|
||||
if (index == -1 || index < _jointData.size()) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
@ -1517,9 +1502,6 @@ protected:
|
|||
void writeLockWithNamedJointIndex(const QString& name, F f) {
|
||||
int index = getFauxJointIndex(name);
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
if (index == -1) {
|
||||
index = _fstJointIndices.value(name) - 1;
|
||||
}
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
|
|||
// we double check that it is a simple iterator here
|
||||
auto traitType = static_cast<AvatarTraits::TraitType>(std::distance(traitStatusesCopy.simpleCBegin(), simpleIt));
|
||||
|
||||
if (_shouldPerformInitialSend || *simpleIt == Updated) {
|
||||
if (initialSend || *simpleIt == Updated) {
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
_owningAvatar->packTrait(traitType, *traitsPacketList);
|
||||
|
||||
|
|
|
@ -525,6 +525,9 @@ void OpenGLDisplayPlugin::updateFrameData() {
|
|||
if (_newFrameQueue.size() > 1) {
|
||||
_droppedFrameRate.increment(_newFrameQueue.size() - 1);
|
||||
}
|
||||
|
||||
_gpuContext->processProgramsToSync();
|
||||
|
||||
while (!_newFrameQueue.empty()) {
|
||||
_currentFrame = _newFrameQueue.front();
|
||||
_newFrameQueue.pop();
|
||||
|
@ -645,6 +648,7 @@ void OpenGLDisplayPlugin::present() {
|
|||
auto frameId = (uint64_t)presentCount();
|
||||
PROFILE_RANGE_EX(render, __FUNCTION__, 0xffffff00, frameId)
|
||||
uint64_t startPresent = usecTimestampNow();
|
||||
|
||||
{
|
||||
PROFILE_RANGE_EX(render, "updateFrameData", 0xff00ff00, frameId)
|
||||
updateFrameData();
|
||||
|
@ -837,7 +841,6 @@ void OpenGLDisplayPlugin::render(std::function<void(gpu::Batch& batch)> f) {
|
|||
_gpuContext->executeBatch(batch);
|
||||
}
|
||||
|
||||
|
||||
OpenGLDisplayPlugin::~OpenGLDisplayPlugin() {
|
||||
}
|
||||
|
||||
|
|
|
@ -417,6 +417,19 @@ QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) {
|
|||
return filepath.mid(filepath.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
QMap<QString, QString> getJointNameMapping(const QVariantHash& mapping) {
|
||||
static const QString JOINT_NAME_MAPPING_FIELD = "jointMap";
|
||||
QMap<QString, QString> hfmToHifiJointNameMap;
|
||||
if (!mapping.isEmpty() && mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) {
|
||||
auto jointNames = mapping[JOINT_NAME_MAPPING_FIELD].toHash();
|
||||
for (auto itr = jointNames.begin(); itr != jointNames.end(); itr++) {
|
||||
hfmToHifiJointNameMap.insert(itr.key(), itr.value().toString());
|
||||
qCDebug(modelformat) << "the mapped key " << itr.key() << " has a value of " << hfmToHifiJointNameMap[itr.key()];
|
||||
}
|
||||
}
|
||||
return hfmToHifiJointNameMap;
|
||||
}
|
||||
|
||||
QMap<QString, glm::quat> getJointRotationOffsets(const QVariantHash& mapping) {
|
||||
QMap<QString, glm::quat> jointRotationOffsets;
|
||||
static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset";
|
||||
|
@ -465,14 +478,14 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
std::map<QString, HFMLight> lights;
|
||||
|
||||
QVariantHash joints = mapping.value("joint").toHash();
|
||||
QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft")));
|
||||
QString jointEyeRightName = processID(getString(joints.value("jointEyeRight", "jointEyeRight")));
|
||||
QString jointNeckName = processID(getString(joints.value("jointNeck", "jointNeck")));
|
||||
QString jointRootName = processID(getString(joints.value("jointRoot", "jointRoot")));
|
||||
QString jointLeanName = processID(getString(joints.value("jointLean", "jointLean")));
|
||||
QString jointHeadName = processID(getString(joints.value("jointHead", "jointHead")));
|
||||
QString jointLeftHandName = processID(getString(joints.value("jointLeftHand", "jointLeftHand")));
|
||||
QString jointRightHandName = processID(getString(joints.value("jointRightHand", "jointRightHand")));
|
||||
QString jointEyeLeftName = "EyeLeft";
|
||||
QString jointEyeRightName = "EyeRight";
|
||||
QString jointNeckName = "Neck";
|
||||
QString jointRootName = "Hips";
|
||||
QString jointLeanName = "Spine";
|
||||
QString jointHeadName = "Head";
|
||||
QString jointLeftHandName = "LeftHand";
|
||||
QString jointRightHandName = "RightHand";
|
||||
QString jointEyeLeftID;
|
||||
QString jointEyeRightID;
|
||||
QString jointNeckID;
|
||||
|
@ -519,6 +532,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
HFMModel& hfmModel = *hfmModelPtr;
|
||||
|
||||
hfmModel.originalURL = url;
|
||||
hfmModel.hfmToHifiJointNameMapping.clear();
|
||||
hfmModel.hfmToHifiJointNameMapping = getJointNameMapping(mapping);
|
||||
|
||||
float unitScaleFactor = 1.0f;
|
||||
glm::vec3 ambientColor;
|
||||
|
@ -587,34 +602,34 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
hifiGlobalNodeID = id;
|
||||
}
|
||||
|
||||
if (name == jointEyeLeftName || name == "EyeL" || name == "joint_Leye") {
|
||||
if (name == jointEyeLeftName || name == "EyeL" || name == "joint_Leye" || (hfmModel.hfmToHifiJointNameMapping.contains(jointEyeLeftName) && (name == hfmModel.hfmToHifiJointNameMapping[jointEyeLeftName]))) {
|
||||
jointEyeLeftID = getID(object.properties);
|
||||
|
||||
} else if (name == jointEyeRightName || name == "EyeR" || name == "joint_Reye") {
|
||||
} else if (name == jointEyeRightName || name == "EyeR" || name == "joint_Reye" || (hfmModel.hfmToHifiJointNameMapping.contains(jointEyeRightName) && (name == hfmModel.hfmToHifiJointNameMapping[jointEyeRightName]))) {
|
||||
jointEyeRightID = getID(object.properties);
|
||||
|
||||
} else if (name == jointNeckName || name == "NeckRot" || name == "joint_neck") {
|
||||
} else if (name == jointNeckName || name == "NeckRot" || name == "joint_neck" || (hfmModel.hfmToHifiJointNameMapping.contains(jointNeckName) && (name == hfmModel.hfmToHifiJointNameMapping[jointNeckName]))) {
|
||||
jointNeckID = getID(object.properties);
|
||||
|
||||
} else if (name == jointRootName) {
|
||||
} else if (name == jointRootName || (hfmModel.hfmToHifiJointNameMapping.contains(jointRootName) && (name == hfmModel.hfmToHifiJointNameMapping[jointRootName]))) {
|
||||
jointRootID = getID(object.properties);
|
||||
|
||||
} else if (name == jointLeanName) {
|
||||
} else if (name == jointLeanName || (hfmModel.hfmToHifiJointNameMapping.contains(jointLeanName) && (name == hfmModel.hfmToHifiJointNameMapping[jointLeanName]))) {
|
||||
jointLeanID = getID(object.properties);
|
||||
|
||||
} else if (name == jointHeadName) {
|
||||
} else if ((name == jointHeadName) || (hfmModel.hfmToHifiJointNameMapping.contains(jointHeadName) && (name == hfmModel.hfmToHifiJointNameMapping[jointHeadName]))) {
|
||||
jointHeadID = getID(object.properties);
|
||||
|
||||
} else if (name == jointLeftHandName || name == "LeftHand" || name == "joint_L_hand") {
|
||||
} else if (name == jointLeftHandName || name == "LeftHand" || name == "joint_L_hand" || (hfmModel.hfmToHifiJointNameMapping.contains(jointLeftHandName) && (name == hfmModel.hfmToHifiJointNameMapping[jointLeftHandName]))) {
|
||||
jointLeftHandID = getID(object.properties);
|
||||
|
||||
} else if (name == jointRightHandName || name == "RightHand" || name == "joint_R_hand") {
|
||||
} else if (name == jointRightHandName || name == "RightHand" || name == "joint_R_hand" || (hfmModel.hfmToHifiJointNameMapping.contains(jointRightHandName) && (name == hfmModel.hfmToHifiJointNameMapping[jointRightHandName]))) {
|
||||
jointRightHandID = getID(object.properties);
|
||||
|
||||
} else if (name == "LeftToe" || name == "joint_L_toe" || name == "LeftToe_End") {
|
||||
} else if (name == "LeftToe" || name == "joint_L_toe" || name == "LeftToe_End" || (hfmModel.hfmToHifiJointNameMapping.contains("LeftToe") && (name == hfmModel.hfmToHifiJointNameMapping["LeftToe"]))) {
|
||||
jointLeftToeID = getID(object.properties);
|
||||
|
||||
} else if (name == "RightToe" || name == "joint_R_toe" || name == "RightToe_End") {
|
||||
} else if (name == "RightToe" || name == "joint_R_toe" || name == "RightToe_End" || (hfmModel.hfmToHifiJointNameMapping.contains("RightToe") && (name == hfmModel.hfmToHifiJointNameMapping["RightToe"]))) {
|
||||
jointRightToeID = getID(object.properties);
|
||||
}
|
||||
|
||||
|
@ -1388,6 +1403,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
}
|
||||
joint.inverseBindRotation = joint.inverseDefaultRotation;
|
||||
joint.name = fbxModel.name;
|
||||
if (hfmModel.hfmToHifiJointNameMapping.contains(hfmModel.hfmToHifiJointNameMapping.key(joint.name))) {
|
||||
joint.name = hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name);
|
||||
}
|
||||
|
||||
foreach (const QString& childID, _connectionChildMap.values(modelID)) {
|
||||
QString type = typeFlags.value(childID);
|
||||
|
@ -1400,7 +1418,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
joint.bindTransformFoundInCluster = false;
|
||||
|
||||
hfmModel.joints.append(joint);
|
||||
hfmModel.jointIndices.insert(fbxModel.name, hfmModel.joints.size());
|
||||
hfmModel.jointIndices.insert(joint.name, hfmModel.joints.size());
|
||||
|
||||
QString rotationID = localRotations.value(modelID);
|
||||
AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID));
|
||||
|
@ -1824,6 +1842,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr
|
|||
QString jointName = itr.key();
|
||||
glm::quat rotationOffset = itr.value();
|
||||
int jointIndex = hfmModel.getJointIndex(jointName);
|
||||
if (hfmModel.hfmToHifiJointNameMapping.contains(jointName)) {
|
||||
jointIndex = hfmModel.getJointIndex(jointName);
|
||||
}
|
||||
if (jointIndex != -1) {
|
||||
hfmModel.jointRotationOffsets.insert(jointIndex, rotationOffset);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ static const QString JOINT_FIELD = "joint";
|
|||
static const QString FREE_JOINT_FIELD = "freeJoint";
|
||||
static const QString BLENDSHAPE_FIELD = "bs";
|
||||
static const QString SCRIPT_FIELD = "script";
|
||||
static const QString JOINT_NAME_MAPPING_FIELD = "jointMap";
|
||||
|
||||
class FSTReader {
|
||||
public:
|
||||
|
|
|
@ -859,3 +859,7 @@ void GLBackend::setCameraCorrection(const Mat4& correction, const Mat4& prevRend
|
|||
_pipeline._cameraCorrectionBuffer._buffer->setSubData(0, _transform._correction);
|
||||
_pipeline._cameraCorrectionBuffer._buffer->flush();
|
||||
}
|
||||
|
||||
void GLBackend::syncProgram(const gpu::ShaderPointer& program) {
|
||||
gpu::gl::GLShader::sync(*this, *program);
|
||||
}
|
|
@ -249,6 +249,8 @@ public:
|
|||
// Let's try to avoid to do that as much as possible!
|
||||
void syncCache() final override;
|
||||
|
||||
void syncProgram(const gpu::ShaderPointer& program) override;
|
||||
|
||||
// This is the ugly "download the pixels to sysmem for taking a snapshot"
|
||||
// Just avoid using it, it's ugly and will break performances
|
||||
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer,
|
||||
|
|
|
@ -37,6 +37,7 @@ GLShader* GLShader::sync(GLBackend& backend, const Shader& shader, const Shader:
|
|||
if (object) {
|
||||
return object;
|
||||
}
|
||||
PROFILE_RANGE(render, "/GLShader::sync");
|
||||
// need to have a gpu object?
|
||||
if (shader.isProgram()) {
|
||||
GLShader* tempObject = backend.compileBackendProgram(shader, handler);
|
||||
|
|
|
@ -760,4 +760,4 @@ void Batch::flush() {
|
|||
}
|
||||
buffer->flush();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,8 +18,8 @@
|
|||
|
||||
float color_scalar_sRGBToLinear(float value) {
|
||||
const float SRGB_ELBOW = 0.04045;
|
||||
|
||||
return (value <= SRGB_ELBOW) ? value / 12.92 : pow((value + 0.055) / 1.055, 2.4);
|
||||
|
||||
return mix(pow((value + 0.055) / 1.055, 2.4), value / 12.92, float(value <= SRGB_ELBOW));
|
||||
}
|
||||
|
||||
vec3 color_sRGBToLinear(vec3 srgb) {
|
||||
|
|
|
@ -51,6 +51,7 @@ Context::~Context() {
|
|||
delete batch;
|
||||
}
|
||||
_batchPool.clear();
|
||||
_syncedPrograms.clear();
|
||||
}
|
||||
|
||||
void Context::shutdown() {
|
||||
|
@ -346,6 +347,40 @@ Size Context::getTextureResourceIdealGPUMemSize() {
|
|||
return Backend::textureResourceIdealGPUMemSize.getValue();
|
||||
}
|
||||
|
||||
void Context::pushProgramsToSync(const std::vector<uint32_t>& programIDs, std::function<void()> callback, size_t rate) {
|
||||
std::vector<gpu::ShaderPointer> programs;
|
||||
for (auto programID : programIDs) {
|
||||
programs.push_back(gpu::Shader::createProgram(programID));
|
||||
}
|
||||
pushProgramsToSync(programs, callback, rate);
|
||||
}
|
||||
|
||||
void Context::pushProgramsToSync(const std::vector<gpu::ShaderPointer>& programs, std::function<void()> callback, size_t rate) {
|
||||
Lock lock(_programsToSyncMutex);
|
||||
_programsToSyncQueue.emplace(programs, callback, rate == 0 ? programs.size() : rate);
|
||||
}
|
||||
|
||||
void Context::processProgramsToSync() {
|
||||
if (!_programsToSyncQueue.empty()) {
|
||||
Lock lock(_programsToSyncMutex);
|
||||
ProgramsToSync programsToSync = _programsToSyncQueue.front();
|
||||
size_t numSynced = 0;
|
||||
while (_nextProgramToSyncIndex < programsToSync.programs.size() && numSynced < programsToSync.rate) {
|
||||
auto nextProgram = programsToSync.programs.at(_nextProgramToSyncIndex);
|
||||
_backend->syncProgram(nextProgram);
|
||||
_syncedPrograms.push_back(nextProgram);
|
||||
_nextProgramToSyncIndex++;
|
||||
numSynced++;
|
||||
}
|
||||
|
||||
if (_nextProgramToSyncIndex == programsToSync.programs.size()) {
|
||||
programsToSync.callback();
|
||||
_nextProgramToSyncIndex = 0;
|
||||
_programsToSyncQueue.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BatchPointer Context::acquireBatch(const char* name) {
|
||||
Batch* rawBatch = nullptr;
|
||||
{
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <assert.h>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
|
||||
|
@ -61,6 +62,7 @@ public:
|
|||
|
||||
virtual void render(const Batch& batch) = 0;
|
||||
virtual void syncCache() = 0;
|
||||
virtual void syncProgram(const gpu::ShaderPointer& program) = 0;
|
||||
virtual void recycle() const = 0;
|
||||
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0;
|
||||
|
||||
|
@ -247,6 +249,20 @@ public:
|
|||
static Size getTextureResourcePopulatedGPUMemSize();
|
||||
static Size getTextureResourceIdealGPUMemSize();
|
||||
|
||||
struct ProgramsToSync {
|
||||
ProgramsToSync(const std::vector<gpu::ShaderPointer>& programs, std::function<void()> callback, size_t rate) :
|
||||
programs(programs), callback(callback), rate(rate) {}
|
||||
|
||||
std::vector<gpu::ShaderPointer> programs;
|
||||
std::function<void()> callback;
|
||||
size_t rate;
|
||||
};
|
||||
|
||||
void pushProgramsToSync(const std::vector<uint32_t>& programIDs, std::function<void()> callback, size_t rate = 0);
|
||||
void pushProgramsToSync(const std::vector<gpu::ShaderPointer>& programs, std::function<void()> callback, size_t rate = 0);
|
||||
|
||||
void processProgramsToSync();
|
||||
|
||||
protected:
|
||||
Context(const Context& context);
|
||||
|
||||
|
@ -258,6 +274,11 @@ protected:
|
|||
RangeTimerPointer _frameRangeTimer;
|
||||
StereoState _stereo;
|
||||
|
||||
std::mutex _programsToSyncMutex;
|
||||
std::queue<ProgramsToSync> _programsToSyncQueue;
|
||||
gpu::Shaders _syncedPrograms;
|
||||
size_t _nextProgramToSyncIndex { 0 };
|
||||
|
||||
// Sampled at the end of every frame, the stats of all the counters
|
||||
mutable ContextStats _frameStats;
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ public:
|
|||
// Let's try to avoid to do that as much as possible!
|
||||
void syncCache() final { }
|
||||
|
||||
void syncProgram(const gpu::ShaderPointer& program) final {}
|
||||
|
||||
// This is the ugly "download the pixels to sysmem for taking a snapshot"
|
||||
// Just avoid using it, it's ugly and will break performances
|
||||
virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) final { }
|
||||
|
|
|
@ -313,6 +313,7 @@ public:
|
|||
QList<QString> blendshapeChannelNames;
|
||||
|
||||
QMap<int, glm::quat> jointRotationOffsets;
|
||||
QMap<QString, QString> hfmToHifiJointNameMapping;
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -29,7 +29,7 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
|||
|
||||
void main(void) {
|
||||
vec4 texel = texture(originalTexture, _texCoord0);
|
||||
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
|
||||
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
|
||||
texel.rgb *= _color.rgb;
|
||||
|
||||
packDeferredFragment(
|
||||
|
|
|
@ -41,9 +41,9 @@ void main(void) {
|
|||
applyFade(fadeParams, _positionWS.xyz, fadeEmissive);
|
||||
|
||||
vec4 texel = texture(originalTexture, _texCoord0);
|
||||
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
|
||||
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
|
||||
texel.rgb *= _color.rgb;
|
||||
texel.a = abs(_color.a);
|
||||
texel.a *= abs(_color.a);
|
||||
|
||||
const float ALPHA_THRESHOLD = 0.999;
|
||||
if (texel.a < ALPHA_THRESHOLD) {
|
||||
|
|
|
@ -29,9 +29,9 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
|||
|
||||
void main(void) {
|
||||
vec4 texel = texture(originalTexture, _texCoord0);
|
||||
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
|
||||
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
|
||||
texel.rgb *= _color.rgb;
|
||||
texel.a = abs(_color.a);
|
||||
texel.a *= abs(_color.a);
|
||||
|
||||
const float ALPHA_THRESHOLD = 0.999;
|
||||
if (texel.a < ALPHA_THRESHOLD) {
|
||||
|
|
|
@ -41,9 +41,9 @@ void main(void) {
|
|||
applyFade(fadeParams, _positionWS.xyz, fadeEmissive);
|
||||
|
||||
vec4 texel = texture(originalTexture, _texCoord0);
|
||||
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
|
||||
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
|
||||
texel.rgb *= _color.rgb;
|
||||
texel.a = abs(_color.a);
|
||||
texel.a *= abs(_color.a);
|
||||
|
||||
const float ALPHA_THRESHOLD = 0.999;
|
||||
if (texel.a < ALPHA_THRESHOLD) {
|
||||
|
|
|
@ -29,7 +29,7 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
|
|||
|
||||
void main(void) {
|
||||
vec4 texel = texture(originalTexture, _texCoord0);
|
||||
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
|
||||
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
|
||||
texel.rgb *= _color.rgb;
|
||||
texel.a *= abs(_color.a);
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ void main(void) {
|
|||
applyFade(fadeParams, _positionWS.xyz, fadeEmissive);
|
||||
|
||||
vec4 texel = texture(originalTexture, _texCoord0);
|
||||
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
|
||||
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
|
||||
texel.rgb *= _color.rgb;
|
||||
texel.a *= abs(_color.a);
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ layout(location=0) out vec4 _fragColor0;
|
|||
|
||||
void main(void) {
|
||||
vec4 texel = texture(originalTexture, _texCoord0);
|
||||
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
|
||||
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
|
||||
texel.rgb *= _color.rgb;
|
||||
texel.a *= abs(_color.a);
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ void main(void) {
|
|||
applyFade(fadeParams, _positionWS.xyz, fadeEmissive);
|
||||
|
||||
vec4 texel = texture(originalTexture, _texCoord0);
|
||||
texel = mix(color_sRGBAToLinear(texel), texel, float(_color.a <= 0.0));
|
||||
texel = mix(texel, color_sRGBAToLinear(texel), float(_color.a <= 0.0));
|
||||
texel.rgb *= _color.rgb;
|
||||
texel.a *= abs(_color.a);
|
||||
|
||||
|
|
|
@ -2061,68 +2061,6 @@ bool ScriptEngine::hasEntityScriptDetails(const EntityItemID& entityID) const {
|
|||
return _entityScripts.contains(entityID);
|
||||
}
|
||||
|
||||
const static EntityItemID BAD_SCRIPT_UUID_PLACEHOLDER { "{20170224-dead-face-0000-cee000021114}" };
|
||||
|
||||
void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const EntityItemID& leaderID) {
|
||||
QList<DeferredLoadEntity> retryLoads;
|
||||
QMutableListIterator<DeferredLoadEntity> i(_deferredEntityLoads);
|
||||
while (i.hasNext()) {
|
||||
auto retry = i.next();
|
||||
if (retry.entityScript == entityScript) {
|
||||
retryLoads << retry;
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
foreach(DeferredLoadEntity retry, retryLoads) {
|
||||
// check whether entity was since been deleted
|
||||
|
||||
EntityScriptDetails details;
|
||||
if (!getEntityScriptDetails(retry.entityID, details)) {
|
||||
qCDebug(scriptengine) << "processDeferredEntityLoads -- entity details gone (entity deleted?)"
|
||||
<< retry.entityID;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check whether entity has since been unloaded or otherwise errored-out
|
||||
if (details.status != EntityScriptStatus::PENDING) {
|
||||
qCDebug(scriptengine) << "processDeferredEntityLoads -- entity status no longer PENDING; "
|
||||
<< retry.entityID << details.status;
|
||||
continue;
|
||||
}
|
||||
|
||||
// propagate leader's failure reasons to the pending entity
|
||||
EntityScriptDetails leaderDetails;
|
||||
{
|
||||
QWriteLocker locker { &_entityScriptsLock };
|
||||
leaderDetails = _entityScripts[leaderID];
|
||||
}
|
||||
if (leaderDetails.status != EntityScriptStatus::RUNNING) {
|
||||
qCDebug(scriptengine) << QString("... pending load of %1 cancelled (leader: %2 status: %3)")
|
||||
.arg(retry.entityID.toString()).arg(leaderID.toString()).arg(leaderDetails.status);
|
||||
|
||||
auto extraDetail = QString("\n(propagated from %1)").arg(leaderID.toString());
|
||||
if (leaderDetails.status == EntityScriptStatus::ERROR_LOADING_SCRIPT ||
|
||||
leaderDetails.status == EntityScriptStatus::ERROR_RUNNING_SCRIPT) {
|
||||
// propagate same error so User doesn't have to hunt down stampede's leader
|
||||
updateEntityScriptStatus(retry.entityID, leaderDetails.status, leaderDetails.errorInfo + extraDetail);
|
||||
} else {
|
||||
// the leader Entity somehow ended up in some other state (rapid-fire delete or unload could cause)
|
||||
updateEntityScriptStatus(retry.entityID, EntityScriptStatus::ERROR_LOADING_SCRIPT,
|
||||
"A previous Entity failed to load using this script URL; reload to try again." + extraDetail);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_occupiedScriptURLs.contains(retry.entityScript)) {
|
||||
qCWarning(scriptengine) << "--- SHOULD NOT HAPPEN -- recursive call into processDeferredEntityLoads" << retry.entityScript;
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we made it here then the leading entity was successful so proceed with normal load
|
||||
loadEntityScript(retry.entityID, retry.entityScript, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "loadEntityScript",
|
||||
|
@ -2147,40 +2085,6 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString&
|
|||
updateEntityScriptStatus(entityID, EntityScriptStatus::PENDING, "...pending...");
|
||||
}
|
||||
|
||||
// This "occupied" approach allows multiple Entities to boot from the same script URL while still taking
|
||||
// full advantage of cacheable require modules. This only affects Entities literally coming in back-to-back
|
||||
// before the first one has time to finish loading.
|
||||
if (_occupiedScriptURLs.contains(entityScript)) {
|
||||
auto currentEntityID = _occupiedScriptURLs[entityScript];
|
||||
if (currentEntityID == BAD_SCRIPT_UUID_PLACEHOLDER) {
|
||||
if (forceRedownload) {
|
||||
// script was previously marked unusable, but we're reloading so reset it
|
||||
_occupiedScriptURLs.remove(entityScript);
|
||||
} else {
|
||||
// since not reloading, assume that the exact same input would produce the exact same output again
|
||||
// note: this state gets reset with "reload all scripts," leaving/returning to a Domain, clear cache, etc.
|
||||
#ifdef DEBUG_ENTITY_STATES
|
||||
qCDebug(scriptengine) << QString("loadEntityScript.cancelled entity: %1 (previous script failure)")
|
||||
.arg(entityID.toString());
|
||||
#endif
|
||||
updateEntityScriptStatus(entityID, EntityScriptStatus::ERROR_LOADING_SCRIPT,
|
||||
"A previous Entity failed to load using this script URL; reload to try again.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// another entity is busy loading from this script URL so wait for them to finish
|
||||
#ifdef DEBUG_ENTITY_STATES
|
||||
qCDebug(scriptengine) << QString("loadEntityScript.deferring[%0] entity: %1 (waiting on %2 )")
|
||||
.arg(_deferredEntityLoads.size()).arg(entityID.toString()).arg(currentEntityID.toString());
|
||||
#endif
|
||||
_deferredEntityLoads.push_back({ entityID, entityScript });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// the scriptURL slot is available; flag as in-use
|
||||
_occupiedScriptURLs[entityScript] = entityID;
|
||||
|
||||
#ifdef DEBUG_ENTITY_STATES
|
||||
{
|
||||
EntityScriptDetails details;
|
||||
|
@ -2223,10 +2127,6 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString&
|
|||
qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting";
|
||||
#endif
|
||||
}
|
||||
// recheck whether us since may have been set to BAD_SCRIPT_UUID_PLACEHOLDER in entityScriptContentAvailable
|
||||
if (_occupiedScriptURLs.contains(entityScript) && _occupiedScriptURLs[entityScript] == entityID) {
|
||||
_occupiedScriptURLs.remove(entityScript);
|
||||
}
|
||||
});
|
||||
}, forceRedownload);
|
||||
}
|
||||
|
@ -2301,13 +2201,6 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
newDetails.errorInfo = errorInfo;
|
||||
newDetails.status = status;
|
||||
setEntityScriptDetails(entityID, newDetails);
|
||||
|
||||
#ifdef DEBUG_ENTITY_STATES
|
||||
qCDebug(scriptengine) << "entityScriptContentAvailable -- flagging as BAD_SCRIPT_UUID_PLACEHOLDER";
|
||||
#endif
|
||||
// flag the original entityScript as unusuable
|
||||
_occupiedScriptURLs[entityScript] = BAD_SCRIPT_UUID_PLACEHOLDER;
|
||||
processDeferredEntityLoads(entityScript, entityID);
|
||||
};
|
||||
|
||||
// NETWORK / FILESYSTEM ERRORS
|
||||
|
@ -2441,9 +2334,6 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
callEntityScriptMethod(entityID, "preload");
|
||||
|
||||
emit entityScriptPreloadFinished(entityID);
|
||||
|
||||
_occupiedScriptURLs.remove(entityScript);
|
||||
processDeferredEntityLoads(entityScript, entityID);
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
|
@ -2499,10 +2389,6 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR
|
|||
}
|
||||
|
||||
stopAllTimersForEntityScript(entityID);
|
||||
{
|
||||
// FIXME: shouldn't have to do this here, but currently something seems to be firing unloads moments after firing initial load requests
|
||||
processDeferredEntityLoads(scriptText, entityID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2532,7 +2418,6 @@ void ScriptEngine::unloadAllEntityScripts() {
|
|||
_entityScripts.clear();
|
||||
}
|
||||
emit entityScriptDetailsUpdated();
|
||||
_occupiedScriptURLs.clear();
|
||||
|
||||
#ifdef DEBUG_ENGINE_STATE
|
||||
_debugDump(
|
||||
|
|
|
@ -747,7 +747,6 @@ protected:
|
|||
void updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus& status, const QString& errorInfo = QString());
|
||||
void setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details);
|
||||
void setParentURL(const QString& parentURL) { _parentURL = parentURL; }
|
||||
void processDeferredEntityLoads(const QString& entityScript, const EntityItemID& leaderID);
|
||||
|
||||
QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot);
|
||||
void stopTimer(QTimer* timer);
|
||||
|
@ -783,8 +782,6 @@ protected:
|
|||
QSet<QUrl> _includedURLs;
|
||||
mutable QReadWriteLock _entityScriptsLock { QReadWriteLock::Recursive };
|
||||
QHash<EntityItemID, EntityScriptDetails> _entityScripts;
|
||||
QHash<QString, EntityItemID> _occupiedScriptURLs;
|
||||
QList<DeferredLoadEntity> _deferredEntityLoads;
|
||||
EntityScriptContentAvailableMap _contentAvailableQueue;
|
||||
|
||||
bool _isThreaded { false };
|
||||
|
|
|
@ -138,6 +138,8 @@ static QString outOfRangeDataStrategyToString(ViveControllerManager::OutOfRangeD
|
|||
return "Freeze";
|
||||
case ViveControllerManager::OutOfRangeDataStrategy::Drop:
|
||||
return "Drop";
|
||||
case ViveControllerManager::OutOfRangeDataStrategy::DropAfterDelay:
|
||||
return "DropAfterDelay";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,6 +148,8 @@ static ViveControllerManager::OutOfRangeDataStrategy stringToOutOfRangeDataStrat
|
|||
return ViveControllerManager::OutOfRangeDataStrategy::Drop;
|
||||
} else if (string == "Freeze") {
|
||||
return ViveControllerManager::OutOfRangeDataStrategy::Freeze;
|
||||
} else if (string == "DropAfterDelay") {
|
||||
return ViveControllerManager::OutOfRangeDataStrategy::DropAfterDelay;
|
||||
} else {
|
||||
return ViveControllerManager::OutOfRangeDataStrategy::None;
|
||||
}
|
||||
|
@ -302,7 +306,7 @@ void ViveControllerManager::loadSettings() {
|
|||
if (_inputDevice) {
|
||||
const double DEFAULT_ARM_CIRCUMFERENCE = 0.33;
|
||||
const double DEFAULT_SHOULDER_WIDTH = 0.48;
|
||||
const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "Drop";
|
||||
const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "DropAfterDelay";
|
||||
_inputDevice->_armCircumference = settings.value("armCircumference", QVariant(DEFAULT_ARM_CIRCUMFERENCE)).toDouble();
|
||||
_inputDevice->_shoulderWidth = settings.value("shoulderWidth", QVariant(DEFAULT_SHOULDER_WIDTH)).toDouble();
|
||||
_inputDevice->_outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(settings.value("outOfRangeDataStrategy", QVariant(DEFAULT_OUT_OF_RANGE_STRATEGY)).toString());
|
||||
|
@ -516,6 +520,7 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde
|
|||
_nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid &&
|
||||
poseIndex <= controller::TRACKED_OBJECT_15) {
|
||||
|
||||
uint64_t now = usecTimestampNow();
|
||||
controller::Pose pose;
|
||||
switch (_outOfRangeDataStrategy) {
|
||||
case OutOfRangeDataStrategy::Drop:
|
||||
|
@ -544,6 +549,22 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde
|
|||
_nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex];
|
||||
}
|
||||
break;
|
||||
case OutOfRangeDataStrategy::DropAfterDelay:
|
||||
const uint64_t DROP_DELAY_TIME = 500 * USECS_PER_MSEC;
|
||||
|
||||
// All Running_OK results are valid.
|
||||
if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult == vr::TrackingResult_Running_OK) {
|
||||
pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]);
|
||||
// update the timer
|
||||
_simDataRunningOkTimestampMap[deviceIndex] = now;
|
||||
} else if (now - _simDataRunningOkTimestampMap[deviceIndex] < DROP_DELAY_TIME) {
|
||||
// report the pose, even though pose is out-of-range
|
||||
pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]);
|
||||
} else {
|
||||
// this pose has been out-of-range for too long.
|
||||
pose.valid = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (pose.valid) {
|
||||
|
|
|
@ -63,7 +63,8 @@ public:
|
|||
enum class OutOfRangeDataStrategy {
|
||||
None,
|
||||
Freeze,
|
||||
Drop
|
||||
Drop,
|
||||
DropAfterDelay
|
||||
};
|
||||
|
||||
private:
|
||||
|
@ -205,6 +206,8 @@ private:
|
|||
|
||||
bool _hmdTrackingEnabled { true };
|
||||
|
||||
std::map<uint32_t, uint64_t> _simDataRunningOkTimestampMap;
|
||||
|
||||
QString configToString(Config config);
|
||||
friend class ViveControllerManager;
|
||||
};
|
||||
|
|
|
@ -193,22 +193,52 @@
|
|||
"emitterShouldTrail": {
|
||||
"tooltip": "If enabled, then particles are \"left behind\" as the emitter moves, otherwise they are not."
|
||||
},
|
||||
"particleRadiusTriple": {
|
||||
"tooltip": "The size of each particle.",
|
||||
"jsPropertyName": "particleRadius"
|
||||
},
|
||||
"particleRadius": {
|
||||
"tooltip": "The size of each particle."
|
||||
},
|
||||
"radiusStart": {
|
||||
"tooltip": "The start size of each particle."
|
||||
},
|
||||
"radiusFinish": {
|
||||
"tooltip": "The finish size of each particle."
|
||||
},
|
||||
"radiusSpread": {
|
||||
"tooltip": "The spread in size that each particle is given, resulting in a variety of sizes."
|
||||
},
|
||||
"particleColorTriple": {
|
||||
"tooltip": "The color of each particle.",
|
||||
"jsPropertyName": "color"
|
||||
},
|
||||
"particleColor": {
|
||||
"tooltip": "The color of each particle.",
|
||||
"jsPropertyName": "color"
|
||||
},
|
||||
"colorStart": {
|
||||
"tooltip": "The start color of each particle."
|
||||
},
|
||||
"colorFinish": {
|
||||
"tooltip": "The finish color of each particle."
|
||||
},
|
||||
"colorSpread": {
|
||||
"tooltip": "The spread in color that each particle is given, resulting in a variety of colors."
|
||||
},
|
||||
"particleAlphaTriple": {
|
||||
"tooltip": "The alpha of each particle.",
|
||||
"jsPropertyName": "alpha"
|
||||
},
|
||||
"alpha": {
|
||||
"tooltip": "The alpha of each particle."
|
||||
},
|
||||
"alphaStart": {
|
||||
"tooltip": "The start alpha of each particle."
|
||||
},
|
||||
"alphaFinish": {
|
||||
"tooltip": "The finish alpha of each particle."
|
||||
},
|
||||
"alphaSpread": {
|
||||
"tooltip": "The spread in alpha that each particle is given, resulting in a variety of alphas."
|
||||
},
|
||||
|
@ -218,20 +248,44 @@
|
|||
"accelerationSpread": {
|
||||
"tooltip": "The spread in accelerations that each particle is given, resulting in a variety of accelerations."
|
||||
},
|
||||
"particleSpinTriple": {
|
||||
"tooltip": "The spin of each particle.",
|
||||
"jsPropertyName": "particleSpin"
|
||||
},
|
||||
"particleSpin": {
|
||||
"tooltip": "The spin of each particle in the system."
|
||||
"tooltip": "The spin of each particle."
|
||||
},
|
||||
"spinStart": {
|
||||
"tooltip": "The start spin of each particle."
|
||||
},
|
||||
"spinFinish": {
|
||||
"tooltip": "The finish spin of each particle."
|
||||
},
|
||||
"spinSpread": {
|
||||
"tooltip": "The spread in spin that each particle is given, resulting in a variety of spins."
|
||||
},
|
||||
"rotateWithEntity": {
|
||||
"tooltip": "If enabled, each particle will spin relative to the roation of the entity as a whole."
|
||||
"tooltip": "If enabled, each particle will spin relative to the rotation of the entity as a whole."
|
||||
},
|
||||
"particlePolarTriple": {
|
||||
"tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"polarStart": {
|
||||
"tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis."
|
||||
"tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis."
|
||||
},
|
||||
"polarFinish": {
|
||||
"tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis."
|
||||
},
|
||||
"particleAzimuthTriple": {
|
||||
"tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"azimuthStart": {
|
||||
"tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis."
|
||||
"tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis."
|
||||
},
|
||||
"azimuthFinish": {
|
||||
"tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis."
|
||||
},
|
||||
"lightColor": {
|
||||
"tooltip": "The color of the light emitted.",
|
||||
|
@ -352,7 +406,7 @@
|
|||
"tooltip": "The URL of a sound to play when the entity collides with something else."
|
||||
},
|
||||
"grab.grabbable": {
|
||||
"tooltip": "If enabled, this entity will allow grabbing input and will be moveable."
|
||||
"tooltip": "If enabled, this entity will allow grabbing input and will be movable."
|
||||
},
|
||||
"grab.triggerable": {
|
||||
"tooltip": "If enabled, the collider on this entity is used for triggering events."
|
||||
|
|
|
@ -600,6 +600,7 @@ div.section[collapsed="true"], div.section[collapsed="true"] > .section-header {
|
|||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
padding: 6px 0;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.triple-item {
|
||||
|
@ -1390,6 +1391,10 @@ input[type=button]#export {
|
|||
cursor: col-resize;
|
||||
}
|
||||
|
||||
#entity-table .dragging {
|
||||
background-color: #b3ecff;
|
||||
}
|
||||
|
||||
#entity-table td {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
@ -1517,6 +1522,7 @@ input.rename-entity {
|
|||
}
|
||||
|
||||
.create-app-tooltip {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
background: #6a6a6a;
|
||||
border: 1px solid black;
|
||||
|
@ -1651,6 +1657,7 @@ input.number-slider {
|
|||
font-family: Raleway-Light;
|
||||
font-size: 14px;
|
||||
margin: 6px 0;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#property-name, #property-id {
|
||||
|
@ -1663,9 +1670,38 @@ input.number-slider {
|
|||
}
|
||||
|
||||
#placeholder-property-type {
|
||||
min-width: 0px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.collapse-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#property-userData-editor.error {
|
||||
border: 2px solid red;
|
||||
}
|
||||
|
||||
#property-userData-editorStatus {
|
||||
color: white;
|
||||
background-color: red;
|
||||
padding: 5px;
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#property-materialData-editor.error {
|
||||
border: 2px solid red;
|
||||
}
|
||||
|
||||
#property-materialData-editorStatus {
|
||||
color: white;
|
||||
background-color: red;
|
||||
padding: 5px;
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type=number].hide-spinner::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<script type="text/javascript" src="js/spinButtons.js"></script>
|
||||
<script type="text/javascript" src="js/listView.js"></script>
|
||||
<script type="text/javascript" src="js/entityListContextMenu.js"></script>
|
||||
<script type="text/javascript" src="js/utils.js"></script>
|
||||
<script type="text/javascript" src="js/entityList.js"></script>
|
||||
</head>
|
||||
<body onload='loaded();' id="entity-list-body">
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
<script type="text/javascript" src="js/underscore-min.js"></script>
|
||||
<script type="text/javascript" src="js/createAppTooltip.js"></script>
|
||||
<script type="text/javascript" src="js/draggableNumber.js"></script>
|
||||
<script type="text/javascript" src="js/utils.js"></script>
|
||||
<script type="text/javascript" src="js/entityProperties.js"></script>
|
||||
<script src="js/jsoneditor.min.js"></script>
|
||||
</head>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
|
||||
<script type="text/javascript" src="js/spinButtons.js"></script>
|
||||
<script type="text/javascript" src="js/utils.js"></script>
|
||||
<script type="text/javascript" src="js/gridControls.js"></script>
|
||||
</head>
|
||||
<body onload='loaded();'>
|
||||
|
|
|
@ -21,6 +21,9 @@ const MAX_LENGTH_RADIUS = 9;
|
|||
const MINIMUM_COLUMN_WIDTH = 24;
|
||||
const SCROLLBAR_WIDTH = 20;
|
||||
const RESIZER_WIDTH = 10;
|
||||
const DELTA_X_MOVE_COLUMNS_THRESHOLD = 2;
|
||||
const DELTA_X_COLUMN_SWAP_POSITION = 5;
|
||||
const CERTIFIED_PLACEHOLDER = "** Certified **";
|
||||
|
||||
const COLUMNS = {
|
||||
type: {
|
||||
|
@ -108,8 +111,8 @@ const COLUMNS = {
|
|||
};
|
||||
|
||||
const COMPARE_ASCENDING = function(a, b) {
|
||||
let va = a[currentSortColumn];
|
||||
let vb = b[currentSortColumn];
|
||||
let va = a[currentSortColumnID];
|
||||
let vb = b[currentSortColumnID];
|
||||
|
||||
if (va < vb) {
|
||||
return -1;
|
||||
|
@ -172,7 +175,7 @@ let entityList = null; // The ListView
|
|||
*/
|
||||
let entityListContextMenu = null;
|
||||
|
||||
let currentSortColumn = 'type';
|
||||
let currentSortColumnID = 'type';
|
||||
let currentSortOrder = ASCENDING_SORT;
|
||||
let elSortOrders = {};
|
||||
let typeFilters = [];
|
||||
|
@ -180,10 +183,13 @@ let isFilterInView = false;
|
|||
|
||||
let columns = [];
|
||||
let columnsByID = {};
|
||||
let currentResizeEl = null;
|
||||
let startResizeEvent = null;
|
||||
let lastResizeEvent = null;
|
||||
let resizeColumnIndex = 0;
|
||||
let startThClick = null;
|
||||
let elTargetTh = null;
|
||||
let elTargetSpan = null;
|
||||
let targetColumnIndex = 0;
|
||||
let lastColumnSwapPosition = -1;
|
||||
let initialThEvent = null;
|
||||
let renameTimeout = null;
|
||||
let renameLastBlur = null;
|
||||
let renameLastEntityID = null;
|
||||
|
@ -230,10 +236,6 @@ const PROFILE = !ENABLE_PROFILING ? PROFILE_NOOP : function(name, fn, args) {
|
|||
console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms");
|
||||
};
|
||||
|
||||
debugPrint = function (message) {
|
||||
console.log(message);
|
||||
};
|
||||
|
||||
function loaded() {
|
||||
openEventBridge(function() {
|
||||
elEntityTable = document.getElementById("entity-table");
|
||||
|
@ -324,10 +326,11 @@ function loaded() {
|
|||
for (let columnID in COLUMNS) {
|
||||
let columnData = COLUMNS[columnID];
|
||||
|
||||
let thID = "entity-" + columnID;
|
||||
let elTh = document.createElement("th");
|
||||
let thID = "entity-" + columnID;
|
||||
elTh.setAttribute("id", thID);
|
||||
elTh.setAttribute("data-resizable-column-id", thID);
|
||||
elTh.setAttribute("columnIndex", columnIndex);
|
||||
elTh.setAttribute("columnID", columnID);
|
||||
if (columnData.glyph) {
|
||||
let elGlyph = document.createElement("span");
|
||||
elGlyph.className = "glyph";
|
||||
|
@ -336,20 +339,20 @@ function loaded() {
|
|||
} else {
|
||||
elTh.innerText = columnData.columnHeader;
|
||||
}
|
||||
elTh.onmousedown = function() {
|
||||
startThClick = this;
|
||||
};
|
||||
elTh.onmouseup = function() {
|
||||
if (startThClick === this) {
|
||||
setSortColumn(columnID);
|
||||
elTh.onmousedown = function(event) {
|
||||
if (event.target.nodeName === 'TH') {
|
||||
elTargetTh = event.target;
|
||||
targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex"));
|
||||
lastColumnSwapPosition = event.clientX;
|
||||
} else if (event.target.nodeName === 'SPAN') {
|
||||
elTargetSpan = event.target;
|
||||
}
|
||||
startThClick = null;
|
||||
initialThEvent = event;
|
||||
};
|
||||
|
||||
let elResizer = document.createElement("span");
|
||||
elResizer.className = "resizer";
|
||||
elResizer.innerHTML = " ";
|
||||
elResizer.setAttribute("columnIndex", columnIndex);
|
||||
elResizer.onmousedown = onStartResize;
|
||||
elTh.appendChild(elResizer);
|
||||
|
||||
|
@ -633,10 +636,11 @@ function loaded() {
|
|||
id: entity.id,
|
||||
name: entity.name,
|
||||
type: type,
|
||||
url: filename,
|
||||
fullUrl: entity.url,
|
||||
url: entity.certificateID === "" ? filename : "<i>" + CERTIFIED_PLACEHOLDER + "</i>",
|
||||
fullUrl: entity.certificateID === "" ? filename : CERTIFIED_PLACEHOLDER,
|
||||
locked: entity.locked,
|
||||
visible: entity.visible,
|
||||
certificateID: entity.certificateID,
|
||||
verticesCount: displayIfNonZero(entity.verticesCount),
|
||||
texturesCount: displayIfNonZero(entity.texturesCount),
|
||||
texturesSize: decimalMegabytes(entity.texturesSize),
|
||||
|
@ -762,13 +766,13 @@ function loaded() {
|
|||
refreshNoEntitiesMessage();
|
||||
}
|
||||
|
||||
function setSortColumn(column) {
|
||||
function setSortColumn(columnID) {
|
||||
PROFILE("set-sort-column", function() {
|
||||
if (currentSortColumn === column) {
|
||||
if (currentSortColumnID === columnID) {
|
||||
currentSortOrder *= -1;
|
||||
} else {
|
||||
elSortOrders[currentSortColumn].innerHTML = "";
|
||||
currentSortColumn = column;
|
||||
elSortOrders[currentSortColumnID].innerHTML = "";
|
||||
currentSortColumnID = columnID;
|
||||
currentSortOrder = ASCENDING_SORT;
|
||||
}
|
||||
refreshSortOrder();
|
||||
|
@ -777,7 +781,7 @@ function loaded() {
|
|||
}
|
||||
|
||||
function refreshSortOrder() {
|
||||
elSortOrders[currentSortColumn].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING;
|
||||
elSortOrders[currentSortColumnID].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING;
|
||||
}
|
||||
|
||||
function refreshEntities() {
|
||||
|
@ -874,7 +878,7 @@ function loaded() {
|
|||
if (column.data.glyph) {
|
||||
elCell.innerHTML = itemData[column.data.propertyID] ? column.data.columnHeader : null;
|
||||
} else {
|
||||
elCell.innerText = itemData[column.data.propertyID];
|
||||
elCell.innerHTML = itemData[column.data.propertyID];
|
||||
}
|
||||
elCell.style = "min-width:" + column.widthPx + "px;" + "max-width:" + column.widthPx + "px;";
|
||||
elCell.className = createColumnClassName(column.columnID);
|
||||
|
@ -1093,8 +1097,8 @@ function loaded() {
|
|||
}
|
||||
|
||||
function onStartResize(event) {
|
||||
startResizeEvent = event;
|
||||
resizeColumnIndex = parseInt(this.getAttribute("columnIndex"));
|
||||
lastResizeEvent = event;
|
||||
resizeColumnIndex = parseInt(this.parentNode.getAttribute("columnIndex"));
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
|
@ -1137,8 +1141,37 @@ function loaded() {
|
|||
entityList.refresh();
|
||||
}
|
||||
|
||||
document.onmousemove = function(ev) {
|
||||
if (startResizeEvent) {
|
||||
function swapColumns(columnAIndex, columnBIndex) {
|
||||
let columnA = columns[columnAIndex];
|
||||
let columnB = columns[columnBIndex];
|
||||
let columnATh = columns[columnAIndex].elTh;
|
||||
let columnBTh = columns[columnBIndex].elTh;
|
||||
let columnThParent = columnATh.parentNode;
|
||||
columnThParent.removeChild(columnBTh);
|
||||
columnThParent.insertBefore(columnBTh, columnATh);
|
||||
columnATh.setAttribute("columnIndex", columnBIndex);
|
||||
columnBTh.setAttribute("columnIndex", columnAIndex);
|
||||
columnA.elResizer.setAttribute("columnIndex", columnBIndex);
|
||||
columnB.elResizer.setAttribute("columnIndex", columnAIndex);
|
||||
|
||||
for (let i = 0; i < visibleEntities.length; ++i) {
|
||||
let elRow = visibleEntities[i].elRow;
|
||||
if (elRow) {
|
||||
let columnACell = elRow.childNodes[columnAIndex];
|
||||
let columnBCell = elRow.childNodes[columnBIndex];
|
||||
elRow.removeChild(columnBCell);
|
||||
elRow.insertBefore(columnBCell, columnACell);
|
||||
}
|
||||
}
|
||||
|
||||
columns[columnAIndex] = columnB;
|
||||
columns[columnBIndex] = columnA;
|
||||
|
||||
updateColumnWidths();
|
||||
}
|
||||
|
||||
document.onmousemove = function(event) {
|
||||
if (lastResizeEvent) {
|
||||
startTh = null;
|
||||
|
||||
let column = columns[resizeColumnIndex];
|
||||
|
@ -1150,7 +1183,7 @@ function loaded() {
|
|||
}
|
||||
|
||||
let fullWidth = elEntityTableBody.offsetWidth;
|
||||
let dx = ev.clientX - startResizeEvent.clientX;
|
||||
let dx = event.clientX - lastResizeEvent.clientX;
|
||||
let dPct = dx / fullWidth;
|
||||
|
||||
let newColWidth = column.width + dPct;
|
||||
|
@ -1160,14 +1193,60 @@ function loaded() {
|
|||
column.width += dPct;
|
||||
nextColumn.width -= dPct;
|
||||
updateColumnWidths();
|
||||
startResizeEvent = ev;
|
||||
lastResizeEvent = event;
|
||||
}
|
||||
} else if (elTargetTh) {
|
||||
let dxFromInitial = event.clientX - initialThEvent.clientX;
|
||||
if (Math.abs(dxFromInitial) >= DELTA_X_MOVE_COLUMNS_THRESHOLD) {
|
||||
elTargetTh.className = "dragging";
|
||||
}
|
||||
if (targetColumnIndex < columns.length - 1) {
|
||||
let nextColumnIndex = targetColumnIndex + 1;
|
||||
let nextColumnTh = columns[nextColumnIndex].elTh;
|
||||
let nextColumnStartX = nextColumnTh.getBoundingClientRect().left;
|
||||
if (event.clientX >= nextColumnStartX && event.clientX - lastColumnSwapPosition >= DELTA_X_COLUMN_SWAP_POSITION) {
|
||||
swapColumns(targetColumnIndex, nextColumnIndex);
|
||||
targetColumnIndex = nextColumnIndex;
|
||||
lastColumnSwapPosition = event.clientX;
|
||||
}
|
||||
}
|
||||
if (targetColumnIndex >= 1) {
|
||||
let prevColumnIndex = targetColumnIndex - 1;
|
||||
let prevColumnTh = columns[prevColumnIndex].elTh;
|
||||
let prevColumnEndX = prevColumnTh.getBoundingClientRect().right;
|
||||
if (event.clientX <= prevColumnEndX && lastColumnSwapPosition - event.clientX >= DELTA_X_COLUMN_SWAP_POSITION) {
|
||||
swapColumns(prevColumnIndex, targetColumnIndex);
|
||||
targetColumnIndex = prevColumnIndex;
|
||||
lastColumnSwapPosition = event.clientX;
|
||||
}
|
||||
}
|
||||
} else if (elTargetSpan) {
|
||||
let dxFromInitial = event.clientX - initialThEvent.clientX;
|
||||
if (Math.abs(dxFromInitial) >= DELTA_X_MOVE_COLUMNS_THRESHOLD) {
|
||||
elTargetTh = elTargetSpan.parentNode;
|
||||
elTargetTh.className = "dragging";
|
||||
targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex"));
|
||||
lastColumnSwapPosition = event.clientX;
|
||||
elTargetSpan = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.onmouseup = function(ev) {
|
||||
startResizeEvent = null;
|
||||
ev.stopPropagation();
|
||||
document.onmouseup = function(event) {
|
||||
if (elTargetTh) {
|
||||
if (elTargetTh.className !== "dragging" && elTargetTh === event.target) {
|
||||
let columnID = elTargetTh.getAttribute("columnID");
|
||||
setSortColumn(columnID);
|
||||
}
|
||||
elTargetTh.className = "";
|
||||
} else if (elTargetSpan) {
|
||||
let columnID = elTargetSpan.parentNode.getAttribute("columnID");
|
||||
setSortColumn(columnID);
|
||||
}
|
||||
lastResizeEvent = null;
|
||||
elTargetTh = null;
|
||||
elTargetSpan = null;
|
||||
initialThEvent = null;
|
||||
};
|
||||
|
||||
function setSpaceMode(spaceMode) {
|
||||
|
@ -1287,8 +1366,9 @@ function loaded() {
|
|||
});
|
||||
|
||||
augmentSpinButtons();
|
||||
disableDragDrop();
|
||||
|
||||
document.addEventListener("contextmenu", function (event) {
|
||||
document.addEventListener("contextmenu", function(event) {
|
||||
entityListContextMenu.close();
|
||||
|
||||
// Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked
|
||||
|
|
|
@ -75,7 +75,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Parent Joint Index",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
propertyID: "parentJointIndex",
|
||||
},
|
||||
{
|
||||
|
@ -135,7 +135,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Line Height",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
step: 0.005,
|
||||
decimals: 4,
|
||||
|
@ -183,7 +183,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Light Intensity",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 0.1,
|
||||
|
@ -193,7 +193,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Light Horizontal Angle",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
multiplier: DEGREES_TO_RADIANS,
|
||||
decimals: 2,
|
||||
unit: "deg",
|
||||
|
@ -202,7 +202,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Light Vertical Angle",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
multiplier: DEGREES_TO_RADIANS,
|
||||
decimals: 2,
|
||||
unit: "deg",
|
||||
|
@ -241,7 +241,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Ambient Intensity",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 0.1,
|
||||
|
@ -270,7 +270,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Range",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 5,
|
||||
max: 10000,
|
||||
step: 5,
|
||||
|
@ -287,7 +287,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Base",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: -1000,
|
||||
max: 1000,
|
||||
step: 10,
|
||||
|
@ -298,7 +298,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Ceiling",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: -1000,
|
||||
max: 5000,
|
||||
step: 10,
|
||||
|
@ -315,7 +315,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Background Blend",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -337,7 +337,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Glare Angle",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 180,
|
||||
step: 1,
|
||||
|
@ -353,7 +353,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Bloom Intensity",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -363,7 +363,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Bloom Threshold",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -373,7 +373,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Bloom Size",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
|
@ -390,7 +390,9 @@ const GROUPS = [
|
|||
{
|
||||
label: "Model",
|
||||
type: "string",
|
||||
placeholder: "URL",
|
||||
propertyID: "modelURL",
|
||||
hideIfCertified: true,
|
||||
},
|
||||
{
|
||||
label: "Collision Shape",
|
||||
|
@ -404,11 +406,13 @@ const GROUPS = [
|
|||
label: "Compound Shape",
|
||||
type: "string",
|
||||
propertyID: "compoundShapeURL",
|
||||
hideIfCertified: true,
|
||||
},
|
||||
{
|
||||
label: "Animation",
|
||||
type: "string",
|
||||
propertyID: "animation.url",
|
||||
hideIfCertified: true,
|
||||
},
|
||||
{
|
||||
label: "Play Automatically",
|
||||
|
@ -432,22 +436,22 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Animation Frame",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
propertyID: "animation.currentFrame",
|
||||
},
|
||||
{
|
||||
label: "First Frame",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
propertyID: "animation.firstFrame",
|
||||
},
|
||||
{
|
||||
label: "Last Frame",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
propertyID: "animation.lastFrame",
|
||||
},
|
||||
{
|
||||
label: "Animation FPS",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
propertyID: "animation.fps",
|
||||
},
|
||||
{
|
||||
|
@ -460,6 +464,7 @@ const GROUPS = [
|
|||
type: "textarea",
|
||||
propertyID: "originalTextures",
|
||||
readOnly: true,
|
||||
hideIfCertified: true,
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -486,7 +491,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Source Resolution",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
propertyID: "dpi",
|
||||
},
|
||||
]
|
||||
|
@ -503,7 +508,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Intensity",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
step: 0.1,
|
||||
decimals: 1,
|
||||
|
@ -511,7 +516,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Fall-Off Radius",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
step: 0.1,
|
||||
decimals: 1,
|
||||
|
@ -525,14 +530,14 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Spotlight Exponent",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
step: 0.01,
|
||||
decimals: 2,
|
||||
propertyID: "exponent",
|
||||
},
|
||||
{
|
||||
label: "Spotlight Cut-Off",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
step: 0.01,
|
||||
decimals: 2,
|
||||
propertyID: "cutoff",
|
||||
|
@ -563,7 +568,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Submesh to Replace",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
step: 1,
|
||||
propertyID: "submeshToReplace",
|
||||
|
@ -577,7 +582,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Priority",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
propertyID: "priority",
|
||||
},
|
||||
|
@ -612,7 +617,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Material Rotation",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
step: 0.1,
|
||||
decimals: 2,
|
||||
unit: "deg",
|
||||
|
@ -636,7 +641,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Lifespan",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
unit: "s",
|
||||
min: 0.01,
|
||||
max: 10,
|
||||
|
@ -646,7 +651,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Max Particles",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 1,
|
||||
max: 10000,
|
||||
step: 1,
|
||||
|
@ -667,7 +672,7 @@ const GROUPS = [
|
|||
properties: [
|
||||
{
|
||||
label: "Emit Rate",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 1,
|
||||
max: 1000,
|
||||
step: 1,
|
||||
|
@ -675,7 +680,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Emit Speed",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 5,
|
||||
step: 0.01,
|
||||
|
@ -684,7 +689,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Speed Spread",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 5,
|
||||
step: 0.01,
|
||||
|
@ -703,7 +708,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Emit Radius Start",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -735,10 +740,11 @@ const GROUPS = [
|
|||
{
|
||||
type: "triple",
|
||||
label: "Size",
|
||||
propertyID: "particleRadiusTriple",
|
||||
properties: [
|
||||
{
|
||||
label: "Start",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 4,
|
||||
step: 0.01,
|
||||
|
@ -748,7 +754,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Middle",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 4,
|
||||
step: 0.01,
|
||||
|
@ -757,7 +763,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Finish",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 4,
|
||||
step: 0.01,
|
||||
|
@ -769,7 +775,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Size Spread",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 4,
|
||||
step: 0.01,
|
||||
|
@ -786,6 +792,7 @@ const GROUPS = [
|
|||
{
|
||||
type: "triple",
|
||||
label: "Color",
|
||||
propertyID: "particleColorTriple",
|
||||
properties: [
|
||||
{
|
||||
label: "Start",
|
||||
|
@ -822,10 +829,11 @@ const GROUPS = [
|
|||
{
|
||||
type: "triple",
|
||||
label: "Alpha",
|
||||
propertyID: "particleAlphaTriple",
|
||||
properties: [
|
||||
{
|
||||
label: "Start",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -835,7 +843,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Middle",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -844,7 +852,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Finish",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -856,7 +864,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Alpha Spread",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -898,10 +906,11 @@ const GROUPS = [
|
|||
{
|
||||
type: "triple",
|
||||
label: "Spin",
|
||||
propertyID: "particleSpinTriple",
|
||||
properties: [
|
||||
{
|
||||
label: "Start",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: -360,
|
||||
max: 360,
|
||||
step: 1,
|
||||
|
@ -913,7 +922,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Middle",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: -360,
|
||||
max: 360,
|
||||
step: 1,
|
||||
|
@ -924,7 +933,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Finish",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: -360,
|
||||
max: 360,
|
||||
step: 1,
|
||||
|
@ -938,7 +947,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Spin Spread",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 360,
|
||||
step: 1,
|
||||
|
@ -962,10 +971,11 @@ const GROUPS = [
|
|||
{
|
||||
type: "triple",
|
||||
label: "Horizontal Angle",
|
||||
propertyID: "particlePolarTriple",
|
||||
properties: [
|
||||
{
|
||||
label: "Start",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 180,
|
||||
step: 1,
|
||||
|
@ -976,7 +986,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Finish",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 180,
|
||||
step: 1,
|
||||
|
@ -990,10 +1000,11 @@ const GROUPS = [
|
|||
{
|
||||
type: "triple",
|
||||
label: "Vertical Angle",
|
||||
propertyID: "particleAzimuthTriple",
|
||||
properties: [
|
||||
{
|
||||
label: "Start",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: -180,
|
||||
max: 180,
|
||||
step: 1,
|
||||
|
@ -1004,7 +1015,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Finish",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: -180,
|
||||
max: 180,
|
||||
step: 1,
|
||||
|
@ -1089,7 +1100,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Scale",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
defaultValue: 100,
|
||||
unit: "%",
|
||||
buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions },
|
||||
|
@ -1131,14 +1142,14 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Clone Lifetime",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
unit: "s",
|
||||
propertyID: "cloneLifetime",
|
||||
showPropertyRule: { "cloneable": "true" },
|
||||
},
|
||||
{
|
||||
label: "Clone Limit",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
propertyID: "cloneLimit",
|
||||
showPropertyRule: { "cloneable": "true" },
|
||||
},
|
||||
|
@ -1181,6 +1192,7 @@ const GROUPS = [
|
|||
buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ],
|
||||
propertyID: "script",
|
||||
placeholder: "URL",
|
||||
hideIfCertified: true,
|
||||
},
|
||||
{
|
||||
label: "Server Script",
|
||||
|
@ -1197,7 +1209,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Lifetime",
|
||||
type: "string",
|
||||
type: "number",
|
||||
unit: "s",
|
||||
propertyID: "lifetime",
|
||||
},
|
||||
|
@ -1267,6 +1279,7 @@ const GROUPS = [
|
|||
placeholder: "URL",
|
||||
propertyID: "collisionSoundURL",
|
||||
showPropertyRule: { "collisionless": "false" },
|
||||
hideIfCertified: true,
|
||||
},
|
||||
{
|
||||
label: "Dynamic",
|
||||
|
@ -1291,7 +1304,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Linear Damping",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -1310,7 +1323,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Angular Damping",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -1319,7 +1332,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Bounciness",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
|
@ -1328,7 +1341,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Friction",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 0.1,
|
||||
|
@ -1337,7 +1350,7 @@ const GROUPS = [
|
|||
},
|
||||
{
|
||||
label: "Density",
|
||||
type: "number",
|
||||
type: "number-draggable",
|
||||
min: 100,
|
||||
max: 10000,
|
||||
step: 1,
|
||||
|
@ -1431,13 +1444,13 @@ const TEXTURE_ELEMENTS = {
|
|||
|
||||
const JSON_EDITOR_ROW_DIV_INDEX = 2;
|
||||
|
||||
var elGroups = {};
|
||||
var properties = {};
|
||||
var colorPickers = {};
|
||||
var particlePropertyUpdates = {};
|
||||
var selectedEntityProperties;
|
||||
var lastEntityID = null;
|
||||
var createAppTooltip = new CreateAppTooltip();
|
||||
let elGroups = {};
|
||||
let properties = {};
|
||||
let colorPickers = {};
|
||||
let particlePropertyUpdates = {};
|
||||
let selectedEntityProperties;
|
||||
let lastEntityID = null;
|
||||
let createAppTooltip = new CreateAppTooltip();
|
||||
let currentSpaceMode = PROPERTY_SPACE_MODE.LOCAL;
|
||||
|
||||
function createElementFromHTML(htmlString) {
|
||||
|
@ -1454,12 +1467,13 @@ function getPropertyInputElement(propertyID) {
|
|||
let property = properties[propertyID];
|
||||
switch (property.data.type) {
|
||||
case 'string':
|
||||
case 'number':
|
||||
case 'bool':
|
||||
case 'dropdown':
|
||||
case 'textarea':
|
||||
case 'texture':
|
||||
return property.elInput;
|
||||
case 'number':
|
||||
case 'number-draggable':
|
||||
return property.elNumber.elInput;
|
||||
case 'vec3':
|
||||
case 'vec2':
|
||||
|
@ -1529,15 +1543,20 @@ function resetProperties() {
|
|||
let propertyData = property.data;
|
||||
|
||||
switch (propertyData.type) {
|
||||
case 'number':
|
||||
case 'string': {
|
||||
property.elInput.value = "";
|
||||
if (propertyData.defaultValue !== undefined) {
|
||||
property.elInput.value = propertyData.defaultValue;
|
||||
} else {
|
||||
property.elInput.value = "";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'bool': {
|
||||
property.elInput.checked = false;
|
||||
break;
|
||||
}
|
||||
case 'number': {
|
||||
case 'number-draggable': {
|
||||
if (propertyData.defaultValue !== undefined) {
|
||||
property.elNumber.setValue(propertyData.defaultValue);
|
||||
} else {
|
||||
|
@ -1691,7 +1710,7 @@ function updateProperty(originalPropertyName, propertyValue, isParticleProperty)
|
|||
}
|
||||
}
|
||||
|
||||
var particleSyncDebounce = _.debounce(function () {
|
||||
let particleSyncDebounce = _.debounce(function () {
|
||||
updateProperties(particlePropertyUpdates);
|
||||
particlePropertyUpdates = {};
|
||||
}, DEBOUNCE_TIMEOUT);
|
||||
|
@ -1825,7 +1844,7 @@ function createStringProperty(property, elProperty) {
|
|||
type="text"
|
||||
${propertyData.placeholder ? 'placeholder="' + propertyData.placeholder + '"' : ''}
|
||||
${propertyData.readOnly ? 'readonly' : ''}></input>
|
||||
`)
|
||||
`);
|
||||
|
||||
|
||||
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
|
||||
|
@ -1873,7 +1892,45 @@ function createBoolProperty(property, elProperty) {
|
|||
return elInput;
|
||||
}
|
||||
|
||||
function createNumberProperty(property, elProperty) {
|
||||
function createNumberProperty(property, elProperty) {
|
||||
let elementID = property.elementID;
|
||||
let propertyData = property.data;
|
||||
|
||||
elProperty.className = "text";
|
||||
|
||||
let elInput = createElementFromHTML(`
|
||||
<input id="${elementID}"
|
||||
class='hide-spinner'
|
||||
type="number"
|
||||
${propertyData.placeholder ? 'placeholder="' + propertyData.placeholder + '"' : ''}
|
||||
${propertyData.readOnly ? 'readonly' : ''}></input>
|
||||
`)
|
||||
|
||||
if (propertyData.min !== undefined) {
|
||||
elInput.setAttribute("min", propertyData.min);
|
||||
}
|
||||
if (propertyData.max !== undefined) {
|
||||
elInput.setAttribute("max", propertyData.max);
|
||||
}
|
||||
if (propertyData.step !== undefined) {
|
||||
elInput.setAttribute("step", propertyData.step);
|
||||
}
|
||||
if (propertyData.defaultValue !== undefined) {
|
||||
elInput.value = propertyData.defaultValue;
|
||||
}
|
||||
|
||||
elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(property));
|
||||
|
||||
elProperty.appendChild(elInput);
|
||||
|
||||
if (propertyData.buttons !== undefined) {
|
||||
addButtons(elProperty, elementID, propertyData.buttons, false);
|
||||
}
|
||||
|
||||
return elInput;
|
||||
}
|
||||
|
||||
function createNumberDraggableProperty(property, elProperty) {
|
||||
let elementID = property.elementID;
|
||||
let propertyData = property.data;
|
||||
|
||||
|
@ -2217,7 +2274,11 @@ function createProperty(propertyData, propertyElementID, propertyName, propertyI
|
|||
break;
|
||||
}
|
||||
case 'number': {
|
||||
property.elNumber = createNumberProperty(property, elProperty);
|
||||
property.elInput = createNumberProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'number-draggable': {
|
||||
property.elNumber = createNumberDraggableProperty(property, elProperty);
|
||||
break;
|
||||
}
|
||||
case 'vec3': {
|
||||
|
@ -2362,31 +2423,41 @@ function saveUserData() {
|
|||
saveJSONUserData(true);
|
||||
}
|
||||
|
||||
function setJSONError(property, isError) {
|
||||
$("#property-"+ property + "-editor").toggleClass('error', isError);
|
||||
let $propertyUserDataEditorStatus = $("#property-"+ property + "-editorStatus");
|
||||
$propertyUserDataEditorStatus.css('display', isError ? 'block' : 'none');
|
||||
$propertyUserDataEditorStatus.text(isError ? 'Invalid JSON code - look for red X in your code' : '');
|
||||
}
|
||||
|
||||
function setUserDataFromEditor(noUpdate) {
|
||||
let json = null;
|
||||
let errorFound = false;
|
||||
try {
|
||||
json = editor.get();
|
||||
} catch (e) {
|
||||
alert('Invalid JSON code - look for red X in your code ', +e);
|
||||
errorFound = true;
|
||||
}
|
||||
if (json === null) {
|
||||
|
||||
setJSONError('userData', errorFound);
|
||||
|
||||
if (errorFound) {
|
||||
return;
|
||||
}
|
||||
|
||||
let text = editor.getText();
|
||||
if (noUpdate) {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "saveUserData",
|
||||
properties: {
|
||||
userData: text
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
let text = editor.getText();
|
||||
if (noUpdate === true) {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "saveUserData",
|
||||
properties: {
|
||||
userData: text
|
||||
}
|
||||
})
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
updateProperty('userData', text, false);
|
||||
}
|
||||
updateProperty('userData', text, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2445,7 +2516,7 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r
|
|||
updateProperties(propertyUpdate, false);
|
||||
}
|
||||
|
||||
var editor = null;
|
||||
let editor = null;
|
||||
|
||||
function createJSONEditor() {
|
||||
let container = document.getElementById("property-userData-editor");
|
||||
|
@ -2508,9 +2579,10 @@ function hideUserDataSaved() {
|
|||
|
||||
function showStaticUserData() {
|
||||
if (editor !== null) {
|
||||
$('#property-userData-static').show();
|
||||
$('#property-userData-static').css('height', $('#property-userData-editor').height());
|
||||
$('#property-userData-static').text(editor.getText());
|
||||
let $propertyUserDataStatic = $('#property-userData-static');
|
||||
$propertyUserDataStatic.show();
|
||||
$propertyUserDataStatic.css('height', $('#property-userData-editor').height());
|
||||
$propertyUserDataStatic.text(editor.getText());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2531,12 +2603,13 @@ function getEditorJSON() {
|
|||
|
||||
function deleteJSONEditor() {
|
||||
if (editor !== null) {
|
||||
setJSONError('userData', false);
|
||||
editor.destroy();
|
||||
editor = null;
|
||||
}
|
||||
}
|
||||
|
||||
var savedJSONTimer = null;
|
||||
let savedJSONTimer = null;
|
||||
|
||||
function saveJSONUserData(noUpdate) {
|
||||
setUserDataFromEditor(noUpdate);
|
||||
|
@ -2581,33 +2654,35 @@ function saveMaterialData() {
|
|||
|
||||
function setMaterialDataFromEditor(noUpdate) {
|
||||
let json = null;
|
||||
let errorFound = false;
|
||||
try {
|
||||
json = materialEditor.get();
|
||||
} catch (e) {
|
||||
alert('Invalid JSON code - look for red X in your code ', +e);
|
||||
errorFound = true;
|
||||
}
|
||||
if (json === null) {
|
||||
|
||||
setJSONError('materialData', errorFound);
|
||||
|
||||
if (errorFound) {
|
||||
return;
|
||||
}
|
||||
let text = materialEditor.getText();
|
||||
if (noUpdate) {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "saveMaterialData",
|
||||
properties: {
|
||||
materialData: text
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
let text = materialEditor.getText();
|
||||
if (noUpdate === true) {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "saveMaterialData",
|
||||
properties: {
|
||||
materialData: text
|
||||
}
|
||||
})
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
updateProperty('materialData', text, false);
|
||||
}
|
||||
updateProperty('materialData', text, false);
|
||||
}
|
||||
}
|
||||
|
||||
var materialEditor = null;
|
||||
let materialEditor = null;
|
||||
|
||||
function createJSONMaterialEditor() {
|
||||
let container = document.getElementById("property-materialData-editor");
|
||||
|
@ -2670,9 +2745,10 @@ function hideMaterialDataSaved() {
|
|||
|
||||
function showStaticMaterialData() {
|
||||
if (materialEditor !== null) {
|
||||
$('#property-materialData-static').show();
|
||||
$('#property-materialData-static').css('height', $('#property-materialData-editor').height());
|
||||
$('#property-materialData-static').text(materialEditor.getText());
|
||||
let $propertyMaterialDataStatic = $('#property-materialData-static');
|
||||
$propertyMaterialDataStatic.show();
|
||||
$propertyMaterialDataStatic.css('height', $('#property-materialData-editor').height());
|
||||
$propertyMaterialDataStatic.text(materialEditor.getText());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2693,12 +2769,13 @@ function getMaterialEditorJSON() {
|
|||
|
||||
function deleteJSONMaterialEditor() {
|
||||
if (materialEditor !== null) {
|
||||
setJSONError('materialData', false);
|
||||
materialEditor.destroy();
|
||||
materialEditor = null;
|
||||
}
|
||||
}
|
||||
|
||||
var savedMaterialJSONTimer = null;
|
||||
let savedMaterialJSONTimer = null;
|
||||
|
||||
function saveJSONMaterialData(noUpdate) {
|
||||
setMaterialDataFromEditor(noUpdate);
|
||||
|
@ -2872,23 +2949,20 @@ function loaded() {
|
|||
elGroup.appendChild(elContainer);
|
||||
}
|
||||
|
||||
elLabel = document.createElement('label');
|
||||
elLabel.setAttribute("for", propertyElementID);
|
||||
let labelText = propertyData.label !== undefined ? propertyData.label : "";
|
||||
let className = '';
|
||||
if (propertyData.indentedLabel || propertyData.showPropertyRule !== undefined) {
|
||||
let elSpan = document.createElement('span');
|
||||
elSpan.className = 'indented';
|
||||
elSpan.innerText = propertyData.label !== undefined ? propertyData.label : "";
|
||||
elLabel.appendChild(elSpan);
|
||||
} else {
|
||||
elLabel.innerText = propertyData.label !== undefined ? propertyData.label : "";
|
||||
className = 'indented';
|
||||
}
|
||||
elLabel = createElementFromHTML(
|
||||
`<label><span class="${className}">${labelText}</span></label>`);
|
||||
elContainer.appendChild(elLabel);
|
||||
} else {
|
||||
elContainer = document.getElementById(propertyData.replaceID);
|
||||
}
|
||||
|
||||
if (elLabel) {
|
||||
createAppTooltip.registerTooltipElement(elLabel, propertyID);
|
||||
createAppTooltip.registerTooltipElement(elLabel.childNodes[0], propertyID);
|
||||
}
|
||||
|
||||
let elProperty = createElementFromHTML('<div style="width: 100%;"></div>');
|
||||
|
@ -2912,7 +2986,10 @@ function loaded() {
|
|||
property.elContainer = elContainer;
|
||||
property.spaceMode = propertySpaceMode;
|
||||
|
||||
elWrapper.appendChild(createElementFromHTML(`<div class="triple-label">${innerPropertyData.label}</div>`));
|
||||
let elLabel = createElementFromHTML(`<div class="triple-label">${innerPropertyData.label}</div>`);
|
||||
createAppTooltip.registerTooltipElement(elLabel, propertyID);
|
||||
|
||||
elWrapper.appendChild(elLabel);
|
||||
|
||||
if (property.type !== 'placeholder') {
|
||||
properties[propertyID] = property;
|
||||
|
@ -2958,7 +3035,7 @@ function loaded() {
|
|||
let elServerScriptError = document.getElementById("property-serverScripts-error");
|
||||
let elServerScriptStatus = document.getElementById("property-serverScripts-status");
|
||||
elServerScriptError.value = data.errorInfo;
|
||||
// If we just set elServerScriptError's diplay to block or none, we still end up with
|
||||
// If we just set elServerScriptError's display to block or none, we still end up with
|
||||
// it's parent contributing 21px bottom padding even when elServerScriptError is display:none.
|
||||
// So set it's parent to block or none
|
||||
elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none";
|
||||
|
@ -3068,6 +3145,15 @@ function loaded() {
|
|||
|
||||
showGroupsForType(selectedEntityProperties.type);
|
||||
|
||||
if (selectedEntityProperties.locked) {
|
||||
disableProperties();
|
||||
getPropertyInputElement("locked").removeAttribute('disabled');
|
||||
} else {
|
||||
enableProperties();
|
||||
disableSaveUserDataButton();
|
||||
disableSaveMaterialDataButton()
|
||||
}
|
||||
|
||||
for (let propertyID in properties) {
|
||||
let property = properties[propertyID];
|
||||
let propertyData = property.data;
|
||||
|
@ -3078,10 +3164,19 @@ function loaded() {
|
|||
if (propertyValue === undefined && !isSubProperty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (propertyData.hideIfCertified) {
|
||||
let shouldHide = selectedEntityProperties.certificateID !== "";
|
||||
if (shouldHide) {
|
||||
propertyValue = "** Certified **";
|
||||
}
|
||||
property.elInput.disabled = shouldHide;
|
||||
}
|
||||
|
||||
let isPropertyNotNumber = false;
|
||||
switch (propertyData.type) {
|
||||
case 'number':
|
||||
case 'number-draggable':
|
||||
isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null;
|
||||
break;
|
||||
case 'vec3':
|
||||
|
@ -3114,6 +3209,10 @@ function loaded() {
|
|||
break;
|
||||
}
|
||||
case 'number': {
|
||||
property.elInput.value = propertyValue;
|
||||
break;
|
||||
}
|
||||
case 'number-draggable': {
|
||||
let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1;
|
||||
let value = propertyValue / multiplier;
|
||||
if (propertyData.round !== undefined) {
|
||||
|
@ -3256,15 +3355,6 @@ function loaded() {
|
|||
hideMaterialDataSaved();
|
||||
}
|
||||
|
||||
if (selectedEntityProperties.locked) {
|
||||
disableProperties();
|
||||
getPropertyInputElement("locked").removeAttribute('disabled');
|
||||
} else {
|
||||
enableProperties();
|
||||
disableSaveUserDataButton();
|
||||
disableSaveMaterialDataButton()
|
||||
}
|
||||
|
||||
let activeElement = document.activeElement;
|
||||
if (doSelectElement && typeof activeElement.select !== "undefined") {
|
||||
activeElement.select();
|
||||
|
@ -3317,12 +3407,15 @@ function loaded() {
|
|||
elStaticUserData.setAttribute("id", userDataElementID + "-static");
|
||||
let elUserDataEditor = document.createElement('div');
|
||||
elUserDataEditor.setAttribute("id", userDataElementID + "-editor");
|
||||
let elUserDataEditorStatus = document.createElement('div');
|
||||
elUserDataEditorStatus.setAttribute("id", userDataElementID + "-editorStatus");
|
||||
let elUserDataSaved = document.createElement('span');
|
||||
elUserDataSaved.setAttribute("id", userDataElementID + "-saved");
|
||||
elUserDataSaved.innerText = "Saved!";
|
||||
elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved);
|
||||
elDiv.insertBefore(elStaticUserData, elUserData);
|
||||
elDiv.insertBefore(elUserDataEditor, elUserData);
|
||||
elDiv.insertBefore(elUserDataEditorStatus, elUserData);
|
||||
|
||||
// Material Data
|
||||
let materialDataProperty = properties["materialData"];
|
||||
|
@ -3333,12 +3426,15 @@ function loaded() {
|
|||
elStaticMaterialData.setAttribute("id", materialDataElementID + "-static");
|
||||
let elMaterialDataEditor = document.createElement('div');
|
||||
elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor");
|
||||
let elMaterialDataEditorStatus = document.createElement('div');
|
||||
elMaterialDataEditorStatus.setAttribute("id", materialDataElementID + "-editorStatus");
|
||||
let elMaterialDataSaved = document.createElement('span');
|
||||
elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved");
|
||||
elMaterialDataSaved.innerText = "Saved!";
|
||||
elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved);
|
||||
elDiv.insertBefore(elStaticMaterialData, elMaterialData);
|
||||
elDiv.insertBefore(elMaterialDataEditor, elMaterialData);
|
||||
elDiv.insertBefore(elMaterialDataEditorStatus, elMaterialData);
|
||||
|
||||
// Special Property Callbacks
|
||||
let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace");
|
||||
|
@ -3529,6 +3625,7 @@ function loaded() {
|
|||
});
|
||||
|
||||
augmentSpinButtons();
|
||||
disableDragDrop();
|
||||
|
||||
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
|
||||
document.addEventListener("contextmenu", function(event) {
|
||||
|
|
|
@ -108,6 +108,7 @@ function loaded() {
|
|||
});
|
||||
|
||||
augmentSpinButtons();
|
||||
disableDragDrop();
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' }));
|
||||
});
|
||||
|
|
|
@ -9,10 +9,6 @@
|
|||
const SCROLL_ROWS = 2; // number of rows used as scrolling buffer, each time we pass this number of rows we scroll
|
||||
const FIRST_ROW_INDEX = 2; // the first elRow element's index in the child nodes of the table body
|
||||
|
||||
debugPrint = function (message) {
|
||||
console.log(message);
|
||||
};
|
||||
|
||||
function ListView(elTableBody, elTableScroll, elTableHeaderRow, createRowFunction, updateRowFunction, clearRowFunction,
|
||||
preRefreshFunction, postRefreshFunction, preResizeFunction, WINDOW_NONVARIABLE_HEIGHT) {
|
||||
this.elTableBody = elTableBody;
|
||||
|
@ -246,7 +242,7 @@ ListView.prototype = {
|
|||
|
||||
resize: function() {
|
||||
if (!this.elTableBody || !this.elTableScroll) {
|
||||
debugPrint("ListView.resize - no valid table body or table scroll element");
|
||||
console.log("ListView.resize - no valid table body or table scroll element");
|
||||
return;
|
||||
}
|
||||
this.preResizeFunction();
|
||||
|
@ -288,7 +284,7 @@ ListView.prototype = {
|
|||
|
||||
initialize: function() {
|
||||
if (!this.elTableBody || !this.elTableScroll) {
|
||||
debugPrint("ListView.initialize - no valid table body or table scroll element");
|
||||
console.log("ListView.initialize - no valid table body or table scroll element");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
27
scripts/system/html/js/utils.js
Normal file
27
scripts/system/html/js/utils.js
Normal 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);
|
||||
}
|
|
@ -164,7 +164,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
|||
var cameraPosition = Camera.position;
|
||||
PROFILE("getMultipleProperties", function () {
|
||||
var multipleProperties = Entities.getMultipleEntityProperties(ids, ['name', 'type', 'locked',
|
||||
'visible', 'renderInfo', 'modelURL', 'materialURL', 'script']);
|
||||
'visible', 'renderInfo', 'modelURL', 'materialURL', 'script', 'certificateID']);
|
||||
for (var i = 0; i < multipleProperties.length; i++) {
|
||||
var properties = multipleProperties[i];
|
||||
|
||||
|
@ -182,6 +182,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
|||
url: url,
|
||||
locked: properties.locked,
|
||||
visible: properties.visible,
|
||||
certificateID: properties.certificateID,
|
||||
verticesCount: (properties.renderInfo !== undefined ?
|
||||
valueIfDefined(properties.renderInfo.verticesCount) : ""),
|
||||
texturesCount: (properties.renderInfo !== undefined ?
|
||||
|
|
Loading…
Reference in a new issue