Merge pull request #12747 from Zvork/fade

Update to Transitions debugging / editing script
This commit is contained in:
John Conklin II 2018-04-04 14:31:59 -07:00 committed by GitHub
commit 2e37b4a312
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 358 additions and 120 deletions

View file

@ -110,7 +110,6 @@ bool SelectionScriptingInterface::enableListHighlight(const QString& listName, c
}
if (!(*highlightStyle).isBoundToList()) {
setupHandler(listName);
(*highlightStyle).setBoundToList(true);
}
@ -172,6 +171,18 @@ render::HighlightStyle SelectionScriptingInterface::getHighlightStyle(const QStr
}
}
bool SelectionScriptingInterface::enableListToScene(const QString& listName) {
setupHandler(listName);
return true;
}
bool SelectionScriptingInterface::disableListToScene(const QString& listName) {
removeHandler(listName);
return true;
}
template <class T> bool SelectionScriptingInterface::addToGameplayObjects(const QString& listName, T idToAdd) {
{
QWriteLocker lock(&_selectionListsLock);
@ -303,6 +314,15 @@ void SelectionScriptingInterface::setupHandler(const QString& selectionName) {
(*handler)->initialize(selectionName);
}
void SelectionScriptingInterface::removeHandler(const QString& selectionName) {
QWriteLocker lock(&_selectionHandlersLock);
auto handler = _handlerMap.find(selectionName);
if (handler != _handlerMap.end()) {
delete handler.value();
_handlerMap.erase(handler);
}
}
void SelectionScriptingInterface::onSelectedItemsListChanged(const QString& listName) {
{
QWriteLocker lock(&_selectionHandlersLock);

View file

@ -160,13 +160,14 @@ public:
* If the Selection doesn't exist, it will be created.
* All objects in the list will be displayed with the highlight effect as specified from the highlightStyle.
* The function can be called several times with different values in the style to modify it.
*
*
* @function Selection.enableListHighlight
* @param listName {string} name of the selection
* @param highlightStyle {jsObject} highlight style fields (see Selection.getListHighlightStyle for a detailed description of the highlightStyle).
* @returns {bool} true if the selection was successfully enabled for highlight.
*/
Q_INVOKABLE bool enableListHighlight(const QString& listName, const QVariantMap& highlightStyle);
/**jsdoc
* Disable highlighting for the named selection.
* If the Selection doesn't exist or wasn't enabled for highliting then nothing happens simply returning false.
@ -175,7 +176,27 @@ public:
* @param listName {string} name of the selection
* @returns {bool} true if the selection was successfully disabled for highlight, false otherwise.
*/
Q_INVOKABLE bool disableListHighlight(const QString& listName);
Q_INVOKABLE bool disableListHighlight(const QString& listName);
/**jsdoc
* Enable scene selection for the named selection.
* If the Selection doesn't exist, it will be created.
* All objects in the list will be sent to a scene selection.
*
* @function Selection.enableListToScene
* @param listName {string} name of the selection
* @returns {bool} true if the selection was successfully enabled on the scene.
*/
Q_INVOKABLE bool enableListToScene(const QString& listName);
/**jsdoc
* Disable scene selection for the named selection.
* If the Selection doesn't exist or wasn't enabled on the scene then nothing happens simply returning false.
*
* @function Selection.disableListToScene
* @param listName {string} name of the selection
* @returns {bool} true if the selection was successfully disabled on the scene, false otherwise.
*/
Q_INVOKABLE bool disableListToScene(const QString& listName);
/**jsdoc
* Query the highlight style values for the named selection.
* If the Selection doesn't exist or hasn't been highlight enabled yet, it will return an empty object.
@ -223,7 +244,7 @@ private:
template <class T> bool removeFromGameplayObjects(const QString& listName, T idToRemove);
void setupHandler(const QString& selectionName);
void removeHandler(const QString& selectionName);
};

View file

@ -39,43 +39,52 @@ void FadeEditJob::run(const render::RenderContextPointer& renderContext, const F
auto scene = renderContext->_scene;
if (_isEditEnabled) {
float minIsectDistance = std::numeric_limits<float>::max();
auto& itemBounds = inputs.get0();
auto editedItem = findNearestItem(renderContext, itemBounds, minIsectDistance);
render::Transaction transaction;
bool hasTransaction{ false };
static const std::string selectionName("TransitionEdit");
auto scene = renderContext->_scene;
if (!scene->isSelectionEmpty(selectionName)) {
auto selection = scene->getSelection(selectionName);
auto editedItem = selection.getItems().front();
render::Transaction transaction;
bool hasTransaction{ false };
if (editedItem != _editedItem && render::Item::isValidID(_editedItem)) {
// Remove transition from previously edited item as we've changed edited item
hasTransaction = true;
if (editedItem != _editedItem && render::Item::isValidID(_editedItem)) {
// Remove transition from previously edited item as we've changed edited item
hasTransaction = true;
transaction.removeTransitionFromItem(_editedItem);
}
_editedItem = editedItem;
if (render::Item::isValidID(_editedItem)) {
static const render::Transition::Type categoryToTransition[FADE_CATEGORY_COUNT] = {
render::Transition::ELEMENT_ENTER_DOMAIN,
render::Transition::BUBBLE_ISECT_OWNER,
render::Transition::BUBBLE_ISECT_TRESPASSER,
render::Transition::USER_ENTER_DOMAIN,
render::Transition::AVATAR_CHANGE
};
auto transitionType = categoryToTransition[inputs.get1()];
transaction.queryTransitionOnItem(_editedItem, [transitionType, scene](render::ItemID id, const render::Transition* transition) {
if (transition == nullptr || transition->isFinished || transition->eventType != transitionType) {
// Relaunch transition
render::Transaction transaction;
transaction.addTransitionToItem(id, transitionType);
scene->enqueueTransaction(transaction);
}
});
hasTransaction = true;
}
if (hasTransaction) {
scene->enqueueTransaction(transaction);
}
} else if (render::Item::isValidID(_editedItem)) {
// Remove transition from previously edited item as we've disabled fade edition
render::Transaction transaction;
transaction.removeTransitionFromItem(_editedItem);
}
_editedItem = editedItem;
if (render::Item::isValidID(_editedItem)) {
static const render::Transition::Type categoryToTransition[FADE_CATEGORY_COUNT] = {
render::Transition::ELEMENT_ENTER_DOMAIN,
render::Transition::BUBBLE_ISECT_OWNER,
render::Transition::BUBBLE_ISECT_TRESPASSER,
render::Transition::USER_ENTER_DOMAIN,
render::Transition::AVATAR_CHANGE
};
auto transitionType = categoryToTransition[inputs.get1()];
transaction.queryTransitionOnItem(_editedItem, [transitionType, scene](render::ItemID id, const render::Transition* transition) {
if (transition == nullptr || transition->isFinished || transition->eventType!=transitionType) {
// Relaunch transition
render::Transaction transaction;
transaction.addTransitionToItem(id, transitionType);
scene->enqueueTransaction(transaction);
}
});
hasTransaction = true;
}
if (hasTransaction) {
scene->enqueueTransaction(transaction);
_editedItem = render::Item::INVALID_ITEM_ID;
}
}
else if (render::Item::isValidID(_editedItem)) {
@ -87,28 +96,6 @@ void FadeEditJob::run(const render::RenderContextPointer& renderContext, const F
}
}
render::ItemID FadeEditJob::findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const {
const glm::vec3 rayOrigin = renderContext->args->getViewFrustum().getPosition();
const glm::vec3 rayDirection = renderContext->args->getViewFrustum().getDirection();
BoxFace face;
glm::vec3 normal;
float isectDistance;
render::ItemID nearestItem = render::Item::INVALID_ITEM_ID;
const float minDistance = 1.f;
const float maxDistance = 50.f;
for (const auto& itemBound : inputs) {
if (!itemBound.bound.contains(rayOrigin) && itemBound.bound.findRayIntersection(rayOrigin, rayDirection, isectDistance, face, normal)) {
auto& item = renderContext->_scene->getItem(itemBound.id);
if (item.getKey().isWorldSpace() && isectDistance>minDistance && isectDistance < minIsectDistance && isectDistance<maxDistance) {
nearestItem = itemBound.id;
minIsectDistance = isectDistance;
}
}
}
return nearestItem;
}
FadeConfig::FadeConfig()
{
events[FADE_ELEMENT_ENTER_LEAVE_DOMAIN].noiseSize = glm::vec3{ 0.75f, 0.75f, 0.75f };
@ -353,11 +340,9 @@ QString FadeConfig::eventNames[FADE_CATEGORY_COUNT] = {
"avatar_change",
};
void FadeConfig::save() const {
// Save will only work if the HIFI_USE_SOURCE_TREE_RESOURCES environment variable is set
void FadeConfig::save(const QString& configFilePath) const {
assert(editedCategory < FADE_CATEGORY_COUNT);
QJsonObject lProperties;
const QString configFilePath = PathUtils::resourcesPath() + "config/" + eventNames[editedCategory] + ".json";
QFile file(configFilePath);
if (!file.open(QFile::WriteOnly | QFile::Text)) {
qWarning() << "Fade event configuration file " << configFilePath << " cannot be opened";
@ -382,8 +367,7 @@ void FadeConfig::save() const {
}
}
void FadeConfig::load() {
const QString configFilePath = PathUtils::resourcesPath() + "config/" + eventNames[editedCategory] + ".json";
void FadeConfig::load(const QString& configFilePath) {
QFile file(configFilePath);
if (!file.exists()) {
qWarning() << "Fade event configuration file " << configFilePath << " does not exist";
@ -594,7 +578,7 @@ void FadeJob::run(const render::RenderContextPointer& renderContext, FadeJob::Ou
if (update(*jobConfig, scene, transaction, state, deltaTime)) {
hasTransaction = true;
}
if (isFirstItem && jobConfig->manualFade && (state.threshold != jobConfig->threshold)) {
if (isFirstItem && (state.threshold != jobConfig->threshold)) {
jobConfig->setProperty("threshold", state.threshold);
isFirstItem = false;
}

View file

@ -160,8 +160,8 @@ public:
float manualThreshold{ 0.f };
bool manualFade{ false };
Q_INVOKABLE void save() const;
Q_INVOKABLE void load();
Q_INVOKABLE void save(const QString& filePath) const;
Q_INVOKABLE void load(const QString& filePath);
static QString eventNames[FADE_CATEGORY_COUNT];
@ -190,7 +190,6 @@ private:
bool _isEditEnabled{ false };
render::ItemID _editedItem{ render::Item::INVALID_ITEM_ID };
render::ItemID findNearestItem(const render::RenderContextPointer& renderContext, const render::ItemBounds& inputs, float& minIsectDistance) const;
};
class FadeJob {

View file

@ -1,5 +1,3 @@
"use strict";
//
// debugTransition.js
// developer/utilities/render
@ -12,12 +10,17 @@
//
(function() {
"use strict";
var TABLET_BUTTON_NAME = "Transition";
var QMLAPP_URL = Script.resolvePath("./transition.qml");
var ICON_URL = Script.resolvePath("../../../system/assets/images/transition-i.svg");
var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/transition-a.svg");
Script.include([
Script.resolvePath("../../../system/libraries/stringHelpers.js"),
]);
var onScreen = false;
function onClicked() {
@ -37,6 +40,71 @@
var hasEventBridge = false;
function enableSphereVisualization() {
if (gradientSphere==undefined) {
gradientSphere = Overlays.addOverlay("sphere", {
position: MyAvatar.position,
rotation: Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0),
dimensions: { x: 1.0, y: 1.0, z: 1.0 },
color: { red: 100, green: 150, blue: 255},
alpha: 0.2,
solid: false
});
}
if (noiseSphere==undefined) {
noiseSphere = Overlays.addOverlay("sphere", {
position: MyAvatar.position,
rotation: Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0),
dimensions: { x: 1.0, y: 1.0, z: 1.0 },
color: { red: 255, green: 150, blue: 100},
alpha: 0.2,
solid: false
});
}
}
function disableSphereVisualization() {
Overlays.deleteOverlay(noiseSphere);
Overlays.deleteOverlay(gradientSphere);
noiseSphere = undefined
gradientSphere = undefined
}
// Create a Laser pointer used to pick and add objects to selections
var END_DIMENSIONS = { x: 0.05, y: 0.05, z: 0.05 };
var COLOR1 = {red: 255, green: 0, blue: 0};
var COLOR2 = {red: 0, green: 255, blue: 0};
var end1 = {
type: "sphere",
dimensions: END_DIMENSIONS,
color: COLOR1,
ignoreRayIntersection: true
}
var end2 = {
type: "sphere",
dimensions: END_DIMENSIONS,
color: COLOR2,
ignoreRayIntersection: true
}
var laser
function enablePointer() {
laser = Pointers.createPointer(PickType.Ray, {
joint: "Mouse",
filter: Picks.PICK_ENTITIES,
renderStates: [{name: "one", end: end1}],
defaultRenderStates: [{name: "one", end: end2, distance: 2.0}],
enabled: true
});
Pointers.setRenderState(laser, "one");
Pointers.enablePointer(laser)
}
function disablePointer() {
Pointers.disablePointer(laser)
Pointers.removePointer(laser);
}
function wireEventBridge(on) {
if (!tablet) {
print("Warning in wireEventBridge(): 'tablet' undefined!");
@ -46,11 +114,15 @@
if (!hasEventBridge) {
tablet.fromQml.connect(fromQml);
hasEventBridge = true;
enablePointer();
Render.getConfig("RenderMainView.FadeEdit")["editFade"] = true
}
} else {
if (hasEventBridge) {
tablet.fromQml.disconnect(fromQml);
hasEventBridge = false;
disablePointer();
Render.getConfig("RenderMainView.FadeEdit")["editFade"] = false
}
}
}
@ -66,12 +138,87 @@
wireEventBridge(onScreen);
}
var isEditEnabled = false
var noiseSphere
var gradientSphere
var selectedEntity
var editedCategory
var FADE_MIN_SCALE = 0.001
var FADE_MAX_SCALE = 10000.0
function parameterToValuePow(parameter, minValue, maxOverMinValue) {
return minValue * Math.pow(maxOverMinValue, parameter);
//return parameter
}
function update(dt) {
var gradientProperties = Entities.getEntityProperties(selectedEntity, ["position", "dimensions"]);
if (gradientProperties!=undefined) {
var pos = gradientProperties.position
if (pos!=undefined) {
var config = Render.getConfig("RenderMainView.Fade")
//print("Center at "+pos.x+" "+pos.y+" "+pos.z)
var dim = {x:config.baseSizeX, y:config.baseSizeY, z:config.baseSizeZ}
dim.x = parameterToValuePow(dim.x, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
dim.y = parameterToValuePow(dim.y, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
dim.z = parameterToValuePow(dim.z, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
if (editedCategory==4 || editedCategory==5) {
dim.y = gradientProperties.dimensions.y
pos.y = pos.y - gradientProperties.dimensions.y/2
}
Overlays.editOverlay(gradientSphere, { position: pos, dimensions: dim, alpha: config.baseLevel })
dim.x = parameterToValuePow(config.noiseSizeX, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
dim.y = parameterToValuePow(config.noiseSizeY, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
dim.z = parameterToValuePow(config.noiseSizeZ, FADE_MIN_SCALE, FADE_MAX_SCALE/ FADE_MIN_SCALE)
Overlays.editOverlay(noiseSphere, { position: pos, dimensions: dim, alpha: config.noiseLevel })
}
}
}
Script.update.connect(update);
function loadConfiguration(fileUrl) {
var config = Render.getConfig("RenderMainView.Fade")
config.load(fileUrl)
}
function saveConfiguration(fileUrl) {
var config = Render.getConfig("RenderMainView.Fade")
config.save(fileUrl)
}
function fromQml(message) {
tokens = message.split('*')
//print("Received '"+message+"' from transition.qml")
command = tokens[0].toLowerCase()
if (command=="category") {
editedCategory = parseInt(tokens[1])
} else if (command=="save") {
var filePath = tokens[1]
print("Raw token = "+filePath)
if (filePath.startsWith("file:///")) {
filePath = filePath.substr(8)
print("Saving configuration to "+filePath)
saveConfiguration(filePath)
} else {
print("Configurations can only be saved to local files")
}
} else if (command=="load") {
var filePath = tokens[1]
if (filePath.startsWith("file:///")) {
filePath = filePath.substr(8)
print("Loading configuration from "+filePath)
loadConfiguration(filePath)
} else {
print("Configurations can only be loaded from local files")
}
}
}
button.clicked.connect(onClicked);
tablet.screenChanged.connect(onScreenChanged);
Script.scriptEnding.connect(function () {
if (onScreen) {
tablet.gotoHomeScreen();
@ -81,4 +228,28 @@
tablet.removeButton(button);
});
var currentSelectionName = ""
var SelectionList = "TransitionEdit"
Selection.enableListToScene(SelectionList)
Selection.clearSelectedItemsList(SelectionList)
Entities.clickDownOnEntity.connect(function (id, event) {
if (selectedEntity) {
Selection.removeFromSelectedItemsList(SelectionList, "entity", selectedEntity)
}
selectedEntity = id
Selection.addToSelectedItemsList(SelectionList, "entity", selectedEntity)
update()
})
function cleanup() {
disablePointer();
Selection.removeListFromMap(SelectionList)
Selection.disableListToScene(SelectionList);
Overlays.deleteOverlay(noiseSphere);
Overlays.deleteOverlay(gradientSphere);
}
Script.scriptEnding.connect(cleanup);
}());

View file

@ -11,6 +11,7 @@
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "qrc:///qml/styles-uit"
import "qrc:///qml/controls-uit" as HifiControls
@ -22,11 +23,31 @@ Rectangle {
id: root
anchors.margins: hifi.dimensions.contentMargin.x
signal sendToScript(var message);
color: hifi.colors.baseGray;
property var config: Render.getConfig("RenderMainView.Fade");
property var configEdit: Render.getConfig("RenderMainView.FadeEdit");
FileDialog {
id: fileDialog
title: "Please choose a file"
folder: shortcuts.documents
nameFilters: [ "JSON files (*.json)", "All files (*)" ]
onAccepted: {
root.sendToScript(title.split(" ")[0]+"*"+fileUrl.toString())
// This is a hack to be sure the widgets below properly reflect the change of category: delete the Component
// by setting the loader source to Null and then recreate it 500ms later
paramWidgetLoader.sourceComponent = undefined;
postpone.interval = 500
postpone.start()
}
onRejected: {
}
Component.onCompleted: visible = false
}
ColumnLayout {
spacing: 3
anchors.left: parent.left
@ -37,23 +58,14 @@ Rectangle {
}
RowLayout {
spacing: 20
spacing: 8
Layout.fillWidth: true
id: root_col
HifiControls.CheckBox {
anchors.verticalCenter: parent.verticalCenter
boxSize: 20
text: "Edit"
checked: root.configEdit["editFade"]
onCheckedChanged: {
root.configEdit["editFade"] = checked;
Render.getConfig("RenderMainView.DrawFadedOpaqueBounds").enabled = checked;
}
}
HifiControls.ComboBox {
anchors.verticalCenter: parent.verticalCenter
Layout.fillWidth: true
anchors.left : parent.left
width: 300
id: categoryBox
model: ["Elements enter/leave domain", "Bubble isect. - Owner POV", "Bubble isect. - Trespasser POV", "Another user leaves/arrives", "Changing an avatar"]
Timer {
@ -61,17 +73,48 @@ Rectangle {
interval: 100; running: false; repeat: false
onTriggered: {
paramWidgetLoader.sourceComponent = paramWidgets
var isTimeBased = categoryBox.currentIndex==0 || categoryBox.currentIndex==3
paramWidgetLoader.item.isTimeBased = isTimeBased
}
}
onCurrentIndexChanged: {
var descriptions = [
"Time based threshold, gradients centered on object",
"Fixed threshold, gradients centered on owner avatar",
"Position based threshold (increases when trespasser moves closer to avatar), gradients centered on trespasser avatar",
"Time based threshold, gradients centered on bottom of object",
"UNSUPPORTED"
]
description.text = descriptions[currentIndex]
root.config["editedCategory"] = currentIndex;
// This is a hack to be sure the widgets below properly reflect the change of category: delete the Component
// by setting the loader source to Null and then recreate it 100ms later
paramWidgetLoader.sourceComponent = undefined;
postpone.interval = 100
postpone.start()
root.sendToScript("category*"+currentIndex)
}
}
HifiControls.Button {
action: saveAction
Layout.fillWidth: true
anchors.top: parent.top
anchors.bottom: parent.bottom
}
HifiControls.Button {
action: loadAction
Layout.fillWidth: true
anchors.top: parent.top
anchors.bottom: parent.bottom
}
}
HifiControls.Label {
id: description
text: "..."
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
RowLayout {
@ -104,19 +147,18 @@ Rectangle {
id: saveAction
text: "Save"
onTriggered: {
root.config.save()
fileDialog.title = "Save configuration..."
fileDialog.selectExisting = false
fileDialog.open()
}
}
Action {
id: loadAction
text: "Load"
onTriggered: {
root.config.load()
// This is a hack to be sure the widgets below properly reflect the change of category: delete the Component
// by setting the loader source to Null and then recreate it 500ms later
paramWidgetLoader.sourceComponent = undefined;
postpone.interval = 500
postpone.start()
fileDialog.title = "Load configuration..."
fileDialog.selectExisting = true
fileDialog.open()
}
}
@ -128,13 +170,8 @@ Rectangle {
ColumnLayout {
spacing: 3
width: root_col.width
property bool isTimeBased
HifiControls.CheckBox {
text: "Invert"
boxSize: 20
checked: root.config["isInverted"]
onCheckedChanged: { root.config["isInverted"] = checked }
}
RowLayout {
Layout.fillWidth: true
@ -196,16 +233,32 @@ Rectangle {
}
}
ConfigSlider {
RowLayout {
spacing: 20
height: 36
label: "Edge Width"
integral: false
config: root.config
property: "edgeWidth"
max: 1.0
min: 0.0
HifiControls.CheckBox {
text: "Invert gradient"
anchors.verticalCenter: parent.verticalCenter
boxSize: 20
checked: root.config["isInverted"]
onCheckedChanged: { root.config["isInverted"] = checked }
}
ConfigSlider {
anchors.left: undefined
anchors.verticalCenter: parent.verticalCenter
height: 36
width: 300
label: "Edge Width"
integral: false
config: root.config
property: "edgeWidth"
max: 1.0
min: 0.0
}
}
RowLayout {
Layout.fillWidth: true
@ -278,6 +331,8 @@ Rectangle {
Layout.fillWidth: true
ConfigSlider {
enabled: isTimeBased
opacity: isTimeBased ? 1.0 : 0.0
anchors.left: undefined
anchors.right: undefined
Layout.fillWidth: true
@ -290,6 +345,8 @@ Rectangle {
min: 0.1
}
HifiControls.ComboBox {
enabled: isTimeBased
opacity: isTimeBased ? 1.0 : 0.0
Layout.fillWidth: true
model: ["Linear", "Ease In", "Ease Out", "Ease In / Out"]
currentIndex: root.config["timing"]
@ -364,20 +421,6 @@ Rectangle {
id: paramWidgetLoader
sourceComponent: paramWidgets
}
Row {
anchors.left: parent.left
anchors.right: parent.right
Button {
action: saveAction
}
Button {
action: loadAction
}
}
}
}