mirror of
https://github.com/overte-org/overte.git
synced 2025-04-17 11:20:42 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into oldPropsFilters
This commit is contained in:
commit
76d611d563
128 changed files with 6862 additions and 3336 deletions
|
@ -21,12 +21,7 @@
|
|||
#include "AssetUtils.h"
|
||||
#include "ReceivedMessage.h"
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<QString> {
|
||||
size_t operator()(const QString& v) const { return qHash(v); }
|
||||
};
|
||||
}
|
||||
#include "RegisteredMetaTypes.h"
|
||||
|
||||
struct AssetMeta {
|
||||
int bakeVersion { 0 };
|
||||
|
|
|
@ -22,6 +22,8 @@ setup_memory_debugger()
|
|||
symlink_or_copy_directory_beside_target(${_SHOULD_SYMLINK_RESOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/resources" "resources")
|
||||
|
||||
# link the shared hifi libraries
|
||||
include_hifi_library_headers(gpu)
|
||||
include_hifi_library_headers(graphics)
|
||||
link_hifi_libraries(embedded-webserver networking shared avatars)
|
||||
|
||||
# find OpenSSL
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
{
|
||||
"version": 2.1,
|
||||
"settings": [
|
||||
{
|
||||
"name": "label",
|
||||
"label": "Label",
|
||||
"settings": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "metaverse",
|
||||
"label": "Metaverse / Networking",
|
||||
|
@ -15,7 +9,8 @@
|
|||
"name": "access_token",
|
||||
"label": "Access Token",
|
||||
"help": "This is your OAuth access token to connect this domain-server with your High Fidelity account. <br/>It can be generated by clicking the 'Connect Account' button above.<br/>You can also go to the <a href='https://metaverse.highfidelity.com/user/security' target='_blank'>My Security</a> page of your account and generate a token with the 'domains' scope and paste it here.",
|
||||
"advanced": true
|
||||
"advanced": true,
|
||||
"backup": false
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
|
@ -55,8 +50,8 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"label": "Places / Paths",
|
||||
"html_id": "places_paths",
|
||||
"label": "Paths",
|
||||
"html_id": "paths",
|
||||
"restart": false,
|
||||
"settings": [
|
||||
{
|
||||
|
@ -64,6 +59,7 @@
|
|||
"label": "Paths",
|
||||
"help": "Clients can enter a path to reach an exact viewpoint in your domain.<br/>Add rows to the table below to map a path to a viewpoint.<br/>The index path ( / ) is where clients will enter if they do not enter an explicit path.",
|
||||
"type": "table",
|
||||
"content_setting": true,
|
||||
"can_add_new_rows": true,
|
||||
"key": {
|
||||
"name": "path",
|
||||
|
@ -164,7 +160,8 @@
|
|||
{
|
||||
"name": "http_username",
|
||||
"label": "HTTP Username",
|
||||
"help": "Username used for basic HTTP authentication."
|
||||
"help": "Username used for basic HTTP authentication.",
|
||||
"backup": false
|
||||
},
|
||||
{
|
||||
"name": "http_password",
|
||||
|
@ -172,7 +169,8 @@
|
|||
"type": "password",
|
||||
"help": "Password used for basic HTTP authentication. Leave this alone if you do not want to change it.",
|
||||
"password_placeholder": "******",
|
||||
"value-hidden": true
|
||||
"value-hidden": true,
|
||||
"backup": false
|
||||
},
|
||||
{
|
||||
"name": "verify_http_password",
|
||||
|
@ -935,6 +933,7 @@
|
|||
"name": "persistent_scripts",
|
||||
"type": "table",
|
||||
"label": "Persistent Scripts",
|
||||
"content_setting": true,
|
||||
"help": "Add the URLs for scripts that you would like to ensure are always running in your domain.",
|
||||
"can_add_new_rows": true,
|
||||
"columns": [
|
||||
|
@ -955,99 +954,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "asset_server",
|
||||
"label": "Asset Server (ATP)",
|
||||
"assignment-types": [ 3 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "enabled",
|
||||
"type": "checkbox",
|
||||
"label": "Enabled",
|
||||
"help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)",
|
||||
"default": true,
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "assets_path",
|
||||
"type": "string",
|
||||
"label": "Assets Path",
|
||||
"help": "The path to the directory assets are stored in.<br/>If this path is relative, it will be relative to the application data directory.<br/>If you change this path you will need to manually copy any existing assets from the previous directory.",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "assets_filesize_limit",
|
||||
"type": "int",
|
||||
"label": "File Size Limit",
|
||||
"help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.",
|
||||
"default": 0,
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "entity_script_server",
|
||||
"label": "Entity Script Server (ESS)",
|
||||
"assignment-types": [ 5 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "entity_pps_per_script",
|
||||
"label": "Entity PPS per script",
|
||||
"help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.<br/>Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.",
|
||||
"default": 900,
|
||||
"type": "int",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "max_total_entity_pps",
|
||||
"label": "Maximum Total Entity PPS",
|
||||
"help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.<br/>Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.",
|
||||
"default": 9000,
|
||||
"type": "int",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "avatars",
|
||||
"label": "Avatars",
|
||||
"assignment-types": [ 1, 2 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "min_avatar_height",
|
||||
"type": "double",
|
||||
"label": "Minimum Avatar Height (meters)",
|
||||
"help": "Limits the height of avatars in your domain. Must be at least 0.009.",
|
||||
"placeholder": 0.4,
|
||||
"default": 0.4
|
||||
},
|
||||
{
|
||||
"name": "max_avatar_height",
|
||||
"type": "double",
|
||||
"label": "Maximum Avatar Height (meters)",
|
||||
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.",
|
||||
"placeholder": 5.2,
|
||||
"default": 5.2
|
||||
},
|
||||
{
|
||||
"name": "avatar_whitelist",
|
||||
"label": "Avatars Allowed from:",
|
||||
"help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.",
|
||||
"placeholder": "",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "replacement_avatar",
|
||||
"label": "Replacement Avatar for disallowed avatars",
|
||||
"help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic default avatar is used.",
|
||||
"placeholder": "",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "audio_threading",
|
||||
"label": "Audio Threading",
|
||||
|
@ -1080,6 +986,7 @@
|
|||
"name": "attenuation_per_doubling_in_distance",
|
||||
"label": "Default Domain Attenuation",
|
||||
"help": "Factor between 0 and 1.0 (0: No attenuation, 1.0: extreme attenuation)",
|
||||
"content_setting": true,
|
||||
"placeholder": "0.5",
|
||||
"default": "0.5",
|
||||
"advanced": false
|
||||
|
@ -1105,6 +1012,7 @@
|
|||
"label": "Zones",
|
||||
"help": "In this table you can define a set of zones in which you can specify various audio properties.",
|
||||
"numbered": false,
|
||||
"content_setting": true,
|
||||
"can_add_new_rows": true,
|
||||
"key": {
|
||||
"name": "name",
|
||||
|
@ -1155,6 +1063,7 @@
|
|||
"type": "table",
|
||||
"label": "Attenuation Coefficients",
|
||||
"help": "In this table you can set custom attenuation coefficients between audio zones",
|
||||
"content_setting": true,
|
||||
"numbered": true,
|
||||
"can_order": true,
|
||||
"can_add_new_rows": true,
|
||||
|
@ -1185,6 +1094,7 @@
|
|||
"label": "Reverb Settings",
|
||||
"help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.",
|
||||
"numbered": true,
|
||||
"content_setting": true,
|
||||
"can_add_new_rows": true,
|
||||
"columns": [
|
||||
{
|
||||
|
@ -1266,9 +1176,82 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "avatars",
|
||||
"label": "Avatars",
|
||||
"assignment-types": [ 1, 2 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "min_avatar_height",
|
||||
"type": "double",
|
||||
"label": "Minimum Avatar Height (meters)",
|
||||
"help": "Limits the height of avatars in your domain. Must be at least 0.009.",
|
||||
"placeholder": 0.4,
|
||||
"default": 0.4
|
||||
},
|
||||
{
|
||||
"name": "max_avatar_height",
|
||||
"type": "double",
|
||||
"label": "Maximum Avatar Height (meters)",
|
||||
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.",
|
||||
"placeholder": 5.2,
|
||||
"default": 5.2
|
||||
},
|
||||
{
|
||||
"name": "avatar_whitelist",
|
||||
"label": "Avatars Allowed from:",
|
||||
"help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.",
|
||||
"placeholder": "",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "replacement_avatar",
|
||||
"label": "Replacement Avatar for disallowed avatars",
|
||||
"help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic default avatar is used.",
|
||||
"placeholder": "",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "avatar_mixer",
|
||||
"label": "Avatar Mixer",
|
||||
"assignment-types": [
|
||||
1
|
||||
],
|
||||
"settings": [
|
||||
{
|
||||
"name": "max_node_send_bandwidth",
|
||||
"type": "double",
|
||||
"label": "Per-Node Bandwidth",
|
||||
"help": "Desired maximum send bandwidth (in Megabits per second) to each node",
|
||||
"placeholder": 5.0,
|
||||
"default": 5.0,
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "auto_threads",
|
||||
"label": "Automatically determine thread count",
|
||||
"type": "checkbox",
|
||||
"help": "Allow system to determine number of threads (recommended)",
|
||||
"default": false,
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "num_threads",
|
||||
"label": "Number of Threads",
|
||||
"help": "Threads to spin up for avatar mixing (if not automatically set)",
|
||||
"placeholder": "1",
|
||||
"default": "1",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "entity_server_settings",
|
||||
"label": "Entity Server Settings",
|
||||
"label": "Entities",
|
||||
"assignment-types": [
|
||||
6
|
||||
],
|
||||
|
@ -1309,6 +1292,7 @@
|
|||
"name": "entityEditFilter",
|
||||
"label": "Filter Entity Edits",
|
||||
"help": "Check all entity edits against this filter function.",
|
||||
"content_setting": true,
|
||||
"placeholder": "url whose content is like: function filter(properties) { return properties; }",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
|
@ -1503,35 +1487,55 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"name": "avatar_mixer",
|
||||
"label": "Avatar Mixer",
|
||||
"assignment-types": [
|
||||
1
|
||||
],
|
||||
"name": "asset_server",
|
||||
"label": "Asset Server (ATP)",
|
||||
"assignment-types": [ 3 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "max_node_send_bandwidth",
|
||||
"type": "double",
|
||||
"label": "Per-Node Bandwidth",
|
||||
"help": "Desired maximum send bandwidth (in Megabits per second) to each node",
|
||||
"placeholder": 5.0,
|
||||
"default": 5.0,
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "auto_threads",
|
||||
"label": "Automatically determine thread count",
|
||||
"name": "enabled",
|
||||
"type": "checkbox",
|
||||
"help": "Allow system to determine number of threads (recommended)",
|
||||
"default": false,
|
||||
"label": "Enabled",
|
||||
"help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)",
|
||||
"default": true,
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "num_threads",
|
||||
"label": "Number of Threads",
|
||||
"help": "Threads to spin up for avatar mixing (if not automatically set)",
|
||||
"placeholder": "1",
|
||||
"default": "1",
|
||||
"name": "assets_path",
|
||||
"type": "string",
|
||||
"label": "Assets Path",
|
||||
"help": "The path to the directory assets are stored in.<br/>If this path is relative, it will be relative to the application data directory.<br/>If you change this path you will need to manually copy any existing assets from the previous directory.",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "assets_filesize_limit",
|
||||
"type": "int",
|
||||
"label": "File Size Limit",
|
||||
"help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.",
|
||||
"default": 0,
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "entity_script_server",
|
||||
"label": "Entity Script Server (ESS)",
|
||||
"assignment-types": [ 5 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "entity_pps_per_script",
|
||||
"label": "Entity PPS per script",
|
||||
"help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.<br/>Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.",
|
||||
"default": 900,
|
||||
"type": "int",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "max_total_entity_pps",
|
||||
"label": "Maximum Total Entity PPS",
|
||||
"help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.<br/>Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.",
|
||||
"default": 9000,
|
||||
"type": "int",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
|
|
7
domain-server/resources/web/base-settings-scripts.html
Normal file
7
domain-server/resources/web/base-settings-scripts.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script src='/js/underscore-min.js'></script>
|
||||
<script src='/js/underscore-keypath.min.js'></script>
|
||||
<script src='/js/bootbox.min.js'></script>
|
||||
<script src='/js/form2js.min.js'></script>
|
||||
<script src='/js/bootstrap-switch.min.js'></script>
|
||||
<script src='/js/shared.js'></script>
|
||||
<script src='/js/base-settings.js'></script>
|
59
domain-server/resources/web/base-settings.html
Normal file
59
domain-server/resources/web/base-settings.html
Normal file
|
@ -0,0 +1,59 @@
|
|||
<div class="col-md-10 col-md-offset-1 col-xs-12">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-xs-12">
|
||||
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<span class="alert-link">
|
||||
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Visit Cloud Hosted Domains</a> to manage all your cloud domains
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form id="settings-form" role="form">
|
||||
<script id="panels-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% if (!group.hidden) { %>
|
||||
|
||||
<% var settings = _.partition(group.settings, function(value, index) { %>
|
||||
<% return !value.deprecated %>
|
||||
<% })[0] %>
|
||||
|
||||
<% var split_settings = _.partition(group.settings, function(value, index) { %>
|
||||
<% return !value.advanced %>
|
||||
<% }) %>
|
||||
|
||||
<% isGrouped = !!group.name %>
|
||||
<% panelID = isGrouped ? group.name : group.html_id %>
|
||||
|
||||
<div id="<%- panelID %>_group" class="anchor"></div>
|
||||
<div class="panel panel-default<%- (isGrouped) ? ' grouped' : '' %>"
|
||||
id="<%- panelID %>">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><%- group.label %></h3>
|
||||
<span class="badge"></span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, false) %>
|
||||
<% }); %>
|
||||
|
||||
<% if (split_settings[1].length > 0) { %>
|
||||
<button type="button" class="btn btn-default" data-toggle="collapse" data-target="#<%- panelID %>-advanced">Advanced Settings <span class="caret"></span></button>
|
||||
<div id="<%- panelID %>-advanced" class="collapse advanced-settings-section">
|
||||
<% _.each(split_settings[1], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, true) %>
|
||||
<% }); %>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</script>
|
||||
<div id="panels"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,45 +1,19 @@
|
|||
<!--#include virtual="header.html"-->
|
||||
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="alert" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var Settings = {
|
||||
content_settings: true,
|
||||
endpoint: "/content-settings.json",
|
||||
path: "/content/"
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Upload Entities File</h3>
|
||||
</div>
|
||||
<form id="upload-form" action="upload" enctype="multipart/form-data" method="post">
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.<br>
|
||||
Note: <strong>Your domain's content will be replaced by the content you upload</strong>, but the backup files of your domain's content will not immediately be changed.
|
||||
</p>
|
||||
<p>If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:</p>
|
||||
<label class="control-label">Windows</label>
|
||||
<pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<label class="control-label">OSX</label>
|
||||
<pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<label class="control-label">Linux</label>
|
||||
<pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<br>
|
||||
<input type="file" name="entities-file" class="form-control-file" accept=".json, .gz">
|
||||
<br>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<input type="submit" class="btn btn-info" value="Upload">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--#include virtual="base-settings.html"-->
|
||||
|
||||
<!--#include virtual="footer.html"-->
|
||||
<script src='js/content.js'></script>
|
||||
<script src='/js/sweetalert.min.js'></script>
|
||||
|
||||
<!--#include virtual="base-settings-scripts.html"-->
|
||||
|
||||
<script src="js/content.js"></script>
|
||||
|
||||
<!--#include virtual="page-end.html"-->
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
$(document).ready(function(){
|
||||
|
||||
function showSpinnerAlert(title) {
|
||||
swal({
|
||||
title: title,
|
||||
text: '<div class="spinner" style="color:black;"><div class="bounce1"></div><div class="bounce2"></div><div class="bounce3"></div></div>',
|
||||
html: true,
|
||||
showConfirmButton: false,
|
||||
allowEscapeKey: false
|
||||
});
|
||||
}
|
||||
Settings.afterReloadActions = function() {};
|
||||
|
||||
var frm = $('#upload-form');
|
||||
frm.submit(function (ev) {
|
||||
|
|
|
@ -18,6 +18,14 @@ body {
|
|||
margin-top: 70px;
|
||||
}
|
||||
|
||||
/* unfortunate hack so that anchors go to the right place with fixed navbar */
|
||||
:target:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
height: 70px;
|
||||
margin-top: -70px;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -118,11 +126,6 @@ span.port {
|
|||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#small-save-button {
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
td.buttons {
|
||||
width: 30px;
|
||||
}
|
||||
|
@ -345,3 +348,89 @@ table .headers + .headers td {
|
|||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
ul.nav li.dropdown-on-hover:hover ul.dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
ul.nav li.dropdown ul.dropdown-menu {
|
||||
padding: 0px 0px;
|
||||
}
|
||||
|
||||
ul.nav li.dropdown li a {
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
ul.nav li.dropdown li a:hover {
|
||||
color: white;
|
||||
background-color: #337ab7;
|
||||
}
|
||||
|
||||
ul.nav li.dropdown ul.dropdown-menu .divider {
|
||||
margin: 0px 0;
|
||||
}
|
||||
|
||||
#visit-domain-link {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.navbar-btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#save-settings-xs-button {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#button-bars {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#hamburger-badge {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
#restart-server {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
#restart-server:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.badge {
|
||||
margin-left: 5px;
|
||||
background-color: #00B4EF !important;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#visit-hmd-icon {
|
||||
width: 25px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.advanced-settings-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#restore-settings-button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* fix for https://bugs.webkit.org/show_bug.cgi?id=39620 */
|
||||
.save-button-text {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
@ -17,30 +17,47 @@
|
|||
<div class="container-fluid">
|
||||
<!-- Brand and toggle get grouped for better mobile display -->
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#collapsed-navbar">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span id="hamburger-badge" class="badge"></span>
|
||||
|
||||
<div id="button-bars">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button id="save-settings-xs-button" class="save-button btn btn-success navbar-btn hidden-sm hidden-md hidden-lg" disabled="true"><span class="save-button-text">Save</span></button>
|
||||
</div>
|
||||
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<div class="collapse navbar-collapse" id="collapsed-navbar">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/">Nodes</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assignments <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="/assignment">New Assignment</a></li>
|
||||
</ul>
|
||||
<li><a href="/assignment">Assignment</a></li>
|
||||
|
||||
<li class="dropdown dropdown-on-hover">
|
||||
<a href="/content/" class="hidden-xs">Content <span class="content-settings-badge badge"></span> <span class="caret"></span></a>
|
||||
<a href="#" class="dropdown-toggle hidden-sm hidden-md hidden-lg" data-toggle="dropdown">Content <span class="content-badge badge"></span> <span class="caret"></span></a>
|
||||
<ul id="content-settings-nav-dropdown" class="dropdown-menu" role="menu">
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="dropdown dropdown-on-hover">
|
||||
<a href="/settings/" class="hidden-xs">Settings <span class="domain-settings-badge badge"></span> <span class="caret"></span></a>
|
||||
<a href="#" class="dropdown-toggle hidden-sm hidden-md hidden-lg" data-toggle="dropdown">Settings <span class="domain-settings-badge badge"></span> <span class="caret"></span></a>
|
||||
<ul id="domain-settings-nav-dropdown" class="dropdown-menu" role="menu">
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/content/">Content</a></li>
|
||||
<li><a href="/settings/">Settings</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-right navbar-nav">
|
||||
<li><a id="visit-domain-link" class="blue-link" target="_blank" style="display: none;">Visit domain in VR</a></li>
|
||||
<li><a href="#" id="restart-server"><span class="glyphicon glyphicon-refresh"></span> Restart</a></li>
|
||||
<a id="visit-domain-link" class="btn btn-default navbar-btn" role="button" target="_blank" style="display: none;">
|
||||
<img id="visit-hmd-icon" src="/images/hmd-w-eyes.svg" alt="Head-mounted display" />
|
||||
Visit in VR
|
||||
</a>
|
||||
<button id="save-settings-button" class="save-button btn btn-success navbar-btn hidden-xs" disabled="true"><span class="save-button-text">Save</span></button>
|
||||
<a href="#" id="restart-server" class="navbar-btn btn btn-link"><span class="glyphicon glyphicon-refresh"></span> Restart</a>
|
||||
</ul>
|
||||
</div>
|
||||
</div><!-- /.container-fluid -->
|
||||
|
@ -50,7 +67,7 @@
|
|||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">domain-server is restarting</h4>
|
||||
<h4 class="modal-title">Domain Server is restarting</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h5>This page will automatically refresh in <span id="refresh-time">3 seconds</span>.</h5>
|
||||
|
|
13
domain-server/resources/web/images/hmd-w-eyes.svg
Normal file
13
domain-server/resources/web/images/hmd-w-eyes.svg
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 456 244" style="enable-background:new 0 0 456 244;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#666666;}
|
||||
</style>
|
||||
<path class="st0" d="M352.8,3.4h-250C49.6,3.4,6.3,46.2,6.3,98.9v44.9c0,52.7,43.3,95.5,96.5,95.5h67.6c19.4,0,31.9-17.9,42.9-33.6
|
||||
c4.2-6.1,11.1-15.9,14.8-17.9c3.6,2.1,10.4,12.2,14.5,18.4c10.4,15.5,22.1,33.1,40.3,33.1h69.9c53.3,0,96.5-42.9,96.5-95.5V98.9
|
||||
C449.3,46.2,406,3.4,352.8,3.4z M129.6,157.6c-22.4,0-40.6-18.2-40.6-40.6s18.2-40.6,40.6-40.6c22.4,0,40.6,18.2,40.6,40.6
|
||||
S151.9,157.6,129.6,157.6z M328.4,157.6c-22.4,0-40.6-18.2-40.6-40.6s18.2-40.6,40.6-40.6c22.4,0,40.6,18.2,40.6,40.6
|
||||
S350.8,157.6,328.4,157.6z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 928 B |
1152
domain-server/resources/web/js/base-settings.js
Normal file
1152
domain-server/resources/web/js/base-settings.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -23,16 +23,22 @@ function showRestartModal() {
|
|||
}, 1000);
|
||||
}
|
||||
|
||||
function settingsGroupAnchor(base, html_id) {
|
||||
return base + "#" + html_id + "_group"
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
var url = window.location;
|
||||
|
||||
// Will only work if string in href matches with location
|
||||
$('ul.nav a[href="'+ url +'"]').parent().addClass('active');
|
||||
|
||||
// Will also work for relative and absolute hrefs
|
||||
$('ul.nav a').filter(function() {
|
||||
return this.href == url;
|
||||
}).parent().addClass('active');
|
||||
$('body').on('click', '#restart-server', function(e) {
|
||||
}).parent().addClass('active');
|
||||
|
||||
$('body').on('click', '#restart-server', function(e) {
|
||||
swal( {
|
||||
title: "Are you sure?",
|
||||
text: "This will restart your domain server, causing your domain to be briefly offline.",
|
||||
|
@ -45,4 +51,47 @@ $(document).ready(function(){
|
|||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
var $contentDropdown = $('#content-settings-nav-dropdown');
|
||||
var $settingsDropdown = $('#domain-settings-nav-dropdown');
|
||||
|
||||
// for pages that have the settings dropdowns
|
||||
if ($contentDropdown.length && $settingsDropdown.length) {
|
||||
// make a JSON request to get the dropdown menus for content and settings
|
||||
// we don't error handle here because the top level menu is still clickable and usables if this fails
|
||||
$.getJSON('/settings-menu-groups.json', function(data){
|
||||
function makeGroupDropdownElement(group, base) {
|
||||
var html_id = group.html_id ? group.html_id : group.name;
|
||||
return "<li class='setting-group'><a href='" + settingsGroupAnchor(base, html_id) + "'>" + group.label + "<span class='badge'></span></a></li>";
|
||||
}
|
||||
|
||||
$.each(data.content_settings, function(index, group){
|
||||
if (index > 0) {
|
||||
$contentDropdown.append("<li role='separator' class='divider'></li>");
|
||||
}
|
||||
|
||||
$contentDropdown.append(makeGroupDropdownElement(group, "/content/"));
|
||||
});
|
||||
|
||||
$.each(data.domain_settings, function(index, group){
|
||||
if (index > 0) {
|
||||
$settingsDropdown.append("<li role='separator' class='divider'></li>");
|
||||
}
|
||||
|
||||
$settingsDropdown.append(makeGroupDropdownElement(group, "/settings/"));
|
||||
|
||||
// for domain settings, we add a dummy "Places" group that we fill
|
||||
// via the API - add it to the dropdown menu in the right spot
|
||||
// which is after "Metaverse / Networking"
|
||||
if (group.name == "metaverse") {
|
||||
$settingsDropdown.append("<li role='separator' class='divider'></li>");
|
||||
$settingsDropdown.append(makeGroupDropdownElement({ html_id: 'places', label: 'Places' }, "/settings/"));
|
||||
}
|
||||
});
|
||||
|
||||
// append a link for the "Settings Backup" panel
|
||||
$settingsDropdown.append("<li role='separator' class='divider'></li>");
|
||||
$settingsDropdown.append(makeGroupDropdownElement({ html_id: 'settings_backup', label: 'Settings Backup'}, "/settings"));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
var Settings = {
|
||||
showAdvanced: false,
|
||||
ADVANCED_CLASS: 'advanced-setting',
|
||||
if (typeof Settings === "undefined") {
|
||||
Settings = {};
|
||||
}
|
||||
|
||||
Object.assign(Settings, {
|
||||
DEPRECATED_CLASS: 'deprecated-setting',
|
||||
TRIGGER_CHANGE_CLASS: 'trigger-change',
|
||||
DATA_ROW_CLASS: 'value-row',
|
||||
|
@ -41,7 +43,7 @@ var Settings = {
|
|||
FORM_ID: 'settings-form',
|
||||
INVALID_ROW_CLASS: 'invalid-input',
|
||||
DATA_ROW_INDEX: 'data-row-index'
|
||||
};
|
||||
});
|
||||
|
||||
var URLs = {
|
||||
// STABLE METAVERSE_URL: https://metaverse.highfidelity.com
|
||||
|
@ -95,7 +97,13 @@ var DOMAIN_ID_TYPE_FULL = 2;
|
|||
var DOMAIN_ID_TYPE_UNKNOWN = 3;
|
||||
|
||||
function domainIDIsSet() {
|
||||
return Settings.data.values.metaverse.id.length > 0;
|
||||
if (typeof Settings.data.values.metaverse !== 'undefined' &&
|
||||
typeof Settings.data.values.metaverse.id !== 'undefined') {
|
||||
|
||||
return Settings.data.values.metaverse.id.length > 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentDomainIDType() {
|
||||
|
@ -156,11 +164,12 @@ function getDomainFromAPI(callback) {
|
|||
if (callback === undefined) {
|
||||
callback = function() {};
|
||||
}
|
||||
|
||||
var domainID = Settings.data.values.metaverse.id;
|
||||
if (domainID === null || domainID === undefined || domainID === '') {
|
||||
|
||||
if (!domainIDIsSet()) {
|
||||
callback({ status: 'fail' });
|
||||
return null;
|
||||
} else {
|
||||
var domainID = Settings.data.values.metaverse.id;
|
||||
}
|
||||
|
||||
pendingDomainRequest = $.ajax({
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,104 +1,29 @@
|
|||
<!--#include virtual="header.html"-->
|
||||
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="alert" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var Settings = {
|
||||
content_settings: false,
|
||||
endpoint: "/settings.json",
|
||||
path: "/settings/"
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-sm-3" id="setup-sidebar-col">
|
||||
<div id="setup-sidebar" data-clampedwidth="#setup-sidebar-col">
|
||||
<script id="list-group-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% if (!group.hidden) { %>
|
||||
<% panelID = group.name ? group.name : group.html_id %>
|
||||
<li>
|
||||
<a href="#<%- panelID %>" class="list-group-item">
|
||||
<span class="badge"></span>
|
||||
<%- group.label %>
|
||||
</a>
|
||||
</li>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</script>
|
||||
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
</ul>
|
||||
|
||||
<button id="advanced-toggle-button" class="btn btn-info advanced-toggle">Show advanced</button>
|
||||
<button class="btn btn-success save-button" disabled>Save</button>
|
||||
<div id="manage-cloud-domains-link" style="display: none;">
|
||||
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Manage Cloud Hosted Domains</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-9 col-sm-9 col-xs-12">
|
||||
<div class="col-md-10 col-md-offset-1 col-xs-12">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
|
||||
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<span class="alert-link">
|
||||
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Visit Cloud Hosted Domains</a> to manage all your cloud domains
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form id="settings-form" role="form">
|
||||
|
||||
<script id="panels-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% if (!group.hidden) { %>
|
||||
<% var settings = _.partition(group.settings, function(value, index) { return !value.deprecated })[0] %>
|
||||
<% split_settings = _.partition(settings, function(value, index) { return !value.advanced }) %>
|
||||
<% isAdvanced = _.isEmpty(split_settings[0]) && !_.isEmpty(split_settings[1]) %>
|
||||
<% if (isAdvanced) { %>
|
||||
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
|
||||
<% } %>
|
||||
|
||||
<% isGrouped = !!group.name %>
|
||||
<% panelID = isGrouped ? group.name : group.html_id %>
|
||||
|
||||
<div class="panel panel-default<%- (isAdvanced) ? ' advanced-setting' : '' %><%- (isGrouped) ? ' grouped' : '' %>"
|
||||
id="<%- panelID %>">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><%- group.label %></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, false) %>
|
||||
<% }); %>
|
||||
<% if (!_.isEmpty(split_settings[1])) { %>
|
||||
<% $("#advanced-toggle-button").show() %>
|
||||
<% _.each(split_settings[1], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, true) %>
|
||||
<% }); %>
|
||||
<% }%>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</script>
|
||||
<div id="panels"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 hidden-sm hidden-md hidden-lg">
|
||||
<button class="btn btn-success save-button" id="small-save-button">Save</button>
|
||||
</div>
|
||||
<div class="alert alert-info">Your domain content settings are now available in <a href='/content/'>Content</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--#include virtual="base-settings.html"-->
|
||||
|
||||
<!--#include virtual="footer.html"-->
|
||||
<script src='/js/underscore-min.js'></script>
|
||||
<script src='/js/underscore-keypath.min.js'></script>
|
||||
<script src='/js/bootbox.min.js'></script>
|
||||
|
||||
<script src='/js/sha256.js'></script>
|
||||
<script src='js/form2js.min.js'></script>
|
||||
<script src='js/bootstrap-switch.min.js'></script>
|
||||
<script src='/js/shared.js'></script>
|
||||
<script src='js/settings.js'></script>
|
||||
|
||||
<!--#include virtual="base-settings-scripts.html"-->
|
||||
|
||||
<script src="js/settings.js"></script>
|
||||
|
||||
<!--#include virtual="page-end.html"-->
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -501,7 +501,7 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
|||
// store the new domain ID and auto network setting immediately
|
||||
QString newSettingsJSON = QString("{\"metaverse\": { \"id\": \"%1\", \"automatic_networking\": \"full\"}}").arg(id);
|
||||
auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8());
|
||||
_settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object());
|
||||
_settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object(), DomainSettings);
|
||||
|
||||
// store the new ID and auto networking setting on disk
|
||||
_settingsManager.persistToFile();
|
||||
|
|
|
@ -39,8 +39,11 @@ const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings
|
|||
const QString DESCRIPTION_SETTINGS_KEY = "settings";
|
||||
const QString SETTING_DEFAULT_KEY = "default";
|
||||
const QString DESCRIPTION_NAME_KEY = "name";
|
||||
const QString DESCRIPTION_GROUP_LABEL_KEY = "label";
|
||||
const QString DESCRIPTION_BACKUP_FLAG_KEY = "backup";
|
||||
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
|
||||
const QString DESCRIPTION_COLUMNS_KEY = "columns";
|
||||
const QString CONTENT_SETTING_FLAG_KEY = "content_setting";
|
||||
|
||||
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
|
||||
|
||||
|
@ -63,6 +66,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() {
|
|||
|
||||
if (descriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) {
|
||||
_descriptionArray = descriptionDocument.object()[DESCRIPTION_SETTINGS_KEY].toArray();
|
||||
splitSettingsDescription();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -78,11 +83,99 @@ DomainServerSettingsManager::DomainServerSettingsManager() {
|
|||
Q_ARG(int, MISSING_SETTINGS_DESC_ERROR_CODE));
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::splitSettingsDescription() {
|
||||
// construct separate description arrays for domain settings and content settings
|
||||
// since they are displayed on different pages
|
||||
|
||||
// along the way we also construct one object that holds the groups separated by domain settings
|
||||
// and content settings, so that the DS can setup dropdown menus below "Content" and "Settings"
|
||||
// headers to jump directly to a settings group on the page of either
|
||||
QJsonArray domainSettingsMenuGroups;
|
||||
QJsonArray contentSettingsMenuGroups;
|
||||
|
||||
foreach(const QJsonValue& group, _descriptionArray) {
|
||||
QJsonObject groupObject = group.toObject();
|
||||
|
||||
static const QString HIDDEN_GROUP_KEY = "hidden";
|
||||
bool groupHidden = groupObject.contains(HIDDEN_GROUP_KEY) && groupObject[HIDDEN_GROUP_KEY].toBool();
|
||||
|
||||
QJsonArray domainSettingArray;
|
||||
QJsonArray contentSettingArray;
|
||||
|
||||
foreach(const QJsonValue& settingDescription, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) {
|
||||
QJsonObject settingDescriptionObject = settingDescription.toObject();
|
||||
|
||||
bool isContentSetting = settingDescriptionObject.contains(CONTENT_SETTING_FLAG_KEY)
|
||||
&& settingDescriptionObject[CONTENT_SETTING_FLAG_KEY].toBool();
|
||||
|
||||
if (isContentSetting) {
|
||||
// push the setting description to the pending content setting array
|
||||
contentSettingArray.push_back(settingDescriptionObject);
|
||||
} else {
|
||||
// push the setting description to the pending domain setting array
|
||||
domainSettingArray.push_back(settingDescriptionObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (!domainSettingArray.isEmpty() || !contentSettingArray.isEmpty()) {
|
||||
|
||||
// we know for sure we'll have something to add to our settings menu groups
|
||||
// so setup that object for the group now, as long as the group isn't hidden alltogether
|
||||
QJsonObject settingsDropdownGroup;
|
||||
|
||||
if (!groupHidden) {
|
||||
if (groupObject.contains(DESCRIPTION_NAME_KEY)) {
|
||||
settingsDropdownGroup[DESCRIPTION_NAME_KEY] = groupObject[DESCRIPTION_NAME_KEY];
|
||||
}
|
||||
|
||||
settingsDropdownGroup[DESCRIPTION_GROUP_LABEL_KEY] = groupObject[DESCRIPTION_GROUP_LABEL_KEY];
|
||||
|
||||
static const QString DESCRIPTION_GROUP_HTML_ID_KEY = "html_id";
|
||||
if (groupObject.contains(DESCRIPTION_GROUP_HTML_ID_KEY)) {
|
||||
settingsDropdownGroup[DESCRIPTION_GROUP_HTML_ID_KEY] = groupObject[DESCRIPTION_GROUP_HTML_ID_KEY];
|
||||
}
|
||||
}
|
||||
|
||||
if (!domainSettingArray.isEmpty()) {
|
||||
// we have some domain settings from this group, add the group with the filtered settings
|
||||
QJsonObject filteredGroupObject = groupObject;
|
||||
filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = domainSettingArray;
|
||||
_domainSettingsDescription.push_back(filteredGroupObject);
|
||||
|
||||
// if the group isn't hidden, add its information to the domain settings menu groups
|
||||
if (!groupHidden) {
|
||||
domainSettingsMenuGroups.push_back(settingsDropdownGroup);
|
||||
}
|
||||
}
|
||||
|
||||
if (!contentSettingArray.isEmpty()) {
|
||||
// we have some content settings from this group, add the group with the filtered settings
|
||||
QJsonObject filteredGroupObject = groupObject;
|
||||
filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = contentSettingArray;
|
||||
_contentSettingsDescription.push_back(filteredGroupObject);
|
||||
|
||||
// if the group isn't hidden, add its information to the content settings menu groups
|
||||
if (!groupHidden) {
|
||||
contentSettingsMenuGroups.push_back(settingsDropdownGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// populate the settings menu groups with what we've collected
|
||||
|
||||
static const QString SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY = "domain_settings";
|
||||
static const QString SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY = "content_settings";
|
||||
|
||||
_settingsMenuGroups[SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY] = domainSettingsMenuGroups;
|
||||
_settingsMenuGroups[SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY] = contentSettingsMenuGroups;
|
||||
}
|
||||
|
||||
void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
Assignment::Type type;
|
||||
message->readPrimitive(&type);
|
||||
|
||||
QJsonObject responseObject = responseObjectForType(QString::number(type));
|
||||
QJsonObject responseObject = settingsResponseObjectForType(QString::number(type));
|
||||
auto json = QJsonDocument(responseObject).toJson();
|
||||
|
||||
auto packetList = NLPacketList::create(PacketType::DomainSettings, QByteArray(), true, true);
|
||||
|
@ -314,14 +407,14 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
|
||||
QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH);
|
||||
if (avatarMinScale) {
|
||||
float scale = avatarMinScale->toFloat();
|
||||
_configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT);
|
||||
auto newMinScaleVariant = _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, true);
|
||||
*newMinScaleVariant = avatarMinScale->toFloat() * DEFAULT_AVATAR_HEIGHT;
|
||||
}
|
||||
|
||||
QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH);
|
||||
if (avatarMaxScale) {
|
||||
float scale = avatarMaxScale->toFloat();
|
||||
_configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT);
|
||||
auto newMaxScaleVariant = _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, true);
|
||||
*newMaxScaleVariant = avatarMaxScale->toFloat() * DEFAULT_AVATAR_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -986,48 +1079,246 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin
|
|||
}
|
||||
|
||||
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
|
||||
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == SETTINGS_PATH_JSON) {
|
||||
// this is a POST operation to change one or more settings
|
||||
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
|
||||
QJsonObject postedObject = postedDocument.object();
|
||||
if (connection->requestOperation() == QNetworkAccessManager::PostOperation) {
|
||||
static const QString SETTINGS_RESTORE_PATH = "/settings/restore";
|
||||
|
||||
// we recurse one level deep below each group for the appropriate setting
|
||||
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject);
|
||||
if (url.path() == SETTINGS_PATH_JSON || url.path() == CONTENT_SETTINGS_PATH_JSON) {
|
||||
// this is a POST operation to change one or more settings
|
||||
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
|
||||
QJsonObject postedObject = postedDocument.object();
|
||||
|
||||
// store whatever the current _settingsMap is to file
|
||||
persistToFile();
|
||||
SettingsType endpointType = url.path() == SETTINGS_PATH_JSON ? DomainSettings : ContentSettings;
|
||||
|
||||
// return success to the caller
|
||||
QString jsonSuccess = "{\"status\": \"success\"}";
|
||||
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
||||
// we recurse one level deep below each group for the appropriate setting
|
||||
bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject, endpointType);
|
||||
|
||||
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
|
||||
if (restartRequired) {
|
||||
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
||||
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
||||
} else {
|
||||
unpackPermissions();
|
||||
apiRefreshGroupInformation();
|
||||
emit updateNodePermissions();
|
||||
emit settingsUpdated();
|
||||
// store whatever the current _settingsMap is to file
|
||||
persistToFile();
|
||||
|
||||
// return success to the caller
|
||||
QString jsonSuccess = "{\"status\": \"success\"}";
|
||||
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
||||
|
||||
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
|
||||
if (restartRequired) {
|
||||
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
||||
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
||||
} else {
|
||||
unpackPermissions();
|
||||
apiRefreshGroupInformation();
|
||||
emit updateNodePermissions();
|
||||
emit settingsUpdated();
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (url.path() == SETTINGS_RESTORE_PATH) {
|
||||
// this is an JSON settings file restore, ask the HTTPConnection to parse the data
|
||||
QList<FormData> formData = connection->parseFormData();
|
||||
|
||||
bool wasRestoreSuccessful = false;
|
||||
|
||||
if (formData.size() > 0 && formData[0].second.size() > 0) {
|
||||
// take the posted file and convert it to a QJsonObject
|
||||
auto postedDocument = QJsonDocument::fromJson(formData[0].second);
|
||||
if (postedDocument.isObject()) {
|
||||
wasRestoreSuccessful = restoreSettingsFromObject(postedDocument.object(), DomainSettings);
|
||||
}
|
||||
}
|
||||
|
||||
if (wasRestoreSuccessful) {
|
||||
// respond with a 200 for success
|
||||
QString jsonSuccess = "{\"status\": \"success\"}";
|
||||
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
|
||||
|
||||
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
|
||||
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
|
||||
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
|
||||
} else {
|
||||
// respond with a 400 for failure
|
||||
connection->respond(HTTPConnection::StatusCode400);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
|
||||
static const QString SETTINGS_MENU_GROUPS_PATH = "/settings-menu-groups.json";
|
||||
static const QString SETTINGS_BACKUP_PATH = "/settings/backup.json";
|
||||
|
||||
return true;
|
||||
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
|
||||
// setup a JSON Object with descriptions and non-omitted settings
|
||||
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
|
||||
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
|
||||
if (url.path() == SETTINGS_PATH_JSON || url.path() == CONTENT_SETTINGS_PATH_JSON) {
|
||||
|
||||
QJsonObject rootObject;
|
||||
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
|
||||
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
|
||||
// setup a JSON Object with descriptions and non-omitted settings
|
||||
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
|
||||
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
|
||||
|
||||
QJsonObject rootObject;
|
||||
|
||||
bool forDomainSettings = (url.path() == SETTINGS_PATH_JSON);
|
||||
bool forContentSettings = (url.path() == CONTENT_SETTINGS_PATH_JSON);;
|
||||
|
||||
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = forDomainSettings
|
||||
? _domainSettingsDescription : _contentSettingsDescription;
|
||||
|
||||
// grab a domain settings object for all types, filtered for the right class of settings
|
||||
// and exclude default values
|
||||
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = settingsResponseObjectForType("", true,
|
||||
forDomainSettings, forContentSettings,
|
||||
true);
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
|
||||
|
||||
return true;
|
||||
} else if (url.path() == SETTINGS_MENU_GROUPS_PATH) {
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(_settingsMenuGroups).toJson(), "application/json");
|
||||
|
||||
return true;
|
||||
} else if (url.path() == SETTINGS_BACKUP_PATH) {
|
||||
// grab the settings backup as an authenticated user
|
||||
// for the domain settings type only, excluding hidden and default values
|
||||
auto currentDomainSettingsJSON = settingsResponseObjectForType("", true, true, false, false, true);
|
||||
|
||||
// setup headers that tell the client to download the file wth a special name
|
||||
Headers downloadHeaders;
|
||||
downloadHeaders.insert("Content-Transfer-Encoding", "binary");
|
||||
|
||||
// create a timestamped filename for the backup
|
||||
const QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" };
|
||||
auto backupFilename = "domain-settings_" + QDateTime::currentDateTime().toString(DATETIME_FORMAT) + ".json";
|
||||
|
||||
downloadHeaders.insert("Content-Disposition",
|
||||
QString("attachment; filename=\"%1\"").arg(backupFilename).toLocal8Bit());
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(currentDomainSettingsJSON).toJson(),
|
||||
"application/force-download", downloadHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated) {
|
||||
bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType) {
|
||||
QJsonArray* filteredDescriptionArray = settingsType == DomainSettings
|
||||
? &_domainSettingsDescription : &_contentSettingsDescription;
|
||||
|
||||
// grab a copy of the current config before restore, so that we can back out if something bad happens during
|
||||
QVariantMap preRestoreConfig = _configMap.getConfig();
|
||||
|
||||
bool shouldCancelRestore = false;
|
||||
|
||||
// enumerate through the settings in the description
|
||||
// if we have one in the restore then use it, otherwise clear it from current settings
|
||||
foreach(const QJsonValue& descriptionGroupValue, *filteredDescriptionArray) {
|
||||
QJsonObject descriptionGroupObject = descriptionGroupValue.toObject();
|
||||
QString groupKey = descriptionGroupObject[DESCRIPTION_NAME_KEY].toString();
|
||||
QJsonArray descriptionGroupSettings = descriptionGroupObject[DESCRIPTION_SETTINGS_KEY].toArray();
|
||||
|
||||
// grab the matching group from the restore so we can look at its settings
|
||||
QJsonObject restoreGroup;
|
||||
QVariantMap* configGroupMap = nullptr;
|
||||
|
||||
if (groupKey.isEmpty()) {
|
||||
// this is for a setting at the root, use the full object as our restore group
|
||||
restoreGroup = settingsToRestore;
|
||||
|
||||
// the variant map for this "group" is just the config map since there's no group
|
||||
configGroupMap = &_configMap.getConfig();
|
||||
} else {
|
||||
if (settingsToRestore.contains(groupKey)) {
|
||||
restoreGroup = settingsToRestore[groupKey].toObject();
|
||||
}
|
||||
|
||||
// grab the variant for the group
|
||||
auto groupMapVariant = _configMap.valueForKeyPath(groupKey);
|
||||
|
||||
// if it existed, double check that it is a map - any other value is unexpected and should cancel a restore
|
||||
if (groupMapVariant) {
|
||||
if (groupMapVariant->canConvert<QVariantMap>()) {
|
||||
configGroupMap = static_cast<QVariantMap*>(groupMapVariant->data());
|
||||
} else {
|
||||
shouldCancelRestore = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach(const QJsonValue& descriptionSettingValue, descriptionGroupSettings) {
|
||||
|
||||
QJsonObject descriptionSettingObject = descriptionSettingValue.toObject();
|
||||
|
||||
// we'll override this setting with the default or what is in the restore as long as
|
||||
// it isn't specifically excluded from backups
|
||||
bool isBackedUpSetting = !descriptionSettingObject.contains(DESCRIPTION_BACKUP_FLAG_KEY)
|
||||
|| descriptionSettingObject[DESCRIPTION_BACKUP_FLAG_KEY].toBool();
|
||||
|
||||
if (isBackedUpSetting) {
|
||||
QString settingName = descriptionSettingObject[DESCRIPTION_NAME_KEY].toString();
|
||||
|
||||
// check if we have a matching setting for this in the restore
|
||||
QJsonValue restoreValue;
|
||||
if (restoreGroup.contains(settingName)) {
|
||||
restoreValue = restoreGroup[settingName];
|
||||
}
|
||||
|
||||
// we should create the value for this key path in our current config map
|
||||
// if we had value in the restore file
|
||||
bool shouldCreateIfMissing = !restoreValue.isNull();
|
||||
|
||||
// get a QVariant pointer to this setting in our config map
|
||||
QString fullSettingKey = !groupKey.isEmpty()
|
||||
? groupKey + "." + settingName : settingName;
|
||||
|
||||
QVariant* variantValue = _configMap.valueForKeyPath(fullSettingKey, shouldCreateIfMissing);
|
||||
|
||||
if (restoreValue.isNull()) {
|
||||
if (variantValue && !variantValue->isNull() && configGroupMap) {
|
||||
// we didn't have a value to restore, but there might be a value in the config map
|
||||
// so we need to remove the value in the config map which will set it back to the default
|
||||
qDebug() << "Removing" << fullSettingKey << "from settings since it is not in the restored JSON";
|
||||
configGroupMap->remove(settingName);
|
||||
}
|
||||
} else {
|
||||
// we have a value to restore, use update setting to set it
|
||||
|
||||
// we might need to re-grab config group map in case it didn't exist when we looked for it before
|
||||
// but was created by the call to valueForKeyPath before
|
||||
if (!configGroupMap) {
|
||||
auto groupMapVariant = _configMap.valueForKeyPath(groupKey);
|
||||
if (groupMapVariant && groupMapVariant->canConvert<QVariantMap>()) {
|
||||
configGroupMap = static_cast<QVariantMap*>(groupMapVariant->data());
|
||||
} else {
|
||||
shouldCancelRestore = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Updating setting" << fullSettingKey << "from restored JSON";
|
||||
|
||||
updateSetting(settingName, restoreValue, *configGroupMap, descriptionSettingObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCancelRestore) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCancelRestore) {
|
||||
// if we cancelled the restore, go back to our state before and return false
|
||||
qDebug() << "Restore cancelled, settings have not been changed";
|
||||
_configMap.getConfig() = preRestoreConfig;
|
||||
return false;
|
||||
} else {
|
||||
// restore completed, persist the new settings
|
||||
qDebug() << "Restore completed, persisting restored settings to file";
|
||||
persistToFile();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated,
|
||||
bool includeDomainSettings,
|
||||
bool includeContentSettings,
|
||||
bool includeDefaults, bool isForBackup) {
|
||||
QJsonObject responseObject;
|
||||
|
||||
if (!typeValue.isEmpty() || isAuthenticated) {
|
||||
|
@ -1036,8 +1327,16 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
|
|||
|
||||
const QString AFFECTED_TYPES_JSON_KEY = "assignment-types";
|
||||
|
||||
// enumerate the groups in the description object to find which settings to pass
|
||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
||||
// only enumerate the requested settings type (domain setting or content setting)
|
||||
QJsonArray* filteredDescriptionArray = &_descriptionArray;
|
||||
if (includeDomainSettings && !includeContentSettings) {
|
||||
filteredDescriptionArray = &_domainSettingsDescription;
|
||||
} else if (includeContentSettings && !includeDomainSettings) {
|
||||
filteredDescriptionArray = &_contentSettingsDescription;
|
||||
}
|
||||
|
||||
// enumerate the groups in the potentially filtered object to find which settings to pass
|
||||
foreach(const QJsonValue& groupValue, *filteredDescriptionArray) {
|
||||
QJsonObject groupObject = groupValue.toObject();
|
||||
QString groupKey = groupObject[DESCRIPTION_NAME_KEY].toString();
|
||||
QJsonArray groupSettingsArray = groupObject[DESCRIPTION_SETTINGS_KEY].toArray();
|
||||
|
@ -1045,11 +1344,17 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
|
|||
QJsonObject groupResponseObject;
|
||||
|
||||
foreach(const QJsonValue& settingValue, groupSettingsArray) {
|
||||
|
||||
const QString VALUE_HIDDEN_FLAG_KEY = "value-hidden";
|
||||
|
||||
QJsonObject settingObject = settingValue.toObject();
|
||||
|
||||
if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) {
|
||||
// consider this setting as long as it isn't hidden
|
||||
// and either this isn't for a backup or it's a value included in backups
|
||||
bool includedInBackups = !settingObject.contains(DESCRIPTION_BACKUP_FLAG_KEY)
|
||||
|| settingObject[DESCRIPTION_BACKUP_FLAG_KEY].toBool();
|
||||
|
||||
if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool() && (!isForBackup || includedInBackups)) {
|
||||
QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray();
|
||||
if (affectedTypesArray.isEmpty()) {
|
||||
affectedTypesArray = groupObject[AFFECTED_TYPES_JSON_KEY].toArray();
|
||||
|
@ -1057,8 +1362,6 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
|
|||
|
||||
if (affectedTypesArray.contains(queryType) ||
|
||||
(queryType.isNull() && isAuthenticated)) {
|
||||
// this is a setting we should include in the responseObject
|
||||
|
||||
QString settingName = settingObject[DESCRIPTION_NAME_KEY].toString();
|
||||
|
||||
// we need to check if the settings map has a value for this setting
|
||||
|
@ -1074,28 +1377,31 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
|
|||
variantValue = _configMap.value(settingName);
|
||||
}
|
||||
|
||||
QJsonValue result;
|
||||
// final check for inclusion
|
||||
// either we include default values or we don't but this isn't a default value
|
||||
if (includeDefaults || !variantValue.isNull()) {
|
||||
QJsonValue result;
|
||||
|
||||
if (variantValue.isNull()) {
|
||||
// no value for this setting, pass the default
|
||||
if (settingObject.contains(SETTING_DEFAULT_KEY)) {
|
||||
result = settingObject[SETTING_DEFAULT_KEY];
|
||||
if (variantValue.isNull()) {
|
||||
// no value for this setting, pass the default
|
||||
if (settingObject.contains(SETTING_DEFAULT_KEY)) {
|
||||
result = settingObject[SETTING_DEFAULT_KEY];
|
||||
} else {
|
||||
// users are allowed not to provide a default for string values
|
||||
// if so we set to the empty string
|
||||
result = QString("");
|
||||
}
|
||||
} else {
|
||||
// users are allowed not to provide a default for string values
|
||||
// if so we set to the empty string
|
||||
result = QString("");
|
||||
result = QJsonValue::fromVariant(variantValue);
|
||||
}
|
||||
|
||||
} else {
|
||||
result = QJsonValue::fromVariant(variantValue);
|
||||
}
|
||||
|
||||
if (!groupKey.isEmpty()) {
|
||||
// this belongs in the group object
|
||||
groupResponseObject[settingName] = result;
|
||||
} else {
|
||||
// this is a value that should be at the root
|
||||
responseObject[settingName] = result;
|
||||
if (!groupKey.isEmpty()) {
|
||||
// this belongs in the group object
|
||||
groupResponseObject[settingName] = result;
|
||||
} else {
|
||||
// this is a value that should be at the root
|
||||
responseObject[settingName] = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1108,7 +1414,6 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
return responseObject;
|
||||
}
|
||||
|
||||
|
@ -1140,6 +1445,8 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
|
|||
settingMap[key] = sanitizedValue;
|
||||
}
|
||||
}
|
||||
} else if (newValue.isDouble()) {
|
||||
settingMap[key] = newValue.toDouble();
|
||||
} else if (newValue.isBool()) {
|
||||
settingMap[key] = newValue.toBool();
|
||||
} else if (newValue.isObject()) {
|
||||
|
@ -1212,7 +1519,8 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson
|
|||
return QJsonObject();
|
||||
}
|
||||
|
||||
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) {
|
||||
bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
|
||||
SettingsType settingsType) {
|
||||
static const QString SECURITY_ROOT_KEY = "security";
|
||||
static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist";
|
||||
static const QString BROADCASTING_KEY = "broadcasting";
|
||||
|
@ -1222,6 +1530,8 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
|||
auto& settingsVariant = _configMap.getConfig();
|
||||
bool needRestart = false;
|
||||
|
||||
auto& filteredDescriptionArray = settingsType == DomainSettings ? _domainSettingsDescription : _contentSettingsDescription;
|
||||
|
||||
// Iterate on the setting groups
|
||||
foreach(const QString& rootKey, postedObject.keys()) {
|
||||
const QJsonValue& rootValue = postedObject[rootKey];
|
||||
|
@ -1236,7 +1546,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
|||
QJsonObject groupDescriptionObject;
|
||||
|
||||
// we need to check the description array to see if this is a root setting or a group setting
|
||||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
||||
foreach(const QJsonValue& groupValue, filteredDescriptionArray) {
|
||||
if (groupValue.toObject()[DESCRIPTION_NAME_KEY] == rootKey) {
|
||||
// we matched a group - keep this since we'll use it below to update the settings
|
||||
groupDescriptionObject = groupValue.toObject();
|
||||
|
@ -1257,7 +1567,9 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
|||
foreach(const QJsonValue& groupValue, _descriptionArray) {
|
||||
// find groups with root values (they don't have a group name)
|
||||
QJsonObject groupObject = groupValue.toObject();
|
||||
|
||||
if (!groupObject.contains(DESCRIPTION_NAME_KEY)) {
|
||||
|
||||
// this is a group with root values - check if our setting is in here
|
||||
matchingDescriptionObject = settingDescriptionFromGroup(groupObject, rootKey);
|
||||
|
||||
|
@ -1269,6 +1581,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
|||
|
||||
if (!matchingDescriptionObject.isEmpty()) {
|
||||
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
|
||||
|
||||
if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
|
||||
rootKey != SETTINGS_PATHS_KEY && rootKey != WIZARD_KEY) {
|
||||
needRestart = true;
|
||||
|
@ -1286,6 +1599,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
|||
if (!matchingDescriptionObject.isEmpty()) {
|
||||
const QJsonValue& settingValue = rootValue.toObject()[settingKey];
|
||||
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
|
||||
|
||||
if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
|
||||
rootKey != DESCRIPTION_ROOT_KEY && rootKey != WIZARD_KEY) ||
|
||||
settingKey == AC_SUBNET_WHITELIST_KEY) {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#define hifi_DomainServerSettingsManager_h
|
||||
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
|
@ -28,6 +29,7 @@ const QString SETTINGS_PATHS_KEY = "paths";
|
|||
|
||||
const QString SETTINGS_PATH = "/settings";
|
||||
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
|
||||
const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.json";
|
||||
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
|
||||
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
|
||||
const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions";
|
||||
|
@ -38,6 +40,10 @@ const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens";
|
|||
|
||||
using GroupByUUIDKey = QPair<QUuid, QUuid>; // groupID, rankID
|
||||
|
||||
enum SettingsType {
|
||||
DomainSettings,
|
||||
ContentSettings
|
||||
};
|
||||
|
||||
class DomainServerSettingsManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -123,8 +129,11 @@ private slots:
|
|||
private:
|
||||
QStringList _argumentList;
|
||||
|
||||
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
|
||||
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject);
|
||||
QJsonArray filteredDescriptionArray(bool isContentSettings);
|
||||
QJsonObject settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated = false,
|
||||
bool includeDomainSettings = true, bool includeContentSettings = true,
|
||||
bool includeDefaults = true, bool isForBackup = false);
|
||||
bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType);
|
||||
|
||||
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
|
||||
const QJsonObject& settingDescription);
|
||||
|
@ -132,8 +141,17 @@ private:
|
|||
void sortPermissions();
|
||||
void persistToFile();
|
||||
|
||||
void splitSettingsDescription();
|
||||
|
||||
bool restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType);
|
||||
|
||||
double _descriptionVersion;
|
||||
|
||||
QJsonArray _descriptionArray;
|
||||
QJsonArray _domainSettingsDescription;
|
||||
QJsonArray _contentSettingsDescription;
|
||||
QJsonObject _settingsMenuGroups;
|
||||
|
||||
HifiConfigVariantMap _configMap;
|
||||
|
||||
friend class DomainServer;
|
||||
|
|
|
@ -88,8 +88,8 @@
|
|||
]
|
||||
},
|
||||
|
||||
{ "from": "Keyboard.W", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||
{ "from": "Keyboard.S", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
||||
{ "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||
{ "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
||||
{ "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" },
|
||||
{ "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" },
|
||||
{ "from": "Keyboard.Left", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" },
|
||||
|
|
|
@ -342,7 +342,7 @@
|
|||
<p>In High Fidelity, your private keys are used to securely access the contents of your Wallet and Purchases.</p>
|
||||
|
||||
<hr>
|
||||
<h3>Where are my private keys stored?"</h3>
|
||||
<h3>Where are my private keys stored?</h3>
|
||||
<p>By default, your private keys are only stored on your hard drive in High Fidelity Interface's <code>AppData</code> directory.</p>
|
||||
<p>Here is the file path of your hifikey - you can browse to it using your file explorer.</p>
|
||||
<div class="alert"> <code>HIFIKEY_PATH_REPLACEME</code> </div>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<path d="M348.5,162.5c1.7,0,3,1.4,3,3v181.4c0,1.7-1.3,3-3,3H167.2c-1.6,0-3-1.3-3-3V165.5c0-1.6,1.4-3,3-3H348.5 M348.5,145.5
|
||||
H167.2c-11,0-20,9-20,20v181.4c0,11,9,20,20,20h181.4c11,0,20-9,20-20V165.5C368.5,154.5,359.6,145.5,348.5,145.5L348.5,145.5z"/>
|
||||
<rect x="161.6" y="253.6" width="96.3" height="96.3"/>
|
||||
<rect x="256.1" y="159.8" width="95.4" height="95.4"/>
|
||||
</svg>
|
After Width: | Height: | Size: 717 B |
|
@ -202,8 +202,4 @@ TreeView {
|
|||
}
|
||||
|
||||
onDoubleClicked: isExpanded(index) ? collapse(index) : expand(index)
|
||||
|
||||
onClicked: {
|
||||
selectionModel.setCurrentIndex(index, ItemSelectionModel.ClearAndSelect);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ Windows.ScrollingWindow {
|
|||
property var assetProxyModel: Assets.proxyModel;
|
||||
property var assetMappingsModel: Assets.mappingModel;
|
||||
property var currentDirectory;
|
||||
property var selectedItems: treeView.selection.selectedIndexes.length;
|
||||
property var selectedItemCount: treeView.selection.selectedIndexes.length;
|
||||
|
||||
Settings {
|
||||
category: "Overlay.AssetServer"
|
||||
|
@ -75,17 +75,17 @@ Windows.ScrollingWindow {
|
|||
});
|
||||
}
|
||||
|
||||
function doDeleteFile(path) {
|
||||
console.log("Deleting " + path);
|
||||
function doDeleteFile(paths) {
|
||||
console.log("Deleting " + paths);
|
||||
|
||||
Assets.deleteMappings(path, function(err) {
|
||||
Assets.deleteMappings(paths, function(err) {
|
||||
if (err) {
|
||||
console.log("Asset browser - error deleting path: ", path, err);
|
||||
console.log("Asset browser - error deleting paths: ", paths, err);
|
||||
|
||||
box = errorMessageBox("There was an error deleting:\n" + path + "\n" + err);
|
||||
box = errorMessageBox("There was an error deleting:\n" + paths + "\n" + err);
|
||||
box.selected.connect(reload);
|
||||
} else {
|
||||
console.log("Asset browser - finished deleting path: ", path);
|
||||
console.log("Asset browser - finished deleting paths: ", paths);
|
||||
reload();
|
||||
}
|
||||
});
|
||||
|
@ -145,7 +145,7 @@ Windows.ScrollingWindow {
|
|||
function canAddToWorld(path) {
|
||||
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i];
|
||||
|
||||
if (selectedItems > 1) {
|
||||
if (selectedItemCount > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ Windows.ScrollingWindow {
|
|||
}
|
||||
|
||||
function canRename() {
|
||||
if (treeView.selection.hasSelection && selectedItems == 1) {
|
||||
if (treeView.selection.hasSelection && selectedItemCount == 1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -344,29 +344,28 @@ Windows.ScrollingWindow {
|
|||
});
|
||||
}
|
||||
function deleteFile(index) {
|
||||
var path = [];
|
||||
var paths = [];
|
||||
|
||||
if (!index) {
|
||||
for (var i = 0; i < selectedItems; i++) {
|
||||
treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100);
|
||||
index = treeView.selection.currentIndex;
|
||||
path[i] = assetProxyModel.data(index, 0x100);
|
||||
for (var i = 0; i < selectedItemCount; ++i) {
|
||||
index = treeView.selection.selectedIndexes[i];
|
||||
paths[i] = assetProxyModel.data(index, 0x100);
|
||||
}
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
if (!paths) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modalMessage = "";
|
||||
var items = selectedItems.toString();
|
||||
var items = selectedItemCount.toString();
|
||||
var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101);
|
||||
var typeString = isFolder ? 'folder' : 'file';
|
||||
|
||||
if (selectedItems > 1) {
|
||||
if (selectedItemCount > 1) {
|
||||
modalMessage = "You are about to delete " + items + " items \nDo you want to continue?";
|
||||
} else {
|
||||
modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?";
|
||||
modalMessage = "You are about to delete the following " + typeString + ":\n" + paths + "\nDo you want to continue?";
|
||||
}
|
||||
|
||||
var object = desktop.messageBox({
|
||||
|
@ -378,7 +377,7 @@ Windows.ScrollingWindow {
|
|||
});
|
||||
object.selected.connect(function(button) {
|
||||
if (button === OriginalDialogs.StandardButton.Yes) {
|
||||
doDeleteFile(path);
|
||||
doDeleteFile(paths);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -705,7 +704,7 @@ Windows.ScrollingWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}// End_OF( itemLoader )
|
||||
|
||||
Rectangle {
|
||||
id: treeLabelToolTip
|
||||
|
@ -742,50 +741,59 @@ Windows.ScrollingWindow {
|
|||
showTimer.stop();
|
||||
treeLabelToolTip.visible = false;
|
||||
}
|
||||
}
|
||||
}// End_OF( treeLabelToolTip )
|
||||
|
||||
MouseArea {
|
||||
propagateComposedEvents: true
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: {
|
||||
if (!HMD.active) { // Popup only displays properly on desktop
|
||||
var index = treeView.indexAt(mouse.x, mouse.y);
|
||||
treeView.selection.setCurrentIndex(index, 0x0002);
|
||||
contextMenu.currentIndex = index;
|
||||
contextMenu.popup();
|
||||
if (treeView.selection.hasSelection && !HMD.active) { // Popup only displays properly on desktop
|
||||
// Only display the popup if the click triggered within
|
||||
// the selection.
|
||||
var clickedIndex = treeView.indexAt(mouse.x, mouse.y);
|
||||
var displayContextMenu = false;
|
||||
for ( var i = 0; i < selectedItemCount; ++i) {
|
||||
var currentSelectedIndex = treeView.selection.selectedIndexes[i];
|
||||
if (clickedIndex === currentSelectedIndex) {
|
||||
contextMenu.popup();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Menu {
|
||||
id: contextMenu
|
||||
title: "Edit"
|
||||
property var url: ""
|
||||
property var currentIndex: null
|
||||
|
||||
MenuItem {
|
||||
text: "Copy URL"
|
||||
enabled: (selectedItemCount == 1)
|
||||
onTriggered: {
|
||||
copyURLToClipboard(contextMenu.currentIndex);
|
||||
copyURLToClipboard(treeView.selection.currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Rename"
|
||||
enabled: (selectedItemCount == 1)
|
||||
onTriggered: {
|
||||
renameFile(contextMenu.currentIndex);
|
||||
renameFile(treeView.selection.currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Delete"
|
||||
enabled: (selectedItemCount > 0)
|
||||
onTriggered: {
|
||||
deleteFile(contextMenu.currentIndex);
|
||||
deleteFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}// End_OF( contextMenu )
|
||||
}// End_OF( treeView )
|
||||
|
||||
Row {
|
||||
id: infoRow
|
||||
|
@ -798,8 +806,8 @@ Windows.ScrollingWindow {
|
|||
|
||||
function makeText() {
|
||||
var numPendingBakes = assetMappingsModel.numPendingBakes;
|
||||
if (selectedItems > 1 || numPendingBakes === 0) {
|
||||
return selectedItems + " items selected";
|
||||
if (selectedItemCount > 1 || numPendingBakes === 0) {
|
||||
return selectedItemCount + " items selected";
|
||||
} else {
|
||||
return numPendingBakes + " bakes pending"
|
||||
}
|
||||
|
@ -896,7 +904,7 @@ Windows.ScrollingWindow {
|
|||
"Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}// End_OF( infoRow )
|
||||
|
||||
HifiControls.ContentSection {
|
||||
id: uploadSection
|
||||
|
@ -956,7 +964,7 @@ Windows.ScrollingWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}// End_OF( uploadSection )
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ Rectangle {
|
|||
// The letterbox used for popup messages
|
||||
LetterboxMessage {
|
||||
id: letterboxMessage;
|
||||
z: 999; // Force the popup on top of everything else
|
||||
z: 998; // Force the popup on top of everything else
|
||||
}
|
||||
Connections {
|
||||
target: GlobalServices
|
||||
|
@ -60,7 +60,7 @@ Rectangle {
|
|||
// The ComboDialog used for setting availability
|
||||
ComboDialog {
|
||||
id: comboDialog;
|
||||
z: 999; // Force the ComboDialog on top of everything else
|
||||
z: 998; // Force the ComboDialog on top of everything else
|
||||
dialogWidth: parent.width - 50;
|
||||
dialogHeight: parent.height - 100;
|
||||
}
|
||||
|
@ -1013,7 +1013,7 @@ Rectangle {
|
|||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
enabled: myData.userName !== "Unknown user";
|
||||
enabled: myData.userName !== "Unknown user" && !userInfoViewer.visible;
|
||||
hoverEnabled: true;
|
||||
onClicked: {
|
||||
popupComboDialog("Set your availability:",
|
||||
|
@ -1044,6 +1044,7 @@ Rectangle {
|
|||
|
||||
HifiControls.TabletWebView {
|
||||
id: userInfoViewer;
|
||||
z: 999;
|
||||
anchors {
|
||||
top: parent.top;
|
||||
bottom: parent.bottom;
|
||||
|
|
|
@ -391,7 +391,7 @@ Item {
|
|||
anchors.topMargin: 15;
|
||||
width: 118;
|
||||
height: paintedHeight;
|
||||
wrapMode: Text.WordWrap;
|
||||
wrapMode: Text.Wrap;
|
||||
// Alignment
|
||||
horizontalAlignment: Text.AlignRight;
|
||||
}
|
||||
|
@ -408,7 +408,7 @@ Item {
|
|||
height: paintedHeight;
|
||||
color: model.status === "invalidated" ? hifi.colors.redAccent : hifi.colors.baseGrayHighlight;
|
||||
linkColor: hifi.colors.blueAccent;
|
||||
wrapMode: Text.WordWrap;
|
||||
wrapMode: Text.Wrap;
|
||||
font.strikeout: model.status === "invalidated";
|
||||
|
||||
onLinkActivated: {
|
||||
|
|
|
@ -39,7 +39,7 @@ Rectangle {
|
|||
property var assetProxyModel: Assets.proxyModel;
|
||||
property var assetMappingsModel: Assets.mappingModel;
|
||||
property var currentDirectory;
|
||||
property var selectedItems: treeView.selection.selectedIndexes.length;
|
||||
property var selectedItemCount: treeView.selection.selectedIndexes.length;
|
||||
|
||||
Settings {
|
||||
category: "Overlay.AssetServer"
|
||||
|
@ -76,17 +76,17 @@ Rectangle {
|
|||
});
|
||||
}
|
||||
|
||||
function doDeleteFile(path) {
|
||||
console.log("Deleting " + path);
|
||||
function doDeleteFile(paths) {
|
||||
console.log("Deleting " + paths);
|
||||
|
||||
Assets.deleteMappings(path, function(err) {
|
||||
Assets.deleteMappings(paths, function(err) {
|
||||
if (err) {
|
||||
console.log("Asset browser - error deleting path: ", path, err);
|
||||
console.log("Asset browser - error deleting paths: ", paths, err);
|
||||
|
||||
box = errorMessageBox("There was an error deleting:\n" + path + "\n" + err);
|
||||
box = errorMessageBox("There was an error deleting:\n" + paths + "\n" + err);
|
||||
box.selected.connect(reload);
|
||||
} else {
|
||||
console.log("Asset browser - finished deleting path: ", path);
|
||||
console.log("Asset browser - finished deleting paths: ", paths);
|
||||
reload();
|
||||
}
|
||||
});
|
||||
|
@ -146,7 +146,7 @@ Rectangle {
|
|||
function canAddToWorld(path) {
|
||||
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i];
|
||||
|
||||
if (selectedItems > 1) {
|
||||
if (selectedItemCount > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
function canRename() {
|
||||
if (treeView.selection.hasSelection && selectedItems == 1) {
|
||||
if (treeView.selection.hasSelection && selectedItemCount == 1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -345,29 +345,28 @@ Rectangle {
|
|||
});
|
||||
}
|
||||
function deleteFile(index) {
|
||||
var path = [];
|
||||
var paths = [];
|
||||
|
||||
if (!index) {
|
||||
for (var i = 0; i < selectedItems; i++) {
|
||||
treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100);
|
||||
index = treeView.selection.currentIndex;
|
||||
path[i] = assetProxyModel.data(index, 0x100);
|
||||
for (var i = 0; i < selectedItemCount; ++i) {
|
||||
index = treeView.selection.selectedIndexes[i];
|
||||
paths[i] = assetProxyModel.data(index, 0x100);
|
||||
}
|
||||
}
|
||||
|
||||
if (!path) {
|
||||
if (!paths) {
|
||||
return;
|
||||
}
|
||||
|
||||
var modalMessage = "";
|
||||
var items = selectedItems.toString();
|
||||
var items = selectedItemCount.toString();
|
||||
var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101);
|
||||
var typeString = isFolder ? 'folder' : 'file';
|
||||
|
||||
if (selectedItems > 1) {
|
||||
if (selectedItemCount > 1) {
|
||||
modalMessage = "You are about to delete " + items + " items \nDo you want to continue?";
|
||||
} else {
|
||||
modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?";
|
||||
modalMessage = "You are about to delete the following " + typeString + ":\n" + paths + "\nDo you want to continue?";
|
||||
}
|
||||
|
||||
var object = tabletRoot.messageBox({
|
||||
|
@ -379,7 +378,7 @@ Rectangle {
|
|||
});
|
||||
object.selected.connect(function(button) {
|
||||
if (button === OriginalDialogs.StandardButton.Yes) {
|
||||
doDeleteFile(path);
|
||||
doDeleteFile(paths);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -704,7 +703,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}// End_OF( itemLoader )
|
||||
|
||||
Rectangle {
|
||||
id: treeLabelToolTip
|
||||
|
@ -741,50 +740,59 @@ Rectangle {
|
|||
showTimer.stop();
|
||||
treeLabelToolTip.visible = false;
|
||||
}
|
||||
}
|
||||
}// End_OF( treeLabelToolTip )
|
||||
|
||||
MouseArea {
|
||||
propagateComposedEvents: true
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: {
|
||||
if (!HMD.active) { // Popup only displays properly on desktop
|
||||
var index = treeView.indexAt(mouse.x, mouse.y);
|
||||
treeView.selection.setCurrentIndex(index, 0x0002);
|
||||
contextMenu.currentIndex = index;
|
||||
contextMenu.popup();
|
||||
if (treeView.selection.hasSelection && !HMD.active) { // Popup only displays properly on desktop
|
||||
// Only display the popup if the click triggered within
|
||||
// the selection.
|
||||
var clickedIndex = treeView.indexAt(mouse.x, mouse.y);
|
||||
var displayContextMenu = false;
|
||||
for ( var i = 0; i < selectedItemCount; ++i) {
|
||||
var currentSelectedIndex = treeView.selection.selectedIndexes[i];
|
||||
if (clickedIndex === currentSelectedIndex) {
|
||||
contextMenu.popup();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Menu {
|
||||
id: contextMenu
|
||||
title: "Edit"
|
||||
property var url: ""
|
||||
property var currentIndex: null
|
||||
|
||||
MenuItem {
|
||||
text: "Copy URL"
|
||||
enabled: (selectedItemCount == 1)
|
||||
onTriggered: {
|
||||
copyURLToClipboard(contextMenu.currentIndex);
|
||||
copyURLToClipboard(treeView.selection.currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Rename"
|
||||
enabled: (selectedItemCount == 1)
|
||||
onTriggered: {
|
||||
renameFile(contextMenu.currentIndex);
|
||||
renameFile(treeView.selection.currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Delete"
|
||||
enabled: (selectedItemCount > 0)
|
||||
onTriggered: {
|
||||
deleteFile(contextMenu.currentIndex);
|
||||
deleteFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}// End_OF( contextMenu )
|
||||
}// End_OF( treeView )
|
||||
|
||||
Row {
|
||||
id: infoRow
|
||||
|
@ -797,8 +805,8 @@ Rectangle {
|
|||
|
||||
function makeText() {
|
||||
var numPendingBakes = assetMappingsModel.numPendingBakes;
|
||||
if (selectedItems > 1 || numPendingBakes === 0) {
|
||||
return selectedItems + " items selected";
|
||||
if (selectedItemCount > 1 || numPendingBakes === 0) {
|
||||
return selectedItemCount + " items selected";
|
||||
} else {
|
||||
return numPendingBakes + " bakes pending"
|
||||
}
|
||||
|
@ -895,7 +903,7 @@ Rectangle {
|
|||
"Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}// End_OF( infoRow )
|
||||
|
||||
HifiControls.TabletContentSection {
|
||||
id: uploadSection
|
||||
|
@ -972,7 +980,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}// End_OF( uploadSection )
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -144,6 +144,17 @@ TabView {
|
|||
editTabView.currentIndex = 4
|
||||
}
|
||||
}
|
||||
|
||||
NewEntityButton {
|
||||
icon: "icons/create-icons/126-material-01.svg"
|
||||
text: "MATERIAL"
|
||||
onClicked: {
|
||||
editRoot.sendToScript({
|
||||
method: "newEntityButtonClicked", params: { buttonName: "newMaterialButton" }
|
||||
});
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
|
|
174
interface/resources/qml/hifi/tablet/NewMaterialDialog.qml
Normal file
174
interface/resources/qml/hifi/tablet/NewMaterialDialog.qml
Normal file
|
@ -0,0 +1,174 @@
|
|||
//
|
||||
// NewMaterialDialog.qml
|
||||
// qml/hifi
|
||||
//
|
||||
// Created by Sam Gondelman on 1/17/18
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
||||
|
||||
import "../../styles-uit"
|
||||
import "../../controls-uit"
|
||||
import "../dialogs"
|
||||
|
||||
Rectangle {
|
||||
id: newMaterialDialog
|
||||
// width: parent.width
|
||||
// height: parent.height
|
||||
HifiConstants { id: hifi }
|
||||
color: hifi.colors.baseGray;
|
||||
signal sendToScript(var message);
|
||||
property bool keyboardEnabled: false
|
||||
property bool punctuationMode: false
|
||||
property bool keyboardRasied: false
|
||||
|
||||
function errorMessageBox(message) {
|
||||
return desktop.messageBox({
|
||||
icon: hifi.icons.warning,
|
||||
defaultButton: OriginalDialogs.StandardButton.Ok,
|
||||
title: "Error",
|
||||
text: message
|
||||
});
|
||||
}
|
||||
|
||||
Item {
|
||||
id: column1
|
||||
anchors.rightMargin: 10
|
||||
anchors.leftMargin: 10
|
||||
anchors.bottomMargin: 10
|
||||
anchors.topMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: keyboard.top
|
||||
|
||||
Text {
|
||||
id: text1
|
||||
text: qsTr("Material URL")
|
||||
color: "#ffffff"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
|
||||
TextInput {
|
||||
id: materialURL
|
||||
height: 20
|
||||
text: qsTr("")
|
||||
color: "white"
|
||||
anchors.top: text1.bottom
|
||||
anchors.topMargin: 5
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
font.pixelSize: 12
|
||||
|
||||
onAccepted: {
|
||||
newMaterialDialog.keyboardEnabled = false;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
newMaterialDialog.keyboardEnabled = HMD.active
|
||||
parent.focus = true;
|
||||
parent.forceActiveFocus();
|
||||
materialURL.cursorPosition = materialURL.positionAt(mouseX, mouseY, TextInput.CursorBetweenCharaters);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: textInputBox
|
||||
color: "white"
|
||||
anchors.fill: materialURL
|
||||
opacity: 0.1
|
||||
}
|
||||
|
||||
Row {
|
||||
id: row1
|
||||
height: 400
|
||||
spacing: 30
|
||||
anchors.top: materialURL.bottom
|
||||
anchors.topMargin: 5
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
|
||||
Column {
|
||||
id: column3
|
||||
height: 400
|
||||
spacing: 10
|
||||
|
||||
/*Text {
|
||||
id: text3
|
||||
text: qsTr("Material Mode")
|
||||
color: "#ffffff"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: materialMappingMode
|
||||
property var materialArray: ["UV space material",
|
||||
"3D projected material"]
|
||||
|
||||
width: 200
|
||||
z: 100
|
||||
transformOrigin: Item.Center
|
||||
model: materialArray
|
||||
}*/
|
||||
|
||||
Row {
|
||||
id: row3
|
||||
width: 200
|
||||
height: 400
|
||||
spacing: 5
|
||||
|
||||
anchors.horizontalCenter: column3.horizontalCenter
|
||||
anchors.horizontalCenterOffset: -20
|
||||
|
||||
Button {
|
||||
id: button1
|
||||
text: qsTr("Add")
|
||||
z: -1
|
||||
onClicked: {
|
||||
newMaterialDialog.sendToScript({
|
||||
method: "newMaterialDialogAdd",
|
||||
params: {
|
||||
textInput: materialURL.text,
|
||||
//comboBox: materialMappingMode.currentIndex
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: button2
|
||||
z: -1
|
||||
text: qsTr("Cancel")
|
||||
onClicked: {
|
||||
newMaterialDialog.sendToScript({method: "newMaterialDialogCancel"})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Keyboard {
|
||||
id: keyboard
|
||||
raised: parent.keyboardEnabled
|
||||
numeric: parent.punctuationMode
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ Item {
|
|||
signal showDesktop();
|
||||
property bool shown: true
|
||||
property int currentApp: -1;
|
||||
property alias tabletApps: tabletApps
|
||||
property alias tabletApps: tabletApps
|
||||
|
||||
function setOption(value) {
|
||||
option = value;
|
||||
|
@ -44,7 +44,7 @@ Item {
|
|||
Component { id: fileDialogBuilder; TabletFileDialog { } }
|
||||
function fileDialog(properties) {
|
||||
openModal = fileDialogBuilder.createObject(tabletRoot, properties);
|
||||
return openModal;
|
||||
return openModal;
|
||||
}
|
||||
|
||||
Component { id: assetDialogBuilder; TabletAssetDialog { } }
|
||||
|
@ -66,6 +66,16 @@ Item {
|
|||
return false;
|
||||
}
|
||||
|
||||
function isUrlLoaded(url) {
|
||||
if (currentApp >= 0) {
|
||||
var currentAppUrl = tabletApps.get(currentApp).appUrl;
|
||||
if (currentAppUrl === url) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function loadSource(url) {
|
||||
tabletApps.clear();
|
||||
tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
|
||||
|
@ -73,23 +83,27 @@ Item {
|
|||
}
|
||||
|
||||
function loadQMLOnTop(url) {
|
||||
tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
|
||||
loader.load(tabletApps.get(currentApp).appUrl, function(){
|
||||
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
|
||||
loader.item.gotoPreviousApp = true;
|
||||
}
|
||||
})
|
||||
if (!isUrlLoaded(url)) {
|
||||
tabletApps.append({"appUrl": url, "isWebUrl": false, "scriptUrl": "", "appWebUrl": ""});
|
||||
loader.load(tabletApps.get(currentApp).appUrl, function(){
|
||||
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
|
||||
loader.item.gotoPreviousApp = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function loadWebContent(source, url, injectJavaScriptUrl) {
|
||||
tabletApps.append({"appUrl": source, "isWebUrl": true, "scriptUrl": injectJavaScriptUrl, "appWebUrl": url});
|
||||
loader.load(source, function() {
|
||||
loader.item.scriptURL = injectJavaScriptUrl;
|
||||
loader.item.url = url;
|
||||
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
|
||||
loader.item.gotoPreviousApp = true;
|
||||
}
|
||||
});
|
||||
if (!isUrlLoaded(url)) {
|
||||
tabletApps.append({"appUrl": source, "isWebUrl": true, "scriptUrl": injectJavaScriptUrl, "appWebUrl": url});
|
||||
loader.load(source, function() {
|
||||
loader.item.scriptURL = injectJavaScriptUrl;
|
||||
loader.item.url = url;
|
||||
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
|
||||
loader.item.gotoPreviousApp = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function loadWebBase(url, injectJavaScriptUrl) {
|
||||
|
@ -99,7 +113,7 @@ Item {
|
|||
function loadTabletWebBase(url, injectJavaScriptUrl) {
|
||||
loadWebContent("hifi/tablet/BlocksWebView.qml", url, injectJavaScriptUrl);
|
||||
}
|
||||
|
||||
|
||||
function returnToPreviousApp() {
|
||||
tabletApps.remove(currentApp);
|
||||
var isWebPage = tabletApps.get(currentApp).isWebUrl;
|
||||
|
@ -190,19 +204,19 @@ Item {
|
|||
property string source: "";
|
||||
property var item: null;
|
||||
signal loaded;
|
||||
|
||||
|
||||
onWidthChanged: {
|
||||
if (loader.item) {
|
||||
loader.item.width = loader.width;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onHeightChanged: {
|
||||
if (loader.item) {
|
||||
loader.item.height = loader.height;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function load(newSource, callback) {
|
||||
if (loader.source == newSource) {
|
||||
loader.loaded();
|
||||
|
@ -226,18 +240,18 @@ Item {
|
|||
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
|
||||
}
|
||||
loader.item.forceActiveFocus();
|
||||
|
||||
|
||||
if (openModal) {
|
||||
openModal.canceled();
|
||||
openModal.destroy();
|
||||
openModal = null;
|
||||
}
|
||||
|
||||
|
||||
if (openBrowser) {
|
||||
openBrowser.destroy();
|
||||
openBrowser = null;
|
||||
}
|
||||
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -1593,6 +1593,73 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
}
|
||||
});
|
||||
|
||||
EntityTree::setAddMaterialToEntityOperator([&](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
// try to find the renderable
|
||||
auto renderable = getEntities()->renderableForEntityId(entityID);
|
||||
if (renderable) {
|
||||
renderable->addMaterial(material, parentMaterialName);
|
||||
}
|
||||
|
||||
// even if we don't find it, try to find the entity
|
||||
auto entity = getEntities()->getEntity(entityID);
|
||||
if (entity) {
|
||||
entity->addMaterial(material, parentMaterialName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
EntityTree::setRemoveMaterialFromEntityOperator([&](const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
// try to find the renderable
|
||||
auto renderable = getEntities()->renderableForEntityId(entityID);
|
||||
if (renderable) {
|
||||
renderable->removeMaterial(material, parentMaterialName);
|
||||
}
|
||||
|
||||
// even if we don't find it, try to find the entity
|
||||
auto entity = getEntities()->getEntity(entityID);
|
||||
if (entity) {
|
||||
entity->removeMaterial(material, parentMaterialName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
EntityTree::setAddMaterialToAvatarOperator([](const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||
auto avatar = avatarManager->getAvatarBySessionID(avatarID);
|
||||
if (avatar) {
|
||||
avatar->addMaterial(material, parentMaterialName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
EntityTree::setRemoveMaterialFromAvatarOperator([](const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||
auto avatar = avatarManager->getAvatarBySessionID(avatarID);
|
||||
if (avatar) {
|
||||
avatar->removeMaterial(material, parentMaterialName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
EntityTree::setAddMaterialToOverlayOperator([&](const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
auto overlay = _overlays.getOverlay(overlayID);
|
||||
if (overlay) {
|
||||
overlay->addMaterial(material, parentMaterialName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
EntityTree::setRemoveMaterialFromOverlayOperator([&](const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
auto overlay = _overlays.getOverlay(overlayID);
|
||||
if (overlay) {
|
||||
overlay->removeMaterial(material, parentMaterialName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Keyboard focus handling for Web overlays.
|
||||
auto overlays = &(qApp->getOverlays());
|
||||
connect(overlays, &Overlays::overlayDeleted, [=](const OverlayID& overlayID) {
|
||||
|
@ -2338,6 +2405,7 @@ void Application::initializeGL() {
|
|||
qFatal("Unable to make offscreen context current");
|
||||
}
|
||||
_offscreenContext->doneCurrent();
|
||||
_offscreenContext->setThreadContext();
|
||||
_renderEventHandler = new RenderEventHandler(_glWidget->qglContext());
|
||||
|
||||
// The UI can't be created until the primary OpenGL
|
||||
|
|
|
@ -1111,7 +1111,7 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) {
|
|||
}
|
||||
|
||||
void MyAvatar::setEnableMeshVisible(bool isEnabled) {
|
||||
_skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE);
|
||||
_skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
|
||||
|
@ -1463,7 +1463,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
_skeletonModelChangeCount++;
|
||||
int skeletonModelChangeCount = _skeletonModelChangeCount;
|
||||
Avatar::setSkeletonModelURL(skeletonModelURL);
|
||||
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE);
|
||||
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true);
|
||||
_headBoneSet.clear();
|
||||
_cauterizationNeedsUpdate = true;
|
||||
|
||||
|
@ -1821,7 +1821,7 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName,
|
|||
|
||||
void MyAvatar::setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visible) {
|
||||
if (model->isActive() && model->isRenderable()) {
|
||||
model->setVisibleInScene(visible, scene, render::ItemKey::TAG_BITS_NONE);
|
||||
model->setVisibleInScene(visible, scene, render::ItemKey::TAG_BITS_NONE, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2015,7 +2015,7 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) {
|
|||
_attachmentData[i].jointName.compare("HeadTop_End", Qt::CaseInsensitive) == 0 ||
|
||||
_attachmentData[i].jointName.compare("Face", Qt::CaseInsensitive) == 0) {
|
||||
_attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene(),
|
||||
render::ItemKey::TAG_BITS_NONE);
|
||||
render::ItemKey::TAG_BITS_NONE, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,16 +120,19 @@ QScriptValue WindowScriptingInterface::confirm(const QString& message) {
|
|||
/// \param const QString& defaultText default text in the text box
|
||||
/// \return QScriptValue string text value in text box if the dialog was accepted, `null` otherwise.
|
||||
QScriptValue WindowScriptingInterface::prompt(const QString& message, const QString& defaultText) {
|
||||
bool ok = false;
|
||||
QString result = OffscreenUi::getText(nullptr, "", message, QLineEdit::Normal, defaultText, &ok);
|
||||
return ok ? QScriptValue(result) : QScriptValue::NullValue;
|
||||
QString result = OffscreenUi::getText(nullptr, "", message, QLineEdit::Normal, defaultText);
|
||||
if (QScriptValue(result).equals("")) {
|
||||
return QScriptValue::NullValue;
|
||||
}
|
||||
return QScriptValue(result);
|
||||
}
|
||||
|
||||
/// Display a prompt with a text box
|
||||
/// \param const QString& message message to display
|
||||
/// \param const QString& defaultText default text in the text box
|
||||
void WindowScriptingInterface::promptAsync(const QString& message, const QString& defaultText) {
|
||||
ModalDialogListener* dlg = OffscreenUi::getTextAsync(nullptr, "", message, QLineEdit::Normal, defaultText);
|
||||
bool ok = false;
|
||||
ModalDialogListener* dlg = OffscreenUi::getTextAsync(nullptr, "", message, QLineEdit::Normal, defaultText, &ok);
|
||||
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant result) {
|
||||
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
|
||||
emit promptTextChanged(result.toString());
|
||||
|
|
|
@ -425,10 +425,10 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* <em>Write-only.</em>
|
||||
* @property {Color} outerColor - Sets the values of <code>outerStartColor</code> and <code>outerEndColor</code>.
|
||||
* <em>Write-only.</em>
|
||||
* @property {Color} innerStartcolor - The color at the inner start point of the overlay. <em>Write-only.</em>
|
||||
* @property {Color} innerEndColor - The color at the inner end point of the overlay. <em>Write-only.</em>
|
||||
* @property {Color} outerStartColor - The color at the outer start point of the overlay. <em>Write-only.</em>
|
||||
* @property {Color} outerEndColor - The color at the outer end point of the overlay. <em>Write-only.</em>
|
||||
* @property {Color} innerStartcolor - The color at the inner start point of the overlay.
|
||||
* @property {Color} innerEndColor - The color at the inner end point of the overlay.
|
||||
* @property {Color} outerStartColor - The color at the outer start point of the overlay.
|
||||
* @property {Color} outerEndColor - The color at the outer end point of the overlay.
|
||||
* @property {number} alpha=0.5 - The opacity of the overlay, <code>0.0</code> - <code>1.0</code>. Setting this value also sets
|
||||
* the values of <code>innerStartAlpha</code>, <code>innerEndAlpha</code>, <code>outerStartAlpha</code>, and
|
||||
* <code>outerEndAlpha</code>. Synonym: <code>Alpha</code>; <em>write-only</em>.
|
||||
|
@ -440,10 +440,10 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* <em>Write-only.</em>
|
||||
* @property {number} outerAlpha - Sets the values of <code>outerStartAlpha</code> and <code>outerEndAlpha</code>.
|
||||
* <em>Write-only.</em>
|
||||
* @property {number} innerStartAlpha=0 - The alpha at the inner start point of the overlay. <em>Write-only.</em>
|
||||
* @property {number} innerEndAlpha=0 - The alpha at the inner end point of the overlay. <em>Write-only.</em>
|
||||
* @property {number} outerStartAlpha=0 - The alpha at the outer start point of the overlay. <em>Write-only.</em>
|
||||
* @property {number} outerEndAlpha=0 - The alpha at the outer end point of the overlay. <em>Write-only.</em>
|
||||
* @property {number} innerStartAlpha=0 - The alpha at the inner start point of the overlay.
|
||||
* @property {number} innerEndAlpha=0 - The alpha at the inner end point of the overlay.
|
||||
* @property {number} outerStartAlpha=0 - The alpha at the outer start point of the overlay.
|
||||
* @property {number} outerEndAlpha=0 - The alpha at the outer end point of the overlay.
|
||||
|
||||
* @property {boolean} hasTickMarks=false - If <code>true</code>, tick marks are drawn.
|
||||
* @property {number} majorTickMarksAngle=0 - The angle between major tick marks, in degrees.
|
||||
|
|
|
@ -83,11 +83,12 @@ void ModelOverlay::update(float deltatime) {
|
|||
auto modelOverlay = static_cast<ModelOverlay*>(&data);
|
||||
modelOverlay->setSubRenderItemIDs(newRenderItemIDs);
|
||||
});
|
||||
processMaterials();
|
||||
}
|
||||
if (_visibleDirty) {
|
||||
_visibleDirty = false;
|
||||
// don't show overlays in mirrors
|
||||
_model->setVisibleInScene(getVisible(), scene, render::ItemKey::TAG_BITS_0);
|
||||
_model->setVisibleInScene(getVisible(), scene, render::ItemKey::TAG_BITS_0, false);
|
||||
}
|
||||
if (_drawInFrontDirty) {
|
||||
_drawInFrontDirty = false;
|
||||
|
@ -108,6 +109,7 @@ void ModelOverlay::update(float deltatime) {
|
|||
bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) {
|
||||
Volume3DOverlay::addToScene(overlay, scene, transaction);
|
||||
_model->addToScene(scene, transaction);
|
||||
processMaterials();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -632,3 +634,29 @@ uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const {
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ModelOverlay::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
Overlay::addMaterial(material, parentMaterialName);
|
||||
if (_model && _model->fetchRenderItemIDs().size() > 0) {
|
||||
_model->addMaterial(material, parentMaterialName);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelOverlay::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
Overlay::removeMaterial(material, parentMaterialName);
|
||||
if (_model && _model->fetchRenderItemIDs().size() > 0) {
|
||||
_model->removeMaterial(material, parentMaterialName);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelOverlay::processMaterials() {
|
||||
assert(_model);
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
for (auto& shapeMaterialPair : _materials) {
|
||||
auto material = shapeMaterialPair.second;
|
||||
while (!material.empty()) {
|
||||
_model->addMaterial(material.top(), shapeMaterialPair.first);
|
||||
material.pop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -59,6 +59,9 @@ public:
|
|||
void setDrawInFront(bool drawInFront) override;
|
||||
void setDrawHUDLayer(bool drawHUDLayer) override;
|
||||
|
||||
void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override;
|
||||
void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override;
|
||||
|
||||
protected:
|
||||
Transform evalRenderTransform() override;
|
||||
|
||||
|
@ -110,6 +113,8 @@ private:
|
|||
bool _drawInFrontDirty { false };
|
||||
bool _drawInHUDDirty { false };
|
||||
|
||||
void processMaterials();
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_ModelOverlay_h
|
||||
|
|
|
@ -235,3 +235,13 @@ QVector<OverlayID> qVectorOverlayIDFromScriptValue(const QScriptValue& array) {
|
|||
}
|
||||
return newVector;
|
||||
}
|
||||
|
||||
void Overlay::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
_materials[parentMaterialName].push(material);
|
||||
}
|
||||
|
||||
void Overlay::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
_materials[parentMaterialName].remove(material);
|
||||
}
|
|
@ -91,6 +91,9 @@ public:
|
|||
unsigned int getStackOrder() const { return _stackOrder; }
|
||||
void setStackOrder(unsigned int stackOrder) { _stackOrder = stackOrder; }
|
||||
|
||||
virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName);
|
||||
virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName);
|
||||
|
||||
protected:
|
||||
float updatePulse();
|
||||
|
||||
|
@ -117,6 +120,9 @@ protected:
|
|||
static const xColor DEFAULT_OVERLAY_COLOR;
|
||||
static const float DEFAULT_ALPHA;
|
||||
|
||||
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
|
||||
std::mutex _materialsLock;
|
||||
|
||||
private:
|
||||
OverlayID _overlayID; // only used for non-3d overlays
|
||||
};
|
||||
|
|
|
@ -50,7 +50,7 @@ const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f);
|
|||
|
||||
namespace render {
|
||||
template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) {
|
||||
return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1);
|
||||
return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1).withMetaCullGroup();
|
||||
}
|
||||
template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar) {
|
||||
return static_pointer_cast<Avatar>(avatar)->getBounds();
|
||||
|
@ -219,7 +219,7 @@ void Avatar::updateAvatarEntities() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (getID() == QUuid()) {
|
||||
if (getID() == QUuid() || getID() == AVATAR_SELF_ID) {
|
||||
return; // wait until MyAvatar gets an ID before doing this.
|
||||
}
|
||||
|
||||
|
@ -570,6 +570,7 @@ void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& sc
|
|||
}
|
||||
transaction.resetItem(_renderItemID, avatarPayloadPointer);
|
||||
_skeletonModel->addToScene(scene, transaction);
|
||||
processMaterials();
|
||||
for (auto& attachmentModel : _attachmentModels) {
|
||||
attachmentModel->addToScene(scene, transaction);
|
||||
}
|
||||
|
@ -761,6 +762,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) {
|
|||
if (_skeletonModel->isRenderable() && _skeletonModel->needsFixupInScene()) {
|
||||
_skeletonModel->removeFromScene(scene, transaction);
|
||||
_skeletonModel->addToScene(scene, transaction);
|
||||
processMaterials();
|
||||
canTryFade = true;
|
||||
_isAnimatingScale = true;
|
||||
}
|
||||
|
@ -1760,3 +1762,31 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const {
|
|||
return DEFAULT_AVATAR_EYE_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
void Avatar::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
_materials[parentMaterialName].push(material);
|
||||
if (_skeletonModel && _skeletonModel->fetchRenderItemIDs().size() > 0) {
|
||||
_skeletonModel->addMaterial(material, parentMaterialName);
|
||||
}
|
||||
}
|
||||
|
||||
void Avatar::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
_materials[parentMaterialName].remove(material);
|
||||
if (_skeletonModel && _skeletonModel->fetchRenderItemIDs().size() > 0) {
|
||||
_skeletonModel->removeMaterial(material, parentMaterialName);
|
||||
}
|
||||
}
|
||||
|
||||
void Avatar::processMaterials() {
|
||||
assert(_skeletonModel);
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
for (auto& shapeMaterialPair : _materials) {
|
||||
auto material = shapeMaterialPair.second;
|
||||
while (!material.empty()) {
|
||||
_skeletonModel->addMaterial(material.top(), shapeMaterialPair.first);
|
||||
material.pop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -272,6 +272,9 @@ public:
|
|||
|
||||
virtual void setAvatarEntityDataChanged(bool value) override;
|
||||
|
||||
void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override;
|
||||
void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override;
|
||||
|
||||
public slots:
|
||||
|
||||
// FIXME - these should be migrated to use Pose data instead
|
||||
|
@ -397,6 +400,11 @@ protected:
|
|||
float _displayNameAlpha { 1.0f };
|
||||
|
||||
ThreadSafeValueCache<float> _unscaledEyeHeightCache { DEFAULT_AVATAR_EYE_HEIGHT };
|
||||
|
||||
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
|
||||
std::mutex _materialsLock;
|
||||
|
||||
void processMaterials();
|
||||
};
|
||||
|
||||
#endif // hifi_Avatar_h
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
set(TARGET_NAME avatars)
|
||||
setup_hifi_library(Network Script)
|
||||
link_hifi_libraries(shared networking)
|
||||
include_hifi_library_headers(gpu)
|
||||
link_hifi_libraries(shared networking graphics)
|
||||
|
|
|
@ -2558,4 +2558,4 @@ void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap&
|
|||
|
||||
value[EntityID] = binaryEntityProperties;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,6 +54,8 @@
|
|||
#include "HeadData.h"
|
||||
#include "PathUtils.h"
|
||||
|
||||
#include <graphics/Material.h>
|
||||
|
||||
using AvatarSharedPointer = std::shared_ptr<AvatarData>;
|
||||
using AvatarWeakPointer = std::weak_ptr<AvatarData>;
|
||||
using AvatarHash = QHash<QUuid, AvatarSharedPointer>;
|
||||
|
@ -694,6 +696,9 @@ public:
|
|||
|
||||
bool getIsReplicated() const { return _isReplicated; }
|
||||
|
||||
virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {}
|
||||
virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {}
|
||||
|
||||
signals:
|
||||
void displayNameChanged();
|
||||
void sessionDisplayNameChanged();
|
||||
|
|
|
@ -30,6 +30,20 @@ QVector<QUuid> AvatarHashMap::getAvatarIdentifiers() {
|
|||
return _avatarHash.keys().toVector();
|
||||
}
|
||||
|
||||
QVector<QUuid> AvatarHashMap::getAvatarsInRange(const glm::vec3& position, float rangeMeters) const {
|
||||
auto hashCopy = getHashCopy();
|
||||
QVector<QUuid> avatarsInRange;
|
||||
auto rangeMetersSquared = rangeMeters * rangeMeters;
|
||||
for (const AvatarSharedPointer& sharedAvatar : hashCopy) {
|
||||
glm::vec3 avatarPosition = sharedAvatar->getWorldPosition();
|
||||
auto distanceSquared = glm::distance2(avatarPosition, position);
|
||||
if (distanceSquared < rangeMetersSquared) {
|
||||
avatarsInRange.push_back(sharedAvatar->getSessionUUID());
|
||||
}
|
||||
}
|
||||
return avatarsInRange;
|
||||
}
|
||||
|
||||
bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) {
|
||||
auto hashCopy = getHashCopy();
|
||||
foreach(const AvatarSharedPointer& sharedAvatar, hashCopy) {
|
||||
|
|
|
@ -35,10 +35,12 @@ class AvatarHashMap : public QObject, public Dependency {
|
|||
|
||||
public:
|
||||
AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; }
|
||||
const AvatarHash getHashCopy() const { QReadLocker lock(&_hashLock); return _avatarHash; }
|
||||
int size() { return _avatarHash.size(); }
|
||||
|
||||
// Currently, your own avatar will be included as the null avatar id.
|
||||
Q_INVOKABLE QVector<QUuid> getAvatarIdentifiers();
|
||||
Q_INVOKABLE QVector<QUuid> getAvatarsInRange(const glm::vec3& position, float rangeMeters) const;
|
||||
|
||||
// Null/Default-constructed QUuids will return MyAvatar
|
||||
Q_INVOKABLE virtual ScriptAvatarData* getAvatar(QUuid avatarID) { return new ScriptAvatarData(getAvatarBySessionID(avatarID)); }
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <gl/GLWidget.h>
|
||||
#include <gl/GLEscrow.h>
|
||||
#include <gl/Context.h>
|
||||
#include <gl/OffscreenGLCanvas.h>
|
||||
|
||||
#include <gpu/Texture.h>
|
||||
#include <gpu/StandardShaderLib.h>
|
||||
|
@ -130,14 +131,14 @@ public:
|
|||
CHECK_GL_ERROR();
|
||||
_context->doneCurrent();
|
||||
while (!_shutdown) {
|
||||
if (_pendingMainThreadOperation) {
|
||||
if (_pendingOtherThreadOperation) {
|
||||
PROFILE_RANGE(render, "MainThreadOp")
|
||||
{
|
||||
Lock lock(_mutex);
|
||||
_context->doneCurrent();
|
||||
// Move the context to the main thread
|
||||
_context->moveToThread(qApp->thread());
|
||||
_pendingMainThreadOperation = false;
|
||||
_context->moveToThread(_targetOperationThread);
|
||||
_pendingOtherThreadOperation = false;
|
||||
// Release the main thread to do it's action
|
||||
_condition.notify_one();
|
||||
}
|
||||
|
@ -146,7 +147,7 @@ public:
|
|||
{
|
||||
// Main thread does it's thing while we wait on the lock to release
|
||||
Lock lock(_mutex);
|
||||
_condition.wait(lock, [&] { return _finishedMainThreadOperation; });
|
||||
_condition.wait(lock, [&] { return _finishedOtherThreadOperation; });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,23 +215,25 @@ public:
|
|||
_condition.notify_one();
|
||||
}
|
||||
|
||||
void withMainThreadContext(std::function<void()> f) {
|
||||
void withOtherThreadContext(std::function<void()> f) {
|
||||
// Signal to the thread that there is work to be done on the main thread
|
||||
Lock lock(_mutex);
|
||||
_pendingMainThreadOperation = true;
|
||||
_finishedMainThreadOperation = false;
|
||||
_condition.wait(lock, [&] { return !_pendingMainThreadOperation; });
|
||||
_targetOperationThread = QThread::currentThread();
|
||||
_pendingOtherThreadOperation = true;
|
||||
_finishedOtherThreadOperation = false;
|
||||
_condition.wait(lock, [&] { return !_pendingOtherThreadOperation; });
|
||||
|
||||
_context->makeCurrent();
|
||||
f();
|
||||
_context->doneCurrent();
|
||||
|
||||
_targetOperationThread = nullptr;
|
||||
// Move the context back to the presentation thread
|
||||
_context->moveToThread(this);
|
||||
|
||||
// restore control of the context to the presentation thread and signal
|
||||
// the end of the operation
|
||||
_finishedMainThreadOperation = true;
|
||||
_finishedOtherThreadOperation = true;
|
||||
lock.unlock();
|
||||
_condition.notify_one();
|
||||
}
|
||||
|
@ -244,9 +247,11 @@ private:
|
|||
Mutex _mutex;
|
||||
// Used to allow the main thread to perform context operations
|
||||
Condition _condition;
|
||||
bool _pendingMainThreadOperation { false };
|
||||
bool _finishedMainThreadOperation { false };
|
||||
QThread* _mainThread { nullptr };
|
||||
|
||||
|
||||
QThread* _targetOperationThread { nullptr };
|
||||
bool _pendingOtherThreadOperation { false };
|
||||
bool _finishedOtherThreadOperation { false };
|
||||
std::queue<OpenGLDisplayPlugin*> _newPluginQueue;
|
||||
gl::Context* _context { nullptr };
|
||||
};
|
||||
|
@ -744,10 +749,12 @@ void OpenGLDisplayPlugin::swapBuffers() {
|
|||
context->swapBuffers();
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::withMainThreadContext(std::function<void()> f) const {
|
||||
void OpenGLDisplayPlugin::withOtherThreadContext(std::function<void()> f) const {
|
||||
static auto presentThread = DependencyManager::get<PresentThread>();
|
||||
presentThread->withMainThreadContext(f);
|
||||
_container->makeRenderingContextCurrent();
|
||||
presentThread->withOtherThreadContext(f);
|
||||
if (!OffscreenGLCanvas::restoreThreadContext()) {
|
||||
qWarning("Unable to restore original OpenGL context");
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenGLDisplayPlugin::setDisplayTexture(const QString& name) {
|
||||
|
@ -784,7 +791,7 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
|
|||
}
|
||||
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
|
||||
QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32);
|
||||
withMainThreadContext([&] {
|
||||
withOtherThreadContext([&] {
|
||||
glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot);
|
||||
});
|
||||
return screenshot.mirrored(false, true);
|
||||
|
@ -797,7 +804,7 @@ QImage OpenGLDisplayPlugin::getSecondaryCameraScreenshot() const {
|
|||
|
||||
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
|
||||
QImage screenshot(region.z, region.w, QImage::Format_ARGB32);
|
||||
withMainThreadContext([&] {
|
||||
withOtherThreadContext([&] {
|
||||
glBackend->downloadFramebuffer(secondaryCameraFramebuffer, region, screenshot);
|
||||
});
|
||||
return screenshot.mirrored(false, true);
|
||||
|
@ -886,7 +893,7 @@ void OpenGLDisplayPlugin::updateCompositeFramebuffer() {
|
|||
void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer networkTexture, QOpenGLFramebufferObject* target, GLsync* fenceSync) {
|
||||
#if !defined(USE_GLES)
|
||||
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
|
||||
withMainThreadContext([&] {
|
||||
withOtherThreadContext([&] {
|
||||
GLuint sourceTexture = glBackend->getTextureID(networkTexture->getGPUTexture());
|
||||
GLuint targetTexture = target->texture();
|
||||
GLuint fbo[2] {0, 0};
|
||||
|
|
|
@ -119,7 +119,7 @@ protected:
|
|||
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor);
|
||||
virtual void updateFrameData();
|
||||
|
||||
void withMainThreadContext(std::function<void()> f) const;
|
||||
void withOtherThreadContext(std::function<void()> f) const;
|
||||
|
||||
void present();
|
||||
virtual void swapBuffers();
|
||||
|
|
|
@ -79,7 +79,7 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
|
|||
QHash<QByteArray, QByteArray> redirectHeader;
|
||||
redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8());
|
||||
|
||||
connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader);
|
||||
connection->respond(HTTPConnection::StatusCode302, "", HTTPConnection::DefaultContentType, redirectHeader);
|
||||
}
|
||||
|
||||
// if the last thing is a trailing slash then we want to look for index file
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "RenderableTextEntityItem.h"
|
||||
#include "RenderableWebEntityItem.h"
|
||||
#include "RenderableZoneEntityItem.h"
|
||||
#include "RenderableMaterialEntityItem.h"
|
||||
|
||||
|
||||
using namespace render;
|
||||
|
@ -145,6 +146,7 @@ EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity
|
|||
_needsRenderUpdate = true;
|
||||
emit requestRenderUpdate();
|
||||
});
|
||||
_materials = entity->getMaterials();
|
||||
}
|
||||
|
||||
EntityRenderer::~EntityRenderer() { }
|
||||
|
@ -252,6 +254,10 @@ EntityRenderer::Pointer EntityRenderer::addToScene(EntityTreeRenderer& renderer,
|
|||
result = make_renderer<ZoneEntityRenderer>(entity);
|
||||
break;
|
||||
|
||||
case Type::Material:
|
||||
result = make_renderer<MaterialEntityRenderer>(entity);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -395,3 +401,13 @@ void EntityRenderer::onRemoveFromScene(const EntityItemPointer& entity) {
|
|||
entity->deregisterChangeHandler(_changeHandlerId);
|
||||
QObject::disconnect(this, &EntityRenderer::requestRenderUpdate, this, nullptr);
|
||||
}
|
||||
|
||||
void EntityRenderer::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
_materials[parentMaterialName].push(material);
|
||||
}
|
||||
|
||||
void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
_materials[parentMaterialName].remove(material);
|
||||
}
|
|
@ -54,12 +54,14 @@ public:
|
|||
|
||||
const uint64_t& getUpdateTime() const { return _updateTime; }
|
||||
|
||||
virtual void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName);
|
||||
virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName);
|
||||
|
||||
protected:
|
||||
virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); }
|
||||
virtual void onAddToScene(const EntityItemPointer& entity);
|
||||
virtual void onRemoveFromScene(const EntityItemPointer& entity);
|
||||
|
||||
protected:
|
||||
EntityRenderer(const EntityItemPointer& entity);
|
||||
~EntityRenderer();
|
||||
|
||||
|
@ -130,6 +132,8 @@ protected:
|
|||
// Only touched on the rendering thread
|
||||
bool _renderUpdateQueued{ false };
|
||||
|
||||
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
|
||||
std::mutex _materialsLock;
|
||||
|
||||
private:
|
||||
// The base class relies on comparing the model transform to the entity transform in order
|
||||
|
|
269
libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp
Normal file
269
libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp
Normal file
|
@ -0,0 +1,269 @@
|
|||
//
|
||||
// Created by Sam Gondelman on 1/18/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "RenderableMaterialEntityItem.h"
|
||||
|
||||
#include "RenderPipelines.h"
|
||||
|
||||
using namespace render;
|
||||
using namespace render::entities;
|
||||
|
||||
bool MaterialEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
|
||||
if (entity->getMaterial() != _drawMaterial) {
|
||||
return true;
|
||||
}
|
||||
if (entity->getParentID() != _parentID || entity->getClientOnly() != _clientOnly || entity->getOwningAvatarID() != _owningAvatarID) {
|
||||
return true;
|
||||
}
|
||||
if (entity->getMaterialMappingPos() != _materialMappingPos || entity->getMaterialMappingScale() != _materialMappingScale || entity->getMaterialMappingRot() != _materialMappingRot) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MaterialEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
|
||||
withWriteLock([&] {
|
||||
_drawMaterial = entity->getMaterial();
|
||||
_parentID = entity->getParentID();
|
||||
_clientOnly = entity->getClientOnly();
|
||||
_owningAvatarID = entity->getOwningAvatarID();
|
||||
_materialMappingPos = entity->getMaterialMappingPos();
|
||||
_materialMappingScale = entity->getMaterialMappingScale();
|
||||
_materialMappingRot = entity->getMaterialMappingRot();
|
||||
_renderTransform = getModelTransform();
|
||||
const float MATERIAL_ENTITY_SCALE = 0.5f;
|
||||
_renderTransform.postScale(MATERIAL_ENTITY_SCALE);
|
||||
_renderTransform.postScale(ENTITY_ITEM_DEFAULT_DIMENSIONS);
|
||||
});
|
||||
}
|
||||
|
||||
ItemKey MaterialEntityRenderer::getKey() {
|
||||
ItemKey::Builder builder;
|
||||
builder.withTypeShape().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1);
|
||||
|
||||
if (!_visible) {
|
||||
builder.withInvisible();
|
||||
}
|
||||
|
||||
if (_drawMaterial) {
|
||||
auto matKey = _drawMaterial->getKey();
|
||||
if (matKey.isTranslucent()) {
|
||||
builder.withTransparent();
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
ShapeKey MaterialEntityRenderer::getShapeKey() {
|
||||
graphics::MaterialKey drawMaterialKey;
|
||||
if (_drawMaterial) {
|
||||
drawMaterialKey = _drawMaterial->getKey();
|
||||
}
|
||||
|
||||
bool isTranslucent = drawMaterialKey.isTranslucent();
|
||||
bool hasTangents = drawMaterialKey.isNormalMap();
|
||||
bool hasSpecular = drawMaterialKey.isMetallicMap();
|
||||
bool hasLightmap = drawMaterialKey.isLightmapMap();
|
||||
bool isUnlit = drawMaterialKey.isUnlit();
|
||||
|
||||
ShapeKey::Builder builder;
|
||||
builder.withMaterial();
|
||||
|
||||
if (isTranslucent) {
|
||||
builder.withTranslucent();
|
||||
}
|
||||
if (hasTangents) {
|
||||
builder.withTangents();
|
||||
}
|
||||
if (hasSpecular) {
|
||||
builder.withSpecular();
|
||||
}
|
||||
if (hasLightmap) {
|
||||
builder.withLightmap();
|
||||
}
|
||||
if (isUnlit) {
|
||||
builder.withUnlit();
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
glm::vec3 MaterialEntityRenderer::getVertexPos(float phi, float theta) {
|
||||
return glm::vec3(glm::sin(theta) * glm::cos(phi), glm::cos(theta), glm::sin(theta) * glm::sin(phi));
|
||||
}
|
||||
|
||||
glm::vec3 MaterialEntityRenderer::getTangent(float phi, float theta) {
|
||||
return glm::vec3(-glm::cos(theta) * glm::cos(phi), glm::sin(theta), -glm::cos(theta) * glm::sin(phi));
|
||||
}
|
||||
|
||||
void MaterialEntityRenderer::addVertex(std::vector<float>& buffer, const glm::vec3& pos, const glm::vec3& tan, const glm::vec2 uv) {
|
||||
buffer.push_back(pos.x); buffer.push_back(pos.y); buffer.push_back(pos.z);
|
||||
buffer.push_back(tan.x); buffer.push_back(tan.y); buffer.push_back(tan.z);
|
||||
buffer.push_back(uv.x); buffer.push_back(uv.y);
|
||||
}
|
||||
|
||||
void MaterialEntityRenderer::addTriangleFan(std::vector<float>& buffer, int stack, int step) {
|
||||
float v1 = ((float)stack) / STACKS;
|
||||
float theta1 = v1 * (float)M_PI;
|
||||
glm::vec3 tip = getVertexPos(0, theta1);
|
||||
float v2 = ((float)(stack + step)) / STACKS;
|
||||
float theta2 = v2 * (float)M_PI;
|
||||
for (int i = 0; i < SLICES; i++) {
|
||||
float u1 = ((float)i) / SLICES;
|
||||
float u2 = ((float)(i + step)) / SLICES;
|
||||
float phi1 = u1 * M_PI_TIMES_2;
|
||||
float phi2 = u2 * M_PI_TIMES_2;
|
||||
/* (flipped for negative step)
|
||||
p1
|
||||
/ \
|
||||
/ \
|
||||
/ \
|
||||
p3 ------ p2
|
||||
*/
|
||||
|
||||
glm::vec3 pos2 = getVertexPos(phi2, theta2);
|
||||
glm::vec3 pos3 = getVertexPos(phi1, theta2);
|
||||
|
||||
glm::vec3 tan1 = getTangent(0, theta1);
|
||||
glm::vec3 tan2 = getTangent(phi2, theta2);
|
||||
glm::vec3 tan3 = getTangent(phi1, theta2);
|
||||
|
||||
glm::vec2 uv1 = glm::vec2((u1 + u2) / 2.0f, v1);
|
||||
glm::vec2 uv2 = glm::vec2(u2, v2);
|
||||
glm::vec2 uv3 = glm::vec2(u1, v2);
|
||||
|
||||
addVertex(buffer, tip, tan1, uv1);
|
||||
addVertex(buffer, pos2, tan2, uv2);
|
||||
addVertex(buffer, pos3, tan3, uv3);
|
||||
|
||||
_numVertices += 3;
|
||||
}
|
||||
}
|
||||
|
||||
int MaterialEntityRenderer::_numVertices = 0;
|
||||
std::shared_ptr<gpu::Stream::Format> MaterialEntityRenderer::_streamFormat = nullptr;
|
||||
std::shared_ptr<gpu::BufferStream> MaterialEntityRenderer::_stream = nullptr;
|
||||
std::shared_ptr<gpu::Buffer> MaterialEntityRenderer::_verticesBuffer = nullptr;
|
||||
|
||||
void MaterialEntityRenderer::generateMesh() {
|
||||
_streamFormat = std::make_shared<gpu::Stream::Format>();
|
||||
_stream = std::make_shared<gpu::BufferStream>();
|
||||
_verticesBuffer = std::make_shared<gpu::Buffer>();
|
||||
|
||||
const int NUM_POS_COORDS = 3;
|
||||
const int NUM_TANGENT_COORDS = 3;
|
||||
const int VERTEX_TANGENT_OFFSET = NUM_POS_COORDS * sizeof(float);
|
||||
const int VERTEX_TEXCOORD_OFFSET = VERTEX_TANGENT_OFFSET + NUM_TANGENT_COORDS * sizeof(float);
|
||||
|
||||
_streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
|
||||
_streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
|
||||
_streamFormat->setAttribute(gpu::Stream::TANGENT, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_TANGENT_OFFSET);
|
||||
_streamFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), VERTEX_TEXCOORD_OFFSET);
|
||||
|
||||
_stream->addBuffer(_verticesBuffer, 0, _streamFormat->getChannels().at(0)._stride);
|
||||
|
||||
std::vector<float> vertexBuffer;
|
||||
|
||||
// Top
|
||||
addTriangleFan(vertexBuffer, 0, 1);
|
||||
|
||||
// Middle section
|
||||
for (int j = 1; j < STACKS - 1; j++) {
|
||||
float v1 = ((float)j) / STACKS;
|
||||
float v2 = ((float)(j + 1)) / STACKS;
|
||||
float theta1 = v1 * (float)M_PI;
|
||||
float theta2 = v2 * (float)M_PI;
|
||||
for (int i = 0; i < SLICES; i++) {
|
||||
float u1 = ((float)i) / SLICES;
|
||||
float u2 = ((float)(i + 1)) / SLICES;
|
||||
float phi1 = u1 * M_PI_TIMES_2;
|
||||
float phi2 = u2 * M_PI_TIMES_2;
|
||||
|
||||
/*
|
||||
p2 ---- p3
|
||||
| / |
|
||||
| / |
|
||||
| / |
|
||||
p1 ---- p4
|
||||
*/
|
||||
|
||||
glm::vec3 pos1 = getVertexPos(phi1, theta2);
|
||||
glm::vec3 pos2 = getVertexPos(phi1, theta1);
|
||||
glm::vec3 pos3 = getVertexPos(phi2, theta1);
|
||||
glm::vec3 pos4 = getVertexPos(phi2, theta2);
|
||||
|
||||
glm::vec3 tan1 = getTangent(phi1, theta2);
|
||||
glm::vec3 tan2 = getTangent(phi1, theta1);
|
||||
glm::vec3 tan3 = getTangent(phi2, theta1);
|
||||
glm::vec3 tan4 = getTangent(phi2, theta2);
|
||||
|
||||
glm::vec2 uv1 = glm::vec2(u1, v2);
|
||||
glm::vec2 uv2 = glm::vec2(u1, v1);
|
||||
glm::vec2 uv3 = glm::vec2(u2, v1);
|
||||
glm::vec2 uv4 = glm::vec2(u2, v2);
|
||||
|
||||
addVertex(vertexBuffer, pos1, tan1, uv1);
|
||||
addVertex(vertexBuffer, pos2, tan2, uv2);
|
||||
addVertex(vertexBuffer, pos3, tan3, uv3);
|
||||
|
||||
addVertex(vertexBuffer, pos3, tan3, uv3);
|
||||
addVertex(vertexBuffer, pos4, tan4, uv4);
|
||||
addVertex(vertexBuffer, pos1, tan1, uv1);
|
||||
|
||||
_numVertices += 6;
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom
|
||||
addTriangleFan(vertexBuffer, STACKS, -1);
|
||||
|
||||
_verticesBuffer->append(vertexBuffer.size() * sizeof(float), (gpu::Byte*) vertexBuffer.data());
|
||||
}
|
||||
|
||||
void MaterialEntityRenderer::doRender(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableMaterialEntityItem::render");
|
||||
Q_ASSERT(args->_batch);
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
|
||||
// Don't render if our parent is set or our material is null
|
||||
QUuid parentID;
|
||||
Transform renderTransform;
|
||||
graphics::MaterialPointer drawMaterial;
|
||||
Transform textureTransform;
|
||||
withReadLock([&] {
|
||||
parentID = _clientOnly ? _owningAvatarID : _parentID;
|
||||
renderTransform = _renderTransform;
|
||||
drawMaterial = _drawMaterial;
|
||||
textureTransform.setTranslation(glm::vec3(_materialMappingPos, 0));
|
||||
textureTransform.setRotation(glm::vec3(0, 0, glm::radians(_materialMappingRot)));
|
||||
textureTransform.setScale(glm::vec3(_materialMappingScale, 1));
|
||||
});
|
||||
if (!parentID.isNull() || !drawMaterial) {
|
||||
return;
|
||||
}
|
||||
|
||||
batch.setModelTransform(renderTransform);
|
||||
drawMaterial->setTextureTransforms(textureTransform);
|
||||
|
||||
// bind the material
|
||||
RenderPipelines::bindMaterial(drawMaterial, batch, args->_enableTexturing);
|
||||
args->_details._materialSwitches++;
|
||||
|
||||
// Draw!
|
||||
if (_numVertices == 0) {
|
||||
generateMesh();
|
||||
}
|
||||
|
||||
batch.setInputFormat(_streamFormat);
|
||||
batch.setInputStream(0, *_stream);
|
||||
batch.draw(gpu::TRIANGLES, _numVertices, 0);
|
||||
|
||||
const int NUM_VERTICES_PER_TRIANGLE = 3;
|
||||
args->_details._trianglesRendered += _numVertices / NUM_VERTICES_PER_TRIANGLE;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// Created by Sam Gondelman on 1/18/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_RenderableMaterialEntityItem_h
|
||||
#define hifi_RenderableMaterialEntityItem_h
|
||||
|
||||
#include "RenderableEntityItem.h"
|
||||
|
||||
#include <MaterialEntityItem.h>
|
||||
|
||||
class NetworkMaterial;
|
||||
|
||||
namespace render { namespace entities {
|
||||
|
||||
class MaterialEntityRenderer : public TypedEntityRenderer<MaterialEntityItem> {
|
||||
using Parent = TypedEntityRenderer<MaterialEntityItem>;
|
||||
using Pointer = std::shared_ptr<MaterialEntityRenderer>;
|
||||
public:
|
||||
MaterialEntityRenderer(const EntityItemPointer& entity) : Parent(entity) {}
|
||||
|
||||
private:
|
||||
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
|
||||
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
|
||||
virtual void doRender(RenderArgs* args) override;
|
||||
|
||||
ItemKey getKey() override;
|
||||
ShapeKey getShapeKey() override;
|
||||
|
||||
QUuid _parentID;
|
||||
bool _clientOnly;
|
||||
QUuid _owningAvatarID;
|
||||
glm::vec2 _materialMappingPos;
|
||||
glm::vec2 _materialMappingScale;
|
||||
float _materialMappingRot;
|
||||
Transform _renderTransform;
|
||||
|
||||
std::shared_ptr<NetworkMaterial> _drawMaterial;
|
||||
|
||||
static int _numVertices;
|
||||
static std::shared_ptr<gpu::Stream::Format> _streamFormat;
|
||||
static std::shared_ptr<gpu::BufferStream> _stream;
|
||||
static std::shared_ptr<gpu::Buffer> _verticesBuffer;
|
||||
|
||||
void generateMesh();
|
||||
void addTriangleFan(std::vector<float>& buffer, int stack, int step);
|
||||
static glm::vec3 getVertexPos(float phi, float theta);
|
||||
static glm::vec3 getTangent(float phi, float theta);
|
||||
static void addVertex(std::vector<float>& buffer, const glm::vec3& pos, const glm::vec3& tan, const glm::vec2 uv);
|
||||
const int SLICES = 15;
|
||||
const int STACKS = 9;
|
||||
const float M_PI_TIMES_2 = 2.0f * (float)M_PI;
|
||||
};
|
||||
|
||||
} }
|
||||
#endif // hifi_RenderableMaterialEntityItem_h
|
|
@ -985,6 +985,7 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() {
|
|||
return;
|
||||
}
|
||||
|
||||
bool changed { false };
|
||||
// relay any inbound joint changes from scripts/animation/network to the model/rig
|
||||
_jointDataLock.withWriteLock([&] {
|
||||
for (int index = 0; index < _localJointData.size(); ++index) {
|
||||
|
@ -992,13 +993,21 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() {
|
|||
if (jointData.rotationDirty) {
|
||||
model->setJointRotation(index, true, jointData.joint.rotation, 1.0f);
|
||||
jointData.rotationDirty = false;
|
||||
changed = true;
|
||||
}
|
||||
if (jointData.translationDirty) {
|
||||
model->setJointTranslation(index, true, jointData.joint.translation, 1.0f);
|
||||
jointData.translationDirty = false;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (changed) {
|
||||
forEachChild([&](SpatiallyNestablePointer object) {
|
||||
object->locationChanged(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
using namespace render;
|
||||
|
@ -1343,7 +1352,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
|
|||
// FIXME: this seems like it could be optimized if we tracked our last known visible state in
|
||||
// the renderable item. As it stands now the model checks it's visible/invisible state
|
||||
// so most of the time we don't do anything in this function.
|
||||
model->setVisibleInScene(_visible, scene, viewTaskBits);
|
||||
model->setVisibleInScene(_visible, scene, viewTaskBits, false);
|
||||
}
|
||||
// TODO? early exit here when not visible?
|
||||
|
||||
|
@ -1380,6 +1389,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
|
|||
auto entityRenderer = static_cast<EntityRenderer*>(&data);
|
||||
entityRenderer->setSubRenderItemIDs(newRenderItemIDs);
|
||||
});
|
||||
processMaterials();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1466,3 +1476,28 @@ void ModelEntityRenderer::mapJoints(const TypedEntityPointer& entity, const QStr
|
|||
}
|
||||
}
|
||||
|
||||
void ModelEntityRenderer::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
Parent::addMaterial(material, parentMaterialName);
|
||||
if (_model && _model->fetchRenderItemIDs().size() > 0) {
|
||||
_model->addMaterial(material, parentMaterialName);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelEntityRenderer::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
Parent::removeMaterial(material, parentMaterialName);
|
||||
if (_model && _model->fetchRenderItemIDs().size() > 0) {
|
||||
_model->removeMaterial(material, parentMaterialName);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelEntityRenderer::processMaterials() {
|
||||
assert(_model);
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
for (auto& shapeMaterialPair : _materials) {
|
||||
auto material = shapeMaterialPair.second;
|
||||
while (!material.empty()) {
|
||||
_model->addMaterial(material.top(), shapeMaterialPair.first);
|
||||
material.pop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -138,10 +138,14 @@ namespace render { namespace entities {
|
|||
class ModelEntityRenderer : public TypedEntityRenderer<RenderableModelEntityItem> {
|
||||
using Parent = TypedEntityRenderer<RenderableModelEntityItem>;
|
||||
friend class EntityRenderer;
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelEntityRenderer(const EntityItemPointer& entity);
|
||||
|
||||
void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) override;
|
||||
void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) override;
|
||||
|
||||
protected:
|
||||
virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction) override;
|
||||
virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override;
|
||||
|
@ -194,6 +198,8 @@ private:
|
|||
uint64_t _lastAnimated { 0 };
|
||||
|
||||
render::ItemKey _itemKey { render::ItemKey::Builder().withTypeMeta() };
|
||||
|
||||
void processMaterials();
|
||||
};
|
||||
|
||||
} } // namespace
|
||||
|
|
|
@ -54,8 +54,7 @@ bool ShapeEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin
|
|||
if (_lastUserData != entity->getUserData()) {
|
||||
return true;
|
||||
}
|
||||
glm::vec4 newColor(toGlm(entity->getXColor()), entity->getLocalRenderAlpha());
|
||||
if (newColor != _color) {
|
||||
if (_material != entity->getMaterial()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -78,7 +77,9 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
|
|||
_procedural.setProceduralData(ProceduralData::parse(_lastUserData));
|
||||
}
|
||||
|
||||
_color = vec4(toGlm(entity->getXColor()), entity->getLocalRenderAlpha());
|
||||
removeMaterial(_material, "0");
|
||||
_material = entity->getMaterial();
|
||||
addMaterial(graphics::MaterialLayer(_material, 0), "0");
|
||||
|
||||
_shape = entity->getShape();
|
||||
_position = entity->getWorldPosition();
|
||||
|
@ -112,14 +113,13 @@ bool ShapeEntityRenderer::isTransparent() const {
|
|||
return Parent::isTransparent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ShapeEntityRenderer::doRender(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableShapeEntityItem::render");
|
||||
Q_ASSERT(args->_batch);
|
||||
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
|
||||
std::shared_ptr<graphics::Material> mat;
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
GeometryCache::Shape geometryShape;
|
||||
bool proceduralRender = false;
|
||||
|
@ -127,15 +127,22 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
|
|||
withReadLock([&] {
|
||||
geometryShape = geometryCache->getShapeForEntityShape(_shape);
|
||||
batch.setModelTransform(_renderTransform); // use a transform with scale, rotation, registration point and translation
|
||||
outColor = _color;
|
||||
if (_procedural.isReady()) {
|
||||
_procedural.prepare(batch, _position, _dimensions, _orientation);
|
||||
outColor = _procedural.getColor(_color);
|
||||
outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f;
|
||||
proceduralRender = true;
|
||||
mat = _materials["0"].top().material;
|
||||
if (mat) {
|
||||
outColor = glm::vec4(mat->getAlbedo(), mat->getOpacity());
|
||||
if (_procedural.isReady()) {
|
||||
_procedural.prepare(batch, _position, _dimensions, _orientation);
|
||||
outColor = _procedural.getColor(outColor);
|
||||
outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f;
|
||||
proceduralRender = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!mat) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (proceduralRender) {
|
||||
if (render::ShapeKey(args->_globalShapeKey).isWireframe()) {
|
||||
geometryCache->renderWireShape(batch, geometryShape, outColor);
|
||||
|
|
|
@ -34,7 +34,7 @@ private:
|
|||
QString _lastUserData;
|
||||
Transform _renderTransform;
|
||||
entity::Shape _shape { entity::Sphere };
|
||||
glm::vec4 _color;
|
||||
std::shared_ptr<graphics::Material> _material;
|
||||
glm::vec3 _position;
|
||||
glm::vec3 _dimensions;
|
||||
glm::quat _orientation;
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
set(TARGET_NAME entities)
|
||||
setup_hifi_library(Network Script)
|
||||
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
|
||||
link_hifi_libraries(shared networking octree avatars graphics)
|
||||
include_hifi_library_headers(fbx)
|
||||
include_hifi_library_headers(gpu)
|
||||
include_hifi_library_headers(image)
|
||||
include_hifi_library_headers(ktx)
|
||||
link_hifi_libraries(shared networking octree avatars graphics model-networking)
|
|
@ -93,7 +93,7 @@ bool DeleteEntityOperator::preRecursion(const OctreeElementPointer& element) {
|
|||
// and we can stop searching.
|
||||
if (entityTreeElement == details.containingElement) {
|
||||
EntityItemPointer theEntity = details.entity;
|
||||
bool entityDeleted = entityTreeElement->removeEntityItem(theEntity); // remove it from the element
|
||||
bool entityDeleted = entityTreeElement->removeEntityItem(theEntity, true); // remove it from the element
|
||||
assert(entityDeleted);
|
||||
(void)entityDeleted; // quite warning
|
||||
_tree->clearEntityMapEntry(details.entity->getEntityItemID());
|
||||
|
|
|
@ -60,14 +60,6 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) :
|
|||
}
|
||||
|
||||
EntityItem::~EntityItem() {
|
||||
// clear out any left-over actions
|
||||
EntityTreeElementPointer element = _element; // use local copy of _element for logic below
|
||||
EntityTreePointer entityTree = element ? element->getTree() : nullptr;
|
||||
EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr;
|
||||
if (simulation) {
|
||||
clearActions(simulation);
|
||||
}
|
||||
|
||||
// these pointers MUST be correct at delete, else we probably have a dangling backpointer
|
||||
// to this EntityItem in the corresponding data structure.
|
||||
assert(!_simulated);
|
||||
|
@ -2937,3 +2929,32 @@ void EntityItem::retrieveMarketplacePublicKey() {
|
|||
networkReply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void EntityItem::preDelete() {
|
||||
// clear out any left-over actions
|
||||
EntityTreeElementPointer element = _element; // use local copy of _element for logic below
|
||||
EntityTreePointer entityTree = element ? element->getTree() : nullptr;
|
||||
EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr;
|
||||
if (simulation) {
|
||||
clearActions(simulation);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityItem::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
_materials[parentMaterialName].push(material);
|
||||
}
|
||||
|
||||
void EntityItem::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
_materials[parentMaterialName].remove(material);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, graphics::MultiMaterial> EntityItem::getMaterials() {
|
||||
std::unordered_map<std::string, graphics::MultiMaterial> toReturn;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
toReturn = _materials;
|
||||
}
|
||||
return toReturn;
|
||||
}
|
|
@ -36,6 +36,8 @@
|
|||
#include "SimulationFlags.h"
|
||||
#include "EntityDynamicInterface.h"
|
||||
|
||||
#include "graphics/Material.h"
|
||||
|
||||
class EntitySimulation;
|
||||
class EntityTreeElement;
|
||||
class EntityTreeElementExtraEncodeData;
|
||||
|
@ -49,7 +51,6 @@ typedef std::shared_ptr<EntityTreeElement> EntityTreeElementPointer;
|
|||
using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr<EntityTreeElementExtraEncodeData>;
|
||||
|
||||
|
||||
|
||||
#define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0;
|
||||
#define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() override { };
|
||||
|
||||
|
@ -443,10 +444,10 @@ public:
|
|||
void scriptHasUnloaded() { _loadedScript = ""; _loadedScriptTimestamp = 0; }
|
||||
|
||||
bool getClientOnly() const { return _clientOnly; }
|
||||
void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; }
|
||||
virtual void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; }
|
||||
// if this entity is client-only, which avatar is it associated with?
|
||||
QUuid getOwningAvatarID() const { return _owningAvatarID; }
|
||||
void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; }
|
||||
virtual void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; }
|
||||
|
||||
virtual bool wantsHandControllerPointerEvents() const { return false; }
|
||||
virtual bool wantsKeyboardFocus() const { return false; }
|
||||
|
@ -477,6 +478,13 @@ public:
|
|||
void setCauterized(bool value) { _cauterized = value; }
|
||||
bool getCauterized() const { return _cauterized; }
|
||||
|
||||
virtual void preDelete();
|
||||
virtual void postParentFixup() {}
|
||||
|
||||
void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName);
|
||||
void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName);
|
||||
std::unordered_map<std::string, graphics::MultiMaterial> getMaterials();
|
||||
|
||||
signals:
|
||||
void requestRenderUpdate();
|
||||
|
||||
|
@ -631,6 +639,11 @@ protected:
|
|||
quint64 _lastUpdatedQueryAACubeTimestamp { 0 };
|
||||
|
||||
bool _cauterized { false }; // if true, don't draw because it would obscure 1st-person camera
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
|
||||
std::mutex _materialsLock;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_EntityItem_h
|
||||
|
|
|
@ -115,6 +115,17 @@ void buildStringToShapeTypeLookup() {
|
|||
addShapeType(SHAPE_TYPE_STATIC_MESH);
|
||||
}
|
||||
|
||||
QHash<QString, MaterialMappingMode> stringToMaterialMappingModeLookup;
|
||||
|
||||
void addMaterialMappingMode(MaterialMappingMode mode) {
|
||||
stringToMaterialMappingModeLookup[MaterialMappingModeHelpers::getNameForMaterialMappingMode(mode)] = mode;
|
||||
}
|
||||
|
||||
void buildStringToMaterialMappingModeLookup() {
|
||||
addMaterialMappingMode(UV);
|
||||
addMaterialMappingMode(PROJECTED);
|
||||
}
|
||||
|
||||
QString getCollisionGroupAsString(uint8_t group) {
|
||||
switch (group) {
|
||||
case USER_COLLISION_GROUP_DYNAMIC:
|
||||
|
@ -259,6 +270,21 @@ void EntityItemProperties::setSkyboxModeFromString(const QString& skyboxMode) {
|
|||
}
|
||||
}
|
||||
|
||||
QString EntityItemProperties::getMaterialMappingModeAsString() const {
|
||||
return MaterialMappingModeHelpers::getNameForMaterialMappingMode(_materialMappingMode);
|
||||
}
|
||||
|
||||
void EntityItemProperties::setMaterialMappingModeFromString(const QString& materialMappingMode) {
|
||||
if (stringToMaterialMappingModeLookup.empty()) {
|
||||
buildStringToMaterialMappingModeLookup();
|
||||
}
|
||||
auto materialMappingModeItr = stringToMaterialMappingModeLookup.find(materialMappingMode.toLower());
|
||||
if (materialMappingModeItr != stringToMaterialMappingModeLookup.end()) {
|
||||
_materialMappingMode = materialMappingModeItr.value();
|
||||
_materialMappingModeChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
||||
EntityPropertyFlags changedProperties;
|
||||
|
||||
|
@ -329,6 +355,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_RADIUS_SPREAD, radiusSpread);
|
||||
CHECK_PROPERTY_CHANGE(PROP_RADIUS_START, radiusStart);
|
||||
CHECK_PROPERTY_CHANGE(PROP_RADIUS_FINISH, radiusFinish);
|
||||
CHECK_PROPERTY_CHANGE(PROP_MATERIAL_URL, materialURL);
|
||||
CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_MODE, materialMappingMode);
|
||||
CHECK_PROPERTY_CHANGE(PROP_MATERIAL_PRIORITY, priority);
|
||||
CHECK_PROPERTY_CHANGE(PROP_PARENT_MATERIAL_NAME, parentMaterialName);
|
||||
CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_POS, materialMappingPos);
|
||||
CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale);
|
||||
CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot);
|
||||
|
||||
// Certifiable Properties
|
||||
CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName);
|
||||
|
@ -625,6 +658,17 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_UV_MODE_STRETCH, isUVModeStretch);
|
||||
}
|
||||
|
||||
// Materials
|
||||
if (_type == EntityTypes::Material) {
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_URL, materialURL);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_MATERIAL_MAPPING_MODE, materialMappingMode, getMaterialMappingModeAsString());
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_PRIORITY, priority);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_MATERIAL_NAME, parentMaterialName);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_POS, materialMappingPos);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot);
|
||||
}
|
||||
|
||||
if (!skipDefaults && !strictSemantics) {
|
||||
AABox aaBox = getAABox();
|
||||
QScriptValue boundingBox = engine->newObject();
|
||||
|
@ -758,6 +802,13 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusStart, float, setRadiusStart);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusFinish, float, setRadiusFinish);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(relayParentJoints, bool, setRelayParentJoints);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(materialURL, QString, setMaterialURL);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(materialMappingMode, MaterialMappingMode);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(priority, quint16, setPriority);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(parentMaterialName, QString, setParentMaterialName);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingPos, glmVec2, setMaterialMappingPos);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingScale, glmVec2, setMaterialMappingScale);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingRot, float, setMaterialMappingRot);
|
||||
|
||||
// Certifiable Properties
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName);
|
||||
|
@ -1112,6 +1163,14 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
ADD_PROPERTY_TO_MAP(PROP_RADIUS_START, RadiusStart, radiusStart, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float);
|
||||
|
||||
ADD_PROPERTY_TO_MAP(PROP_MATERIAL_URL, MaterialURL, materialURL, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, materialMappingMode, MaterialMappingMode);
|
||||
ADD_PROPERTY_TO_MAP(PROP_MATERIAL_PRIORITY, Priority, priority, quint16);
|
||||
ADD_PROPERTY_TO_MAP(PROP_PARENT_MATERIAL_NAME, ParentMaterialName, parentMaterialName, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2);
|
||||
ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2);
|
||||
ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float);
|
||||
|
||||
// Certifiable Properties
|
||||
ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString);
|
||||
|
@ -1495,6 +1554,17 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
|
|||
APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape());
|
||||
}
|
||||
|
||||
// Materials
|
||||
if (properties.getType() == EntityTypes::Material) {
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_URL, properties.getMaterialURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_MODE, (uint32_t)properties.getMaterialMappingMode());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_PRIORITY, properties.getPriority());
|
||||
APPEND_ENTITY_PROPERTY(PROP_PARENT_MATERIAL_NAME, properties.getParentMaterialName());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, properties.getMaterialMappingPos());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, properties.getMaterialMappingScale());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, properties.getMaterialMappingRot());
|
||||
}
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName());
|
||||
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData());
|
||||
|
@ -1851,6 +1921,17 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape);
|
||||
}
|
||||
|
||||
// Materials
|
||||
if (properties.getType() == EntityTypes::Material) {
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_URL, QString, setMaterialURL);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, setMaterialMappingMode);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_PRIORITY, quint16, setPriority);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_MATERIAL_NAME, QString, setParentMaterialName);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_POS, glmVec2, setMaterialMappingPos);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_SCALE, glmVec2, setMaterialMappingScale);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_ROT, float, setMaterialMappingRot);
|
||||
}
|
||||
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACTION_DATA, QByteArray, setActionData);
|
||||
|
@ -2024,6 +2105,14 @@ void EntityItemProperties::markAllChanged() {
|
|||
//_alphaStartChanged = true;
|
||||
//_alphaFinishChanged = true;
|
||||
|
||||
_materialURLChanged = true;
|
||||
_materialMappingModeChanged = true;
|
||||
_priorityChanged = true;
|
||||
_parentMaterialNameChanged = true;
|
||||
_materialMappingPosChanged = true;
|
||||
_materialMappingScaleChanged = true;
|
||||
_materialMappingRotChanged = true;
|
||||
|
||||
// Certifiable Properties
|
||||
_itemNameChanged = true;
|
||||
_itemDescriptionChanged = true;
|
||||
|
@ -2347,6 +2436,27 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
if (radiusFinishChanged()) {
|
||||
out += "radiusFinish";
|
||||
}
|
||||
if (materialURLChanged()) {
|
||||
out += "materialURL";
|
||||
}
|
||||
if (materialMappingModeChanged()) {
|
||||
out += "materialMappingMode";
|
||||
}
|
||||
if (priorityChanged()) {
|
||||
out += "priority";
|
||||
}
|
||||
if (parentMaterialNameChanged()) {
|
||||
out += "parentMaterialName";
|
||||
}
|
||||
if (materialMappingPosChanged()) {
|
||||
out += "materialMappingPos";
|
||||
}
|
||||
if (materialMappingScaleChanged()) {
|
||||
out += "materialMappingScale";
|
||||
}
|
||||
if (materialMappingRotChanged()) {
|
||||
out += "materialMappingRot";
|
||||
}
|
||||
|
||||
// Certifiable Properties
|
||||
if (itemNameChanged()) {
|
||||
|
|
|
@ -44,6 +44,8 @@
|
|||
#include "TextEntityItem.h"
|
||||
#include "ZoneEntityItem.h"
|
||||
|
||||
#include "MaterialMappingMode.h"
|
||||
|
||||
const quint64 UNKNOWN_CREATED_TIME = 0;
|
||||
|
||||
using ComponentPair = std::pair<const ComponentMode, const QString>;
|
||||
|
@ -58,19 +60,21 @@ const std::array<ComponentPair, COMPONENT_MODE_ITEM_COUNT> COMPONENT_MODES = { {
|
|||
/// set of entity item properties via JavaScript hashes/QScriptValues
|
||||
/// all units for SI units (meter, second, radian, etc)
|
||||
class EntityItemProperties {
|
||||
friend class EntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class ModelEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class BoxEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class SphereEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class LightEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class TextEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class WebEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class PolyLineEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
friend class ShapeEntityItem; // TODO: consider removing this friend relationship and use public methods
|
||||
// TODO: consider removing these friend relationship and use public methods
|
||||
friend class EntityItem;
|
||||
friend class ModelEntityItem;
|
||||
friend class BoxEntityItem;
|
||||
friend class SphereEntityItem;
|
||||
friend class LightEntityItem;
|
||||
friend class TextEntityItem;
|
||||
friend class ParticleEffectEntityItem;
|
||||
friend class ZoneEntityItem;
|
||||
friend class WebEntityItem;
|
||||
friend class LineEntityItem;
|
||||
friend class PolyVoxEntityItem;
|
||||
friend class PolyLineEntityItem;
|
||||
friend class ShapeEntityItem;
|
||||
friend class MaterialEntityItem;
|
||||
public:
|
||||
EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags());
|
||||
virtual ~EntityItemProperties() = default;
|
||||
|
@ -218,6 +222,14 @@ public:
|
|||
DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube());
|
||||
DEFINE_PROPERTY_REF(PROP_SHAPE, Shape, shape, QString, "Sphere");
|
||||
|
||||
DEFINE_PROPERTY_REF(PROP_MATERIAL_URL, MaterialURL, materialURL, QString, "");
|
||||
DEFINE_PROPERTY_REF_ENUM(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, materialMappingMode, MaterialMappingMode, UV);
|
||||
DEFINE_PROPERTY_REF(PROP_MATERIAL_PRIORITY, Priority, priority, quint16, 0);
|
||||
DEFINE_PROPERTY_REF(PROP_PARENT_MATERIAL_NAME, ParentMaterialName, parentMaterialName, QString, "0");
|
||||
DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2, glm::vec2(0, 0));
|
||||
DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2, glm::vec2(1, 1));
|
||||
DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float, 0);
|
||||
|
||||
// Certifiable Properties - related to Proof of Purchase certificates
|
||||
DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME);
|
||||
DEFINE_PROPERTY_REF(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString, ENTITY_ITEM_DEFAULT_ITEM_DESCRIPTION);
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
changedProperties += P; \
|
||||
}
|
||||
|
||||
inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::vec2& v) { return vec2toScriptValue(e, v); }
|
||||
inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::vec3& v) { return vec3toScriptValue(e, v); }
|
||||
inline QScriptValue convertScriptValue(QScriptEngine* e, float v) { return QScriptValue(v); }
|
||||
inline QScriptValue convertScriptValue(QScriptEngine* e, int v) { return QScriptValue(v); }
|
||||
|
@ -183,6 +184,7 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { retu
|
|||
properties.setProperty(#P, V); \
|
||||
}
|
||||
|
||||
typedef glm::vec2 glmVec2;
|
||||
typedef glm::vec3 glmVec3;
|
||||
typedef glm::quat glmQuat;
|
||||
typedef QVector<glm::vec3> qVectorVec3;
|
||||
|
@ -221,6 +223,23 @@ inline QByteArray QByteArray_convertFromScriptValue(const QScriptValue& v, bool&
|
|||
return QByteArray::fromBase64(b64.toUtf8());
|
||||
}
|
||||
|
||||
inline glmVec2 glmVec2_convertFromScriptValue(const QScriptValue& v, bool& isValid) {
|
||||
isValid = false; /// assume it can't be converted
|
||||
QScriptValue x = v.property("x");
|
||||
QScriptValue y = v.property("y");
|
||||
if (x.isValid() && y.isValid()) {
|
||||
glm::vec4 newValue(0);
|
||||
newValue.x = x.toVariant().toFloat();
|
||||
newValue.y = y.toVariant().toFloat();
|
||||
isValid = !glm::isnan(newValue.x) &&
|
||||
!glm::isnan(newValue.y);
|
||||
if (isValid) {
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
return glm::vec2(0);
|
||||
}
|
||||
|
||||
inline glmVec3 glmVec3_convertFromScriptValue(const QScriptValue& v, bool& isValid) {
|
||||
isValid = false; /// assume it can't be converted
|
||||
QScriptValue x = v.property("x");
|
||||
|
|
|
@ -227,6 +227,14 @@ enum EntityPropertyList {
|
|||
|
||||
PROP_LOCAL_DIMENSIONS, // only used to convert values to and from scripts
|
||||
|
||||
PROP_MATERIAL_URL,
|
||||
PROP_MATERIAL_MAPPING_MODE,
|
||||
PROP_MATERIAL_PRIORITY,
|
||||
PROP_PARENT_MATERIAL_NAME,
|
||||
PROP_MATERIAL_MAPPING_POS,
|
||||
PROP_MATERIAL_MAPPING_SCALE,
|
||||
PROP_MATERIAL_MAPPING_ROT,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ATTENTION: add new properties to end of list just ABOVE this line
|
||||
PROP_AFTER_LAST_ITEM,
|
||||
|
|
|
@ -1739,7 +1739,8 @@ void EntityTree::fixupNeedsParentFixups() {
|
|||
}
|
||||
});
|
||||
entity->locationChanged(true);
|
||||
} else if (getIsServer() && _avatarIDs.contains(entity->getParentID())) {
|
||||
entity->postParentFixup();
|
||||
} else if (getIsServer() || _avatarIDs.contains(entity->getParentID())) {
|
||||
// this is a child of an avatar, which the entity server will never have
|
||||
// a SpatiallyNestable object for. Add it to a list for cleanup when the avatar leaves.
|
||||
if (!_childrenOfAvatars.contains(entity->getParentID())) {
|
||||
|
@ -2412,3 +2413,51 @@ QStringList EntityTree::getJointNames(const QUuid& entityID) const {
|
|||
return entity->getJointNames();
|
||||
}
|
||||
|
||||
std::function<bool(const QUuid&, graphics::MaterialLayer, const std::string&)> EntityTree::_addMaterialToEntityOperator = nullptr;
|
||||
std::function<bool(const QUuid&, graphics::MaterialPointer, const std::string&)> EntityTree::_removeMaterialFromEntityOperator = nullptr;
|
||||
std::function<bool(const QUuid&, graphics::MaterialLayer, const std::string&)> EntityTree::_addMaterialToAvatarOperator = nullptr;
|
||||
std::function<bool(const QUuid&, graphics::MaterialPointer, const std::string&)> EntityTree::_removeMaterialFromAvatarOperator = nullptr;
|
||||
std::function<bool(const QUuid&, graphics::MaterialLayer, const std::string&)> EntityTree::_addMaterialToOverlayOperator = nullptr;
|
||||
std::function<bool(const QUuid&, graphics::MaterialPointer, const std::string&)> EntityTree::_removeMaterialFromOverlayOperator = nullptr;
|
||||
|
||||
bool EntityTree::addMaterialToEntity(const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
if (_addMaterialToEntityOperator) {
|
||||
return _addMaterialToEntityOperator(entityID, material, parentMaterialName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntityTree::removeMaterialFromEntity(const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
if (_removeMaterialFromEntityOperator) {
|
||||
return _removeMaterialFromEntityOperator(entityID, material, parentMaterialName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntityTree::addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
if (_addMaterialToAvatarOperator) {
|
||||
return _addMaterialToAvatarOperator(avatarID, material, parentMaterialName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntityTree::removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
if (_removeMaterialFromAvatarOperator) {
|
||||
return _removeMaterialFromAvatarOperator(avatarID, material, parentMaterialName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntityTree::addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
if (_addMaterialToOverlayOperator) {
|
||||
return _addMaterialToOverlayOperator(overlayID, material, parentMaterialName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntityTree::removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
||||
if (_removeMaterialFromOverlayOperator) {
|
||||
return _removeMaterialFromOverlayOperator(overlayID, material, parentMaterialName);
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -283,6 +283,21 @@ public:
|
|||
|
||||
void setMyAvatar(std::shared_ptr<AvatarData> myAvatar) { _myAvatar = myAvatar; }
|
||||
|
||||
static void setAddMaterialToEntityOperator(std::function<bool(const QUuid&, graphics::MaterialLayer, const std::string&)> addMaterialToEntityOperator) { _addMaterialToEntityOperator = addMaterialToEntityOperator; }
|
||||
static void setRemoveMaterialFromEntityOperator(std::function<bool(const QUuid&, graphics::MaterialPointer, const std::string&)> removeMaterialFromEntityOperator) { _removeMaterialFromEntityOperator = removeMaterialFromEntityOperator; }
|
||||
static bool addMaterialToEntity(const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName);
|
||||
static bool removeMaterialFromEntity(const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName);
|
||||
|
||||
static void setAddMaterialToAvatarOperator(std::function<bool(const QUuid&, graphics::MaterialLayer, const std::string&)> addMaterialToAvatarOperator) { _addMaterialToAvatarOperator = addMaterialToAvatarOperator; }
|
||||
static void setRemoveMaterialFromAvatarOperator(std::function<bool(const QUuid&, graphics::MaterialPointer, const std::string&)> removeMaterialFromAvatarOperator) { _removeMaterialFromAvatarOperator = removeMaterialFromAvatarOperator; }
|
||||
static bool addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName);
|
||||
static bool removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName);
|
||||
|
||||
static void setAddMaterialToOverlayOperator(std::function<bool(const QUuid&, graphics::MaterialLayer, const std::string&)> addMaterialToOverlayOperator) { _addMaterialToOverlayOperator = addMaterialToOverlayOperator; }
|
||||
static void setRemoveMaterialFromOverlayOperator(std::function<bool(const QUuid&, graphics::MaterialPointer, const std::string&)> removeMaterialFromOverlayOperator) { _removeMaterialFromOverlayOperator = removeMaterialFromOverlayOperator; }
|
||||
static bool addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName);
|
||||
static bool removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName);
|
||||
|
||||
signals:
|
||||
void deletingEntity(const EntityItemID& entityID);
|
||||
void deletingEntityPointer(EntityItem* entityID);
|
||||
|
@ -390,6 +405,13 @@ private:
|
|||
void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation);
|
||||
|
||||
std::shared_ptr<AvatarData> _myAvatar{ nullptr };
|
||||
|
||||
static std::function<bool(const QUuid&, graphics::MaterialLayer, const std::string&)> _addMaterialToEntityOperator;
|
||||
static std::function<bool(const QUuid&, graphics::MaterialPointer, const std::string&)> _removeMaterialFromEntityOperator;
|
||||
static std::function<bool(const QUuid&, graphics::MaterialLayer, const std::string&)> _addMaterialToAvatarOperator;
|
||||
static std::function<bool(const QUuid&, graphics::MaterialPointer, const std::string&)> _removeMaterialFromAvatarOperator;
|
||||
static std::function<bool(const QUuid&, graphics::MaterialLayer, const std::string&)> _addMaterialToOverlayOperator;
|
||||
static std::function<bool(const QUuid&, graphics::MaterialPointer, const std::string&)> _removeMaterialFromOverlayOperator;
|
||||
};
|
||||
|
||||
#endif // hifi_EntityTree_h
|
||||
|
|
|
@ -896,6 +896,7 @@ EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemI
|
|||
void EntityTreeElement::cleanupEntities() {
|
||||
withWriteLock([&] {
|
||||
foreach(EntityItemPointer entity, _entityItems) {
|
||||
entity->preDelete();
|
||||
// NOTE: only EntityTreeElement should ever be changing the value of entity->_element
|
||||
// NOTE: We explicitly don't delete the EntityItem here because since we only
|
||||
// access it by smart pointers, when we remove it from the _entityItems
|
||||
|
@ -907,26 +908,10 @@ void EntityTreeElement::cleanupEntities() {
|
|||
bumpChangedContent();
|
||||
}
|
||||
|
||||
bool EntityTreeElement::removeEntityWithEntityItemID(const EntityItemID& id) {
|
||||
bool foundEntity = false;
|
||||
withWriteLock([&] {
|
||||
uint16_t numberOfEntities = _entityItems.size();
|
||||
for (uint16_t i = 0; i < numberOfEntities; i++) {
|
||||
EntityItemPointer& entity = _entityItems[i];
|
||||
if (entity->getEntityItemID() == id) {
|
||||
foundEntity = true;
|
||||
// NOTE: only EntityTreeElement should ever be changing the value of entity->_element
|
||||
entity->_element = NULL;
|
||||
_entityItems.removeAt(i);
|
||||
bumpChangedContent();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return foundEntity;
|
||||
}
|
||||
|
||||
bool EntityTreeElement::removeEntityItem(EntityItemPointer entity) {
|
||||
bool EntityTreeElement::removeEntityItem(EntityItemPointer entity, bool deletion) {
|
||||
if (deletion) {
|
||||
entity->preDelete();
|
||||
}
|
||||
int numEntries = 0;
|
||||
withWriteLock([&] {
|
||||
numEntries = _entityItems.removeAll(entity);
|
||||
|
|
|
@ -210,8 +210,7 @@ public:
|
|||
void getEntitiesInside(const AACube& box, QVector<EntityItemPointer>& foundEntities);
|
||||
|
||||
void cleanupEntities(); /// called by EntityTree on cleanup this will free all entities
|
||||
bool removeEntityWithEntityItemID(const EntityItemID& id);
|
||||
bool removeEntityItem(EntityItemPointer entity);
|
||||
bool removeEntityItem(EntityItemPointer entity, bool deletion = false);
|
||||
|
||||
bool containsEntityBounds(EntityItemPointer entity) const;
|
||||
bool bestFitEntityBounds(EntityItemPointer entity) const;
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "PolyVoxEntityItem.h"
|
||||
#include "PolyLineEntityItem.h"
|
||||
#include "ShapeEntityItem.h"
|
||||
#include "MaterialEntityItem.h"
|
||||
|
||||
QMap<EntityTypes::EntityType, QString> EntityTypes::_typeToNameMap;
|
||||
QMap<QString, EntityTypes::EntityType> EntityTypes::_nameToTypeMap;
|
||||
|
@ -50,6 +51,7 @@ REGISTER_ENTITY_TYPE(PolyLine)
|
|||
REGISTER_ENTITY_TYPE(Shape)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, ShapeEntityItem::boxFactory)
|
||||
REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, ShapeEntityItem::sphereFactory)
|
||||
REGISTER_ENTITY_TYPE(Material)
|
||||
|
||||
const QString& EntityTypes::getEntityTypeName(EntityType entityType) {
|
||||
QMap<EntityType, QString>::iterator matchedTypeName = _typeToNameMap.find(entityType);
|
||||
|
|
|
@ -49,7 +49,8 @@ public:
|
|||
PolyVox,
|
||||
PolyLine,
|
||||
Shape,
|
||||
LAST = Shape
|
||||
Material,
|
||||
LAST = Material
|
||||
} EntityType;
|
||||
|
||||
static const QString& getEntityTypeName(EntityType entityType);
|
||||
|
|
339
libraries/entities/src/MaterialEntityItem.cpp
Normal file
339
libraries/entities/src/MaterialEntityItem.cpp
Normal file
|
@ -0,0 +1,339 @@
|
|||
//
|
||||
// Created by Sam Gondelman on 1/12/18
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "MaterialEntityItem.h"
|
||||
|
||||
#include "EntityItemProperties.h"
|
||||
|
||||
#include "QJsonDocument"
|
||||
#include "QJsonArray"
|
||||
|
||||
EntityItemPointer MaterialEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
Pointer entity(new MaterialEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); });
|
||||
entity->setProperties(properties);
|
||||
// When you reload content, setProperties doesn't have any of the propertiesChanged flags set, so it won't trigger a material add
|
||||
entity->removeMaterial();
|
||||
entity->applyMaterial();
|
||||
return entity;
|
||||
}
|
||||
|
||||
// our non-pure virtual subclass for now...
|
||||
MaterialEntityItem::MaterialEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
|
||||
_type = EntityTypes::Material;
|
||||
}
|
||||
|
||||
EntityItemProperties MaterialEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
|
||||
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialURL, getMaterialURL);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingMode, getMaterialMappingMode);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(priority, getPriority);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentMaterialName, getParentMaterialName);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingPos, getMaterialMappingPos);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingScale, getMaterialMappingScale);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingRot, getMaterialMappingRot);
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool MaterialEntityItem::setProperties(const EntityItemProperties& properties) {
|
||||
bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class
|
||||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialURL, setMaterialURL);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingMode, setMaterialMappingMode);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(priority, setPriority);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentMaterialName, setParentMaterialName);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingPos, setMaterialMappingPos);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingScale, setMaterialMappingScale);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingRot, setMaterialMappingRot);
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
if (wantDebug) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
int elapsed = now - getLastEdited();
|
||||
qCDebug(entities) << "MaterialEntityItem::setProperties() AFTER update... edited AGO=" << elapsed <<
|
||||
"now=" << now << " getLastEdited()=" << getLastEdited();
|
||||
}
|
||||
setLastEdited(properties.getLastEdited());
|
||||
}
|
||||
return somethingChanged;
|
||||
}
|
||||
|
||||
int MaterialEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
|
||||
bool& somethingChanged) {
|
||||
|
||||
int bytesRead = 0;
|
||||
const unsigned char* dataAt = data;
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_MATERIAL_URL, QString, setMaterialURL);
|
||||
READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, setMaterialMappingMode);
|
||||
READ_ENTITY_PROPERTY(PROP_MATERIAL_PRIORITY, quint16, setPriority);
|
||||
READ_ENTITY_PROPERTY(PROP_PARENT_MATERIAL_NAME, QString, setParentMaterialName);
|
||||
READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, glm::vec2, setMaterialMappingPos);
|
||||
READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, glm::vec2, setMaterialMappingScale);
|
||||
READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, float, setMaterialMappingRot);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
EntityPropertyFlags MaterialEntityItem::getEntityProperties(EncodeBitstreamParams& params) const {
|
||||
EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params);
|
||||
requestedProperties += PROP_MATERIAL_URL;
|
||||
requestedProperties += PROP_MATERIAL_MAPPING_MODE;
|
||||
requestedProperties += PROP_MATERIAL_PRIORITY;
|
||||
requestedProperties += PROP_PARENT_MATERIAL_NAME;
|
||||
requestedProperties += PROP_MATERIAL_MAPPING_POS;
|
||||
requestedProperties += PROP_MATERIAL_MAPPING_SCALE;
|
||||
requestedProperties += PROP_MATERIAL_MAPPING_ROT;
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
void MaterialEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const {
|
||||
|
||||
bool successPropertyFits = true;
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_URL, getMaterialURL());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_MODE, (uint32_t)getMaterialMappingMode());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_PRIORITY, getPriority());
|
||||
APPEND_ENTITY_PROPERTY(PROP_PARENT_MATERIAL_NAME, getParentMaterialName());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, getMaterialMappingPos());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, getMaterialMappingScale());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, getMaterialMappingRot());
|
||||
}
|
||||
|
||||
void MaterialEntityItem::debugDump() const {
|
||||
quint64 now = usecTimestampNow();
|
||||
qCDebug(entities) << " MATERIAL EntityItem id:" << getEntityItemID() << "---------------------------------------------";
|
||||
qCDebug(entities) << " name:" << _name;
|
||||
qCDebug(entities) << " material url:" << _materialURL;
|
||||
qCDebug(entities) << " current material name:" << _currentMaterialName.c_str();
|
||||
qCDebug(entities) << " material mapping mode:" << _materialMappingMode;
|
||||
qCDebug(entities) << " priority:" << _priority;
|
||||
qCDebug(entities) << " parent material name:" << _parentMaterialName;
|
||||
qCDebug(entities) << " material mapping pos:" << _materialMappingPos;
|
||||
qCDebug(entities) << " material mapping scale:" << _materialMappingRot;
|
||||
qCDebug(entities) << " material mapping rot:" << _materialMappingScale;
|
||||
qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition());
|
||||
qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions());
|
||||
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
|
||||
qCDebug(entities) << "MATERIAL EntityItem Ptr:" << this;
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setUnscaledDimensions(const glm::vec3& value) {
|
||||
EntityItem::setUnscaledDimensions(ENTITY_ITEM_DEFAULT_DIMENSIONS);
|
||||
}
|
||||
|
||||
std::shared_ptr<NetworkMaterial> MaterialEntityItem::getMaterial() const {
|
||||
auto material = _parsedMaterials.networkMaterials.find(_currentMaterialName);
|
||||
if (material != _parsedMaterials.networkMaterials.end()) {
|
||||
return material->second;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool userDataChanged) {
|
||||
bool usingUserData = materialURLString.startsWith("userData");
|
||||
if (_materialURL != materialURLString || (usingUserData && userDataChanged)) {
|
||||
removeMaterial();
|
||||
_materialURL = materialURLString;
|
||||
|
||||
if (materialURLString.contains("?")) {
|
||||
auto split = materialURLString.split("?");
|
||||
_currentMaterialName = split.last().toStdString();
|
||||
}
|
||||
|
||||
if (usingUserData) {
|
||||
_parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getUserData().toUtf8()));
|
||||
|
||||
// Since our material changed, the current name might not be valid anymore, so we need to update
|
||||
setCurrentMaterialName(_currentMaterialName);
|
||||
applyMaterial();
|
||||
} else {
|
||||
_networkMaterial = MaterialCache::instance().getMaterial(materialURLString);
|
||||
auto onMaterialRequestFinished = [&](bool success) {
|
||||
if (success) {
|
||||
_parsedMaterials = _networkMaterial->parsedMaterials;
|
||||
|
||||
setCurrentMaterialName(_currentMaterialName);
|
||||
applyMaterial();
|
||||
}
|
||||
};
|
||||
if (_networkMaterial) {
|
||||
if (_networkMaterial->isLoaded()) {
|
||||
onMaterialRequestFinished(!_networkMaterial->isFailed());
|
||||
} else {
|
||||
connect(_networkMaterial.data(), &Resource::finished, this, onMaterialRequestFinished);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setCurrentMaterialName(const std::string& currentMaterialName) {
|
||||
if (_parsedMaterials.networkMaterials.find(currentMaterialName) != _parsedMaterials.networkMaterials.end()) {
|
||||
_currentMaterialName = currentMaterialName;
|
||||
} else if (_parsedMaterials.names.size() > 0) {
|
||||
_currentMaterialName = _parsedMaterials.names[0];
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setUserData(const QString& userData) {
|
||||
if (_userData != userData) {
|
||||
EntityItem::setUserData(userData);
|
||||
if (_materialURL.startsWith("userData")) {
|
||||
// Trigger material update when user data changes
|
||||
setMaterialURL(_materialURL, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setMaterialMappingPos(const glm::vec2& materialMappingPos) {
|
||||
if (_materialMappingPos != materialMappingPos) {
|
||||
removeMaterial();
|
||||
_materialMappingPos = materialMappingPos;
|
||||
applyMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setMaterialMappingScale(const glm::vec2& materialMappingScale) {
|
||||
if (_materialMappingScale != materialMappingScale) {
|
||||
removeMaterial();
|
||||
_materialMappingScale = materialMappingScale;
|
||||
applyMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setMaterialMappingRot(const float& materialMappingRot) {
|
||||
if (_materialMappingRot != materialMappingRot) {
|
||||
removeMaterial();
|
||||
_materialMappingRot = materialMappingRot;
|
||||
applyMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setPriority(quint16 priority) {
|
||||
if (_priority != priority) {
|
||||
removeMaterial();
|
||||
_priority = priority;
|
||||
applyMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setParentMaterialName(const QString& parentMaterialName) {
|
||||
if (_parentMaterialName != parentMaterialName) {
|
||||
removeMaterial();
|
||||
_parentMaterialName = parentMaterialName;
|
||||
applyMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setParentID(const QUuid& parentID) {
|
||||
if (getParentID() != parentID) {
|
||||
removeMaterial();
|
||||
EntityItem::setParentID(parentID);
|
||||
applyMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setClientOnly(bool clientOnly) {
|
||||
if (getClientOnly() != clientOnly) {
|
||||
removeMaterial();
|
||||
EntityItem::setClientOnly(clientOnly);
|
||||
applyMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setOwningAvatarID(const QUuid& owningAvatarID) {
|
||||
if (getOwningAvatarID() != owningAvatarID) {
|
||||
removeMaterial();
|
||||
EntityItem::setOwningAvatarID(owningAvatarID);
|
||||
applyMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::removeMaterial() {
|
||||
graphics::MaterialPointer material = getMaterial();
|
||||
QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID();
|
||||
if (!material || parentID.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Our parent could be an entity, an avatar, or an overlay
|
||||
if (EntityTree::removeMaterialFromEntity(parentID, material, getParentMaterialName().toStdString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (EntityTree::removeMaterialFromAvatar(parentID, material, getParentMaterialName().toStdString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (EntityTree::removeMaterialFromOverlay(parentID, material, getParentMaterialName().toStdString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if a remove fails, our parent is gone, so we don't need to retry
|
||||
}
|
||||
|
||||
void MaterialEntityItem::applyMaterial() {
|
||||
_retryApply = false;
|
||||
graphics::MaterialPointer material = getMaterial();
|
||||
QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID();
|
||||
if (!material || parentID.isNull()) {
|
||||
return;
|
||||
}
|
||||
Transform textureTransform;
|
||||
textureTransform.setTranslation(glm::vec3(_materialMappingPos, 0));
|
||||
textureTransform.setRotation(glm::vec3(0, 0, glm::radians(_materialMappingRot)));
|
||||
textureTransform.setScale(glm::vec3(_materialMappingScale, 1));
|
||||
material->setTextureTransforms(textureTransform);
|
||||
|
||||
graphics::MaterialLayer materialLayer = graphics::MaterialLayer(material, getPriority());
|
||||
|
||||
// Our parent could be an entity, an avatar, or an overlay
|
||||
if (EntityTree::addMaterialToEntity(parentID, materialLayer, getParentMaterialName().toStdString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (EntityTree::addMaterialToAvatar(parentID, materialLayer, getParentMaterialName().toStdString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (EntityTree::addMaterialToOverlay(parentID, materialLayer, getParentMaterialName().toStdString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we've reached this point, we couldn't find our parent, so we need to try again later
|
||||
_retryApply = true;
|
||||
}
|
||||
|
||||
void MaterialEntityItem::postParentFixup() {
|
||||
removeMaterial();
|
||||
applyMaterial();
|
||||
}
|
||||
|
||||
void MaterialEntityItem::preDelete() {
|
||||
EntityItem::preDelete();
|
||||
removeMaterial();
|
||||
}
|
||||
|
||||
void MaterialEntityItem::update(const quint64& now) {
|
||||
if (_retryApply) {
|
||||
applyMaterial();
|
||||
}
|
||||
|
||||
EntityItem::update(now);
|
||||
}
|
129
libraries/entities/src/MaterialEntityItem.h
Normal file
129
libraries/entities/src/MaterialEntityItem.h
Normal file
|
@ -0,0 +1,129 @@
|
|||
//
|
||||
// Created by Sam Gondelman on 1/12/18
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_MaterialEntityItem_h
|
||||
#define hifi_MaterialEntityItem_h
|
||||
|
||||
#include "EntityItem.h"
|
||||
|
||||
#include "MaterialMappingMode.h"
|
||||
#include <model-networking/ModelCache.h>
|
||||
#include <model-networking/MaterialCache.h>
|
||||
|
||||
class MaterialEntityItem : public EntityItem {
|
||||
using Pointer = std::shared_ptr<MaterialEntityItem>;
|
||||
public:
|
||||
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
|
||||
MaterialEntityItem(const EntityItemID& entityItemID);
|
||||
|
||||
ALLOW_INSTANTIATION // This class can be instantiated
|
||||
|
||||
void update(const quint64& now) override;
|
||||
bool needsToCallUpdate() const override { return true; }
|
||||
|
||||
// methods for getting/setting all properties of an entity
|
||||
virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override;
|
||||
virtual bool setProperties(const EntityItemProperties& properties) override;
|
||||
|
||||
// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time
|
||||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
|
||||
EntityPropertyFlags& requestedProperties,
|
||||
EntityPropertyFlags& propertyFlags,
|
||||
EntityPropertyFlags& propertiesDidntFit,
|
||||
int& propertyCount,
|
||||
OctreeElement::AppendState& appendState) const override;
|
||||
|
||||
|
||||
virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args,
|
||||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
|
||||
bool& somethingChanged) override;
|
||||
|
||||
void debugDump() const override;
|
||||
|
||||
virtual void setUnscaledDimensions(const glm::vec3& value) override;
|
||||
|
||||
QString getMaterialURL() const { return _materialURL; }
|
||||
void setMaterialURL(const QString& materialURLString, bool userDataChanged = false);
|
||||
|
||||
void setCurrentMaterialName(const std::string& currentMaterialName);
|
||||
|
||||
MaterialMappingMode getMaterialMappingMode() const { return _materialMappingMode; }
|
||||
void setMaterialMappingMode(MaterialMappingMode mode) { _materialMappingMode = mode; }
|
||||
|
||||
quint16 getPriority() const { return _priority; }
|
||||
void setPriority(quint16 priority);
|
||||
|
||||
QString getParentMaterialName() const { return _parentMaterialName; }
|
||||
void setParentMaterialName(const QString& parentMaterialName);
|
||||
|
||||
glm::vec2 getMaterialMappingPos() const { return _materialMappingPos; }
|
||||
void setMaterialMappingPos(const glm::vec2& materialMappingPos);
|
||||
glm::vec2 getMaterialMappingScale() const { return _materialMappingScale; }
|
||||
void setMaterialMappingScale(const glm::vec2& materialMappingScale);
|
||||
float getMaterialMappingRot() const { return _materialMappingRot; }
|
||||
void setMaterialMappingRot(const float& materialMappingRot);
|
||||
|
||||
std::shared_ptr<NetworkMaterial> getMaterial() const;
|
||||
|
||||
void setUserData(const QString& userData) override;
|
||||
void setParentID(const QUuid& parentID) override;
|
||||
void setClientOnly(bool clientOnly) override;
|
||||
void setOwningAvatarID(const QUuid& owningAvatarID) override;
|
||||
|
||||
void applyMaterial();
|
||||
void removeMaterial();
|
||||
|
||||
void postParentFixup() override;
|
||||
void preDelete() override;
|
||||
|
||||
private:
|
||||
// URL for this material. Currently, only JSON format is supported. Set to "userData" to use the user data to live edit a material.
|
||||
// The following fields are supported in the JSON:
|
||||
// materialVersion: a uint for the version of this network material (currently, only 1 is supported)
|
||||
// materials, which is either an object or an array of objects, each with the following properties:
|
||||
// strings:
|
||||
// name (NOT YET USED), model (NOT YET USED, should use "hifi_pbr")
|
||||
// floats:
|
||||
// opacity, roughness, metallic, scattering
|
||||
// bool:
|
||||
// unlit
|
||||
// colors (arrays of 3 floats 0-1. Optional fourth value in array can be a boolean isSRGB):
|
||||
// emissive, albedo
|
||||
// urls to textures:
|
||||
// emissiveMap, albedoMap (set opacityMap = albedoMap for transparency), metallicMap or specularMap, roughnessMap or glossMap,
|
||||
// normalMap or bumpMap, occlusionMap, lightmapMap (broken, FIXME), scatteringMap (only works if normal mapped)
|
||||
QString _materialURL;
|
||||
// Type of material. "uv" or "projected". NOT YET IMPLEMENTED, only UV is used
|
||||
MaterialMappingMode _materialMappingMode { UV };
|
||||
// Priority for this material when applying it to its parent. Only the highest priority material will be used. Materials with the same priority are (essentially) randomly sorted.
|
||||
// Base materials that come with models always have priority 0.
|
||||
quint16 _priority { 0 };
|
||||
// An identifier for choosing a submesh or submeshes within a parent. If in the format "mat::<string>", all submeshes with material name "<string>" will be replaced. Otherwise,
|
||||
// parentMaterialName will be parsed as an unsigned int (strings not starting with "mat::" will parse to 0), representing the mesh index to modify.
|
||||
QString _parentMaterialName { "0" };
|
||||
// Offset position in UV-space of top left of material, (0, 0) to (1, 1)
|
||||
glm::vec2 _materialMappingPos { 0, 0 };
|
||||
// How much to scale this material within its parent's UV-space
|
||||
glm::vec2 _materialMappingScale { 1, 1 };
|
||||
// How much to rotate this material within its parent's UV-space (degrees)
|
||||
float _materialMappingRot { 0 };
|
||||
|
||||
NetworkMaterialResourcePointer _networkMaterial;
|
||||
NetworkMaterialResource::ParsedMaterials _parsedMaterials;
|
||||
std::string _currentMaterialName;
|
||||
|
||||
bool _retryApply { false };
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_MaterialEntityItem_h
|
|
@ -721,4 +721,4 @@ bool ModelEntityItem::isAnimatingSomething() const {
|
|||
_animationProperties.getRunning() &&
|
||||
(_animationProperties.getFPS() != 0.0f);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -85,6 +85,7 @@ EntityItemPointer ShapeEntityItem::sphereFactory(const EntityItemID& entityID, c
|
|||
ShapeEntityItem::ShapeEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) {
|
||||
_type = EntityTypes::Shape;
|
||||
_volumeMultiplier *= PI / 6.0f;
|
||||
_material = std::make_shared<graphics::Material>();
|
||||
}
|
||||
|
||||
EntityItemProperties ShapeEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
|
||||
|
@ -184,6 +185,7 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
|
|||
|
||||
void ShapeEntityItem::setColor(const rgbColor& value) {
|
||||
memcpy(_color, value, sizeof(rgbColor));
|
||||
_material->setAlbedo(glm::vec3(_color[0], _color[1], _color[2]) / 255.0f);
|
||||
}
|
||||
|
||||
xColor ShapeEntityItem::getXColor() const {
|
||||
|
@ -204,6 +206,11 @@ void ShapeEntityItem::setColor(const QColor& value) {
|
|||
setAlpha(value.alpha());
|
||||
}
|
||||
|
||||
void ShapeEntityItem::setAlpha(float alpha) {
|
||||
_alpha = alpha;
|
||||
_material->setOpacity(alpha);
|
||||
}
|
||||
|
||||
void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) {
|
||||
const float MAX_FLAT_DIMENSION = 0.0001f;
|
||||
if ((_shape == entity::Shape::Circle || _shape == entity::Shape::Quad) && value.y > MAX_FLAT_DIMENSION) {
|
||||
|
|
|
@ -75,7 +75,7 @@ public:
|
|||
void setShape(const QString& shape) { setShape(entity::shapeFromString(shape)); }
|
||||
|
||||
float getAlpha() const { return _alpha; };
|
||||
void setAlpha(float alpha) { _alpha = alpha; }
|
||||
void setAlpha(float alpha);
|
||||
|
||||
const rgbColor& getColor() const { return _color; }
|
||||
void setColor(const rgbColor& value);
|
||||
|
@ -101,6 +101,8 @@ public:
|
|||
virtual void computeShapeInfo(ShapeInfo& info) override;
|
||||
virtual ShapeType getShapeType() const override;
|
||||
|
||||
std::shared_ptr<graphics::Material> getMaterial() { return _material; }
|
||||
|
||||
protected:
|
||||
|
||||
float _alpha { 1 };
|
||||
|
@ -111,6 +113,8 @@ protected:
|
|||
//! prior functionality where new or unsupported shapes are treated as
|
||||
//! ellipsoids.
|
||||
ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID };
|
||||
|
||||
std::shared_ptr<graphics::Material> _material;
|
||||
};
|
||||
|
||||
#endif // hifi_ShapeEntityItem_h
|
||||
|
|
|
@ -1481,6 +1481,11 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
}
|
||||
while (!remainingModels.isEmpty()) {
|
||||
QString first = *remainingModels.constBegin();
|
||||
foreach (const QString& id, remainingModels) {
|
||||
if (id < first) {
|
||||
first = id;
|
||||
}
|
||||
}
|
||||
QString topID = getTopModelID(_connectionParentMap, models, first, url);
|
||||
appendModelIDs(_connectionParentMap.value(topID), _connectionChildMap, models, remainingModels, modelIDs, true);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include <QtCore/QProcessEnvironment>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtGui/QOffscreenSurface>
|
||||
#include <QtGui/QOpenGLContext>
|
||||
#include <QtGui/QOpenGLDebugLogger>
|
||||
|
@ -119,3 +120,29 @@ void OffscreenGLCanvas::moveToThreadWithContext(QThread* thread) {
|
|||
moveToThread(thread);
|
||||
_context->moveToThread(thread);
|
||||
}
|
||||
|
||||
static const char* THREAD_CONTEXT_PROPERTY = "offscreenGlCanvas";
|
||||
|
||||
void OffscreenGLCanvas::setThreadContext() {
|
||||
QThread::currentThread()->setProperty(THREAD_CONTEXT_PROPERTY, QVariant::fromValue<QObject*>(this));
|
||||
}
|
||||
|
||||
bool OffscreenGLCanvas::restoreThreadContext() {
|
||||
// Restore the rendering context for this thread
|
||||
auto threadCanvasVariant = QThread::currentThread()->property(THREAD_CONTEXT_PROPERTY);
|
||||
if (!threadCanvasVariant.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto threadCanvasObject = qvariant_cast<QObject*>(threadCanvasVariant);
|
||||
auto threadCanvas = static_cast<OffscreenGLCanvas*>(threadCanvasObject);
|
||||
if (!threadCanvas) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!threadCanvas->makeCurrent()) {
|
||||
qFatal("Unable to restore Offscreen rendering context");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,9 @@ public:
|
|||
}
|
||||
QObject* getContextObject();
|
||||
|
||||
void setThreadContext();
|
||||
static bool restoreThreadContext();
|
||||
|
||||
private slots:
|
||||
void onMessageLogged(const QOpenGLDebugMessage &debugMessage);
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
#include "TextureMap.h"
|
||||
|
||||
#include <Transform.h>
|
||||
|
||||
using namespace graphics;
|
||||
using namespace gpu;
|
||||
|
||||
|
@ -30,6 +32,7 @@ Material::Material() :
|
|||
}
|
||||
|
||||
Material::Material(const Material& material) :
|
||||
_name(material._name),
|
||||
_key(material._key),
|
||||
_textureMaps(material._textureMaps)
|
||||
{
|
||||
|
@ -46,6 +49,8 @@ Material::Material(const Material& material) :
|
|||
Material& Material::operator= (const Material& material) {
|
||||
QMutexLocker locker(&_textureMapsMutex);
|
||||
|
||||
_name = material._name;
|
||||
|
||||
_key = (material._key);
|
||||
_textureMaps = (material._textureMaps);
|
||||
_hasCalculatedTextureInfo = false;
|
||||
|
@ -222,3 +227,14 @@ bool Material::calculateMaterialInfo() const {
|
|||
}
|
||||
return _hasCalculatedTextureInfo;
|
||||
}
|
||||
|
||||
void Material::setTextureTransforms(const Transform& transform) {
|
||||
for (auto &textureMapItem : _textureMaps) {
|
||||
if (textureMapItem.second) {
|
||||
textureMapItem.second->setTextureTransform(transform);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < NUM_TEXCOORD_TRANSFORMS; i++) {
|
||||
_texMapArrayBuffer.edit<TexMapArraySchema>()._texcoordTransforms[i] = transform.getMatrix();
|
||||
}
|
||||
}
|
|
@ -15,11 +15,14 @@
|
|||
|
||||
#include <bitset>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
|
||||
#include <ColorUtils.h>
|
||||
|
||||
#include <gpu/Resource.h>
|
||||
|
||||
class Transform;
|
||||
|
||||
namespace graphics {
|
||||
|
||||
class TextureMap;
|
||||
|
@ -351,6 +354,15 @@ public:
|
|||
size_t getTextureSize() const { calculateMaterialInfo(); return _textureSize; }
|
||||
bool hasTextureInfo() const { return _hasCalculatedTextureInfo; }
|
||||
|
||||
void setTextureTransforms(const Transform& transform);
|
||||
|
||||
const std::string& getName() { return _name; }
|
||||
|
||||
void setModel(const std::string& model) { _model = model; }
|
||||
|
||||
protected:
|
||||
std::string _name { "" };
|
||||
|
||||
private:
|
||||
mutable MaterialKey _key;
|
||||
mutable UniformBufferView _schemaBuffer;
|
||||
|
@ -364,10 +376,46 @@ private:
|
|||
mutable bool _hasCalculatedTextureInfo { false };
|
||||
bool calculateMaterialInfo() const;
|
||||
|
||||
std::string _model { "hifi_pbr" };
|
||||
|
||||
};
|
||||
typedef std::shared_ptr< Material > MaterialPointer;
|
||||
|
||||
class MaterialLayer {
|
||||
public:
|
||||
MaterialLayer(MaterialPointer material, quint16 priority) : material(material), priority(priority) {}
|
||||
|
||||
MaterialPointer material { nullptr };
|
||||
quint16 priority { 0 };
|
||||
};
|
||||
|
||||
class MaterialLayerCompare {
|
||||
public:
|
||||
bool operator() (MaterialLayer left, MaterialLayer right) {
|
||||
return left.priority < right.priority;
|
||||
}
|
||||
};
|
||||
|
||||
class MultiMaterial : public std::priority_queue<MaterialLayer, std::vector<MaterialLayer>, MaterialLayerCompare> {
|
||||
public:
|
||||
bool remove(const MaterialPointer& value) {
|
||||
auto it = c.begin();
|
||||
while (it != c.end()) {
|
||||
if (it->material == value) {
|
||||
break;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
if (it != c.end()) {
|
||||
c.erase(it);
|
||||
std::make_heap(c.begin(), c.end(), comp);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -54,11 +54,9 @@ void KeyboardMouseDevice::InputDevice::focusOutEvent() {
|
|||
|
||||
void KeyboardMouseDevice::keyPressEvent(QKeyEvent* event) {
|
||||
auto input = _inputDevice->makeInput((Qt::Key) event->key());
|
||||
if (!(event->modifiers() & Qt::KeyboardModifier::ControlModifier)) {
|
||||
auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel());
|
||||
if (result.second) {
|
||||
// key pressed again ? without catching the release event ?
|
||||
}
|
||||
auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel());
|
||||
if (result.second) {
|
||||
// key pressed again ? without catching the release event ?
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,6 +235,7 @@ controller::Input::NamedVector KeyboardMouseDevice::InputDevice::getAvailableInp
|
|||
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageUp), QKeySequence(Qt::Key_PageUp).toString()));
|
||||
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString()));
|
||||
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Tab), QKeySequence(Qt::Key_Tab).toString()));
|
||||
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Control), "Control"));
|
||||
|
||||
availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseButton"));
|
||||
availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseButton"));
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
//
|
||||
// Created by Sam Gondelman on 2/9/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "MaterialCache.h"
|
||||
|
||||
#include "QJsonObject"
|
||||
#include "QJsonDocument"
|
||||
#include "QJsonArray"
|
||||
|
||||
NetworkMaterialResource::NetworkMaterialResource(const QUrl& url) :
|
||||
Resource(url) {}
|
||||
|
||||
void NetworkMaterialResource::downloadFinished(const QByteArray& data) {
|
||||
parsedMaterials.reset();
|
||||
|
||||
if (_url.toString().contains(".json")) {
|
||||
parsedMaterials = parseJSONMaterials(QJsonDocument::fromJson(data));
|
||||
}
|
||||
|
||||
// TODO: parse other material types
|
||||
|
||||
finishedLoading(true);
|
||||
}
|
||||
|
||||
bool NetworkMaterialResource::parseJSONColor(const QJsonValue& array, glm::vec3& color, bool& isSRGB) {
|
||||
if (array.isArray()) {
|
||||
QJsonArray colorArray = array.toArray();
|
||||
if (colorArray.size() >= 3 && colorArray[0].isDouble() && colorArray[1].isDouble() && colorArray[2].isDouble()) {
|
||||
isSRGB = true;
|
||||
if (colorArray.size() >= 4) {
|
||||
if (colorArray[3].isBool()) {
|
||||
isSRGB = colorArray[3].toBool();
|
||||
}
|
||||
}
|
||||
color = glm::vec3(colorArray[0].toDouble(), colorArray[1].toDouble(), colorArray[2].toDouble());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMaterials(const QJsonDocument& materialJSON) {
|
||||
ParsedMaterials toReturn;
|
||||
if (!materialJSON.isNull() && materialJSON.isObject()) {
|
||||
QJsonObject materialJSONObject = materialJSON.object();
|
||||
for (auto& key : materialJSONObject.keys()) {
|
||||
if (key == "materialVersion") {
|
||||
auto value = materialJSONObject.value(key);
|
||||
if (value.isDouble()) {
|
||||
toReturn.version = (uint)value.toInt();
|
||||
}
|
||||
} else if (key == "materials") {
|
||||
auto materialsValue = materialJSONObject.value(key);
|
||||
if (materialsValue.isArray()) {
|
||||
QJsonArray materials = materialsValue.toArray();
|
||||
for (auto material : materials) {
|
||||
if (!material.isNull() && material.isObject()) {
|
||||
auto parsedMaterial = parseJSONMaterial(material.toObject());
|
||||
toReturn.networkMaterials[parsedMaterial.first] = parsedMaterial.second;
|
||||
toReturn.names.push_back(parsedMaterial.first);
|
||||
}
|
||||
}
|
||||
} else if (materialsValue.isObject()) {
|
||||
auto parsedMaterial = parseJSONMaterial(materialsValue.toObject());
|
||||
toReturn.networkMaterials[parsedMaterial.first] = parsedMaterial.second;
|
||||
toReturn.names.push_back(parsedMaterial.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
std::pair<std::string, std::shared_ptr<NetworkMaterial>> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON) {
|
||||
std::string name = "";
|
||||
std::shared_ptr<NetworkMaterial> material = std::make_shared<NetworkMaterial>();
|
||||
for (auto& key : materialJSON.keys()) {
|
||||
if (key == "name") {
|
||||
auto nameJSON = materialJSON.value(key);
|
||||
if (nameJSON.isString()) {
|
||||
name = nameJSON.toString().toStdString();
|
||||
}
|
||||
} else if (key == "model") {
|
||||
auto modelJSON = materialJSON.value(key);
|
||||
if (modelJSON.isString()) {
|
||||
material->setModel(modelJSON.toString().toStdString());
|
||||
}
|
||||
} else if (key == "emissive") {
|
||||
glm::vec3 color;
|
||||
bool isSRGB;
|
||||
bool valid = parseJSONColor(materialJSON.value(key), color, isSRGB);
|
||||
if (valid) {
|
||||
material->setEmissive(color, isSRGB);
|
||||
}
|
||||
} else if (key == "opacity") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isDouble()) {
|
||||
material->setOpacity(value.toDouble());
|
||||
}
|
||||
} else if (key == "unlit") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isBool()) {
|
||||
material->setUnlit(value.toBool());
|
||||
}
|
||||
} else if (key == "albedo") {
|
||||
glm::vec3 color;
|
||||
bool isSRGB;
|
||||
bool valid = parseJSONColor(materialJSON.value(key), color, isSRGB);
|
||||
if (valid) {
|
||||
material->setAlbedo(color, isSRGB);
|
||||
}
|
||||
} else if (key == "roughness") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isDouble()) {
|
||||
material->setRoughness(value.toDouble());
|
||||
}
|
||||
} else if (key == "metallic") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isDouble()) {
|
||||
material->setMetallic(value.toDouble());
|
||||
}
|
||||
} else if (key == "scattering") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isDouble()) {
|
||||
material->setScattering(value.toDouble());
|
||||
}
|
||||
} else if (key == "emissiveMap") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isString()) {
|
||||
material->setEmissiveMap(value.toString());
|
||||
}
|
||||
} else if (key == "albedoMap") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isString()) {
|
||||
bool useAlphaChannel = false;
|
||||
auto opacityMap = materialJSON.find("opacityMap");
|
||||
if (opacityMap != materialJSON.end() && opacityMap->isString() && opacityMap->toString() == value.toString()) {
|
||||
useAlphaChannel = true;
|
||||
}
|
||||
material->setAlbedoMap(value.toString(), useAlphaChannel);
|
||||
}
|
||||
} else if (key == "roughnessMap") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isString()) {
|
||||
material->setRoughnessMap(value.toString(), false);
|
||||
}
|
||||
} else if (key == "glossMap") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isString()) {
|
||||
material->setRoughnessMap(value.toString(), true);
|
||||
}
|
||||
} else if (key == "metallicMap") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isString()) {
|
||||
material->setMetallicMap(value.toString(), false);
|
||||
}
|
||||
} else if (key == "specularMap") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isString()) {
|
||||
material->setMetallicMap(value.toString(), true);
|
||||
}
|
||||
} else if (key == "normalMap") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isString()) {
|
||||
material->setNormalMap(value.toString(), false);
|
||||
}
|
||||
} else if (key == "bumpMap") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isString()) {
|
||||
material->setNormalMap(value.toString(), true);
|
||||
}
|
||||
} else if (key == "occlusionMap") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isString()) {
|
||||
material->setOcclusionMap(value.toString());
|
||||
}
|
||||
} else if (key == "scatteringMap") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isString()) {
|
||||
material->setScatteringMap(value.toString());
|
||||
}
|
||||
} else if (key == "lightMap") {
|
||||
auto value = materialJSON.value(key);
|
||||
if (value.isString()) {
|
||||
material->setLightmapMap(value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::pair<std::string, std::shared_ptr<NetworkMaterial>>(name, material);
|
||||
}
|
||||
|
||||
MaterialCache& MaterialCache::instance() {
|
||||
static MaterialCache _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
NetworkMaterialResourcePointer MaterialCache::getMaterial(const QUrl& url) {
|
||||
return ResourceCache::getResource(url, QUrl(), nullptr).staticCast<NetworkMaterialResource>();
|
||||
}
|
||||
|
||||
QSharedPointer<Resource> MaterialCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, const void* extra) {
|
||||
return QSharedPointer<Resource>(new NetworkMaterialResource(url), &Resource::deleter);
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Created by Sam Gondelman on 2/9/2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#ifndef hifi_MaterialCache_h
|
||||
#define hifi_MaterialCache_h
|
||||
|
||||
#include <ResourceCache.h>
|
||||
|
||||
#include "glm/glm.hpp"
|
||||
|
||||
#include "ModelCache.h"
|
||||
|
||||
class NetworkMaterialResource : public Resource {
|
||||
public:
|
||||
NetworkMaterialResource(const QUrl& url);
|
||||
|
||||
QString getType() const override { return "NetworkMaterial"; }
|
||||
|
||||
virtual void downloadFinished(const QByteArray& data) override;
|
||||
|
||||
typedef struct ParsedMaterials {
|
||||
uint version { 1 };
|
||||
std::vector<std::string> names;
|
||||
std::unordered_map<std::string, std::shared_ptr<NetworkMaterial>> networkMaterials;
|
||||
|
||||
void reset() {
|
||||
version = 1;
|
||||
names.clear();
|
||||
networkMaterials.clear();
|
||||
}
|
||||
|
||||
} ParsedMaterials;
|
||||
|
||||
ParsedMaterials parsedMaterials;
|
||||
|
||||
static ParsedMaterials parseJSONMaterials(const QJsonDocument& materialJSON);
|
||||
static std::pair<std::string, std::shared_ptr<NetworkMaterial>> parseJSONMaterial(const QJsonObject& materialJSON);
|
||||
|
||||
private:
|
||||
static bool parseJSONColor(const QJsonValue& array, glm::vec3& color, bool& isSRGB);
|
||||
};
|
||||
|
||||
using NetworkMaterialResourcePointer = QSharedPointer<NetworkMaterialResource>;
|
||||
|
||||
class MaterialCache : public ResourceCache {
|
||||
public:
|
||||
static MaterialCache& instance();
|
||||
|
||||
NetworkMaterialResourcePointer getMaterial(const QUrl& url);
|
||||
|
||||
protected:
|
||||
virtual QSharedPointer<Resource> createResource(const QUrl& url, const QSharedPointer<Resource>& fallback, const void* extra) override;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -425,7 +425,7 @@ bool Geometry::areTexturesLoaded() const {
|
|||
return true;
|
||||
}
|
||||
|
||||
const std::shared_ptr<const NetworkMaterial> Geometry::getShapeMaterial(int partID) const {
|
||||
const std::shared_ptr<NetworkMaterial> Geometry::getShapeMaterial(int partID) const {
|
||||
if ((partID >= 0) && (partID < (int)_meshParts->size())) {
|
||||
int materialID = _meshParts->at(partID)->materialID;
|
||||
if ((materialID >= 0) && (materialID < (int)_materials.size())) {
|
||||
|
@ -491,6 +491,15 @@ void GeometryResourceWatcher::resourceRefreshed() {
|
|||
// _instance.reset();
|
||||
}
|
||||
|
||||
NetworkMaterial::NetworkMaterial(const NetworkMaterial& m) :
|
||||
Material(m),
|
||||
_textures(m._textures),
|
||||
_albedoTransform(m._albedoTransform),
|
||||
_lightmapTransform(m._lightmapTransform),
|
||||
_lightmapParams(m._lightmapParams),
|
||||
_isOriginal(m._isOriginal)
|
||||
{}
|
||||
|
||||
const QString NetworkMaterial::NO_TEXTURE = QString();
|
||||
|
||||
const QString& NetworkMaterial::getTextureName(MapChannel channel) {
|
||||
|
@ -532,19 +541,85 @@ graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl
|
|||
}
|
||||
|
||||
graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& url, image::TextureUsage::Type type, MapChannel channel) {
|
||||
const auto texture = DependencyManager::get<TextureCache>()->getTexture(url, type);
|
||||
_textures[channel].texture = texture;
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
if (textureCache) {
|
||||
auto texture = textureCache->getTexture(url, type);
|
||||
_textures[channel].texture = texture;
|
||||
|
||||
auto map = std::make_shared<graphics::TextureMap>();
|
||||
map->setTextureSource(texture->_textureSource);
|
||||
auto map = std::make_shared<graphics::TextureMap>();
|
||||
if (texture) {
|
||||
map->setTextureSource(texture->_textureSource);
|
||||
}
|
||||
|
||||
return map;
|
||||
return map;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NetworkMaterial::setAlbedoMap(const QString& url, bool useAlphaChannel) {
|
||||
auto map = fetchTextureMap(QUrl(url), image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP);
|
||||
if (map) {
|
||||
map->setUseAlphaChannel(useAlphaChannel);
|
||||
setTextureMap(MapChannel::ALBEDO_MAP, map);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkMaterial::setNormalMap(const QString& url, bool isBumpmap) {
|
||||
auto map = fetchTextureMap(QUrl(url), isBumpmap ? image::TextureUsage::BUMP_TEXTURE : image::TextureUsage::NORMAL_TEXTURE, MapChannel::NORMAL_MAP);
|
||||
if (map) {
|
||||
setTextureMap(MapChannel::NORMAL_MAP, map);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkMaterial::setRoughnessMap(const QString& url, bool isGloss) {
|
||||
auto map = fetchTextureMap(QUrl(url), isGloss ? image::TextureUsage::GLOSS_TEXTURE : image::TextureUsage::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP);
|
||||
if (map) {
|
||||
setTextureMap(MapChannel::ROUGHNESS_MAP, map);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkMaterial::setMetallicMap(const QString& url, bool isSpecular) {
|
||||
auto map = fetchTextureMap(QUrl(url), isSpecular ? image::TextureUsage::SPECULAR_TEXTURE : image::TextureUsage::METALLIC_TEXTURE, MapChannel::METALLIC_MAP);
|
||||
if (map) {
|
||||
setTextureMap(MapChannel::METALLIC_MAP, map);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkMaterial::setOcclusionMap(const QString& url) {
|
||||
auto map = fetchTextureMap(QUrl(url), image::TextureUsage::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP);
|
||||
if (map) {
|
||||
setTextureMap(MapChannel::OCCLUSION_MAP, map);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkMaterial::setEmissiveMap(const QString& url) {
|
||||
auto map = fetchTextureMap(QUrl(url), image::TextureUsage::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP);
|
||||
if (map) {
|
||||
setTextureMap(MapChannel::EMISSIVE_MAP, map);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkMaterial::setScatteringMap(const QString& url) {
|
||||
auto map = fetchTextureMap(QUrl(url), image::TextureUsage::SCATTERING_TEXTURE, MapChannel::SCATTERING_MAP);
|
||||
if (map) {
|
||||
setTextureMap(MapChannel::SCATTERING_MAP, map);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkMaterial::setLightmapMap(const QString& url) {
|
||||
auto map = fetchTextureMap(QUrl(url), image::TextureUsage::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP);
|
||||
if (map) {
|
||||
//map->setTextureTransform(_lightmapTransform);
|
||||
//map->setLightmapOffsetScale(_lightmapParams.x, _lightmapParams.y);
|
||||
setTextureMap(MapChannel::LIGHTMAP_MAP, map);
|
||||
}
|
||||
}
|
||||
|
||||
NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl) :
|
||||
graphics::Material(*material._material)
|
||||
graphics::Material(*material._material),
|
||||
_textures(MapChannel::NUM_MAP_CHANNELS)
|
||||
{
|
||||
_textures = Textures(MapChannel::NUM_MAP_CHANNELS);
|
||||
_name = material.name.toStdString();
|
||||
if (!material.albedoTexture.filename.isEmpty()) {
|
||||
auto map = fetchTextureMap(textureBaseUrl, material.albedoTexture, image::TextureUsage::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP);
|
||||
_albedoTransform = material.albedoTexture.transform;
|
||||
|
@ -653,6 +728,7 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) {
|
|||
|
||||
if (!occlusionName.isEmpty()) {
|
||||
auto url = textureMap.contains(occlusionName) ? textureMap[occlusionName].toUrl() : QUrl();
|
||||
// FIXME: we need to handle the occlusion map transform here
|
||||
auto map = fetchTextureMap(url, image::TextureUsage::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP);
|
||||
setTextureMap(MapChannel::OCCLUSION_MAP, map);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ public:
|
|||
|
||||
const FBXGeometry& getFBXGeometry() const { return *_fbxGeometry; }
|
||||
const GeometryMeshes& getMeshes() const { return *_meshes; }
|
||||
const std::shared_ptr<const NetworkMaterial> getShapeMaterial(int shapeID) const;
|
||||
const std::shared_ptr<NetworkMaterial> getShapeMaterial(int shapeID) const;
|
||||
|
||||
const QVariantMap getTextures() const;
|
||||
void setTextures(const QVariantMap& textureMap);
|
||||
|
@ -131,7 +131,6 @@ private:
|
|||
Geometry::Pointer& _geometryRef;
|
||||
};
|
||||
|
||||
|
||||
/// Stores cached model geometries.
|
||||
class ModelCache : public ResourceCache, public Dependency {
|
||||
Q_OBJECT
|
||||
|
@ -161,7 +160,18 @@ class NetworkMaterial : public graphics::Material {
|
|||
public:
|
||||
using MapChannel = graphics::Material::MapChannel;
|
||||
|
||||
NetworkMaterial() : _textures(MapChannel::NUM_MAP_CHANNELS) {}
|
||||
NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl);
|
||||
NetworkMaterial(const NetworkMaterial& material);
|
||||
|
||||
void setAlbedoMap(const QString& url, bool useAlphaChannel);
|
||||
void setNormalMap(const QString& url, bool isBumpmap);
|
||||
void setRoughnessMap(const QString& url, bool isGloss);
|
||||
void setMetallicMap(const QString& url, bool isSpecular);
|
||||
void setOcclusionMap(const QString& url);
|
||||
void setEmissiveMap(const QString& url);
|
||||
void setScatteringMap(const QString& url);
|
||||
void setLightmapMap(const QString& url);
|
||||
|
||||
protected:
|
||||
friend class Geometry;
|
||||
|
|
|
@ -283,6 +283,8 @@ QSharedPointer<Resource> TextureCache::createResource(const QUrl& url, const QSh
|
|||
return QSharedPointer<Resource>(texture, &Resource::deleter);
|
||||
}
|
||||
|
||||
int networkTexturePointerMetaTypeId = qRegisterMetaType<QWeakPointer<NetworkTexture>>();
|
||||
|
||||
NetworkTexture::NetworkTexture(const QUrl& url) :
|
||||
Resource(url),
|
||||
_type(),
|
||||
|
|
|
@ -163,7 +163,6 @@ public:
|
|||
NetworkTexturePointer getTexture(const QUrl& url, image::TextureUsage::Type type = image::TextureUsage::DEFAULT_TEXTURE,
|
||||
const QByteArray& content = QByteArray(), int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS);
|
||||
|
||||
|
||||
gpu::TexturePointer getTextureByHash(const std::string& hash);
|
||||
gpu::TexturePointer cacheTextureByHash(const std::string& hash, const gpu::TexturePointer& texture);
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ void ResourceRequest::send() {
|
|||
QMetaObject::invokeMethod(this, "send", Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Q_ASSERT(_state == NotStarted);
|
||||
|
||||
_state = InProgress;
|
||||
|
|
|
@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::EntityEdit:
|
||||
case PacketType::EntityData:
|
||||
case PacketType::EntityPhysics:
|
||||
return static_cast<PacketVersion>(EntityVersion::SoftEntities);
|
||||
return static_cast<PacketVersion>(EntityVersion::MaterialEntities);
|
||||
case PacketType::EntityQuery:
|
||||
return static_cast<PacketVersion>(EntityQueryPacketVersion::RemovedJurisdictions);
|
||||
case PacketType::AvatarIdentity:
|
||||
|
|
|
@ -217,7 +217,8 @@ enum class EntityVersion : PacketVersion {
|
|||
OwnershipChallengeFix,
|
||||
ZoneLightInheritModes = 82,
|
||||
ZoneStageRemoved,
|
||||
SoftEntities
|
||||
SoftEntities,
|
||||
MaterialEntities
|
||||
};
|
||||
|
||||
enum class EntityScriptCallMethodVersion : PacketVersion {
|
||||
|
|
|
@ -381,6 +381,17 @@ bool OctreePacketData::appendValue(float value) {
|
|||
return success;
|
||||
}
|
||||
|
||||
bool OctreePacketData::appendValue(const glm::vec2& value) {
|
||||
const unsigned char* data = (const unsigned char*)&value;
|
||||
int length = sizeof(value);
|
||||
bool success = append(data, length);
|
||||
if (success) {
|
||||
_bytesOfValues += length;
|
||||
_totalBytesOfValues += length;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool OctreePacketData::appendValue(const glm::vec3& value) {
|
||||
const unsigned char* data = (const unsigned char*)&value;
|
||||
int length = sizeof(value);
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
#include <NLPacket.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
||||
#include "MaterialMappingMode.h"
|
||||
|
||||
#include "OctreeConstants.h"
|
||||
#include "OctreeElement.h"
|
||||
|
||||
|
@ -162,6 +164,9 @@ public:
|
|||
/// appends a float value to the end of the stream, may fail if new data stream is too long to fit in packet
|
||||
bool appendValue(float value);
|
||||
|
||||
/// appends a vec2 to the end of the stream, may fail if new data stream is too long to fit in packet
|
||||
bool appendValue(const glm::vec2& value);
|
||||
|
||||
/// appends a non-position vector to the end of the stream, may fail if new data stream is too long to fit in packet
|
||||
bool appendValue(const glm::vec3& value);
|
||||
|
||||
|
@ -248,6 +253,7 @@ public:
|
|||
static quint64 getTotalBytesOfColor() { return _totalBytesOfColor; } /// total bytes of color
|
||||
|
||||
static int unpackDataFromBytes(const unsigned char* dataBytes, float& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
|
||||
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec2& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
|
||||
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
|
||||
static int unpackDataFromBytes(const unsigned char* dataBytes, bool& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
|
||||
static int unpackDataFromBytes(const unsigned char* dataBytes, quint64& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
|
||||
|
@ -257,6 +263,7 @@ public:
|
|||
static int unpackDataFromBytes(const unsigned char* dataBytes, rgbColor& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
|
||||
static int unpackDataFromBytes(const unsigned char* dataBytes, glm::quat& result) { int bytes = unpackOrientationQuatFromBytes(dataBytes, result); return bytes; }
|
||||
static int unpackDataFromBytes(const unsigned char* dataBytes, ShapeType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
|
||||
static int unpackDataFromBytes(const unsigned char* dataBytes, MaterialMappingMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); }
|
||||
static int unpackDataFromBytes(const unsigned char* dataBytes, QString& result);
|
||||
static int unpackDataFromBytes(const unsigned char* dataBytes, QUuid& result);
|
||||
static int unpackDataFromBytes(const unsigned char* dataBytes, xColor& result);
|
||||
|
|
|
@ -58,6 +58,7 @@ void RenderEventHandler::onInitalize() {
|
|||
return;
|
||||
}
|
||||
|
||||
_canvas.setThreadContext();
|
||||
if (!_canvas.makeCurrent()) {
|
||||
qFatal("Unable to make QML rendering context current on render thread");
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(const Transform
|
|||
_cauterizedTransform = renderTransform;
|
||||
}
|
||||
|
||||
void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const {
|
||||
void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const {
|
||||
bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE && renderMode != RenderArgs::RenderMode::SECONDARY_CAMERA_RENDER_MODE) && _enableCauterization;
|
||||
if (useCauterizedMesh) {
|
||||
if (_cauterizedClusterBuffer) {
|
||||
|
@ -46,7 +46,7 @@ void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::S
|
|||
}
|
||||
batch.setModelTransform(_cauterizedTransform);
|
||||
} else {
|
||||
ModelMeshPartPayload::bindTransform(batch, locations, renderMode);
|
||||
ModelMeshPartPayload::bindTransform(batch, renderMode);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
|
||||
void updateTransformForCauterizedMesh(const Transform& renderTransform);
|
||||
|
||||
void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override;
|
||||
void bindTransform(gpu::Batch& batch, RenderArgs::RenderMode renderMode) const override;
|
||||
|
||||
void setEnableCauterization(bool enableCauterization) { _enableCauterization = enableCauterization; }
|
||||
|
||||
|
|
|
@ -1318,7 +1318,6 @@ void GeometryCache::renderUnitQuad(gpu::Batch& batch, const glm::vec4& color, in
|
|||
renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, color, id);
|
||||
}
|
||||
|
||||
|
||||
void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, const glm::vec2& maxCorner,
|
||||
const glm::vec2& texCoordMinCorner, const glm::vec2& texCoordMaxCorner,
|
||||
const glm::vec4& color, int id) {
|
||||
|
|
|
@ -453,6 +453,10 @@ void SelectionToHighlight::run(const render::RenderContextPointer& renderContext
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numLayers == 0) {
|
||||
renderContext->taskFlow.abortTask();
|
||||
}
|
||||
}
|
||||
|
||||
void ExtractSelectionName::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue