mirror of
https://github.com/lubosz/overte.git
synced 2025-04-15 09:49:57 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into discovery
This commit is contained in:
commit
e6746acf4c
179 changed files with 6158 additions and 3579 deletions
.eslintrc.js
assignment-client/src/audio
cmake/macros
domain-server
resources
src
interface
resources
controllers
images
qml
src
libraries
audio-client/src
audio/src
avatars/src
entities-renderer/src
EntityTreeRenderer.cppEntityTreeRenderer.hRenderableEntityItem.cppRenderableEntityItem.hRenderableLightEntityItem.cppRenderableModelEntityItem.cppRenderableModelEntityItem.hRenderablePolyLineEntityItem.cppRenderablePolyLineEntityItem.hRenderablePolyVoxEntityItem.hRenderableShapeEntityItem.cppRenderableShapeEntityItem.hRenderableTextEntityItem.cppRenderableWebEntityItem.cpppaintStroke.slfpolyvox.slftextured_particle.slv
entities/src
gl/src/gl
gpu-gl/src/gpu/gl
gpu/src/gpu
model-networking/src/model-networking
model/src/model
networking/src
|
@ -72,6 +72,6 @@ module.exports = {
|
|||
"spaced-comment": ["error", "always", {
|
||||
"line": { "markers": ["/"] }
|
||||
}],
|
||||
"space-before-function-paren": ["error", {"anonymous": "always", "named": "never"}]
|
||||
"space-before-function-paren": ["error", {"anonymous": "ignore", "named": "never"}]
|
||||
}
|
||||
};
|
||||
|
|
|
@ -100,6 +100,63 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
|
|||
|
||||
const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f;
|
||||
|
||||
const int IEEE754_MANT_BITS = 23;
|
||||
const int IEEE754_EXPN_BIAS = 127;
|
||||
|
||||
//
|
||||
// for x > 0.0f, returns log2(x)
|
||||
// for x <= 0.0f, returns large negative value
|
||||
//
|
||||
// abs |error| < 8e-3, smooth (exact for x=2^N) for NPOLY=3
|
||||
// abs |error| < 2e-4, smooth (exact for x=2^N) for NPOLY=5
|
||||
// rel |error| < 0.4 from precision loss very close to 1.0f
|
||||
//
|
||||
static inline float fastlog2(float x) {
|
||||
|
||||
union { float f; int32_t i; } mant, bits = { x };
|
||||
|
||||
// split into mantissa and exponent
|
||||
mant.i = (bits.i & ((1 << IEEE754_MANT_BITS) - 1)) | (IEEE754_EXPN_BIAS << IEEE754_MANT_BITS);
|
||||
int32_t expn = (bits.i >> IEEE754_MANT_BITS) - IEEE754_EXPN_BIAS;
|
||||
|
||||
mant.f -= 1.0f;
|
||||
|
||||
// polynomial for log2(1+x) over x=[0,1]
|
||||
//x = (-0.346555386f * mant.f + 1.346555386f) * mant.f;
|
||||
x = (((-0.0821307180f * mant.f + 0.321188984f) * mant.f - 0.677784014f) * mant.f + 1.43872575f) * mant.f;
|
||||
|
||||
return x + expn;
|
||||
}
|
||||
|
||||
//
|
||||
// for -126 <= x < 128, returns exp2(x)
|
||||
//
|
||||
// rel |error| < 3e-3, smooth (exact for x=N) for NPOLY=3
|
||||
// rel |error| < 9e-6, smooth (exact for x=N) for NPOLY=5
|
||||
//
|
||||
static inline float fastexp2(float x) {
|
||||
|
||||
union { float f; int32_t i; } xi;
|
||||
|
||||
// bias such that x > 0
|
||||
x += IEEE754_EXPN_BIAS;
|
||||
//x = MAX(x, 1.0f);
|
||||
//x = MIN(x, 254.9999f);
|
||||
|
||||
// split into integer and fraction
|
||||
xi.i = (int32_t)x;
|
||||
x -= xi.i;
|
||||
|
||||
// construct exp2(xi) as a float
|
||||
xi.i <<= IEEE754_MANT_BITS;
|
||||
|
||||
// polynomial for exp2(x) over x=[0,1]
|
||||
//x = (0.339766028f * x + 0.660233972f) * x + 1.0f;
|
||||
x = (((0.0135557472f * x + 0.0520323690f) * x + 0.241379763f) * x + 0.693032121f) * x + 1.0f;
|
||||
|
||||
return x * xi.f;
|
||||
}
|
||||
|
||||
float AudioMixer::gainForSource(const PositionalAudioStream& streamToAdd,
|
||||
const AvatarAudioStream& listeningNodeStream, const glm::vec3& relativePosition, bool isEcho) {
|
||||
float gain = 1.0f;
|
||||
|
@ -148,7 +205,7 @@ float AudioMixer::gainForSource(const PositionalAudioStream& streamToAdd,
|
|||
g = (g > 1.0f) ? 1.0f : g;
|
||||
|
||||
// calculate the distance coefficient using the distance to this node
|
||||
float distanceCoefficient = exp2f(log2f(g) * log2f(distanceBetween/ATTENUATION_BEGINS_AT_DISTANCE));
|
||||
float distanceCoefficient = fastexp2(fastlog2(g) * fastlog2(distanceBetween/ATTENUATION_BEGINS_AT_DISTANCE));
|
||||
|
||||
// multiply the current attenuation coefficient by the distance coefficient
|
||||
gain *= distanceCoefficient;
|
||||
|
|
|
@ -113,7 +113,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO);
|
||||
qDebug() << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
|
||||
|
||||
connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioMixerClientData::sendSelectAudioFormat);
|
||||
connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioMixerClientData::handleMismatchAudioFormat);
|
||||
|
||||
auto emplaced = _audioStreams.emplace(
|
||||
QUuid(),
|
||||
|
@ -345,6 +345,11 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
|
|||
return result;
|
||||
}
|
||||
|
||||
void AudioMixerClientData::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) {
|
||||
qDebug() << __FUNCTION__ << "sendingNode:" << *node << "currentCodec:" << currentCodec << "recievedCodec:" << recievedCodec;
|
||||
sendSelectAudioFormat(node, currentCodec);
|
||||
}
|
||||
|
||||
void AudioMixerClientData::sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName) {
|
||||
auto replyPacket = NLPacket::create(PacketType::SelectedAudioFormat);
|
||||
replyPacket->writeString(selectedCodecName);
|
||||
|
|
|
@ -84,6 +84,7 @@ signals:
|
|||
void injectorStreamFinished(const QUuid& streamIdentifier);
|
||||
|
||||
public slots:
|
||||
void handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec);
|
||||
void sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName);
|
||||
|
||||
private:
|
||||
|
|
|
@ -23,13 +23,13 @@ function(AUTOSCRIBE_SHADER SHADER_FILE)
|
|||
|
||||
#Extract the unique include shader paths
|
||||
set(INCLUDES ${HIFI_LIBRARIES_SHADER_INCLUDE_FILES})
|
||||
#message(Hifi for includes ${INCLUDES})
|
||||
foreach(EXTRA_SHADER_INCLUDE ${INCLUDES})
|
||||
#message(${TARGET_NAME} Hifi for includes ${INCLUDES})
|
||||
foreach(EXTRA_SHADER_INCLUDE ${INCLUDES})
|
||||
list(APPEND SHADER_INCLUDES_PATHS ${EXTRA_SHADER_INCLUDE})
|
||||
endforeach()
|
||||
|
||||
list(REMOVE_DUPLICATES SHADER_INCLUDES_PATHS)
|
||||
#message(ready for includes ${SHADER_INCLUDES_PATHS})
|
||||
#message(ready for includes ${SHADER_INCLUDES_PATHS})
|
||||
|
||||
# make the scribe include arguments
|
||||
set(SCRIBE_INCLUDES)
|
||||
|
@ -77,6 +77,7 @@ endfunction()
|
|||
|
||||
|
||||
macro(AUTOSCRIBE_SHADER_LIB)
|
||||
set(HIFI_LIBRARIES_SHADER_INCLUDE_FILES "")
|
||||
file(RELATIVE_PATH RELATIVE_LIBRARY_DIR_PATH ${CMAKE_CURRENT_SOURCE_DIR} "${HIFI_LIBRARY_DIR}")
|
||||
foreach(HIFI_LIBRARY ${ARGN})
|
||||
#if (NOT TARGET ${HIFI_LIBRARY})
|
||||
|
@ -86,7 +87,7 @@ macro(AUTOSCRIBE_SHADER_LIB)
|
|||
#file(GLOB_RECURSE HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src/*.slh)
|
||||
list(APPEND HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src)
|
||||
endforeach()
|
||||
#message(${HIFI_LIBRARIES_SHADER_INCLUDE_FILES})
|
||||
#message("${TARGET_NAME} ${HIFI_LIBRARIES_SHADER_INCLUDE_FILES}")
|
||||
|
||||
file(GLOB_RECURSE SHADER_INCLUDE_FILES src/*.slh)
|
||||
file(GLOB_RECURSE SHADER_SOURCE_FILES src/*.slv src/*.slf src/*.slg)
|
||||
|
@ -95,13 +96,14 @@ macro(AUTOSCRIBE_SHADER_LIB)
|
|||
set(SHADERS_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders/${TARGET_NAME}")
|
||||
file(MAKE_DIRECTORY ${SHADERS_DIR})
|
||||
|
||||
#message(${SHADER_INCLUDE_FILES})
|
||||
#message("${TARGET_NAME} ${SHADER_INCLUDE_FILES}")
|
||||
set(AUTOSCRIBE_SHADER_SRC "")
|
||||
foreach(SHADER_FILE ${SHADER_SOURCE_FILES})
|
||||
AUTOSCRIBE_SHADER(${SHADER_FILE} ${SHADER_INCLUDE_FILES})
|
||||
file(TO_CMAKE_PATH "${AUTOSCRIBE_SHADER_RETURN}" AUTOSCRIBE_GENERATED_FILE)
|
||||
list(APPEND AUTOSCRIBE_SHADER_SRC ${AUTOSCRIBE_GENERATED_FILE})
|
||||
endforeach()
|
||||
#message(${AUTOSCRIBE_SHADER_SRC})
|
||||
#message(${TARGET_NAME} ${AUTOSCRIBE_SHADER_SRC})
|
||||
|
||||
if (WIN32)
|
||||
source_group("Shaders" FILES ${SHADER_INCLUDE_FILES})
|
||||
|
@ -116,4 +118,4 @@ macro(AUTOSCRIBE_SHADER_LIB)
|
|||
# Link library shaders, if they exist
|
||||
include_directories("${SHADERS_DIR}")
|
||||
|
||||
endmacro()
|
||||
endmacro()
|
|
@ -54,8 +54,9 @@ macro(SETUP_HIFI_LIBRARY)
|
|||
target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE})
|
||||
endforeach()
|
||||
|
||||
# Don't make scribed shaders cumulative
|
||||
# Don't make scribed shaders or QT resource files cumulative
|
||||
set(AUTOSCRIBE_SHADER_LIB_SRC "")
|
||||
set(QT_RESOURCES_FILE "")
|
||||
|
||||
target_glm()
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": 1.5,
|
||||
"version": 1.7,
|
||||
"settings": [
|
||||
{
|
||||
"name": "metaverse",
|
||||
|
@ -384,18 +384,18 @@
|
|||
"name": "standard_permissions",
|
||||
"type": "table",
|
||||
"label": "Domain-Wide User Permissions",
|
||||
"help": "Indicate which users or groups can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
||||
"help": "Indicate which types of users can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
||||
"caption": "Standard Permissions",
|
||||
"can_add_new_rows": false,
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"label": "User / Group",
|
||||
"label": "Type of User",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||
"span": 6
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -409,7 +409,7 @@
|
|||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": true
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
|
@ -445,6 +445,13 @@
|
|||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -452,33 +459,62 @@
|
|||
"non-deletable-row-values": ["localhost", "anonymous", "logged-in"]
|
||||
},
|
||||
{
|
||||
"name": "permissions",
|
||||
"name": "group_permissions",
|
||||
"type": "table",
|
||||
"caption": "Permissions for Specific Users",
|
||||
"can_add_new_rows": true,
|
||||
"caption": "Permissions for Users in Groups",
|
||||
"categorize_by_key": "permissions_id",
|
||||
"can_add_new_categories": true,
|
||||
"can_add_new_rows": false,
|
||||
"new_category_placeholder": "Add Group",
|
||||
"new_category_message": "Save and reload to see ranks",
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"label": "User / Group",
|
||||
"label": "Rank",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||
"span": 6
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn’t have their own row in the per-account section, below.</p>'>?</a>",
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
"columns": [
|
||||
{
|
||||
"name": "permissions_id",
|
||||
"label": ""
|
||||
"label": "Group Name",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_id",
|
||||
"label": "Rank ID",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_order",
|
||||
"label": "Rank Order",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_name",
|
||||
"label": "",
|
||||
"readonly": true
|
||||
},
|
||||
{
|
||||
"name": "group_id",
|
||||
"label": "Group ID",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect",
|
||||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": true
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
|
@ -514,6 +550,257 @@
|
|||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "group_forbiddens",
|
||||
"type": "table",
|
||||
"caption": "Permissions Denied to Users in Groups",
|
||||
"categorize_by_key": "permissions_id",
|
||||
"can_add_new_categories": true,
|
||||
"can_add_new_rows": false,
|
||||
"new_category_placeholder": "Add Blacklist Group",
|
||||
"new_category_message": "Save and reload to see ranks",
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"label": "Rank",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn’t have their own row in the per-account section, below.</p>'>?</a>",
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
"columns": [
|
||||
{
|
||||
"name": "permissions_id",
|
||||
"label": "Group Name",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_id",
|
||||
"label": "Rank ID",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_order",
|
||||
"label": "Rank Order",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_name",
|
||||
"label": "",
|
||||
"readonly": true
|
||||
},
|
||||
{
|
||||
"name": "group_id",
|
||||
"label": "Group ID",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect",
|
||||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
"label": "Lock / Unlock",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez",
|
||||
"label": "Rez",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp",
|
||||
"label": "Rez Temporary",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect_past_max_capacity",
|
||||
"label": "Ignore Max Capacity",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ip_permissions",
|
||||
"type": "table",
|
||||
"caption": "Permissions for Users from IP Addresses",
|
||||
"can_add_new_rows": true,
|
||||
"groups": [
|
||||
{
|
||||
"label": "IP Address",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
"columns": [
|
||||
{
|
||||
"name": "permissions_id",
|
||||
"label": ""
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect",
|
||||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
"label": "Lock / Unlock",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez",
|
||||
"label": "Rez",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp",
|
||||
"label": "Rez Temporary",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect_past_max_capacity",
|
||||
"label": "Ignore Max Capacity",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "permissions",
|
||||
"type": "table",
|
||||
"caption": "Permissions for Specific Users",
|
||||
"can_add_new_rows": true,
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"label": "User",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
"columns": [
|
||||
{
|
||||
"name": "permissions_id",
|
||||
"label": ""
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect",
|
||||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
"label": "Lock / Unlock",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez",
|
||||
"label": "Rez",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp",
|
||||
"label": "Rez Temporary",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect_past_max_capacity",
|
||||
"label": "Ignore Max Capacity",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -3,6 +3,10 @@ body {
|
|||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.table-lead .lead-line {
|
||||
background-color: black;
|
||||
}
|
||||
|
@ -20,7 +24,9 @@ body {
|
|||
top: 40px;
|
||||
}
|
||||
|
||||
.table .value-row td, .table .inputs td {
|
||||
.table .value-row td,
|
||||
.table .value-category td,
|
||||
.table .inputs td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
@ -31,6 +37,31 @@ body {
|
|||
margin-right: auto;
|
||||
}
|
||||
|
||||
.value-category:not(.inputs) {
|
||||
font-weight: bold;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.table .value-category [message]::after {
|
||||
content: attr(message);
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.table .value-row.contracted,
|
||||
.table .inputs.contracted {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toggle-category {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-category-icon {
|
||||
padding: 4px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.glyphicon-remove {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
@ -44,15 +75,6 @@ span.port {
|
|||
color: #666666;
|
||||
}
|
||||
|
||||
.locked {
|
||||
color: #428bca;
|
||||
}
|
||||
|
||||
.locked-table {
|
||||
cursor: not-allowed;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.advanced-setting {
|
||||
display: none;
|
||||
}
|
||||
|
@ -133,7 +155,7 @@ table .headers + .headers td {
|
|||
color: #222;
|
||||
}
|
||||
|
||||
table[name="security.standard_permissions"] .headers td + td, table[name="security.permissions"] .headers td + td {
|
||||
#security table .headers td + td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<ul class="nav nav-pills nav-stacked">
|
||||
</ul>
|
||||
|
||||
<button id="advanced-toggle-button" hidden=true class="btn btn-info advanced-toggle">Show advanced</button>
|
||||
<button id="advanced-toggle-button" class="btn btn-info advanced-toggle">Show advanced</button>
|
||||
<button class="btn btn-success save-button">Save and restart</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -57,15 +57,13 @@
|
|||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, false,
|
||||
(_.has(locked, group.name) && _.has(locked[group.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,
|
||||
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
|
||||
<%= getFormGroup(keypath, setting, values, true) %>
|
||||
<% }); %>
|
||||
<% }%>
|
||||
</div>
|
||||
|
|
|
@ -5,10 +5,20 @@ var Settings = {
|
|||
TRIGGER_CHANGE_CLASS: 'trigger-change',
|
||||
DATA_ROW_CLASS: 'value-row',
|
||||
DATA_COL_CLASS: 'value-col',
|
||||
DATA_CATEGORY_CLASS: 'value-category',
|
||||
ADD_ROW_BUTTON_CLASS: 'add-row',
|
||||
ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row',
|
||||
DEL_ROW_BUTTON_CLASS: 'del-row',
|
||||
DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row',
|
||||
ADD_CATEGORY_BUTTON_CLASS: 'add-category',
|
||||
ADD_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-plus add-category',
|
||||
TOGGLE_CATEGORY_COLUMN_CLASS: 'toggle-category',
|
||||
TOGGLE_CATEGORY_SPAN_CLASS: 'toggle-category-icon',
|
||||
TOGGLE_CATEGORY_SPAN_CLASSES: 'glyphicon toggle-category-icon',
|
||||
TOGGLE_CATEGORY_EXPANDED_CLASS: 'glyphicon-triangle-bottom',
|
||||
TOGGLE_CATEGORY_CONTRACTED_CLASS: 'glyphicon-triangle-right',
|
||||
DEL_CATEGORY_BUTTON_CLASS: 'del-category',
|
||||
DEL_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-remove del-category',
|
||||
MOVE_UP_BUTTON_CLASS: 'move-up',
|
||||
MOVE_UP_SPAN_CLASSES: 'glyphicon glyphicon-chevron-up move-up',
|
||||
MOVE_DOWN_BUTTON_CLASS: 'move-down',
|
||||
|
@ -31,11 +41,11 @@ var Settings = {
|
|||
};
|
||||
|
||||
var viewHelpers = {
|
||||
getFormGroup: function(keypath, setting, values, isAdvanced, isLocked) {
|
||||
getFormGroup: function(keypath, setting, values, isAdvanced) {
|
||||
form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + "' data-keypath='" + keypath + "'>";
|
||||
setting_value = _(values).valueForKeyPath(keypath);
|
||||
|
||||
if (typeof setting_value == 'undefined' || setting_value === null) {
|
||||
if (_.isUndefined(setting_value) || _.isNull(setting_value)) {
|
||||
if (_.has(setting, 'default')) {
|
||||
setting_value = setting.default;
|
||||
} else {
|
||||
|
@ -44,16 +54,13 @@ var viewHelpers = {
|
|||
}
|
||||
|
||||
label_class = 'control-label';
|
||||
if (isLocked) {
|
||||
label_class += ' locked';
|
||||
}
|
||||
|
||||
function common_attrs(extra_classes) {
|
||||
extra_classes = (typeof extra_classes !== 'undefined' ? extra_classes : "");
|
||||
extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : "");
|
||||
return " class='" + (setting.type !== 'checkbox' ? 'form-control' : '')
|
||||
+ " " + Settings.TRIGGER_CHANGE_CLASS + " " + extra_classes + "' data-short-name='"
|
||||
+ setting.name + "' name='" + keypath + "' "
|
||||
+ "id='" + (typeof setting.html_id !== 'undefined' ? setting.html_id : keypath) + "'";
|
||||
+ "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "'";
|
||||
}
|
||||
|
||||
if (setting.type === 'checkbox') {
|
||||
|
@ -61,9 +68,8 @@ var viewHelpers = {
|
|||
form_group += "<label class='" + label_class + "'>" + setting.label + "</label>"
|
||||
}
|
||||
|
||||
form_group += "<div class='toggle-checkbox-container" + (isLocked ? " disabled" : "") + "'>"
|
||||
form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "")
|
||||
form_group += (isLocked ? " disabled" : "") + "/>"
|
||||
form_group += "<div class='toggle-checkbox-container'>"
|
||||
form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "") + "/>"
|
||||
|
||||
if (setting.help) {
|
||||
form_group += "<span class='help-block checkbox-help'>" + setting.help + "</span>";
|
||||
|
@ -78,7 +84,7 @@ var viewHelpers = {
|
|||
}
|
||||
|
||||
if (input_type === 'table') {
|
||||
form_group += makeTable(setting, keypath, setting_value, isLocked)
|
||||
form_group += makeTable(setting, keypath, setting_value)
|
||||
} else {
|
||||
if (input_type === 'select') {
|
||||
form_group += "<select class='form-control' data-hidden-input='" + keypath + "'>'"
|
||||
|
@ -97,12 +103,10 @@ var viewHelpers = {
|
|||
|
||||
if (setting.href) {
|
||||
form_group += "<a href='" + setting.href + "'style='display: block;' role='button'"
|
||||
+ (isLocked ? " disabled" : "")
|
||||
+ common_attrs("btn " + setting.classes) + " target='_blank'>"
|
||||
+ setting.button_label + "</a>";
|
||||
} else {
|
||||
form_group += "<button " + common_attrs("btn " + setting.classes)
|
||||
+ (isLocked ? " disabled" : "") + ">"
|
||||
form_group += "<button " + common_attrs("btn " + setting.classes) + ">"
|
||||
+ setting.button_label + "</button>";
|
||||
}
|
||||
|
||||
|
@ -114,7 +118,7 @@ var viewHelpers = {
|
|||
|
||||
form_group += "<input type='" + input_type + "'" + common_attrs() +
|
||||
"placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
|
||||
"' value='" + setting_value + "'" + (isLocked ? " disabled" : "") + "/>"
|
||||
"' value='" + setting_value + "'/>"
|
||||
}
|
||||
|
||||
form_group += "<span class='help-block'>" + setting.help + "</span>"
|
||||
|
@ -162,19 +166,31 @@ $(document).ready(function(){
|
|||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){
|
||||
addTableRow(this);
|
||||
addTableRow($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){
|
||||
deleteTableRow(this);
|
||||
deleteTableRow($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_CATEGORY_BUTTON_CLASS, function(){
|
||||
addTableCategory($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_CATEGORY_BUTTON_CLASS, function(){
|
||||
deleteTableCategory($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.TOGGLE_CATEGORY_COLUMN_CLASS, function(){
|
||||
toggleTableCategory($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){
|
||||
moveTableRow(this, true);
|
||||
moveTableRow($(this).closest('tr'), true);
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){
|
||||
moveTableRow(this, false);
|
||||
moveTableRow($(this).closest('tr'), false);
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('keyup', function(e){
|
||||
|
@ -196,10 +212,11 @@ $(document).ready(function(){
|
|||
}
|
||||
|
||||
if (sibling.hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
|
||||
sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click()
|
||||
sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click();
|
||||
sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).click();
|
||||
|
||||
// set focus to the first input in the new row
|
||||
$target.closest('table').find('tr.inputs input:first').focus()
|
||||
$target.closest('table').find('tr.inputs input:first').focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -436,10 +453,8 @@ function setupHFAccountButton() {
|
|||
$("[data-keypath='metaverse.automatic_networking']").hide();
|
||||
}
|
||||
|
||||
var tokenLocked = _(Settings.data).valueForKeyPath("locked.metaverse.access_token");
|
||||
|
||||
// use the existing getFormGroup helper to ask for a button
|
||||
var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values, false, tokenLocked);
|
||||
var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values, false);
|
||||
|
||||
// add the button group to the top of the metaverse panel
|
||||
$('#metaverse .panel-body').prepend(buttonGroup);
|
||||
|
@ -629,7 +644,7 @@ function setupPlacesTable() {
|
|||
label: 'Places',
|
||||
html_id: Settings.PLACES_TABLE_ID,
|
||||
help: "The following places currently point to this domain.</br>To point places to this domain, "
|
||||
+ " go to the <a href='https://metaverse.highfidelity.com/user/places'>My Places</a> "
|
||||
+ " go to the <a href='" + Settings.METAVERSE_URL + "/user/places'>My Places</a> "
|
||||
+ "page in your High Fidelity Metaverse account.",
|
||||
read_only: true,
|
||||
columns: [
|
||||
|
@ -650,7 +665,7 @@ function setupPlacesTable() {
|
|||
}
|
||||
|
||||
// get a table for the places
|
||||
var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values, false, false);
|
||||
var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values, false);
|
||||
|
||||
// append the places table in the right place
|
||||
$('#places_paths .panel-body').prepend(placesTableGroup);
|
||||
|
@ -771,7 +786,7 @@ function chooseFromHighFidelityDomains(clickedButton) {
|
|||
modal_buttons["success"] = {
|
||||
label: 'Create new domain',
|
||||
callback: function() {
|
||||
window.open("https://metaverse.highfidelity.com/user/domains", '_blank');
|
||||
window.open(Settings.METAVERSE_URL + "/user/domains", '_blank');
|
||||
}
|
||||
}
|
||||
modal_body = "<p>You do not have any domains in your High Fidelity account." +
|
||||
|
@ -850,10 +865,8 @@ function reloadSettings(callback) {
|
|||
Settings.data = data;
|
||||
Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true);
|
||||
|
||||
if (!_.has(data["locked"], "metaverse") && !_.has(data["locked"]["metaverse"], "id")) {
|
||||
// append the domain selection modal, as long as it's not locked
|
||||
appendDomainIDButtons();
|
||||
}
|
||||
// append the domain selection modal
|
||||
appendDomainIDButtons();
|
||||
|
||||
// call our method to setup the HF account button
|
||||
setupHFAccountButton();
|
||||
|
@ -866,12 +879,6 @@ function reloadSettings(callback) {
|
|||
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
// add tooltip to locked settings
|
||||
$('label.locked').tooltip({
|
||||
placement: 'right',
|
||||
title: 'This setting is in the master config file and cannot be changed'
|
||||
});
|
||||
|
||||
// call the callback now that settings are loaded
|
||||
callback(true);
|
||||
}).fail(function() {
|
||||
|
@ -920,9 +927,10 @@ $('body').on('click', '.save-button', function(e){
|
|||
return false;
|
||||
});
|
||||
|
||||
function makeTable(setting, keypath, setting_value, isLocked) {
|
||||
function makeTable(setting, keypath, setting_value) {
|
||||
var isArray = !_.has(setting, 'key');
|
||||
var isHash = !isArray;
|
||||
var categoryKey = setting.categorize_by_key;
|
||||
var isCategorized = !!categoryKey && isArray;
|
||||
|
||||
if (!isArray && setting.can_order) {
|
||||
setting.can_order = false;
|
||||
|
@ -937,9 +945,10 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
var nonDeletableRowKey = setting["non-deletable-row-key"];
|
||||
var nonDeletableRowValues = setting["non-deletable-row-values"];
|
||||
|
||||
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' data-short-name='" + setting.name
|
||||
+ "' name='" + keypath + "' id='" + (typeof setting.html_id !== 'undefined' ? setting.html_id : keypath)
|
||||
+ "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>";
|
||||
html += "<table class='table table-bordered' " +
|
||||
"data-short-name='" + setting.name + "' name='" + keypath + "' " +
|
||||
"id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "' " +
|
||||
"data-setting-type='" + (isArray ? 'array' : 'hash') + "'>";
|
||||
|
||||
if (setting.caption) {
|
||||
html += "<caption>" + setting.caption + "</caption>"
|
||||
|
@ -951,7 +960,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
_.each(setting.groups, function (group) {
|
||||
html += "<td colspan='" + group.span + "'><strong>" + group.label + "</strong></td>"
|
||||
})
|
||||
if (!isLocked && !setting.read_only) {
|
||||
if (!setting.read_only) {
|
||||
if (setting.can_order) {
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES +
|
||||
"'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>";
|
||||
|
@ -972,24 +981,43 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
html += "<td class='key'><strong>" + setting.key.label + "</strong></td>" // Key
|
||||
}
|
||||
|
||||
var numVisibleColumns = 0;
|
||||
_.each(setting.columns, function(col) {
|
||||
html += "<td class='data " + (col.class ? col.class : '') + "'><strong>" + col.label + "</strong></td>" // Data
|
||||
if (!col.hidden) numVisibleColumns++;
|
||||
html += "<td " + (col.hidden ? "style='display: none;'" : "") + "class='data " +
|
||||
(col.class ? col.class : '') + "'><strong>" + col.label + "</strong></td>" // Data
|
||||
})
|
||||
|
||||
if (!isLocked && !setting.read_only) {
|
||||
if (!setting.read_only) {
|
||||
if (setting.can_order) {
|
||||
numVisibleColumns++;
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES +
|
||||
"'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>";
|
||||
}
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'></td></tr>"
|
||||
numVisibleColumns++;
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'></td></tr>";
|
||||
}
|
||||
|
||||
// populate rows in the table from existing values
|
||||
var row_num = 1;
|
||||
|
||||
if (keypath.length > 0 && _.size(setting_value) > 0) {
|
||||
var rowIsObject = setting.columns.length > 1;
|
||||
|
||||
_.each(setting_value, function(row, rowIndexOrName) {
|
||||
html += "<tr class='" + Settings.DATA_ROW_CLASS + "'" + (isArray ? "" : "name='" + keypath + "." + rowIndexOrName + "'") + ">"
|
||||
var categoryPair = {};
|
||||
var categoryValue = "";
|
||||
if (isCategorized) {
|
||||
categoryValue = rowIsObject ? row[categoryKey] : row;
|
||||
categoryPair[categoryKey] = categoryValue;
|
||||
if (_.findIndex(setting_value, categoryPair) === rowIndexOrName) {
|
||||
html += makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, setting.can_add_new_categories, "");
|
||||
}
|
||||
}
|
||||
|
||||
html += "<tr class='" + Settings.DATA_ROW_CLASS + "' " +
|
||||
(isCategorized ? ("data-category='" + categoryValue + "'") : "") + " " +
|
||||
(isArray ? "" : "name='" + keypath + "." + rowIndexOrName + "'") + ">";
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='numbered'>" + row_num + "</td>"
|
||||
|
@ -1003,8 +1031,8 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
|
||||
_.each(setting.columns, function(col) {
|
||||
|
||||
var colValue, colName;
|
||||
if (isArray) {
|
||||
rowIsObject = setting.columns.length > 1;
|
||||
colValue = rowIsObject ? row[col.name] : row;
|
||||
colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : "");
|
||||
} else {
|
||||
|
@ -1016,22 +1044,30 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
|| (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1);
|
||||
|
||||
if (isArray && col.type === "checkbox" && col.editable) {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||
+ "<input type='checkbox' class='form-control table-checkbox' "
|
||||
+ "name='" + colName + "'" + (colValue ? " checked" : "") + " /></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
|
||||
"<input type='checkbox' class='form-control table-checkbox' " +
|
||||
"name='" + colName + "'" + (colValue ? " checked" : "") + "/>" +
|
||||
"</td>";
|
||||
} else if (isArray && col.type === "time" && col.editable) {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||
+ "<input type='time' class='form-control table-time' "
|
||||
+ "name='" + colName + "' value='" + (colValue || col.default || "00:00") + "' /></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
|
||||
"<input type='time' class='form-control table-time' name='" + colName + "' " +
|
||||
"value='" + (colValue || col.default || "00:00") + "'/>" +
|
||||
"</td>";
|
||||
} else {
|
||||
// Use a hidden input so that the values are posted.
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "' name='" + colName + "'>"
|
||||
+ colValue + "<input type='hidden' name='" + colName + "' value='" + colValue + "'/></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "' " + (col.hidden ? "style='display: none;'" : "") +
|
||||
"name='" + colName + "'>" +
|
||||
colValue +
|
||||
"<input type='hidden' name='" + colName + "' value='" + colValue + "'/>" +
|
||||
"</td>";
|
||||
}
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
if (!isLocked && !setting.read_only) {
|
||||
if (!setting.read_only) {
|
||||
if (setting.can_order) {
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES+
|
||||
"'><a href='javascript:void(0);' class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></a>"
|
||||
|
@ -1047,24 +1083,53 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
|
||||
html += "</tr>"
|
||||
|
||||
if (isCategorized && setting.can_add_new_rows && _.findLastIndex(setting_value, categoryPair) === rowIndexOrName) {
|
||||
html += makeTableInputs(setting, categoryPair, categoryValue);
|
||||
}
|
||||
|
||||
row_num++
|
||||
});
|
||||
}
|
||||
|
||||
// populate inputs in the table for new values
|
||||
if (!isLocked && !setting.read_only && setting.can_add_new_rows) {
|
||||
html += makeTableInputs(setting)
|
||||
if (!setting.read_only) {
|
||||
if (setting.can_add_new_categories) {
|
||||
html += makeTableCategoryInput(setting, numVisibleColumns);
|
||||
}
|
||||
if (setting.can_add_new_rows || setting.can_add_new_categories) {
|
||||
html += makeTableInputs(setting, {}, "");
|
||||
}
|
||||
}
|
||||
html += "</table>"
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function makeTableInputs(setting) {
|
||||
var html = "<tr class='inputs'>"
|
||||
function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, message) {
|
||||
var html =
|
||||
"<tr class='" + Settings.DATA_CATEGORY_CLASS + "' data-key='" + categoryKey + "' data-category='" + categoryValue + "'>" +
|
||||
"<td colspan='" + (numVisibleColumns - 1) + "' class='" + Settings.TOGGLE_CATEGORY_COLUMN_CLASS + "'>" +
|
||||
"<span class='" + Settings.TOGGLE_CATEGORY_SPAN_CLASSES + " " + Settings.TOGGLE_CATEGORY_EXPANDED_CLASS + "'></span>" +
|
||||
"<span message='" + message + "'>" + categoryValue + "</span>" +
|
||||
"</td>" +
|
||||
((canRemove) ? (
|
||||
"<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'>" +
|
||||
"<a href='javascript:void(0);' class='" + Settings.DEL_CATEGORY_SPAN_CLASSES + "'></a>" +
|
||||
"</td>"
|
||||
) : (
|
||||
"<td></td>"
|
||||
)) +
|
||||
"</tr>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function makeTableInputs(setting, initialValues, categoryValue) {
|
||||
var html = "<tr class='inputs'" + (setting.can_add_new_categories && !categoryValue ? " hidden" : "") + " " +
|
||||
(categoryValue ? ("data-category='" + categoryValue + "'") : "") + " " +
|
||||
(setting.categorize_by_key ? ("data-keep-field='" + setting.categorize_by_key + "'") : "") + ">";
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='numbered'></td>"
|
||||
html += "<td class='numbered'></td>";
|
||||
}
|
||||
|
||||
if (setting.key) {
|
||||
|
@ -1074,15 +1139,21 @@ function makeTableInputs(setting) {
|
|||
}
|
||||
|
||||
_.each(setting.columns, function(col) {
|
||||
var defaultValue = _.has(initialValues, col.name) ? initialValues[col.name] : col.default;
|
||||
if (col.type === "checkbox") {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||
+ "<input type='checkbox' class='form-control table-checkbox' "
|
||||
+ "name='" + col.name + "'" + (col.default ? " checked" : "") + "/></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
|
||||
"<input type='checkbox' class='form-control table-checkbox' " +
|
||||
"name='" + col.name + "'" + (defaultValue ? " checked" : "") + "/>" +
|
||||
"</td>";
|
||||
} else {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
|
||||
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\
|
||||
value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'>\
|
||||
</td>"
|
||||
html +=
|
||||
"<td " + (col.hidden ? "style='display: none;'" : "") + " class='" + Settings.DATA_COL_CLASS + "' " +
|
||||
"name='" + col.name + "'>" +
|
||||
"<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "' " +
|
||||
"value='" + (defaultValue || "") + "' data-default='" + (defaultValue || "") + "'" +
|
||||
(col.readonly ? " readonly" : "") + ">" +
|
||||
"</td>";
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1090,12 +1161,30 @@ function makeTableInputs(setting) {
|
|||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'></td>"
|
||||
}
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
|
||||
"'><a href='javascript:void(0);' class='glyphicon glyphicon-plus " + Settings.ADD_ROW_BUTTON_CLASS + "'></a></td>"
|
||||
"'><a href='javascript:void(0);' class='" + Settings.ADD_ROW_SPAN_CLASSES + "'></a></td>"
|
||||
html += "</tr>"
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
function makeTableCategoryInput(setting, numVisibleColumns) {
|
||||
var canAddRows = setting.can_add_new_rows;
|
||||
var categoryKey = setting.categorize_by_key;
|
||||
var placeholder = setting.new_category_placeholder || "";
|
||||
var message = setting.new_category_message || "";
|
||||
var html =
|
||||
"<tr class='" + Settings.DATA_CATEGORY_CLASS + " inputs' data-can-add-rows='" + canAddRows + "' " +
|
||||
"data-key='" + categoryKey + "' data-message='" + message + "'>" +
|
||||
"<td colspan='" + (numVisibleColumns - 1) + "'>" +
|
||||
"<input type='text' class='form-control' placeholder='" + placeholder + "'/>" +
|
||||
"</td>" +
|
||||
"<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'>" +
|
||||
"<a href='javascript:void(0);' class='" + Settings.ADD_CATEGORY_SPAN_CLASSES + "'></a>" +
|
||||
"</td>" +
|
||||
"</tr>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function badgeSidebarForDifferences(changedElement) {
|
||||
// figure out which group this input is in
|
||||
var panelParentID = changedElement.closest('.panel').attr('id');
|
||||
|
@ -1134,13 +1223,12 @@ function badgeSidebarForDifferences(changedElement) {
|
|||
$("a[href='#" + panelParentID + "'] .badge").html(badgeValue);
|
||||
}
|
||||
|
||||
function addTableRow(add_glyphicon) {
|
||||
var row = $(add_glyphicon).closest('tr')
|
||||
function addTableRow(row) {
|
||||
var table = row.parents('table');
|
||||
var isArray = table.data('setting-type') === 'array';
|
||||
var keepField = row.data("keep-field");
|
||||
|
||||
var table = row.parents('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
|
||||
var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS)
|
||||
var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS);
|
||||
|
||||
if (!isArray) {
|
||||
// Check key spaces
|
||||
|
@ -1257,10 +1345,12 @@ function addTableRow(add_glyphicon) {
|
|||
} else {
|
||||
console.log("Unknown table element")
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
input_clone.find('input').each(function(){
|
||||
$(this).val($(this).attr('data-default'));
|
||||
input_clone.children('td').each(function () {
|
||||
if ($(this).attr("name") !== keepField) {
|
||||
$(this).find("input").val($(this).attr('data-default'));
|
||||
}
|
||||
});
|
||||
|
||||
if (isArray) {
|
||||
|
@ -1272,44 +1362,132 @@ function addTableRow(add_glyphicon) {
|
|||
|
||||
badgeSidebarForDifferences($(table))
|
||||
|
||||
row.parent().append(input_clone)
|
||||
row.after(input_clone)
|
||||
}
|
||||
|
||||
function deleteTableRow(delete_glyphicon) {
|
||||
var row = $(delete_glyphicon).closest('tr')
|
||||
function deleteTableRow($row) {
|
||||
var $table = $row.closest('table');
|
||||
var categoryName = $row.data("category");
|
||||
var isArray = $table.data('setting-type') === 'array';
|
||||
|
||||
var table = $(row).closest('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
|
||||
row.empty();
|
||||
$row.empty();
|
||||
|
||||
if (!isArray) {
|
||||
row.html("<input type='hidden' class='form-control' name='"
|
||||
+ row.attr('name') + "' data-changed='true' value=''>");
|
||||
$row.html("<input type='hidden' class='form-control' name='" + $row.attr('name') + "' data-changed='true' value=''>");
|
||||
} else {
|
||||
if (table.find('.' + Settings.DATA_ROW_CLASS).length > 1) {
|
||||
updateDataChangedForSiblingRows(row)
|
||||
if ($table.find('.' + Settings.DATA_ROW_CLASS + "[data-category='" + categoryName + "']").length <= 1) {
|
||||
// This is the last row of the category, so delete the header
|
||||
$table.find('.' + Settings.DATA_CATEGORY_CLASS + "[data-category='" + categoryName + "']").remove();
|
||||
}
|
||||
|
||||
if ($table.find('.' + Settings.DATA_ROW_CLASS).length > 1) {
|
||||
updateDataChangedForSiblingRows($row);
|
||||
|
||||
// this isn't the last row - we can just remove it
|
||||
row.remove()
|
||||
$row.remove();
|
||||
} else {
|
||||
// this is the last row, we can't remove it completely since we need to post an empty array
|
||||
|
||||
row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS)
|
||||
row.addClass('empty-array-row')
|
||||
|
||||
row.html("<input type='hidden' class='form-control' name='" + table.attr("name").replace('[]', '')
|
||||
+ "' data-changed='true' value=''>");
|
||||
$row
|
||||
.removeClass(Settings.DATA_ROW_CLASS)
|
||||
.removeClass(Settings.NEW_ROW_CLASS)
|
||||
.removeAttr("data-category")
|
||||
.addClass('empty-array-row')
|
||||
.html("<input type='hidden' class='form-control' name='" + $table.attr("name").replace('[]', '') + "' " +
|
||||
"data-changed='true' value=''>");
|
||||
}
|
||||
}
|
||||
|
||||
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
|
||||
badgeSidebarForDifferences($(table))
|
||||
badgeSidebarForDifferences($table);
|
||||
}
|
||||
|
||||
function moveTableRow(move_glyphicon, move_up) {
|
||||
var row = $(move_glyphicon).closest('tr')
|
||||
function addTableCategory($categoryInputRow) {
|
||||
var $input = $categoryInputRow.find("input").first();
|
||||
var categoryValue = $input.prop("value");
|
||||
if (!categoryValue || $categoryInputRow.closest("table").find("tr[data-category='" + categoryValue + "']").length !== 0) {
|
||||
$categoryInputRow.addClass("has-warning");
|
||||
|
||||
setTimeout(function () {
|
||||
$categoryInputRow.removeClass("has-warning");
|
||||
}, 400);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var $rowInput = $categoryInputRow.next(".inputs").clone();
|
||||
if (!$rowInput) {
|
||||
console.error("Error cloning inputs");
|
||||
}
|
||||
|
||||
var canAddRows = $categoryInputRow.data("can-add-rows");
|
||||
var message = $categoryInputRow.data("message");
|
||||
var categoryKey = $categoryInputRow.data("key");
|
||||
var width = 0;
|
||||
$categoryInputRow
|
||||
.children("td")
|
||||
.each(function () {
|
||||
width += $(this).prop("colSpan") || 1;
|
||||
});
|
||||
|
||||
$input
|
||||
.prop("value", "")
|
||||
.focus();
|
||||
|
||||
$rowInput.find("td[name='" + categoryKey + "'] > input").first()
|
||||
.prop("value", categoryValue);
|
||||
$rowInput
|
||||
.attr("data-category", categoryValue)
|
||||
.addClass(Settings.NEW_ROW_CLASS);
|
||||
|
||||
var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, " - " + message));
|
||||
$newCategoryRow.addClass(Settings.NEW_ROW_CLASS);
|
||||
|
||||
$categoryInputRow
|
||||
.before($newCategoryRow)
|
||||
.before($rowInput);
|
||||
|
||||
if (canAddRows) {
|
||||
$rowInput.removeAttr("hidden");
|
||||
} else {
|
||||
addTableRow($rowInput);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteTableCategory($categoryHeaderRow) {
|
||||
var categoryName = $categoryHeaderRow.data("category");
|
||||
|
||||
$categoryHeaderRow
|
||||
.closest("table")
|
||||
.find("tr[data-category='" + categoryName + "']")
|
||||
.each(function () {
|
||||
if ($(this).hasClass(Settings.DATA_ROW_CLASS)) {
|
||||
deleteTableRow($(this));
|
||||
} else {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleTableCategory($categoryHeaderRow) {
|
||||
var $icon = $categoryHeaderRow.find("." + Settings.TOGGLE_CATEGORY_SPAN_CLASS).first();
|
||||
var categoryName = $categoryHeaderRow.data("category");
|
||||
var wasExpanded = $icon.hasClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS);
|
||||
if (wasExpanded) {
|
||||
$icon
|
||||
.addClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS)
|
||||
.removeClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS);
|
||||
} else {
|
||||
$icon
|
||||
.addClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS)
|
||||
.removeClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS);
|
||||
}
|
||||
$categoryHeaderRow
|
||||
.closest("table")
|
||||
.find("tr[data-category='" + categoryName + "']")
|
||||
.toggleClass("contracted", wasExpanded);
|
||||
}
|
||||
|
||||
function moveTableRow(row, move_up) {
|
||||
var table = $(row).closest('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
if (!isArray) {
|
||||
|
|
|
@ -120,6 +120,102 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
}
|
||||
}
|
||||
|
||||
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress) {
|
||||
NodePermissions userPerms;
|
||||
|
||||
userPerms.setAll(false);
|
||||
|
||||
if (isLocalUser) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: is local user, so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (verifiedUsername.isEmpty()) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: unverified or no username for" << userPerms.getID() << ", so:" << userPerms;
|
||||
#endif
|
||||
|
||||
if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
|
||||
// this user comes from an IP we have in our permissions table, apply those permissions
|
||||
userPerms = _server->_settingsManager.getPermissionsForIP(senderAddress);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: specific IP matches, so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) {
|
||||
userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: specific user matches, so:" << userPerms;
|
||||
#endif
|
||||
} else if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
|
||||
// this user comes from an IP we have in our permissions table, apply those permissions
|
||||
userPerms = _server->_settingsManager.getPermissionsForIP(senderAddress);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: specific IP matches, so:" << userPerms;
|
||||
#endif
|
||||
} else {
|
||||
// they are logged into metaverse, but we don't have specific permissions for them.
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: user is logged-into metaverse, so:" << userPerms;
|
||||
#endif
|
||||
|
||||
// if this user is a friend of the domain-owner, give them friend's permissions
|
||||
if (_domainOwnerFriends.contains(verifiedUsername)) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameFriends);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: user is friends with domain-owner, so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
|
||||
// if this user is a known member of a group, give them the implied permissions
|
||||
foreach (QUuid groupID, _server->_settingsManager.getGroupIDs()) {
|
||||
QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID);
|
||||
if (rankID != QUuid()) {
|
||||
userPerms |= _server->_settingsManager.getPermissionsForGroup(groupID, rankID);
|
||||
|
||||
GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: user " << verifiedUsername << "is in group:" << groupID << " rank:"
|
||||
<< rank.name << "so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// if this user is a known member of a blacklist group, remove the implied permissions
|
||||
foreach (QUuid groupID, _server->_settingsManager.getBlacklistGroupIDs()) {
|
||||
QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID);
|
||||
if (rankID != QUuid()) {
|
||||
QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID);
|
||||
if (rankID != QUuid()) {
|
||||
userPerms &= ~_server->_settingsManager.getForbiddensForGroup(groupID, rankID);
|
||||
|
||||
GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: user is in blacklist group:" << groupID << " rank:" << rank.name
|
||||
<< "so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userPerms.setID(verifiedUsername);
|
||||
userPerms.setVerifiedUserName(verifiedUsername);
|
||||
}
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: final:" << userPerms;
|
||||
#endif
|
||||
return userPerms;
|
||||
}
|
||||
|
||||
void DomainGatekeeper::updateNodePermissions() {
|
||||
// If the permissions were changed on the domain-server webpage (and nothing else was), a restart isn't required --
|
||||
// we reprocess the permissions map and update the nodes here. The node list is frequently sent out to all
|
||||
|
@ -129,40 +225,34 @@ void DomainGatekeeper::updateNodePermissions() {
|
|||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){
|
||||
QString username = node->getPermissions().getUserName();
|
||||
NodePermissions userPerms(username);
|
||||
// the id and the username in NodePermissions will often be the same, but id is set before
|
||||
// authentication and verifiedUsername is only set once they user's key has been confirmed.
|
||||
QString verifiedUsername = node->getPermissions().getVerifiedUserName();
|
||||
NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0));
|
||||
|
||||
if (node->getPermissions().isAssignment) {
|
||||
// this node is an assignment-client
|
||||
userPerms.isAssignment = true;
|
||||
userPerms.canAdjustLocks = true;
|
||||
userPerms.canRezPermanentEntities = true;
|
||||
userPerms.canRezTemporaryEntities = true;
|
||||
userPerms.permissions |= NodePermissions::Permission::canConnectToDomain;
|
||||
userPerms.permissions |= NodePermissions::Permission::canAdjustLocks;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities;
|
||||
} else {
|
||||
// this node is an agent
|
||||
userPerms.setAll(false);
|
||||
|
||||
const QHostAddress& addr = node->getLocalSocket().getAddress();
|
||||
bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() ||
|
||||
addr == QHostAddress::LocalHost);
|
||||
if (isLocalUser) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||
}
|
||||
|
||||
if (username.isEmpty()) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
} else {
|
||||
if (_server->_settingsManager.havePermissionsForName(username)) {
|
||||
userPerms = _server->_settingsManager.getPermissionsForName(username);
|
||||
} else {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||
}
|
||||
}
|
||||
// at this point we don't have a sending socket for packets from this node - assume it is the active socket
|
||||
// or the public socket if we haven't activated a socket for the node yet
|
||||
HifiSockAddr connectingAddr = node->getActiveSocket() ? *node->getActiveSocket() : node->getPublicSocket();
|
||||
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress());
|
||||
}
|
||||
|
||||
node->setPermissions(userPerms);
|
||||
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
qDebug() << "node" << node->getUUID() << "no longer has permission to connect.";
|
||||
// hang up on this node
|
||||
nodesToKill << node;
|
||||
|
@ -215,12 +305,13 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
|
|||
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting
|
||||
_pendingAssignedNodes.erase(it);
|
||||
|
||||
// always allow assignment clients to create and destroy entities
|
||||
NodePermissions userPerms;
|
||||
userPerms.isAssignment = true;
|
||||
userPerms.canAdjustLocks = true;
|
||||
userPerms.canRezPermanentEntities = true;
|
||||
userPerms.canRezTemporaryEntities = true;
|
||||
userPerms.permissions |= NodePermissions::Permission::canConnectToDomain;
|
||||
// always allow assignment clients to create and destroy entities
|
||||
userPerms.permissions |= NodePermissions::Permission::canAdjustLocks;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities;
|
||||
newNode->setPermissions(userPerms);
|
||||
return newNode;
|
||||
}
|
||||
|
@ -234,64 +325,58 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
// start with empty permissions
|
||||
NodePermissions userPerms(username);
|
||||
NodePermissions userPerms(NodePermissionsKey(username, 0));
|
||||
userPerms.setAll(false);
|
||||
|
||||
// check if this user is on our local machine - if this is true set permissions to those for a "localhost" connection
|
||||
QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress();
|
||||
bool isLocalUser =
|
||||
(senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost);
|
||||
if (isLocalUser) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||
qDebug() << "user-permissions: is local user, so:" << userPerms;
|
||||
}
|
||||
|
||||
if (!username.isEmpty() && usernameSignature.isEmpty()) {
|
||||
// user is attempting to prove their identity to us, but we don't have enough information
|
||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||
// ask for their public key right now to make sure we have it
|
||||
requestUserPublicKey(username);
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
QString verifiedUsername; // if this remains empty, consider this an anonymous connection attempt
|
||||
if (!username.isEmpty()) {
|
||||
if (usernameSignature.isEmpty()) {
|
||||
// user is attempting to prove their identity to us, but we don't have enough information
|
||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||
// ask for their public key right now to make sure we have it
|
||||
requestUserPublicKey(username);
|
||||
getGroupMemberships(username); // optimistically get started on group memberships
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login because we have no username-signature:" << username;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
}
|
||||
}
|
||||
|
||||
if (username.isEmpty()) {
|
||||
// they didn't tell us who they are
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
qDebug() << "user-permissions: no username, so:" << userPerms;
|
||||
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
||||
// they are sent us a username and the signature verifies it
|
||||
if (_server->_settingsManager.havePermissionsForName(username)) {
|
||||
// we have specific permissions for this user.
|
||||
userPerms = _server->_settingsManager.getPermissionsForName(username);
|
||||
qDebug() << "user-permissions: specific user matches, so:" << userPerms;
|
||||
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
||||
// they sent us a username and the signature verifies it
|
||||
getGroupMemberships(username);
|
||||
verifiedUsername = username;
|
||||
} else {
|
||||
// they are logged into metaverse, but we don't have specific permissions for them.
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||
qDebug() << "user-permissions: user is logged in, so:" << userPerms;
|
||||
}
|
||||
userPerms.setUserName(username);
|
||||
} else {
|
||||
// they sent us a username, but it didn't check out
|
||||
requestUserPublicKey(username);
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
// they sent us a username, but it didn't check out
|
||||
requestUserPublicKey(username);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login because signature verification failed:" << username;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "user-permissions: final:" << userPerms;
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress());
|
||||
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
|
||||
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login due to permissions:" << username;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
}
|
||||
|
||||
if (!userPerms.canConnectPastMaxCapacity && !isWithinMaxCapacity()) {
|
||||
if (!userPerms.can(NodePermissions::Permission::canConnectPastMaxCapacity) && !isWithinMaxCapacity()) {
|
||||
// we can't allow this user to connect because we are at max capacity
|
||||
sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr,
|
||||
DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login due to max capacity:" << username;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
}
|
||||
|
||||
|
@ -305,10 +390,8 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
// we have a node that already has these exact sockets - this occurs if a node
|
||||
// is unable to connect to the domain
|
||||
hintNodeID = node->getUUID();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
@ -328,6 +411,10 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
||||
uuidStringWithoutCurlyBraces(newNode->getUUID()), username);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "accepting login:" << username;
|
||||
#endif
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
|
@ -365,11 +452,11 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
|
|||
bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||
const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
|
||||
// it's possible this user can be allowed to connect, but we need to check their username signature
|
||||
QByteArray publicKeyArray = _userPublicKeys.value(username);
|
||||
auto lowerUsername = username.toLower();
|
||||
QByteArray publicKeyArray = _userPublicKeys.value(lowerUsername);
|
||||
|
||||
const QUuid& connectionToken = _connectionTokenHash.value(username.toLower());
|
||||
const QUuid& connectionToken = _connectionTokenHash.value(lowerUsername);
|
||||
|
||||
if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) {
|
||||
// if we do have a public key for the user, check for a signature match
|
||||
|
@ -379,8 +466,8 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
|||
// first load up the public key into an RSA struct
|
||||
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size());
|
||||
|
||||
QByteArray lowercaseUsername = username.toLower().toUtf8();
|
||||
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()),
|
||||
QByteArray lowercaseUsernameUTF8 = lowerUsername.toUtf8();
|
||||
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsernameUTF8.append(connectionToken.toRfc4122()),
|
||||
QCryptographicHash::Sha256);
|
||||
|
||||
if (rsaPublicKey) {
|
||||
|
@ -471,10 +558,20 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
|||
return;
|
||||
}
|
||||
|
||||
QString lowerUsername = username.toLower();
|
||||
if (_inFlightPublicKeyRequests.contains(lowerUsername)) {
|
||||
// public-key request for this username is already flight, not rerequesting
|
||||
return;
|
||||
}
|
||||
_inFlightPublicKeyRequests += lowerUsername;
|
||||
|
||||
// even if we have a public key for them right now, request a new one in case it has just changed
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
|
||||
callbackParams.errorCallbackReceiver = this;
|
||||
callbackParams.errorCallbackMethod = "publicKeyJSONErrorCallback";
|
||||
|
||||
|
||||
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
|
||||
|
||||
|
@ -485,28 +582,37 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
|||
QNetworkAccessManager::GetOperation, callbackParams);
|
||||
}
|
||||
|
||||
QString extractUsernameFromPublicKeyRequest(QNetworkReply& requestReply) {
|
||||
// extract the username from the request url
|
||||
QString username;
|
||||
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
|
||||
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
|
||||
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||
username = usernameRegex.cap(1);
|
||||
}
|
||||
return username.toLower();
|
||||
}
|
||||
|
||||
void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
QString username = extractUsernameFromPublicKeyRequest(requestReply);
|
||||
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
// figure out which user this is for
|
||||
if (jsonObject["status"].toString() == "success" && !username.isEmpty()) {
|
||||
// pull the public key as a QByteArray from this response
|
||||
const QString JSON_DATA_KEY = "data";
|
||||
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
||||
|
||||
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
|
||||
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
|
||||
|
||||
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||
QString username = usernameRegex.cap(1);
|
||||
|
||||
qDebug() << "Storing a public key for user" << username;
|
||||
|
||||
// pull the public key as a QByteArray from this response
|
||||
const QString JSON_DATA_KEY = "data";
|
||||
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
||||
|
||||
_userPublicKeys[username] =
|
||||
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
||||
}
|
||||
_userPublicKeys[username.toLower()] =
|
||||
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
||||
}
|
||||
|
||||
_inFlightPublicKeyRequests.remove(username);
|
||||
}
|
||||
|
||||
void DomainGatekeeper::publicKeyJSONErrorCallback(QNetworkReply& requestReply) {
|
||||
qDebug() << "publicKey api call failed:" << requestReply.error();
|
||||
QString username = extractUsernameFromPublicKeyRequest(requestReply);
|
||||
_inFlightPublicKeyRequests.remove(username);
|
||||
}
|
||||
|
||||
void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr) {
|
||||
|
@ -645,3 +751,159 @@ void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<ReceivedMessage>
|
|||
sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr());
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getGroupMemberships(const QString& username) {
|
||||
// loop through the groups mentioned on the settings page and ask if this user is in each. The replies
|
||||
// will be received asynchronously and permissions will be updated as the answers come in.
|
||||
|
||||
// if we've already asked, wait for the answer before asking again
|
||||
QString lowerUsername = username.toLower();
|
||||
if (_inFlightGroupMembershipsRequests.contains(lowerUsername)) {
|
||||
// public-key request for this username is already flight, not rerequesting
|
||||
return;
|
||||
}
|
||||
_inFlightGroupMembershipsRequests += lowerUsername;
|
||||
|
||||
QJsonObject json;
|
||||
QSet<QString> groupIDSet;
|
||||
foreach (QUuid groupID, _server->_settingsManager.getGroupIDs() + _server->_settingsManager.getBlacklistGroupIDs()) {
|
||||
groupIDSet += groupID.toString().mid(1,36);
|
||||
}
|
||||
QJsonArray groupIDs = QJsonArray::fromStringList(groupIDSet.toList());
|
||||
json["groups"] = groupIDs;
|
||||
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "getIsGroupMemberJSONCallback";
|
||||
callbackParams.errorCallbackReceiver = this;
|
||||
callbackParams.errorCallbackMethod = "getIsGroupMemberErrorCallback";
|
||||
|
||||
const QString GET_IS_GROUP_MEMBER_PATH = "api/v1/groups/members/%2";
|
||||
DependencyManager::get<AccountManager>()->sendRequest(GET_IS_GROUP_MEMBER_PATH.arg(username),
|
||||
AccountManagerAuth::Required,
|
||||
QNetworkAccessManager::PostOperation, callbackParams,
|
||||
QJsonDocument(json).toJson());
|
||||
|
||||
}
|
||||
|
||||
QString extractUsernameFromGroupMembershipsReply(QNetworkReply& requestReply) {
|
||||
// extract the username from the request url
|
||||
QString username;
|
||||
const QString GROUP_MEMBERSHIPS_URL_REGEX_STRING = "api\\/v1\\/groups\\/members\\/([A-Za-z0-9_\\.]+)";
|
||||
QRegExp usernameRegex(GROUP_MEMBERSHIPS_URL_REGEX_STRING);
|
||||
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||
username = usernameRegex.cap(1);
|
||||
}
|
||||
return username.toLower();
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) {
|
||||
// {
|
||||
// "data":{
|
||||
// "username":"sethalves",
|
||||
// "groups":{
|
||||
// "fd55479a-265d-4990-854e-3d04214ad1b0":{
|
||||
// "name":"Blerg Blah",
|
||||
// "rank":{
|
||||
// "name":"admin",
|
||||
// "order":1
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// "status":"success"
|
||||
// }
|
||||
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
QJsonObject data = jsonObject["data"].toObject();
|
||||
QJsonObject groups = data["groups"].toObject();
|
||||
QString username = data["username"].toString();
|
||||
_server->_settingsManager.clearGroupMemberships(username);
|
||||
foreach (auto groupID, groups.keys()) {
|
||||
QJsonObject group = groups[groupID].toObject();
|
||||
QJsonObject rank = group["rank"].toObject();
|
||||
QUuid rankID = QUuid(rank["id"].toString());
|
||||
_server->_settingsManager.recordGroupMembership(username, groupID, rankID);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "getIsGroupMember api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
_inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply));
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply& requestReply) {
|
||||
qDebug() << "getIsGroupMember api call failed:" << requestReply.error();
|
||||
_inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply));
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getDomainOwnerFriendsList() {
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "getDomainOwnerFriendsListJSONCallback";
|
||||
callbackParams.errorCallbackReceiver = this;
|
||||
callbackParams.errorCallbackMethod = "getDomainOwnerFriendsListErrorCallback";
|
||||
|
||||
const QString GET_FRIENDS_LIST_PATH = "api/v1/user/friends";
|
||||
DependencyManager::get<AccountManager>()->sendRequest(GET_FRIENDS_LIST_PATH, AccountManagerAuth::Required,
|
||||
QNetworkAccessManager::GetOperation, callbackParams, QByteArray(),
|
||||
NULL, QVariantMap());
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply) {
|
||||
// {
|
||||
// status: "success",
|
||||
// data: {
|
||||
// friends: [
|
||||
// "chris",
|
||||
// "freidrica",
|
||||
// "G",
|
||||
// "huffman",
|
||||
// "leo",
|
||||
// "philip",
|
||||
// "ryan",
|
||||
// "sam",
|
||||
// "ZappoMan"
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
_domainOwnerFriends.clear();
|
||||
QJsonArray friends = jsonObject["data"].toObject()["friends"].toArray();
|
||||
for (int i = 0; i < friends.size(); i++) {
|
||||
_domainOwnerFriends += friends.at(i).toString();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "getDomainOwnerFriendsList api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply) {
|
||||
qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply.error();
|
||||
}
|
||||
|
||||
void DomainGatekeeper::refreshGroupsCache() {
|
||||
// if agents are connected to this domain, refresh our cached information about groups and memberships in such.
|
||||
getDomainOwnerFriendsList();
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||
if (!node->getPermissions().isAssignment) {
|
||||
// this node is an agent
|
||||
const QString& verifiedUserName = node->getPermissions().getVerifiedUserName();
|
||||
if (!verifiedUserName.isEmpty()) {
|
||||
getGroupMemberships(verifiedUserName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_server->_settingsManager.apiRefreshGroupInformation();
|
||||
|
||||
updateNodePermissions();
|
||||
|
||||
#if WANT_DEBUG
|
||||
_server->_settingsManager.debugDumpGroupsState();
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -51,7 +51,16 @@ public slots:
|
|||
void processICEPeerInformationPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void publicKeyJSONCallback(QNetworkReply& requestReply);
|
||||
|
||||
void publicKeyJSONErrorCallback(QNetworkReply& requestReply);
|
||||
|
||||
void getIsGroupMemberJSONCallback(QNetworkReply& requestReply);
|
||||
void getIsGroupMemberErrorCallback(QNetworkReply& requestReply);
|
||||
|
||||
void getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply);
|
||||
void getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply);
|
||||
|
||||
void refreshGroupsCache();
|
||||
|
||||
signals:
|
||||
void killNode(SharedNodePointer node);
|
||||
void connectedNode(SharedNodePointer node);
|
||||
|
@ -93,6 +102,15 @@ private:
|
|||
|
||||
QHash<QString, QUuid> _connectionTokenHash;
|
||||
QHash<QString, QByteArray> _userPublicKeys;
|
||||
QSet<QString> _inFlightPublicKeyRequests; // keep track of which we've already asked for
|
||||
QSet<QString> _domainOwnerFriends; // keep track of friends of the domain owner
|
||||
QSet<QString> _inFlightGroupMembershipsRequests; // keep track of which we've already asked for
|
||||
|
||||
NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress);
|
||||
|
||||
void getGroupMemberships(const QString& username);
|
||||
// void getIsGroupMember(const QString& username, const QUuid groupID);
|
||||
void getDomainOwnerFriendsList();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -184,10 +184,10 @@ void DomainMetadata::securityChanged(bool send) {
|
|||
QString restriction;
|
||||
|
||||
const auto& settingsManager = static_cast<DomainServer*>(parent())->_settingsManager;
|
||||
bool hasAnonymousAccess =
|
||||
settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).canConnectToDomain;
|
||||
bool hasHifiAccess =
|
||||
settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).canConnectToDomain;
|
||||
bool hasAnonymousAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).can(
|
||||
NodePermissions::Permission::canConnectToDomain);
|
||||
bool hasHifiAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).can(
|
||||
NodePermissions::Permission::canConnectToDomain);
|
||||
if (hasAnonymousAccess) {
|
||||
restriction = hasHifiAccess ? RESTRICTION_OPEN : RESTRICTION_ANON;
|
||||
} else if (hasHifiAccess) {
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <UUID.h>
|
||||
#include <LogHandler.h>
|
||||
#include <ServerPathUtils.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
#include "DomainServerNodeData.h"
|
||||
#include "NodeConnectionData.h"
|
||||
|
@ -106,15 +107,28 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions,
|
||||
&_gatekeeper, &DomainGatekeeper::updateNodePermissions);
|
||||
|
||||
setupGroupCacheRefresh();
|
||||
|
||||
// if we were given a certificate/private key or oauth credentials they must succeed
|
||||
if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) {
|
||||
return;
|
||||
}
|
||||
|
||||
_settingsManager.apiRefreshGroupInformation();
|
||||
|
||||
setupNodeListAndAssignments();
|
||||
|
||||
if (_type == MetaverseDomain) {
|
||||
// if we have a metaverse domain, we'll need an access token to heartbeat handle auto-networking
|
||||
resetAccountManagerAccessToken();
|
||||
}
|
||||
|
||||
setupAutomaticNetworking();
|
||||
if (!getID().isNull()) {
|
||||
|
||||
if (!getID().isNull() && _type != NonMetaverse) {
|
||||
// setup periodic heartbeats to metaverse API
|
||||
setupHeartbeatToMetaverse();
|
||||
|
||||
// send the first heartbeat immediately
|
||||
sendHeartbeatToMetaverse();
|
||||
}
|
||||
|
@ -296,16 +310,22 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
|||
// store the new ID and auto networking setting on disk
|
||||
_settingsManager.persistToFile();
|
||||
|
||||
// change our domain ID immediately
|
||||
DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id });
|
||||
|
||||
// store the new token to the account info
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
accountManager->setTemporaryDomain(id, key);
|
||||
|
||||
// change our domain ID immediately
|
||||
DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id });
|
||||
|
||||
// change our type to reflect that we are a temporary domain now
|
||||
_type = MetaverseTemporaryDomain;
|
||||
|
||||
// update our heartbeats to use the correct id
|
||||
setupICEHeartbeatForFullNetworking();
|
||||
setupHeartbeatToMetaverse();
|
||||
|
||||
// if we have a current ICE server address, update it in the API for the new temporary domain
|
||||
sendICEServerAddressToMetaverseAPI();
|
||||
} else {
|
||||
qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again"
|
||||
<< "via domain-server relaunch or from the domain-server settings.";
|
||||
|
@ -389,6 +409,16 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH);
|
||||
if (idValueVariant) {
|
||||
nodeList->setSessionUUID(idValueVariant->toString());
|
||||
|
||||
// if we have an ID, we'll assume we're a metaverse domain
|
||||
// now see if we think we're a temp domain (we have an API key) or a full domain
|
||||
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
|
||||
if (temporaryDomainKey.isEmpty()) {
|
||||
_type = MetaverseDomain;
|
||||
} else {
|
||||
_type = MetaverseTemporaryDomain;
|
||||
}
|
||||
|
||||
} else {
|
||||
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
|
||||
}
|
||||
|
@ -406,6 +436,7 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
|
||||
// NodeList won't be available to the settings manager when it is created, so call registerListener here
|
||||
packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeKickRequest, &_settingsManager, "processNodeKickRequestPacket");
|
||||
|
||||
// register the gatekeeper for the packets it needs to receive
|
||||
packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket");
|
||||
|
@ -471,42 +502,46 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
|||
}
|
||||
|
||||
void DomainServer::setupAutomaticNetworking() {
|
||||
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
|
||||
|
||||
resetAccountManagerAccessToken();
|
||||
|
||||
_automaticNetworkingSetting =
|
||||
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
const QUuid& domainID = getID();
|
||||
qDebug() << "Configuring automatic networking in domain-server as" << _automaticNetworkingSetting;
|
||||
|
||||
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
setupICEHeartbeatForFullNetworking();
|
||||
}
|
||||
if (_automaticNetworkingSetting != DISABLED_AUTOMATIC_NETWORKING_VALUE) {
|
||||
const QUuid& domainID = getID();
|
||||
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
|
||||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
setupICEHeartbeatForFullNetworking();
|
||||
}
|
||||
|
||||
if (!domainID.isNull()) {
|
||||
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
||||
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
|
||||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
|
||||
// send any public socket changes to the data server so nodes can find us at our new IP
|
||||
connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
|
||||
this, &DomainServer::performIPAddressUpdate);
|
||||
if (!domainID.isNull()) {
|
||||
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
||||
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
||||
|
||||
// have the LNL enable public socket updating via STUN
|
||||
nodeList->startSTUNPublicSocketUpdate();
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
// send any public socket changes to the data server so nodes can find us at our new IP
|
||||
connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
|
||||
this, &DomainServer::performIPAddressUpdate);
|
||||
|
||||
// have the LNL enable public socket updating via STUN
|
||||
nodeList->startSTUNPublicSocketUpdate();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
||||
<< "Please add an ID to your config file or via the web interface.";
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
||||
<< "Please add an ID to your config file or via the web interface.";
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DomainServer::setupHeartbeatToMetaverse() {
|
||||
|
@ -1098,12 +1133,11 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
|||
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
||||
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
|
||||
|
||||
|
||||
// add access level for anonymous connections
|
||||
// consider the domain to be "restricted" if anonymous connections are disallowed
|
||||
static const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
||||
NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain;
|
||||
domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.can(NodePermissions::Permission::canConnectToDomain);
|
||||
|
||||
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
|
||||
if (!temporaryDomainKey.isEmpty()) {
|
||||
|
@ -1134,42 +1168,45 @@ void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) {
|
|||
return;
|
||||
}
|
||||
|
||||
// check if we need to force a new temporary domain name
|
||||
switch (requestReply.error()) {
|
||||
// if we have a temporary domain with a bad token, we get a 401
|
||||
case QNetworkReply::NetworkError::AuthenticationRequiredError: {
|
||||
static const QString DATA_KEY = "data";
|
||||
static const QString TOKEN_KEY = "api_key";
|
||||
// only attempt to grab a new temporary name if we're already a temporary domain server
|
||||
if (_type == MetaverseTemporaryDomain) {
|
||||
// check if we need to force a new temporary domain name
|
||||
switch (requestReply.error()) {
|
||||
// if we have a temporary domain with a bad token, we get a 401
|
||||
case QNetworkReply::NetworkError::AuthenticationRequiredError: {
|
||||
static const QString DATA_KEY = "data";
|
||||
static const QString TOKEN_KEY = "api_key";
|
||||
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY];
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY];
|
||||
|
||||
if (!tokenFailure.isNull()) {
|
||||
qWarning() << "Temporary domain name lacks a valid API key, and is being reset.";
|
||||
if (!tokenFailure.isNull()) {
|
||||
qWarning() << "Temporary domain name lacks a valid API key, and is being reset.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// if the domain does not (or no longer) exists, we get a 404
|
||||
case QNetworkReply::NetworkError::ContentNotFoundError:
|
||||
qWarning() << "Domain not found, getting a new temporary domain.";
|
||||
break;
|
||||
// otherwise, we erred on something else, and should not force a temporary domain
|
||||
default:
|
||||
return;
|
||||
}
|
||||
// if the domain does not (or no longer) exists, we get a 404
|
||||
case QNetworkReply::NetworkError::ContentNotFoundError:
|
||||
qWarning() << "Domain not found, getting a new temporary domain.";
|
||||
break;
|
||||
// otherwise, we erred on something else, and should not force a temporary domain
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// halt heartbeats until we have a token
|
||||
_metaverseHeartbeatTimer->deleteLater();
|
||||
_metaverseHeartbeatTimer = nullptr;
|
||||
// halt heartbeats until we have a token
|
||||
_metaverseHeartbeatTimer->deleteLater();
|
||||
_metaverseHeartbeatTimer = nullptr;
|
||||
|
||||
// give up eventually to avoid flooding traffic
|
||||
static const int MAX_ATTEMPTS = 5;
|
||||
static int attempt = 0;
|
||||
if (++attempt < MAX_ATTEMPTS) {
|
||||
// get a new temporary name and token
|
||||
getTemporaryName(true);
|
||||
} else {
|
||||
qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart.";
|
||||
// give up eventually to avoid flooding traffic
|
||||
static const int MAX_ATTEMPTS = 5;
|
||||
static int attempt = 0;
|
||||
if (++attempt < MAX_ATTEMPTS) {
|
||||
// get a new temporary name and token
|
||||
getTemporaryName(true);
|
||||
} else {
|
||||
qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1196,7 +1233,10 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
|||
callbackParameters.errorCallbackReceiver = this;
|
||||
callbackParameters.errorCallbackMethod = "handleFailedICEServerAddressUpdate";
|
||||
|
||||
qDebug() << "Updating ice-server address in High Fidelity Metaverse API to" << _iceServerSocket.getAddress().toString();
|
||||
static QString repeatedMessage = LogHandler::getInstance().addOnlyOnceMessageRegex
|
||||
("Updating ice-server address in High Fidelity Metaverse API to [^ \n]+");
|
||||
qDebug() << "Updating ice-server address in High Fidelity Metaverse API to"
|
||||
<< _iceServerSocket.getAddress().toString();
|
||||
|
||||
static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address";
|
||||
|
||||
|
@ -2327,3 +2367,14 @@ void DomainServer::randomizeICEServerAddress(bool shouldTriggerHostLookup) {
|
|||
// immediately send an update to the metaverse API when our ice-server changes
|
||||
sendICEServerAddressToMetaverseAPI();
|
||||
}
|
||||
|
||||
void DomainServer::setupGroupCacheRefresh() {
|
||||
const int REFRESH_GROUPS_INTERVAL_MSECS = 15 * MSECS_PER_SECOND;
|
||||
|
||||
if (!_metaverseGroupCacheTimer) {
|
||||
// setup a timer to refresh this server's cached group details
|
||||
_metaverseGroupCacheTimer = new QTimer { this };
|
||||
connect(_metaverseGroupCacheTimer, &QTimer::timeout, &_gatekeeper, &DomainGatekeeper::refreshGroupsCache);
|
||||
_metaverseGroupCacheTimer->start(REFRESH_GROUPS_INTERVAL_MSECS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,13 @@ class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
|||
public:
|
||||
DomainServer(int argc, char* argv[]);
|
||||
~DomainServer();
|
||||
|
||||
|
||||
enum DomainType {
|
||||
NonMetaverse,
|
||||
MetaverseDomain,
|
||||
MetaverseTemporaryDomain
|
||||
};
|
||||
|
||||
static int const EXIT_CODE_REBOOT;
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
|
@ -64,7 +70,7 @@ public slots:
|
|||
void processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEServerHeartbeatACK(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
|
||||
private slots:
|
||||
void aboutToQuit();
|
||||
|
||||
|
@ -74,7 +80,7 @@ private slots:
|
|||
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
|
||||
void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); }
|
||||
void sendHeartbeatToIceServer();
|
||||
|
||||
|
||||
void handleConnectedNode(SharedNodePointer newNode);
|
||||
|
||||
void handleTempDomainSuccess(QNetworkReply& requestReply);
|
||||
|
@ -96,7 +102,7 @@ signals:
|
|||
void iceServerChanged();
|
||||
void userConnected();
|
||||
void userDisconnected();
|
||||
|
||||
|
||||
private:
|
||||
const QUuid& getID();
|
||||
|
||||
|
@ -136,7 +142,7 @@ private:
|
|||
SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
|
||||
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
|
||||
void addStaticAssignmentsToQueue();
|
||||
|
||||
|
||||
QUrl oauthRedirectURL();
|
||||
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
|
||||
|
||||
|
@ -151,7 +157,9 @@ private:
|
|||
|
||||
QJsonObject jsonForSocket(const HifiSockAddr& socket);
|
||||
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
|
||||
|
||||
|
||||
void setupGroupCacheRefresh();
|
||||
|
||||
DomainGatekeeper _gatekeeper;
|
||||
|
||||
HTTPManager _httpManager;
|
||||
|
@ -184,6 +192,7 @@ private:
|
|||
DomainMetadata* _metadata { nullptr };
|
||||
QTimer* _iceHeartbeatTimer { nullptr };
|
||||
QTimer* _metaverseHeartbeatTimer { nullptr };
|
||||
QTimer* _metaverseGroupCacheTimer { nullptr };
|
||||
|
||||
QList<QHostAddress> _iceServerAddresses;
|
||||
QSet<QHostAddress> _failedIceServerAddresses;
|
||||
|
@ -192,6 +201,8 @@ private:
|
|||
int _numHeartbeatDenials { 0 };
|
||||
bool _connectedToICEServer { false };
|
||||
|
||||
DomainType _type { DomainType::NonMetaverse };
|
||||
|
||||
friend class DomainGatekeeper;
|
||||
friend class DomainMetadata;
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -27,6 +27,12 @@ const QString SETTINGS_PATH = "/settings";
|
|||
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".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";
|
||||
const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions";
|
||||
const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens";
|
||||
|
||||
using GroupByUUIDKey = QPair<QUuid, QUuid>; // groupID, rankID
|
||||
|
||||
|
||||
class DomainServerSettingsManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -38,23 +44,67 @@ public:
|
|||
void setupConfigMap(const QStringList& argumentList);
|
||||
QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
|
||||
|
||||
QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); }
|
||||
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
|
||||
QVariantMap& getSettingsMap() { return _configMap.getConfig(); }
|
||||
|
||||
QVariantMap& getDescriptorsMap();
|
||||
|
||||
bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name); }
|
||||
bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name); }
|
||||
NodePermissions getStandardPermissionsForName(const QString& name) const;
|
||||
// these give access to anonymous/localhost/logged-in settings from the domain-server settings page
|
||||
bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name, 0); }
|
||||
NodePermissions getStandardPermissionsForName(const NodePermissionsKey& name) const;
|
||||
|
||||
// these give access to permissions for specific user-names from the domain-server settings page
|
||||
bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name, 0); }
|
||||
NodePermissions getPermissionsForName(const QString& name) const;
|
||||
QStringList getAllNames() { return _agentPermissions.keys(); }
|
||||
NodePermissions getPermissionsForName(const NodePermissionsKey& key) const { return getPermissionsForName(key.first); }
|
||||
QStringList getAllNames() const;
|
||||
|
||||
// these give access to permissions for specific IPs from the domain-server settings page
|
||||
bool hasPermissionsForIP(const QHostAddress& address) const { return _ipPermissions.contains(address.toString(), 0); }
|
||||
NodePermissions getPermissionsForIP(const QHostAddress& address) const;
|
||||
|
||||
// these give access to permissions for specific groups from the domain-server settings page
|
||||
bool havePermissionsForGroup(const QString& groupName, QUuid rankID) const {
|
||||
return _groupPermissions.contains(groupName, rankID);
|
||||
}
|
||||
NodePermissions getPermissionsForGroup(const QString& groupName, QUuid rankID) const;
|
||||
NodePermissions getPermissionsForGroup(const QUuid& groupID, QUuid rankID) const;
|
||||
|
||||
// these remove permissions from users in certain groups
|
||||
bool haveForbiddensForGroup(const QString& groupName, QUuid rankID) const {
|
||||
return _groupForbiddens.contains(groupName, rankID);
|
||||
}
|
||||
NodePermissions getForbiddensForGroup(const QString& groupName, QUuid rankID) const;
|
||||
NodePermissions getForbiddensForGroup(const QUuid& groupID, QUuid rankID) const;
|
||||
|
||||
QStringList getAllKnownGroupNames();
|
||||
bool setGroupID(const QString& groupName, const QUuid& groupID);
|
||||
GroupRank getGroupRank(QUuid groupID, QUuid rankID) { return _groupRanks[groupID][rankID]; }
|
||||
|
||||
QList<QUuid> getGroupIDs();
|
||||
QList<QUuid> getBlacklistGroupIDs();
|
||||
|
||||
// these are used to locally cache the result of calling "api/v1/groups/.../is_member/..." on metaverse's api
|
||||
void clearGroupMemberships(const QString& name) { _groupMembership[name.toLower()].clear(); }
|
||||
void recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID);
|
||||
QUuid isGroupMember(const QString& name, const QUuid& groupID); // returns rank or -1 if not a member
|
||||
|
||||
// calls http api to refresh group information
|
||||
void apiRefreshGroupInformation();
|
||||
|
||||
void debugDumpGroupsState();
|
||||
|
||||
signals:
|
||||
void updateNodePermissions();
|
||||
|
||||
public slots:
|
||||
void apiGetGroupIDJSONCallback(QNetworkReply& requestReply);
|
||||
void apiGetGroupIDErrorCallback(QNetworkReply& requestReply);
|
||||
void apiGetGroupRanksJSONCallback(QNetworkReply& requestReply);
|
||||
void apiGetGroupRanksErrorCallback(QNetworkReply& requestReply);
|
||||
|
||||
private slots:
|
||||
void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processNodeKickRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
private:
|
||||
QStringList _argumentList;
|
||||
|
@ -76,11 +126,37 @@ private:
|
|||
|
||||
void validateDescriptorsMap();
|
||||
|
||||
void packPermissionsForMap(QString mapName, NodePermissionsMap& agentPermissions, QString keyPath);
|
||||
// these cause calls to metaverse's group api
|
||||
void apiGetGroupID(const QString& groupName);
|
||||
void apiGetGroupRanks(const QUuid& groupID);
|
||||
|
||||
void initializeGroupPermissions(NodePermissionsMap& permissionsRows, QString groupName, NodePermissionsPointer perms);
|
||||
void packPermissionsForMap(QString mapName, NodePermissionsMap& permissionsRows, QString keyPath);
|
||||
void packPermissions();
|
||||
void unpackPermissions();
|
||||
NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost
|
||||
bool unpackPermissionsForKeypath(const QString& keyPath, NodePermissionsMap* destinationMapPointer,
|
||||
std::function<void(NodePermissionsPointer)> customUnpacker = {});
|
||||
bool ensurePermissionsForGroupRanks();
|
||||
|
||||
NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost, friend-of-domain-owner
|
||||
NodePermissionsMap _agentPermissions; // specific account-names
|
||||
|
||||
NodePermissionsMap _ipPermissions; // permissions granted by node IP address
|
||||
|
||||
NodePermissionsMap _groupPermissions; // permissions granted by membership to specific groups
|
||||
NodePermissionsMap _groupForbiddens; // permissions denied due to membership in a specific group
|
||||
// these are like _groupPermissions and _groupForbiddens but with uuids rather than group-names in the keys
|
||||
QHash<GroupByUUIDKey, NodePermissionsPointer> _groupPermissionsByUUID;
|
||||
QHash<GroupByUUIDKey, NodePermissionsPointer> _groupForbiddensByUUID;
|
||||
|
||||
QHash<QString, QUuid> _groupIDs; // keep track of group-name to group-id mappings
|
||||
QHash<QUuid, QString> _groupNames; // keep track of group-id to group-name mappings
|
||||
|
||||
// remember the responses to api/v1/groups/%1/ranks
|
||||
QHash<QUuid, QHash<QUuid, GroupRank>> _groupRanks; // QHash<group-id, QHash<rankID, rank>>
|
||||
|
||||
// keep track of answers to api queries about which users are in which groups
|
||||
QHash<QString, QHash<QUuid, QUuid>> _groupMembership; // QHash<user-name, QHash<group-id, rank-id>>
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServerSettingsManager_h
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
{
|
||||
"name": "Hydra to Standard",
|
||||
"channels": [
|
||||
{ "from": "Hydra.LY", "filters": "invert", "to": "Standard.LY" },
|
||||
{ "from": "Hydra.LX", "to": "Standard.LX" },
|
||||
{ "from": "Hydra.LY", "to": "Standard.LY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "Hydra.LX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.LX" },
|
||||
{ "from": "Hydra.LT", "to": "Standard.LTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
},
|
||||
{ "from": "Hydra.LT", "to": "Standard.LT" },
|
||||
{ "from": "Hydra.RY", "filters": "invert", "to": "Standard.RY" },
|
||||
{ "from": "Hydra.RX", "to": "Standard.RX" },
|
||||
{ "from": "Hydra.LT", "to": "Standard.LT" },
|
||||
|
||||
{ "from": "Hydra.RY", "to": "Standard.RY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "Hydra.RX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.RX" },
|
||||
{ "from": "Hydra.RT", "to": "Standard.RTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
|
@ -28,7 +39,6 @@
|
|||
{ "from": [ "Hydra.R1", "Hydra.R3" ], "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": [ "Hydra.R2", "Hydra.R4" ], "to": "Standard.RightSecondaryThumb" },
|
||||
{ "from": [ "Hydra.L2", "Hydra.L4" ], "to": "Standard.LeftSecondaryThumb" },
|
||||
|
||||
{ "from": "Hydra.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Hydra.RightHand", "to": "Standard.RightHand" }
|
||||
]
|
||||
|
|
|
@ -63,6 +63,19 @@
|
|||
["Keyboard.D", "Keyboard.Right", "Keyboard.TouchpadRight"]
|
||||
]
|
||||
},
|
||||
"when": "Application.CameraFirstPerson",
|
||||
"to": "Actions.Yaw"
|
||||
},
|
||||
{ "from": { "makeAxis" : [
|
||||
["Keyboard.A", "Keyboard.Left", "Keyboard.TouchpadLeft"],
|
||||
["Keyboard.D", "Keyboard.Right", "Keyboard.TouchpadRight"]
|
||||
]
|
||||
},
|
||||
"when": "Application.CameraThirdPerson",
|
||||
"to": "Actions.Yaw"
|
||||
},
|
||||
{ "from": { "makeAxis" : [ ["Keyboard.A"], ["Keyboard.D"] ] },
|
||||
"when": "Application.CameraFSM",
|
||||
"to": "Actions.Yaw"
|
||||
},
|
||||
|
||||
|
@ -81,9 +94,10 @@
|
|||
{ "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" },
|
||||
{ "from": "Keyboard.Down", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" },
|
||||
{ "from": "Keyboard.Up", "when": "Keyboard.Shift", "to": "Actions.PITCH_UP" },
|
||||
|
||||
{ "from": "Keyboard.Up", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||
{ "from": "Keyboard.Down", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
||||
{ "from": "Keyboard.Up", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||
{ "from": "Keyboard.Up", "when": "Application.CameraThirdPerson", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||
{ "from": "Keyboard.Down", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
||||
{ "from": "Keyboard.Down", "when": "Application.CameraThirdPerson", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
||||
|
||||
{ "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" },
|
||||
{ "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" },
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
{
|
||||
"name": "Oculus Touch to Standard",
|
||||
"channels": [
|
||||
{ "from": "OculusTouch.A", "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": "OculusTouch.B", "to": "Standard.RightSecondaryThumb" },
|
||||
{ "from": "OculusTouch.X", "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": "OculusTouch.Y", "to": "Standard.LeftSecondaryThumb" },
|
||||
{ "from": "OculusTouch.A", "to": "Standard.RightPrimaryThumb", "peek": true },
|
||||
{ "from": "OculusTouch.X", "to": "Standard.LeftPrimaryThumb", "peek": true },
|
||||
|
||||
{ "from": "OculusTouch.LY", "filters": "invert", "to": "Standard.LY" },
|
||||
{ "from": "OculusTouch.LX", "to": "Standard.LX" },
|
||||
{ "from": "OculusTouch.A", "to": "Standard.A" },
|
||||
{ "from": "OculusTouch.B", "to": "Standard.B" },
|
||||
{ "from": "OculusTouch.X", "to": "Standard.X" },
|
||||
{ "from": "OculusTouch.Y", "to": "Standard.Y" },
|
||||
|
||||
{ "from": "OculusTouch.LY", "to": "Standard.LY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.LX" },
|
||||
{ "from": "OculusTouch.LT", "to": "Standard.LTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
|
@ -17,8 +25,13 @@
|
|||
{ "from": "OculusTouch.LeftGrip", "to": "Standard.LeftGrip" },
|
||||
{ "from": "OculusTouch.LeftHand", "to": "Standard.LeftHand" },
|
||||
|
||||
{ "from": "OculusTouch.RY", "filters": "invert", "to": "Standard.RY" },
|
||||
{ "from": "OculusTouch.RX", "to": "Standard.RX" },
|
||||
{ "from": "OculusTouch.RY", "to": "Standard.RY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.RX" },
|
||||
{ "from": "OculusTouch.RT", "to": "Standard.RTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
|
|
|
@ -32,9 +32,6 @@
|
|||
{ "from": "Standard.Back", "to": "Actions.CycleCamera" },
|
||||
{ "from": "Standard.Start", "to": "Actions.ContextMenu" },
|
||||
|
||||
{ "from": [ "Standard.DU", "Standard.DL", "Standard.DR", "Standard.DD" ], "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": [ "Standard.A", "Standard.B", "Standard.X", "Standard.Y" ], "to": "Standard.RightPrimaryThumb" },
|
||||
|
||||
{ "from": "Standard.LT", "to": "Actions.LeftHandClick" },
|
||||
{ "from": "Standard.RT", "to": "Actions.RightHandClick" },
|
||||
|
||||
|
|
|
@ -15,12 +15,14 @@
|
|||
|
||||
{ "from": "GamePad.Back", "to": "Standard.Back" },
|
||||
{ "from": "GamePad.Start", "to": "Standard.Start" },
|
||||
|
||||
|
||||
{ "from": [ "GamePad.DU", "GamePad.DL", "GamePad.DR", "GamePad.DD" ], "to": "Standard.LeftPrimaryThumb", "peek": true },
|
||||
{ "from": "GamePad.DU", "to": "Standard.DU" },
|
||||
{ "from": "GamePad.DD", "to": "Standard.DD" },
|
||||
{ "from": "GamePad.DL", "to": "Standard.DL" },
|
||||
{ "from": "GamePad.DR", "to": "Standard.DR" },
|
||||
|
||||
{ "from": [ "GamePad.A", "GamePad.B", "GamePad.X", "GamePad.Y" ], "to": "Standard.RightPrimaryThumb", "peek": true },
|
||||
{ "from": "GamePad.A", "to": "Standard.A" },
|
||||
{ "from": "GamePad.B", "to": "Standard.B" },
|
||||
{ "from": "GamePad.X", "to": "Standard.X" },
|
||||
|
|
BIN
interface/resources/images/Default-Sky-9-ambient.jpg
Normal file
BIN
interface/resources/images/Default-Sky-9-ambient.jpg
Normal file
Binary file not shown.
After ![]() (image error) Size: 6.1 KiB |
BIN
interface/resources/images/Default-Sky-9-cubemap.jpg
Normal file
BIN
interface/resources/images/Default-Sky-9-cubemap.jpg
Normal file
Binary file not shown.
After ![]() (image error) Size: 394 KiB |
|
@ -314,6 +314,14 @@ ScrollingWindow {
|
|||
});
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: doUploadTimer
|
||||
property var url
|
||||
property bool isConnected: false
|
||||
interval: 5
|
||||
repeat: false
|
||||
running: false
|
||||
}
|
||||
|
||||
property var uploadOpen: false;
|
||||
Timer {
|
||||
|
@ -366,6 +374,10 @@ ScrollingWindow {
|
|||
}, dropping);
|
||||
}
|
||||
|
||||
function initiateUpload(url) {
|
||||
doUpload(doUploadTimer.url, false);
|
||||
}
|
||||
|
||||
if (fileUrl) {
|
||||
doUpload(fileUrl, true);
|
||||
} else {
|
||||
|
@ -373,12 +385,21 @@ ScrollingWindow {
|
|||
selectDirectory: false,
|
||||
dir: currentDirectory
|
||||
});
|
||||
|
||||
browser.canceled.connect(function() {
|
||||
uploadOpen = false;
|
||||
});
|
||||
|
||||
browser.selectedFile.connect(function(url) {
|
||||
currentDirectory = browser.dir;
|
||||
doUpload(url, false);
|
||||
|
||||
// Initiate upload from a timer so that file browser dialog can close beforehand.
|
||||
doUploadTimer.url = url;
|
||||
if (!doUploadTimer.isConnected) {
|
||||
doUploadTimer.triggered.connect(function() { initiateUpload(); });
|
||||
doUploadTimer.isConnected = true;
|
||||
}
|
||||
doUploadTimer.start();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -500,14 +521,15 @@ ScrollingWindow {
|
|||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: {
|
||||
var index = treeView.indexAt(mouse.x, mouse.y);
|
||||
|
||||
treeView.selection.setCurrentIndex(index, 0x0002);
|
||||
|
||||
contextMenu.currentIndex = index;
|
||||
contextMenu.popup();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
HifiControls.ContentSection {
|
||||
id: uploadSection
|
||||
|
|
|
@ -39,6 +39,23 @@ Windows.ScrollingWindow {
|
|||
// missing signal
|
||||
signal sendToScript(var message);
|
||||
|
||||
signal moved(vector2d position);
|
||||
signal resized(size size);
|
||||
|
||||
function notifyMoved() {
|
||||
moved(Qt.vector2d(x, y));
|
||||
}
|
||||
|
||||
function notifyResized() {
|
||||
resized(Qt.size(width, height));
|
||||
}
|
||||
|
||||
onXChanged: notifyMoved();
|
||||
onYChanged: notifyMoved();
|
||||
|
||||
onWidthChanged: notifyResized();
|
||||
onHeightChanged: notifyResized();
|
||||
|
||||
Item {
|
||||
width: pane.contentWidth
|
||||
implicitHeight: pane.scrollHeight
|
||||
|
|
|
@ -35,7 +35,6 @@ WebEngineView {
|
|||
}
|
||||
|
||||
onUrlChanged: {
|
||||
console.log("Url changed to " + url);
|
||||
var originalUrl = url.toString();
|
||||
newUrl = urlHandler.fixupUrl(originalUrl).toString();
|
||||
if (newUrl !== originalUrl) {
|
||||
|
|
|
@ -26,7 +26,6 @@ WebEngineView {
|
|||
}
|
||||
|
||||
onUrlChanged: {
|
||||
console.log("Url changed to " + url);
|
||||
var originalUrl = url.toString();
|
||||
newUrl = urlHandler.fixupUrl(originalUrl).toString();
|
||||
if (newUrl !== originalUrl) {
|
||||
|
|
|
@ -87,6 +87,15 @@ ModalWindow {
|
|||
currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder));
|
||||
}
|
||||
|
||||
helper.contentsChanged.connect(function() {
|
||||
if (folderListModel) {
|
||||
// Make folderListModel refresh.
|
||||
var save = folderListModel.folder;
|
||||
folderListModel.folder = "";
|
||||
folderListModel.folder = save;
|
||||
}
|
||||
});
|
||||
|
||||
fileTableView.forceActiveFocus();
|
||||
}
|
||||
|
||||
|
@ -343,12 +352,14 @@ ModalWindow {
|
|||
onFolderChanged: {
|
||||
if (folder === rootFolder) {
|
||||
model = driveListModel;
|
||||
helper.monitorDirectory("");
|
||||
update();
|
||||
} else {
|
||||
var needsUpdate = model === driveListModel && folder === folderListModel.folder;
|
||||
|
||||
model = folderListModel;
|
||||
folderListModel.folder = folder;
|
||||
helper.monitorDirectory(helper.urlToPath(folder));
|
||||
|
||||
if (needsUpdate) {
|
||||
update();
|
||||
|
|
|
@ -24,6 +24,7 @@ Item {
|
|||
Rectangle { color: hifi.colors.baseGray; anchors.fill: parent; radius: 4 }
|
||||
|
||||
Component.onCompleted: {
|
||||
jointChooser.model = MyAvatar.jointNames;
|
||||
completed = true;
|
||||
}
|
||||
|
||||
|
@ -82,7 +83,6 @@ Item {
|
|||
HifiControls.ComboBox {
|
||||
id: jointChooser;
|
||||
anchors { bottom: parent.bottom; left: parent.left; right: parent.right }
|
||||
model: MyAvatar.jointNames
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
currentIndex: attachment ? model.indexOf(attachment.jointName) : -1
|
||||
onCurrentIndexChanged: {
|
||||
|
|
|
@ -78,7 +78,10 @@ Decoration {
|
|||
id: closeClickArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: window.shown = false;
|
||||
onClicked: {
|
||||
window.shown = false;
|
||||
window.windowClosed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ Fadable {
|
|||
//
|
||||
// Signals
|
||||
//
|
||||
signal windowClosed();
|
||||
signal windowDestroyed();
|
||||
signal mouseEntered();
|
||||
signal mouseExited();
|
||||
|
|
|
@ -139,7 +139,6 @@
|
|||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
#include "SpeechRecognizer.h"
|
||||
#endif
|
||||
#include "Stars.h"
|
||||
#include "ui/AddressBarDialog.h"
|
||||
#include "ui/AvatarInputs.h"
|
||||
#include "ui/DialogsManager.h"
|
||||
|
@ -391,6 +390,11 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt
|
|||
}
|
||||
|
||||
static const QString STATE_IN_HMD = "InHMD";
|
||||
static const QString STATE_CAMERA_FULL_SCREEN_MIRROR = "CameraFSM";
|
||||
static const QString STATE_CAMERA_FIRST_PERSON = "CameraFirstPerson";
|
||||
static const QString STATE_CAMERA_THIRD_PERSON = "CameraThirdPerson";
|
||||
static const QString STATE_CAMERA_ENTITY = "CameraEntity";
|
||||
static const QString STATE_CAMERA_INDEPENDENT = "CameraIndependent";
|
||||
static const QString STATE_SNAP_TURN = "SnapTurn";
|
||||
static const QString STATE_GROUNDED = "Grounded";
|
||||
static const QString STATE_NAV_FOCUSED = "NavigationFocused";
|
||||
|
@ -470,7 +474,9 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
DependencyManager::set<InterfaceActionFactory>();
|
||||
DependencyManager::set<AudioInjectorManager>();
|
||||
DependencyManager::set<MessagesClient>();
|
||||
controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_SNAP_TURN, STATE_GROUNDED, STATE_NAV_FOCUSED } });
|
||||
controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR,
|
||||
STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_THIRD_PERSON, STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT,
|
||||
STATE_SNAP_TURN, STATE_GROUNDED, STATE_NAV_FOCUSED } });
|
||||
DependencyManager::set<UserInputMapper>();
|
||||
DependencyManager::set<controller::ScriptingInterface, ControllerScriptingInterface>();
|
||||
DependencyManager::set<InterfaceParentFinder>();
|
||||
|
@ -739,7 +745,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
connect(&identityPacketTimer, &QTimer::timeout, getMyAvatar(), &MyAvatar::sendIdentityPacket);
|
||||
identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
||||
|
||||
ResourceCache::setRequestLimit(MAX_CONCURRENT_RESOURCE_DOWNLOADS);
|
||||
const char** constArgv = const_cast<const char**>(argv);
|
||||
QString concurrentDownloadsStr = getCmdOption(argc, constArgv, "--concurrent-downloads");
|
||||
bool success;
|
||||
int concurrentDownloads = concurrentDownloadsStr.toInt(&success);
|
||||
if (!success) {
|
||||
concurrentDownloads = MAX_CONCURRENT_RESOURCE_DOWNLOADS;
|
||||
}
|
||||
ResourceCache::setRequestLimit(concurrentDownloads);
|
||||
|
||||
_glWidget = new GLCanvas();
|
||||
getApplicationCompositor().setRenderingWidget(_glWidget);
|
||||
|
@ -811,6 +824,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
}
|
||||
UserActivityLogger::getInstance().logAction("launch", properties);
|
||||
|
||||
_connectionMonitor.init();
|
||||
|
||||
// Tell our entity edit sender about our known jurisdictions
|
||||
_entityEditSender.setServerJurisdictions(&_entityServerJurisdictions);
|
||||
|
@ -954,6 +968,21 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
_applicationStateDevice->setInputVariant(STATE_IN_HMD, []() -> float {
|
||||
return qApp->isHMDMode() ? 1 : 0;
|
||||
});
|
||||
_applicationStateDevice->setInputVariant(STATE_CAMERA_FULL_SCREEN_MIRROR, []() -> float {
|
||||
return qApp->getCamera()->getMode() == CAMERA_MODE_MIRROR ? 1 : 0;
|
||||
});
|
||||
_applicationStateDevice->setInputVariant(STATE_CAMERA_FIRST_PERSON, []() -> float {
|
||||
return qApp->getCamera()->getMode() == CAMERA_MODE_FIRST_PERSON ? 1 : 0;
|
||||
});
|
||||
_applicationStateDevice->setInputVariant(STATE_CAMERA_THIRD_PERSON, []() -> float {
|
||||
return qApp->getCamera()->getMode() == CAMERA_MODE_THIRD_PERSON ? 1 : 0;
|
||||
});
|
||||
_applicationStateDevice->setInputVariant(STATE_CAMERA_ENTITY, []() -> float {
|
||||
return qApp->getCamera()->getMode() == CAMERA_MODE_ENTITY ? 1 : 0;
|
||||
});
|
||||
_applicationStateDevice->setInputVariant(STATE_CAMERA_INDEPENDENT, []() -> float {
|
||||
return qApp->getCamera()->getMode() == CAMERA_MODE_INDEPENDENT ? 1 : 0;
|
||||
});
|
||||
_applicationStateDevice->setInputVariant(STATE_SNAP_TURN, []() -> float {
|
||||
return qApp->getMyAvatar()->getSnapTurn() ? 1 : 0;
|
||||
});
|
||||
|
@ -1204,6 +1233,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
|
||||
qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0);
|
||||
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
|
||||
QString skyboxUrl { PathUtils::resourcesPath() + "images/Default-Sky-9-cubemap.jpg" };
|
||||
QString skyboxAmbientUrl { PathUtils::resourcesPath() + "images/Default-Sky-9-ambient.jpg" };
|
||||
|
||||
_defaultSkyboxTexture = textureCache->getImageTexture(skyboxUrl, NetworkTexture::CUBE_TEXTURE, { { "generateIrradiance", false } });
|
||||
_defaultSkyboxAmbientTexture = textureCache->getImageTexture(skyboxAmbientUrl, NetworkTexture::CUBE_TEXTURE, { { "generateIrradiance", true } });
|
||||
|
||||
_defaultSkybox->setCubemap(_defaultSkyboxTexture);
|
||||
_defaultSkybox->setColor({ 1.0, 1.0, 1.0 });
|
||||
|
||||
// After all of the constructor is completed, then set firstRun to false.
|
||||
Setting::Handle<bool> firstRun{ Settings::firstRun, true };
|
||||
firstRun.set(false);
|
||||
|
@ -2272,7 +2312,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
}
|
||||
|
||||
case Qt::Key_Asterisk:
|
||||
Menu::getInstance()->triggerOption(MenuOption::Stars);
|
||||
Menu::getInstance()->triggerOption(MenuOption::DefaultSkybox);
|
||||
break;
|
||||
|
||||
case Qt::Key_S:
|
||||
|
@ -3240,6 +3280,18 @@ void Application::init() {
|
|||
getEntities()->setViewFrustum(_viewFrustum);
|
||||
}
|
||||
|
||||
getEntities()->setEntityLoadingPriorityFunction([this](const EntityItem& item) {
|
||||
auto dims = item.getDimensions();
|
||||
auto maxSize = glm::max(dims.x, dims.y, dims.z);
|
||||
|
||||
if (maxSize <= 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
auto distance = glm::distance(getMyAvatar()->getPosition(), item.getPosition());
|
||||
return atan2(maxSize, distance);
|
||||
});
|
||||
|
||||
ObjectMotionState::setShapeManager(&_shapeManager);
|
||||
_physicsEngine->init();
|
||||
|
||||
|
@ -4185,8 +4237,6 @@ public:
|
|||
typedef render::Payload<BackgroundRenderData> Payload;
|
||||
typedef Payload::DataPointer Pointer;
|
||||
|
||||
Stars _stars;
|
||||
|
||||
static render::ItemID _item; // unique WorldBoxRenderData
|
||||
};
|
||||
|
||||
|
@ -4221,15 +4271,26 @@ namespace render {
|
|||
|
||||
// Fall through: if no skybox is available, render the SKY_DOME
|
||||
case model::SunSkyStage::SKY_DOME: {
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) {
|
||||
PerformanceTimer perfTimer("stars");
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::payloadRender<BackgroundRenderData>() ... My god, it's full of stars...");
|
||||
// should be the first rendering pass - w/o depth buffer / lighting
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::DefaultSkybox)) {
|
||||
static const glm::vec3 DEFAULT_SKYBOX_COLOR { 255.0f / 255.0f, 220.0f / 255.0f, 194.0f / 255.0f };
|
||||
static const float DEFAULT_SKYBOX_INTENSITY { 0.2f };
|
||||
static const float DEFAULT_SKYBOX_AMBIENT_INTENSITY { 2.0f };
|
||||
static const glm::vec3 DEFAULT_SKYBOX_DIRECTION { 0.0f, 0.0f, -1.0f };
|
||||
|
||||
static const float alpha = 1.0f;
|
||||
background->_stars.render(args, alpha);
|
||||
}
|
||||
auto scene = DependencyManager::get<SceneScriptingInterface>()->getStage();
|
||||
auto sceneKeyLight = scene->getKeyLight();
|
||||
scene->setSunModelEnable(false);
|
||||
sceneKeyLight->setColor(DEFAULT_SKYBOX_COLOR);
|
||||
sceneKeyLight->setIntensity(DEFAULT_SKYBOX_INTENSITY);
|
||||
sceneKeyLight->setAmbientIntensity(DEFAULT_SKYBOX_AMBIENT_INTENSITY);
|
||||
sceneKeyLight->setDirection(DEFAULT_SKYBOX_DIRECTION);
|
||||
|
||||
auto defaultSkyboxAmbientTexture = qApp->getDefaultSkyboxAmbientTexture();
|
||||
sceneKeyLight->setAmbientSphere(defaultSkyboxAmbientTexture->getIrradiance());
|
||||
sceneKeyLight->setAmbientMap(defaultSkyboxAmbientTexture);
|
||||
|
||||
qApp->getDefaultSkybox()->render(batch, args->getViewFrustum());
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -4423,7 +4484,6 @@ void Application::updateWindowTitle() const {
|
|||
#endif
|
||||
_window->setWindowTitle(title);
|
||||
}
|
||||
|
||||
void Application::clearDomainOctreeDetails() {
|
||||
|
||||
// if we're about to quit, we really don't need to do any of these things...
|
||||
|
@ -4449,6 +4509,7 @@ void Application::clearDomainOctreeDetails() {
|
|||
getEntities()->clear();
|
||||
|
||||
auto skyStage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
|
||||
|
||||
skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME);
|
||||
|
||||
_recentlyClearedDomain = true;
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
#include "avatar/MyAvatar.h"
|
||||
#include "Bookmarks.h"
|
||||
#include "Camera.h"
|
||||
#include "ConnectionMonitor.h"
|
||||
#include "FileLogger.h"
|
||||
#include "gpu/Context.h"
|
||||
#include "Menu.h"
|
||||
|
@ -64,6 +65,9 @@
|
|||
#include "ui/overlays/Overlays.h"
|
||||
#include "UndoStackScriptingInterface.h"
|
||||
|
||||
#include <procedural/ProceduralSkybox.h>
|
||||
#include <model/Skybox.h>
|
||||
|
||||
class OffscreenGLCanvas;
|
||||
class GLCanvas;
|
||||
class FaceTracker;
|
||||
|
@ -251,6 +255,10 @@ public:
|
|||
void takeSnapshot(bool notify);
|
||||
void shareSnapshot(const QString& filename);
|
||||
|
||||
model::SkyboxPointer getDefaultSkybox() const { return _defaultSkybox; }
|
||||
gpu::TexturePointer getDefaultSkyboxTexture() const { return _defaultSkyboxTexture; }
|
||||
gpu::TexturePointer getDefaultSkyboxAmbientTexture() const { return _defaultSkyboxAmbientTexture; }
|
||||
|
||||
signals:
|
||||
void svoImportRequested(const QString& url);
|
||||
|
||||
|
@ -563,6 +571,13 @@ private:
|
|||
bool _recentlyClearedDomain { false };
|
||||
|
||||
QString _returnFromFullScreenMirrorTo;
|
||||
|
||||
ConnectionMonitor _connectionMonitor;
|
||||
|
||||
model::SkyboxPointer _defaultSkybox { new ProceduralSkybox() } ;
|
||||
gpu::TexturePointer _defaultSkyboxTexture;
|
||||
gpu::TexturePointer _defaultSkyboxAmbientTexture;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_Application_h
|
||||
|
|
52
interface/src/ConnectionMonitor.cpp
Normal file
52
interface/src/ConnectionMonitor.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// ConnectionMonitor.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Ryan Huffman on 8/4/15.
|
||||
// Copyright 2015 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 "ConnectionMonitor.h"
|
||||
|
||||
#include "ui/DialogsManager.h"
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <DomainHandler.h>
|
||||
#include <AddressManager.h>
|
||||
|
||||
static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000;
|
||||
|
||||
void ConnectionMonitor::init() {
|
||||
// Connect to domain disconnected message
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &ConnectionMonitor::disconnectedFromDomain);
|
||||
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &ConnectionMonitor::connectedToDomain);
|
||||
|
||||
// Connect to AddressManager::hostChanged
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
connect(addressManager.data(), &AddressManager::hostChanged, this, &ConnectionMonitor::hostChanged);
|
||||
|
||||
_timer.setSingleShot(true);
|
||||
_timer.setInterval(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS);
|
||||
_timer.start();
|
||||
|
||||
auto dialogsManager = DependencyManager::get<DialogsManager>();
|
||||
connect(&_timer, &QTimer::timeout, dialogsManager.data(), &DialogsManager::showAddressBar);
|
||||
}
|
||||
|
||||
void ConnectionMonitor::disconnectedFromDomain() {
|
||||
_timer.start();
|
||||
}
|
||||
|
||||
void ConnectionMonitor::connectedToDomain(const QString& name) {
|
||||
_timer.stop();
|
||||
}
|
||||
|
||||
void ConnectionMonitor::hostChanged(const QString& name) {
|
||||
_timer.start();
|
||||
}
|
34
interface/src/ConnectionMonitor.h
Normal file
34
interface/src/ConnectionMonitor.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// ConnectionMonitor.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Ryan Huffman on 8/4/15.
|
||||
// Copyright 2015 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_ConnectionMonitor_h
|
||||
#define hifi_ConnectionMonitor_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
class QString;
|
||||
|
||||
class ConnectionMonitor : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
void init();
|
||||
|
||||
private slots:
|
||||
void disconnectedFromDomain();
|
||||
void connectedToDomain(const QString& name);
|
||||
void hostChanged(const QString& name);
|
||||
|
||||
private:
|
||||
QTimer _timer;
|
||||
};
|
||||
|
||||
#endif // hifi_ConnectionMonitor_h
|
|
@ -337,7 +337,7 @@ Menu::Menu() {
|
|||
// Developer > Render >>>
|
||||
MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render");
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::WorldAxes);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DefaultSkybox, 0, true);
|
||||
|
||||
// Developer > Render > Throttle FPS If Not Focus
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ThrottleFPSIfNotFocus, 0, true);
|
||||
|
|
|
@ -80,6 +80,7 @@ namespace MenuOption {
|
|||
const QString CrashNewFaultThreaded = "New Fault (threaded)";
|
||||
const QString DeadlockInterface = "Deadlock Interface";
|
||||
const QString DecreaseAvatarSize = "Decrease Avatar Size";
|
||||
const QString DefaultSkybox = "Default Skybox";
|
||||
const QString DeleteBookmark = "Delete Bookmark...";
|
||||
const QString DisableActivityLogger = "Disable Activity Logger";
|
||||
const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment";
|
||||
|
@ -175,7 +176,6 @@ namespace MenuOption {
|
|||
const QString StandingHMDSensorMode = "Standing HMD Sensor Mode";
|
||||
const QString SimulateEyeTracking = "Simulate";
|
||||
const QString SMIEyeTracking = "SMI Eye Tracking";
|
||||
const QString Stars = "Stars";
|
||||
const QString Stats = "Stats";
|
||||
const QString StopAllScripts = "Stop All Scripts";
|
||||
const QString SuppressShortTimings = "Suppress Timings Less than 10ms";
|
||||
|
|
|
@ -1,216 +0,0 @@
|
|||
//
|
||||
// Stars.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Tobias Schwinger on 3/22/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "Stars.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <NumericalConstants.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <TextureCache.h>
|
||||
#include <RenderArgs.h>
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
#include <render-utils/stars_vert.h>
|
||||
#include <render-utils/stars_frag.h>
|
||||
|
||||
#include <render-utils/standardTransformPNTC_vert.h>
|
||||
#include <render-utils/starsGrid_frag.h>
|
||||
|
||||
//static const float TILT = 0.23f;
|
||||
static const float TILT = 0.0f;
|
||||
static const unsigned int STARFIELD_NUM_STARS = 50000;
|
||||
static const unsigned int STARFIELD_SEED = 1;
|
||||
static const float STAR_COLORIZATION = 0.1f;
|
||||
|
||||
static const float TAU = 6.28318530717958f;
|
||||
//static const float HALF_TAU = TAU / 2.0f;
|
||||
//static const float QUARTER_TAU = TAU / 4.0f;
|
||||
//static const float MILKY_WAY_WIDTH = TAU / 30.0f; // width in radians of one half of the Milky Way
|
||||
//static const float MILKY_WAY_INCLINATION = 0.0f; // angle of Milky Way from horizontal in degrees
|
||||
//static const float MILKY_WAY_RATIO = 0.4f;
|
||||
static const char* UNIFORM_TIME_NAME = "iGlobalTime";
|
||||
|
||||
// Produce a random float value between 0 and 1
|
||||
static float frand() {
|
||||
return (float)rand() / (float)RAND_MAX;
|
||||
}
|
||||
|
||||
// http://mathworld.wolfram.com/SpherePointPicking.html
|
||||
static vec2 randPolar() {
|
||||
vec2 result(frand(), frand());
|
||||
result.x *= TAU;
|
||||
result.y = powf(result.y, 2.0) / 2.0f;
|
||||
if (frand() > 0.5f) {
|
||||
result.y = 0.5f - result.y;
|
||||
} else {
|
||||
result.y += 0.5f;
|
||||
}
|
||||
result.y = acos((2.0f * result.y) - 1.0f);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static vec3 fromPolar(const vec2& polar) {
|
||||
float sinTheta = sin(polar.x);
|
||||
float cosTheta = cos(polar.x);
|
||||
float sinPhi = sin(polar.y);
|
||||
float cosPhi = cos(polar.y);
|
||||
return vec3(
|
||||
cosTheta * sinPhi,
|
||||
cosPhi,
|
||||
sinTheta * sinPhi);
|
||||
}
|
||||
|
||||
|
||||
// computeStarColor
|
||||
// - Generate a star color.
|
||||
//
|
||||
// colorization can be a value between 0 and 1 specifying how colorful the resulting star color is.
|
||||
//
|
||||
// 0 = completely black & white
|
||||
// 1 = very colorful
|
||||
unsigned computeStarColor(float colorization) {
|
||||
unsigned char red, green, blue;
|
||||
if (randFloat() < 0.3f) {
|
||||
// A few stars are colorful
|
||||
red = 2 + (rand() % 254);
|
||||
green = 2 + round((red * (1 - colorization)) + ((rand() % 254) * colorization));
|
||||
blue = 2 + round((red * (1 - colorization)) + ((rand() % 254) * colorization));
|
||||
} else {
|
||||
// Most stars are dimmer and white
|
||||
red = green = blue = 2 + (rand() % 128);
|
||||
}
|
||||
return red | (green << 8) | (blue << 16);
|
||||
}
|
||||
|
||||
struct StarVertex {
|
||||
vec4 position;
|
||||
vec4 colorAndSize;
|
||||
};
|
||||
|
||||
static const int STARS_VERTICES_SLOT{ 0 };
|
||||
static const int STARS_COLOR_SLOT{ 1 };
|
||||
|
||||
gpu::PipelinePointer Stars::_gridPipeline{};
|
||||
gpu::PipelinePointer Stars::_starsPipeline{};
|
||||
int32_t Stars::_timeSlot{ -1 };
|
||||
|
||||
void Stars::init() {
|
||||
if (!_gridPipeline) {
|
||||
auto vs = gpu::Shader::createVertex(std::string(standardTransformPNTC_vert));
|
||||
auto ps = gpu::Shader::createPixel(std::string(starsGrid_frag));
|
||||
auto program = gpu::Shader::createProgram(vs, ps);
|
||||
gpu::Shader::makeProgram((*program));
|
||||
_timeSlot = program->getBuffers().findLocation(UNIFORM_TIME_NAME);
|
||||
if (_timeSlot == gpu::Shader::INVALID_LOCATION) {
|
||||
_timeSlot = program->getUniforms().findLocation(UNIFORM_TIME_NAME);
|
||||
}
|
||||
auto state = gpu::StatePointer(new gpu::State());
|
||||
// enable decal blend
|
||||
state->setDepthTest(gpu::State::DepthTest(false));
|
||||
state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
|
||||
_gridPipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
|
||||
if (!_starsPipeline) {
|
||||
auto vs = gpu::Shader::createVertex(std::string(stars_vert));
|
||||
auto ps = gpu::Shader::createPixel(std::string(stars_frag));
|
||||
auto program = gpu::Shader::createProgram(vs, ps);
|
||||
gpu::Shader::makeProgram((*program));
|
||||
auto state = gpu::StatePointer(new gpu::State());
|
||||
// enable decal blend
|
||||
state->setDepthTest(gpu::State::DepthTest(false));
|
||||
state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
||||
state->setAntialiasedLineEnable(true); // line smoothing also smooth points
|
||||
state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA);
|
||||
_starsPipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
|
||||
unsigned limit = STARFIELD_NUM_STARS;
|
||||
std::vector<StarVertex> points;
|
||||
points.resize(limit);
|
||||
|
||||
{ // generate stars
|
||||
QElapsedTimer startTime;
|
||||
startTime.start();
|
||||
|
||||
vertexBuffer.reset(new gpu::Buffer);
|
||||
|
||||
srand(STARFIELD_SEED);
|
||||
for (size_t star = 0; star < limit; ++star) {
|
||||
points[star].position = vec4(fromPolar(randPolar()), 1);
|
||||
float size = frand() * 2.5f + 0.5f;
|
||||
if (frand() < STAR_COLORIZATION) {
|
||||
vec3 color(frand() / 2.0f + 0.5f, frand() / 2.0f + 0.5f, frand() / 2.0f + 0.5f);
|
||||
points[star].colorAndSize = vec4(color, size);
|
||||
} else {
|
||||
vec3 color(frand() / 2.0f + 0.5f);
|
||||
points[star].colorAndSize = vec4(color, size);
|
||||
}
|
||||
}
|
||||
|
||||
double timeDiff = (double)startTime.nsecsElapsed() / 1000000.0; // ns to ms
|
||||
qDebug() << "Total time to generate stars: " << timeDiff << " msec";
|
||||
}
|
||||
|
||||
gpu::Element positionElement, colorElement;
|
||||
const size_t VERTEX_STRIDE = sizeof(StarVertex);
|
||||
|
||||
vertexBuffer->append(VERTEX_STRIDE * limit, (const gpu::Byte*)&points[0]);
|
||||
streamFormat.reset(new gpu::Stream::Format()); // 1 for everyone
|
||||
streamFormat->setAttribute(gpu::Stream::POSITION, STARS_VERTICES_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), 0);
|
||||
streamFormat->setAttribute(gpu::Stream::COLOR, STARS_COLOR_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::RGBA));
|
||||
positionElement = streamFormat->getAttributes().at(gpu::Stream::POSITION)._element;
|
||||
colorElement = streamFormat->getAttributes().at(gpu::Stream::COLOR)._element;
|
||||
|
||||
size_t offset = offsetof(StarVertex, position);
|
||||
positionView = gpu::BufferView(vertexBuffer, offset, vertexBuffer->getSize(), VERTEX_STRIDE, positionElement);
|
||||
|
||||
offset = offsetof(StarVertex, colorAndSize);
|
||||
colorView = gpu::BufferView(vertexBuffer, offset, vertexBuffer->getSize(), VERTEX_STRIDE, colorElement);
|
||||
}
|
||||
|
||||
// FIXME star colors
|
||||
void Stars::render(RenderArgs* renderArgs, float alpha) {
|
||||
std::call_once(once, [&]{ init(); });
|
||||
|
||||
|
||||
auto modelCache = DependencyManager::get<ModelCache>();
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
||||
|
||||
gpu::Batch& batch = *renderArgs->_batch;
|
||||
batch.setViewTransform(Transform());
|
||||
batch.setProjectionTransform(renderArgs->getViewFrustum().getProjection());
|
||||
batch.setModelTransform(Transform().setRotation(glm::inverse(renderArgs->getViewFrustum().getOrientation()) *
|
||||
quat(vec3(TILT, 0, 0))));
|
||||
batch.setResourceTexture(0, textureCache->getWhiteTexture());
|
||||
|
||||
// Render the world lines
|
||||
batch.setPipeline(_gridPipeline);
|
||||
static auto start = usecTimestampNow();
|
||||
float msecs = (float)(usecTimestampNow() - start) / (float)USECS_PER_MSEC;
|
||||
float secs = msecs / (float)MSECS_PER_SECOND;
|
||||
batch._glUniform1f(_timeSlot, secs);
|
||||
geometryCache->renderCube(batch);
|
||||
|
||||
// Render the stars
|
||||
batch.setPipeline(_starsPipeline);
|
||||
batch.setInputFormat(streamFormat);
|
||||
batch.setInputBuffer(STARS_VERTICES_SLOT, positionView);
|
||||
batch.setInputBuffer(STARS_COLOR_SLOT, colorView);
|
||||
batch.draw(gpu::Primitive::POINTS, STARFIELD_NUM_STARS);
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
//
|
||||
// Stars.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Tobias Schwinger on 3/22/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_Stars_h
|
||||
#define hifi_Stars_h
|
||||
|
||||
#include <gpu/Context.h>
|
||||
|
||||
class RenderArgs;
|
||||
|
||||
// Starfield rendering component.
|
||||
class Stars {
|
||||
public:
|
||||
Stars() = default;
|
||||
~Stars() = default;
|
||||
|
||||
Stars(Stars const&) = delete;
|
||||
Stars& operator=(Stars const&) = delete;
|
||||
|
||||
// Renders the starfield from a local viewer's perspective.
|
||||
// The parameters specifiy the field of view.
|
||||
void render(RenderArgs* args, float alpha);
|
||||
|
||||
private:
|
||||
// Pipelines
|
||||
static gpu::PipelinePointer _gridPipeline;
|
||||
static gpu::PipelinePointer _starsPipeline;
|
||||
static int32_t _timeSlot;
|
||||
|
||||
// Buffers
|
||||
gpu::BufferPointer vertexBuffer;
|
||||
gpu::Stream::FormatPointer streamFormat;
|
||||
gpu::BufferView positionView;
|
||||
gpu::BufferView colorView;
|
||||
std::once_flag once;
|
||||
|
||||
void init();
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_Stars_h
|
|
@ -98,6 +98,7 @@ Avatar::Avatar(RigPointer rig) :
|
|||
_headData = static_cast<HeadData*>(new Head(this));
|
||||
|
||||
_skeletonModel = std::make_shared<SkeletonModel>(this, nullptr, rig);
|
||||
connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished);
|
||||
}
|
||||
|
||||
Avatar::~Avatar() {
|
||||
|
@ -298,7 +299,9 @@ void Avatar::simulate(float deltaTime) {
|
|||
{
|
||||
PerformanceTimer perfTimer("head");
|
||||
glm::vec3 headPosition = getPosition();
|
||||
_skeletonModel->getHeadPosition(headPosition);
|
||||
if (!_skeletonModel->getHeadPosition(headPosition)) {
|
||||
headPosition = getPosition();
|
||||
}
|
||||
Head* head = getHead();
|
||||
head->setPosition(headPosition);
|
||||
head->setScale(getUniformScale());
|
||||
|
@ -306,6 +309,7 @@ void Avatar::simulate(float deltaTime) {
|
|||
}
|
||||
} else {
|
||||
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
|
||||
getHead()->setPosition(getPosition());
|
||||
_skeletonModel->simulate(deltaTime, false);
|
||||
}
|
||||
|
||||
|
@ -776,7 +780,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const
|
|||
|
||||
{
|
||||
PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderBevelCornersRect");
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, true, true, true);
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, false, true, true, true);
|
||||
DependencyManager::get<GeometryCache>()->renderBevelCornersRect(batch, left, bottom, width, height,
|
||||
bevelDistance, backgroundColor);
|
||||
}
|
||||
|
@ -916,6 +920,17 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
}
|
||||
}
|
||||
|
||||
void Avatar::setModelURLFinished(bool success) {
|
||||
if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) {
|
||||
qDebug() << "Using default after failing to load Avatar model: " << _skeletonModelURL;
|
||||
// call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that
|
||||
// we don't redo this every time we receive an identity packet from the avatar with the bad url.
|
||||
QMetaObject::invokeMethod(_skeletonModel.get(), "setURL",
|
||||
Qt::QueuedConnection, Q_ARG(QUrl, AvatarData::defaultFullAvatarModelUrl()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// create new model, can return an instance of a SoftAttachmentModel rather then Model
|
||||
static std::shared_ptr<Model> allocateAttachmentModel(bool isSoft, RigPointer rigOverride) {
|
||||
if (isSoft) {
|
||||
|
|
|
@ -184,6 +184,8 @@ public slots:
|
|||
glm::vec3 getRightPalmPosition() const;
|
||||
glm::quat getRightPalmRotation() const;
|
||||
|
||||
void setModelURLFinished(bool success);
|
||||
|
||||
protected:
|
||||
friend class AvatarManager;
|
||||
|
||||
|
|
|
@ -430,6 +430,7 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
|
||||
if (!_skeletonModel->hasSkeleton()) {
|
||||
// All the simulation that can be done has been done
|
||||
getHead()->setPosition(getPosition()); // so audio-position isn't 0,0,0
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,8 +62,10 @@ void AssetMappingsScriptingInterface::getMapping(QString path, QJSValue callback
|
|||
auto request = assetClient->createGetMappingRequest(path);
|
||||
|
||||
connect(request, &GetMappingRequest::finished, this, [this, callback](GetMappingRequest* request) mutable {
|
||||
auto hash = request->getHash();
|
||||
|
||||
if (callback.isCallable()) {
|
||||
QJSValueList args { request->getErrorString() };
|
||||
QJSValueList args { request->getErrorString(), hash };
|
||||
callback.call(args);
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
#include "Util.h"
|
||||
#include "ui/Stats.h"
|
||||
#include "ui/AvatarInputs.h"
|
||||
#include "OffscreenUi.h"
|
||||
#include <QQmlContext>
|
||||
|
||||
const vec4 CONNECTION_STATUS_BORDER_COLOR{ 1.0f, 0.0f, 0.0f, 0.8f };
|
||||
static const float ORTHO_NEAR_CLIP = -1000.0f;
|
||||
|
@ -177,13 +179,11 @@ void ApplicationOverlay::renderRearView(RenderArgs* renderArgs) {
|
|||
glm::vec2 texCoordMinCorner(0.0f, 0.0f);
|
||||
glm::vec2 texCoordMaxCorner(viewport.width() * renderRatio / float(selfieTexture->getWidth()), viewport.height() * renderRatio / float(selfieTexture->getHeight()));
|
||||
|
||||
|
||||
geometryCache->useSimpleDrawPipeline(batch, true);
|
||||
batch.setResourceTexture(0, selfieTexture);
|
||||
geometryCache->renderQuad(batch, bottomLeft, topRight, texCoordMinCorner, texCoordMaxCorner, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
float alpha = DependencyManager::get<OffscreenUi>()->getDesktop()->property("unpinnedAlpha").toFloat();
|
||||
geometryCache->renderQuad(batch, bottomLeft, topRight, texCoordMinCorner, texCoordMaxCorner, glm::vec4(1.0f, 1.0f, 1.0f, alpha));
|
||||
|
||||
batch.setResourceTexture(0, renderArgs->_whiteTexture);
|
||||
geometryCache->useSimpleDrawPipeline(batch, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,10 @@ void DialogsManager::toggleAddressBar() {
|
|||
emit addressBarToggled();
|
||||
}
|
||||
|
||||
void DialogsManager::showAddressBar() {
|
||||
AddressBarDialog::show();
|
||||
}
|
||||
|
||||
void DialogsManager::toggleDiskCacheEditor() {
|
||||
maybeCreateDialog(_diskCacheEditor);
|
||||
_diskCacheEditor->toggle();
|
||||
|
|
|
@ -44,6 +44,7 @@ public:
|
|||
|
||||
public slots:
|
||||
void toggleAddressBar();
|
||||
void showAddressBar();
|
||||
void toggleDiskCacheEditor();
|
||||
void toggleLoginDialog();
|
||||
void showLoginDialog();
|
||||
|
|
|
@ -47,7 +47,7 @@ void Cube3DOverlay::render(RenderArgs* args) {
|
|||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
auto pipeline = args->_pipeline;
|
||||
if (!pipeline) {
|
||||
pipeline = _isSolid ? geometryCache->getShapePipeline() : geometryCache->getWireShapePipeline();
|
||||
pipeline = _isSolid ? geometryCache->getOpaqueShapePipeline() : geometryCache->getWireShapePipeline();
|
||||
}
|
||||
|
||||
if (_isSolid) {
|
||||
|
@ -55,7 +55,7 @@ void Cube3DOverlay::render(RenderArgs* args) {
|
|||
batch->setModelTransform(transform);
|
||||
geometryCache->renderSolidCubeInstance(*batch, cubeColor, pipeline);
|
||||
} else {
|
||||
geometryCache->bindSimpleProgram(*batch, false, false, true, true);
|
||||
geometryCache->bindSimpleProgram(*batch, false, false, false, true, true);
|
||||
if (getIsDashedLine()) {
|
||||
transform.setScale(1.0f);
|
||||
batch->setModelTransform(transform);
|
||||
|
|
|
@ -57,12 +57,12 @@ void Line3DOverlay::render(RenderArgs* args) {
|
|||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
if (getIsDashedLine()) {
|
||||
// TODO: add support for color to renderDashedLine()
|
||||
geometryCache->bindSimpleProgram(*batch, false, false, true, true);
|
||||
geometryCache->bindSimpleProgram(*batch, false, false, false, true, true);
|
||||
geometryCache->renderDashedLine(*batch, _start, _end, colorv4, _geometryCacheID);
|
||||
} else if (_glow > 0.0f) {
|
||||
geometryCache->renderGlowLine(*batch, _start, _end, colorv4, _glow, _glowWidth, _geometryCacheID);
|
||||
} else {
|
||||
geometryCache->bindSimpleProgram(*batch, false, false, true, true);
|
||||
geometryCache->bindSimpleProgram(*batch, false, false, false, true, true);
|
||||
geometryCache->renderLine(*batch, _start, _end, colorv4, _geometryCacheID);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ public slots:
|
|||
/// successful edit, if the input id is for an unknown overlay this function will have no effect
|
||||
bool editOverlays(const QVariant& propertiesById);
|
||||
|
||||
/// deletes a particle
|
||||
/// deletes an overlay
|
||||
void deleteOverlay(unsigned int id);
|
||||
|
||||
/// get the string type of the overlay used in addOverlay
|
||||
|
|
|
@ -61,7 +61,7 @@ void Rectangle3DOverlay::render(RenderArgs* args) {
|
|||
geometryCache->bindSimpleProgram(*batch);
|
||||
geometryCache->renderQuad(*batch, topLeft, bottomRight, rectangleColor);
|
||||
} else {
|
||||
geometryCache->bindSimpleProgram(*batch, false, false, true, true);
|
||||
geometryCache->bindSimpleProgram(*batch, false, false, false, true, true);
|
||||
if (getIsDashedLine()) {
|
||||
glm::vec3 point1(-halfDimensions.x, -halfDimensions.y, 0.0f);
|
||||
glm::vec3 point2(halfDimensions.x, -halfDimensions.y, 0.0f);
|
||||
|
|
|
@ -47,7 +47,7 @@ void Shape3DOverlay::render(RenderArgs* args) {
|
|||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
auto pipeline = args->_pipeline;
|
||||
if (!pipeline) {
|
||||
pipeline = _isSolid ? geometryCache->getShapePipeline() : geometryCache->getWireShapePipeline();
|
||||
pipeline = _isSolid ? geometryCache->getOpaqueShapePipeline() : geometryCache->getWireShapePipeline();
|
||||
}
|
||||
|
||||
transform.setScale(dimensions);
|
||||
|
|
|
@ -46,7 +46,7 @@ void Sphere3DOverlay::render(RenderArgs* args) {
|
|||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
auto pipeline = args->_pipeline;
|
||||
if (!pipeline) {
|
||||
pipeline = _isSolid ? geometryCache->getShapePipeline() : geometryCache->getWireShapePipeline();
|
||||
pipeline = _isSolid ? geometryCache->getOpaqueShapePipeline() : geometryCache->getWireShapePipeline();
|
||||
}
|
||||
|
||||
if (_isSolid) {
|
||||
|
|
|
@ -119,6 +119,9 @@ AudioClient::AudioClient() :
|
|||
this, &AudioClient::processReceivedSamples, Qt::DirectConnection);
|
||||
connect(this, &AudioClient::changeDevice, this, [=](const QAudioDeviceInfo& outputDeviceInfo) { switchOutputToAudioDevice(outputDeviceInfo); });
|
||||
|
||||
connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat);
|
||||
|
||||
|
||||
_inputDevices = getDeviceNames(QAudio::AudioInput);
|
||||
_outputDevices = getDeviceNames(QAudio::AudioOutput);
|
||||
|
||||
|
@ -147,6 +150,12 @@ AudioClient::~AudioClient() {
|
|||
}
|
||||
}
|
||||
|
||||
void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) {
|
||||
qDebug() << __FUNCTION__ << "sendingNode:" << *node << "currentCodec:" << currentCodec << "recievedCodec:" << recievedCodec;
|
||||
selectAudioFormat(recievedCodec);
|
||||
}
|
||||
|
||||
|
||||
void AudioClient::reset() {
|
||||
_receivedAudioStream.reset();
|
||||
_stats.reset();
|
||||
|
@ -532,7 +541,13 @@ void AudioClient::negotiateAudioFormat() {
|
|||
}
|
||||
|
||||
void AudioClient::handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message) {
|
||||
_selectedCodecName = message->readString();
|
||||
QString selectedCodecName = message->readString();
|
||||
selectAudioFormat(selectedCodecName);
|
||||
}
|
||||
|
||||
void AudioClient::selectAudioFormat(const QString& selectedCodecName) {
|
||||
|
||||
_selectedCodecName = selectedCodecName;
|
||||
|
||||
qDebug() << "Selected Codec:" << _selectedCodecName;
|
||||
|
||||
|
|
|
@ -104,6 +104,7 @@ public:
|
|||
};
|
||||
|
||||
void negotiateAudioFormat();
|
||||
void selectAudioFormat(const QString& selectedCodecName);
|
||||
|
||||
const MixedProcessedAudioStream& getReceivedAudioStream() const { return _receivedAudioStream; }
|
||||
MixedProcessedAudioStream& getReceivedAudioStream() { return _receivedAudioStream; }
|
||||
|
@ -153,6 +154,7 @@ public slots:
|
|||
void handleNoisyMutePacket(QSharedPointer<ReceivedMessage> message);
|
||||
void handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message);
|
||||
void handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec);
|
||||
|
||||
void sendDownstreamAudioStatsPacket() { _stats.sendDownstreamAudioStatsPacket(); }
|
||||
void handleAudioInput();
|
||||
|
|
|
@ -147,7 +147,7 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
|
|||
writeDroppableSilentSamples(networkSamples);
|
||||
// inform others of the mismatch
|
||||
auto sendingNode = DependencyManager::get<NodeList>()->nodeWithUUID(message.getSourceID());
|
||||
emit mismatchedAudioCodec(sendingNode, _selectedCodecName);
|
||||
emit mismatchedAudioCodec(sendingNode, _selectedCodecName, codecInPacket);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -182,7 +182,7 @@ public:
|
|||
void cleanupCodec();
|
||||
|
||||
signals:
|
||||
void mismatchedAudioCodec(SharedNodePointer sendingNode, const QString& desiredCodec);
|
||||
void mismatchedAudioCodec(SharedNodePointer sendingNode, const QString& currentCodec, const QString& recievedCodec);
|
||||
|
||||
public slots:
|
||||
/// This function should be called every second for all the stats to function properly. If dynamic jitter buffers
|
||||
|
|
|
@ -1638,7 +1638,9 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) {
|
|||
for (const auto& attachmentVar : variant) {
|
||||
AttachmentData attachment;
|
||||
attachment.fromVariant(attachmentVar);
|
||||
newAttachments.append(attachment);
|
||||
if (!attachment.modelURL.isEmpty()) {
|
||||
newAttachments.append(attachment);
|
||||
}
|
||||
}
|
||||
setAttachmentData(newAttachments);
|
||||
}
|
||||
|
|
|
@ -375,7 +375,6 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptr<ZoneEntityIt
|
|||
}
|
||||
|
||||
skyStage->setBackgroundMode(model::SunSkyStage::SKY_DOME); // let the application background through
|
||||
|
||||
return; // Early exit
|
||||
}
|
||||
|
||||
|
@ -529,7 +528,7 @@ void EntityTreeRenderer::processEraseMessage(ReceivedMessage& message, const Sha
|
|||
std::static_pointer_cast<EntityTree>(_tree)->processEraseMessage(message, sourceNode);
|
||||
}
|
||||
|
||||
ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString& collisionUrl) {
|
||||
ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString& collisionUrl, float loadingPriority) {
|
||||
ModelPointer model = nullptr;
|
||||
|
||||
// Only create and delete models on the thread that owns the EntityTreeRenderer
|
||||
|
@ -543,6 +542,7 @@ ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString
|
|||
}
|
||||
|
||||
model = std::make_shared<Model>(std::make_shared<Rig>());
|
||||
model->setLoadingPriority(loadingPriority);
|
||||
model->init();
|
||||
model->setURL(QUrl(url));
|
||||
model->setCollisionModelURL(QUrl(collisionUrl));
|
||||
|
|
|
@ -28,11 +28,14 @@ class AbstractViewStateInterface;
|
|||
class Model;
|
||||
class ScriptEngine;
|
||||
class ZoneEntityItem;
|
||||
class EntityItem;
|
||||
|
||||
class Model;
|
||||
using ModelPointer = std::shared_ptr<Model>;
|
||||
using ModelWeakPointer = std::weak_ptr<Model>;
|
||||
|
||||
using CalculateEntityLoadingPriority = std::function<float(const EntityItem& item)>;
|
||||
|
||||
// Generic client side Octree renderer class.
|
||||
class EntityTreeRenderer : public OctreeRenderer, public EntityItemFBXService, public Dependency {
|
||||
Q_OBJECT
|
||||
|
@ -46,6 +49,10 @@ public:
|
|||
virtual PacketType getExpectedPacketType() const { return PacketType::EntityData; }
|
||||
virtual void setTree(OctreePointer newTree);
|
||||
|
||||
// Returns the priority at which an entity should be loaded. Higher values indicate higher priority.
|
||||
float getEntityLoadingPriority(const EntityItem& item) const { return _calculateEntityLoadingPriorityFunc(item); }
|
||||
void setEntityLoadingPriorityFunction(CalculateEntityLoadingPriority fn) { this->_calculateEntityLoadingPriorityFunc = fn; }
|
||||
|
||||
void shutdown();
|
||||
void update();
|
||||
|
||||
|
@ -66,7 +73,7 @@ public:
|
|||
void reloadEntityScripts();
|
||||
|
||||
/// if a renderable entity item needs a model, we will allocate it for them
|
||||
Q_INVOKABLE ModelPointer allocateModel(const QString& url, const QString& collisionUrl);
|
||||
Q_INVOKABLE ModelPointer allocateModel(const QString& url, const QString& collisionUrl, float loadingPriority = 0.0f);
|
||||
|
||||
/// if a renderable entity item needs to update the URL of a model, we will handle that for the entity
|
||||
Q_INVOKABLE ModelPointer updateModel(ModelPointer original, const QString& newUrl, const QString& collisionUrl);
|
||||
|
@ -202,6 +209,10 @@ private:
|
|||
QList<EntityItemID> _entityIDsLastInScene;
|
||||
|
||||
static int _entitiesScriptEngineCount;
|
||||
|
||||
CalculateEntityLoadingPriority _calculateEntityLoadingPriorityFunc = [](const EntityItem& item) -> float {
|
||||
return 0.0f;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace render {
|
|||
if (payload->entity->getType() == EntityTypes::Light) {
|
||||
return ItemKey::Builder::light();
|
||||
}
|
||||
if (payload && payload->entity->getType() == EntityTypes::PolyLine) {
|
||||
if (payload && payload->entity->isTransparent()) {
|
||||
return ItemKey::Builder::transparentShape();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,8 +96,17 @@ public: \
|
|||
virtual void removeFromScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override { _renderHelper.removeFromScene(self, scene, pendingChanges); } \
|
||||
virtual void locationChanged(bool tellPhysics = true) override { EntityItem::locationChanged(tellPhysics); _renderHelper.notifyChanged(); } \
|
||||
virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); _renderHelper.notifyChanged(); } \
|
||||
void checkFading() { \
|
||||
bool transparent = isTransparent(); \
|
||||
if (transparent != _prevIsTransparent) { \
|
||||
_renderHelper.notifyChanged(); \
|
||||
_isFading = false; \
|
||||
_prevIsTransparent = transparent; \
|
||||
} \
|
||||
} \
|
||||
private: \
|
||||
SimpleRenderableEntityItem _renderHelper;
|
||||
SimpleRenderableEntityItem _renderHelper; \
|
||||
bool _prevIsTransparent { isTransparent() };
|
||||
|
||||
|
||||
#endif // hifi_RenderableEntityItem_h
|
||||
|
|
|
@ -28,6 +28,8 @@ EntityItemPointer RenderableLightEntityItem::factory(const EntityItemID& entityI
|
|||
void RenderableLightEntityItem::render(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableLightEntityItem::render");
|
||||
assert(getType() == EntityTypes::Light);
|
||||
checkFading();
|
||||
|
||||
glm::vec3 position = getPosition();
|
||||
glm::vec3 dimensions = getDimensions();
|
||||
glm::quat rotation = getRotation();
|
||||
|
@ -35,7 +37,7 @@ void RenderableLightEntityItem::render(RenderArgs* args) {
|
|||
|
||||
glm::vec3 color = toGlm(getXColor());
|
||||
|
||||
float intensity = getIntensity();
|
||||
float intensity = getIntensity() * (_isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f);
|
||||
float falloffRadius = getFalloffRadius();
|
||||
float exponent = getExponent();
|
||||
float cutoff = glm::radians(getCutoff());
|
||||
|
|
|
@ -176,25 +176,6 @@ void RenderableModelEntityItem::doInitialModelSimulation() {
|
|||
_needsInitialSimulation = false;
|
||||
}
|
||||
|
||||
|
||||
// TODO: we need a solution for changes to the postion/rotation/etc of a model...
|
||||
// this current code path only addresses that in this setup case... not the changing/moving case
|
||||
bool RenderableModelEntityItem::readyToAddToScene(RenderArgs* renderArgs) {
|
||||
if (!_model && renderArgs) {
|
||||
// TODO: this getModel() appears to be about 3% of model render time. We should optimize
|
||||
PerformanceTimer perfTimer("getModel");
|
||||
EntityTreeRenderer* renderer = static_cast<EntityTreeRenderer*>(renderArgs->_renderer);
|
||||
getModel(renderer);
|
||||
}
|
||||
if (renderArgs && _model && _needsInitialSimulation && _model->isActive() && _model->isLoaded()) {
|
||||
// make sure to simulate so everything gets set up correctly for rendering
|
||||
doInitialModelSimulation();
|
||||
_model->renderSetup(renderArgs);
|
||||
}
|
||||
bool ready = !_needsInitialSimulation && _model && _model->readyToAddToScene(renderArgs);
|
||||
return ready;
|
||||
}
|
||||
|
||||
class RenderableModelEntityItemMeta {
|
||||
public:
|
||||
RenderableModelEntityItemMeta(EntityItemPointer entity) : entity(entity){ }
|
||||
|
@ -371,6 +352,12 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
|
|||
PerformanceTimer perfTimer("RMEIrender");
|
||||
assert(getType() == EntityTypes::Model);
|
||||
|
||||
// When the individual mesh parts of a model finish fading, they will mark their Model as needing updating
|
||||
// we will watch for that and ask the model to update it's render items
|
||||
if (_model && _model->getRenderItemsNeedUpdate()) {
|
||||
_model->updateRenderItems();
|
||||
}
|
||||
|
||||
if (hasModel()) {
|
||||
// Prepare the current frame
|
||||
{
|
||||
|
@ -484,7 +471,7 @@ ModelPointer RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
|
|||
if (!getModelURL().isEmpty()) {
|
||||
// If we don't have a model, allocate one *immediately*
|
||||
if (!_model) {
|
||||
_model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL());
|
||||
_model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL(), renderer->getEntityLoadingPriority(*this));
|
||||
_needsInitialSimulation = true;
|
||||
// If we need to change URLs, update it *after rendering* (to avoid access violations)
|
||||
} else if ((QUrl(getModelURL()) != _model->getURL() || QUrl(getCompoundShapeURL()) != _model->getCollisionURL())) {
|
||||
|
@ -916,6 +903,18 @@ bool RenderableModelEntityItem::contains(const glm::vec3& point) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool RenderableModelEntityItem::shouldBePhysical() const {
|
||||
// If we have a model, make sure it hasn't failed to download.
|
||||
// If it has, we'll report back that we shouldn't be physical so that physics aren't held waiting for us to be ready.
|
||||
if (_model && getShapeType() == SHAPE_TYPE_COMPOUND && _model->didCollisionGeometryRequestFail()) {
|
||||
return false;
|
||||
} else if (_model && getShapeType() != SHAPE_TYPE_NONE && _model->didVisualGeometryRequestFail()) {
|
||||
return false;
|
||||
} else {
|
||||
return ModelEntityItem::shouldBePhysical();
|
||||
}
|
||||
}
|
||||
|
||||
glm::quat RenderableModelEntityItem::getAbsoluteJointRotationInObjectFrame(int index) const {
|
||||
if (_model) {
|
||||
glm::quat result;
|
||||
|
|
|
@ -40,7 +40,6 @@ public:
|
|||
|
||||
void doInitialModelSimulation();
|
||||
|
||||
virtual bool readyToAddToScene(RenderArgs* renderArgs = nullptr);
|
||||
virtual bool addToScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override;
|
||||
virtual void removeFromScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) override;
|
||||
|
||||
|
@ -52,7 +51,6 @@ public:
|
|||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
|
||||
ModelPointer getModel(EntityTreeRenderer* renderer);
|
||||
|
||||
virtual bool needsToCallUpdate() const override;
|
||||
|
@ -65,6 +63,8 @@ public:
|
|||
|
||||
virtual bool contains(const glm::vec3& point) const override;
|
||||
|
||||
virtual bool shouldBePhysical() const override;
|
||||
|
||||
// these are in the frame of this object (model space)
|
||||
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
|
||||
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
|
||||
|
@ -91,6 +91,9 @@ public:
|
|||
|
||||
render::ItemID getMetaRenderItem() { return _myMetaItem; }
|
||||
|
||||
// Transparency is handled in ModelMeshPartPayload
|
||||
bool isTransparent() override { return false; }
|
||||
|
||||
private:
|
||||
QVariantMap parseTexturesToMap(QString textures);
|
||||
void remapTextures();
|
||||
|
|
|
@ -167,6 +167,8 @@ void RenderablePolyLineEntityItem::update(const quint64& now) {
|
|||
}
|
||||
|
||||
void RenderablePolyLineEntityItem::render(RenderArgs* args) {
|
||||
checkFading();
|
||||
|
||||
QWriteLocker lock(&_quadReadWriteLock);
|
||||
if (_points.size() < 2 || _normals.size () < 2 || _strokeWidths.size() < 2) {
|
||||
return;
|
||||
|
@ -204,5 +206,9 @@ void RenderablePolyLineEntityItem::render(RenderArgs* args) {
|
|||
batch.setInputFormat(_format);
|
||||
batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride);
|
||||
|
||||
if (_isFading) {
|
||||
batch._glColor4f(1.0f, 1.0f, 1.0f, Interpolate::calculateFadeRatio(_fadeStartTime));
|
||||
}
|
||||
|
||||
batch.draw(gpu::TRIANGLE_STRIP, _numVertices, 0);
|
||||
};
|
||||
|
|
|
@ -30,7 +30,9 @@ public:
|
|||
|
||||
virtual void render(RenderArgs* args) override;
|
||||
virtual void update(const quint64& now) override;
|
||||
virtual bool needsToCallUpdate() const override { return true; };
|
||||
virtual bool needsToCallUpdate() const override { return true; }
|
||||
|
||||
bool isTransparent() override { return true; }
|
||||
|
||||
SIMPLE_RENDERABLE();
|
||||
|
||||
|
@ -47,7 +49,6 @@ protected:
|
|||
gpu::BufferView _uniformBuffer;
|
||||
unsigned int _numVertices;
|
||||
QVector<glm::vec3> _vertices;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ public:
|
|||
|
||||
void initializePolyVox();
|
||||
|
||||
virtual void somethingChangedNotification() {
|
||||
virtual void somethingChangedNotification() override {
|
||||
// This gets called from EnityItem::readEntityDataFromBuffer every time a packet describing
|
||||
// this entity comes from the entity-server. It gets called even if nothing has actually changed
|
||||
// (see the comment in EntityItem.cpp). If that gets fixed, this could be used to know if we
|
||||
|
@ -58,19 +58,19 @@ public:
|
|||
// _needsModelReload = true;
|
||||
}
|
||||
|
||||
virtual uint8_t getVoxel(int x, int y, int z);
|
||||
virtual bool setVoxel(int x, int y, int z, uint8_t toValue);
|
||||
virtual uint8_t getVoxel(int x, int y, int z) override;
|
||||
virtual bool setVoxel(int x, int y, int z, uint8_t toValue) override;
|
||||
|
||||
void render(RenderArgs* args);
|
||||
virtual bool supportsDetailedRayIntersection() const { return true; }
|
||||
void render(RenderArgs* args) override;
|
||||
virtual bool supportsDetailedRayIntersection() const override { return true; }
|
||||
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
bool& keepSearching, OctreeElementPointer& element, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
void** intersectedObject, bool precisionPicking) const;
|
||||
void** intersectedObject, bool precisionPicking) const override;
|
||||
|
||||
virtual void setVoxelData(QByteArray voxelData);
|
||||
virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize);
|
||||
virtual void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle);
|
||||
virtual void setVoxelData(QByteArray voxelData) override;
|
||||
virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize) override;
|
||||
virtual void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) override;
|
||||
|
||||
glm::vec3 getSurfacePositionAdjustment() const;
|
||||
glm::mat4 voxelToWorldMatrix() const;
|
||||
|
@ -78,45 +78,45 @@ public:
|
|||
glm::mat4 voxelToLocalMatrix() const;
|
||||
glm::mat4 localToVoxelMatrix() const;
|
||||
|
||||
virtual ShapeType getShapeType() const;
|
||||
virtual bool shouldBePhysical() const { return !isDead(); }
|
||||
virtual bool isReadyToComputeShape();
|
||||
virtual void computeShapeInfo(ShapeInfo& info);
|
||||
virtual ShapeType getShapeType() const override;
|
||||
virtual bool shouldBePhysical() const override { return !isDead(); }
|
||||
virtual bool isReadyToComputeShape() override;
|
||||
virtual void computeShapeInfo(ShapeInfo& info) override;
|
||||
|
||||
virtual glm::vec3 voxelCoordsToWorldCoords(glm::vec3& voxelCoords) const;
|
||||
virtual glm::vec3 worldCoordsToVoxelCoords(glm::vec3& worldCoords) const;
|
||||
virtual glm::vec3 voxelCoordsToLocalCoords(glm::vec3& voxelCoords) const;
|
||||
virtual glm::vec3 localCoordsToVoxelCoords(glm::vec3& localCoords) const;
|
||||
virtual glm::vec3 voxelCoordsToWorldCoords(glm::vec3& voxelCoords) const override;
|
||||
virtual glm::vec3 worldCoordsToVoxelCoords(glm::vec3& worldCoords) const override;
|
||||
virtual glm::vec3 voxelCoordsToLocalCoords(glm::vec3& voxelCoords) const override;
|
||||
virtual glm::vec3 localCoordsToVoxelCoords(glm::vec3& localCoords) const override;
|
||||
|
||||
// coords are in voxel-volume space
|
||||
virtual bool setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue);
|
||||
virtual bool setVoxelInVolume(glm::vec3 position, uint8_t toValue);
|
||||
virtual bool setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) override;
|
||||
virtual bool setVoxelInVolume(glm::vec3 position, uint8_t toValue) override;
|
||||
|
||||
// coords are in world-space
|
||||
virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue);
|
||||
virtual bool setAll(uint8_t toValue);
|
||||
virtual bool setCuboid(const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int toValue);
|
||||
virtual bool setSphere(glm::vec3 center, float radius, uint8_t toValue) override;
|
||||
virtual bool setAll(uint8_t toValue) override;
|
||||
virtual bool setCuboid(const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int toValue) override;
|
||||
|
||||
virtual void setXTextureURL(QString xTextureURL);
|
||||
virtual void setYTextureURL(QString yTextureURL);
|
||||
virtual void setZTextureURL(QString zTextureURL);
|
||||
virtual void setXTextureURL(QString xTextureURL) override;
|
||||
virtual void setYTextureURL(QString yTextureURL) override;
|
||||
virtual void setZTextureURL(QString zTextureURL) override;
|
||||
|
||||
virtual bool addToScene(EntityItemPointer self,
|
||||
std::shared_ptr<render::Scene> scene,
|
||||
render::PendingChanges& pendingChanges);
|
||||
render::PendingChanges& pendingChanges) override;
|
||||
virtual void removeFromScene(EntityItemPointer self,
|
||||
std::shared_ptr<render::Scene> scene,
|
||||
render::PendingChanges& pendingChanges);
|
||||
render::PendingChanges& pendingChanges) override;
|
||||
|
||||
virtual void setXNNeighborID(const EntityItemID& xNNeighborID);
|
||||
virtual void setYNNeighborID(const EntityItemID& yNNeighborID);
|
||||
virtual void setZNNeighborID(const EntityItemID& zNNeighborID);
|
||||
virtual void setXNNeighborID(const EntityItemID& xNNeighborID) override;
|
||||
virtual void setYNNeighborID(const EntityItemID& yNNeighborID) override;
|
||||
virtual void setZNNeighborID(const EntityItemID& zNNeighborID) override;
|
||||
|
||||
virtual void setXPNeighborID(const EntityItemID& xPNeighborID);
|
||||
virtual void setYPNeighborID(const EntityItemID& yPNeighborID);
|
||||
virtual void setZPNeighborID(const EntityItemID& zPNeighborID);
|
||||
virtual void setXPNeighborID(const EntityItemID& xPNeighborID) override;
|
||||
virtual void setYPNeighborID(const EntityItemID& yPNeighborID) override;
|
||||
virtual void setZPNeighborID(const EntityItemID& zPNeighborID) override;
|
||||
|
||||
virtual void updateRegistrationPoint(const glm::vec3& value);
|
||||
virtual void updateRegistrationPoint(const glm::vec3& value) override;
|
||||
|
||||
void setVoxelsFromData(QByteArray uncompressedData, quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize);
|
||||
void forEachVoxelValue(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize,
|
||||
|
@ -131,6 +131,9 @@ public:
|
|||
|
||||
void setVolDataDirty() { withWriteLock([&] { _volDataDirty = true; }); }
|
||||
|
||||
// Transparent polyvox didn't seem to be working so disable for now
|
||||
bool isTransparent() override { return false; }
|
||||
|
||||
private:
|
||||
// The PolyVoxEntityItem class has _voxelData which contains dimensions and compressed voxel data. The dimensions
|
||||
// may not match _voxelVolumeSize.
|
||||
|
@ -161,7 +164,7 @@ private:
|
|||
// these are run off the main thread
|
||||
void decompressVolumeData();
|
||||
void compressVolumeDataAndSendEditPacket();
|
||||
virtual void getMesh(); // recompute mesh
|
||||
virtual void getMesh() override; // recompute mesh
|
||||
void computeShapeInfoWorker();
|
||||
|
||||
// these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID
|
||||
|
|
|
@ -40,7 +40,6 @@ static std::array<GeometryCache::Shape, entity::NUM_SHAPES> MAPPING { {
|
|||
GeometryCache::Cylinder,
|
||||
} };
|
||||
|
||||
|
||||
RenderableShapeEntityItem::Pointer RenderableShapeEntityItem::baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
Pointer entity = std::make_shared<RenderableShapeEntityItem>(entityID);
|
||||
entity->setProperties(properties);
|
||||
|
@ -72,20 +71,26 @@ void RenderableShapeEntityItem::setUserData(const QString& value) {
|
|||
}
|
||||
}
|
||||
|
||||
bool RenderableShapeEntityItem::isTransparent() {
|
||||
if (_procedural && _procedural->isFading()) {
|
||||
float isFading = Interpolate::calculateFadeRatio(_procedural->getFadeStartTime()) < 1.0f;
|
||||
_procedural->setIsFading(isFading);
|
||||
return isFading;
|
||||
} else {
|
||||
return getLocalRenderAlpha() < 1.0f || EntityItem::isTransparent();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableShapeEntityItem::render(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableShapeEntityItem::render");
|
||||
//Q_ASSERT(getType() == EntityTypes::Shape);
|
||||
Q_ASSERT(args->_batch);
|
||||
checkFading();
|
||||
|
||||
if (!_procedural) {
|
||||
_procedural.reset(new Procedural(getUserData()));
|
||||
_procedural->_vertexSource = simple_vert;
|
||||
_procedural->_fragmentSource = simple_frag;
|
||||
_procedural->_state->setCullMode(gpu::State::CULL_NONE);
|
||||
_procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
_procedural->_state->setBlendFunction(false,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
}
|
||||
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
|
@ -102,14 +107,17 @@ void RenderableShapeEntityItem::render(RenderArgs* args) {
|
|||
if (_procedural->ready()) {
|
||||
_procedural->prepare(batch, getPosition(), getDimensions(), getOrientation());
|
||||
auto outColor = _procedural->getColor(color);
|
||||
outColor.a *= _procedural->isFading() ? Interpolate::calculateFadeRatio(_procedural->getFadeStartTime()) : 1.0f;
|
||||
batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a);
|
||||
DependencyManager::get<GeometryCache>()->renderShape(batch, MAPPING[_shape]);
|
||||
} else {
|
||||
// FIXME, support instanced multi-shape rendering using multidraw indirect
|
||||
DependencyManager::get<GeometryCache>()->renderSolidShapeInstance(batch, MAPPING[_shape], color);
|
||||
color.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
auto pipeline = color.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline();
|
||||
geometryCache->renderSolidShapeInstance(batch, MAPPING[_shape], color, pipeline);
|
||||
}
|
||||
|
||||
|
||||
static const auto triCount = DependencyManager::get<GeometryCache>()->getShapeTriangleCount(MAPPING[_shape]);
|
||||
args->_details._trianglesRendered += (int)triCount;
|
||||
}
|
||||
|
|
|
@ -26,10 +26,12 @@ public:
|
|||
void render(RenderArgs* args) override;
|
||||
void setUserData(const QString& value) override;
|
||||
|
||||
SIMPLE_RENDERABLE();
|
||||
bool isTransparent() override;
|
||||
|
||||
private:
|
||||
QSharedPointer<Procedural> _procedural;
|
||||
std::unique_ptr<Procedural> _procedural { nullptr };
|
||||
|
||||
SIMPLE_RENDERABLE();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
#include <PerfStat.h>
|
||||
#include <Transform.h>
|
||||
|
||||
|
||||
|
||||
#include "RenderableTextEntityItem.h"
|
||||
#include "GLMHelpers.h"
|
||||
|
||||
|
@ -29,10 +27,13 @@ EntityItemPointer RenderableTextEntityItem::factory(const EntityItemID& entityID
|
|||
void RenderableTextEntityItem::render(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableTextEntityItem::render");
|
||||
Q_ASSERT(getType() == EntityTypes::Text);
|
||||
checkFading();
|
||||
|
||||
static const float SLIGHTLY_BEHIND = -0.005f;
|
||||
glm::vec4 textColor = glm::vec4(toGlm(getTextColorX()), 1.0f);
|
||||
glm::vec4 backgroundColor = glm::vec4(toGlm(getBackgroundColorX()), 1.0f);
|
||||
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
||||
bool transparent = fadeRatio < 1.0f;
|
||||
glm::vec4 textColor = glm::vec4(toGlm(getTextColorX()), fadeRatio);
|
||||
glm::vec4 backgroundColor = glm::vec4(toGlm(getBackgroundColorX()), fadeRatio);
|
||||
glm::vec3 dimensions = getDimensions();
|
||||
|
||||
// Render background
|
||||
|
@ -62,7 +63,7 @@ void RenderableTextEntityItem::render(RenderArgs* args) {
|
|||
|
||||
batch.setModelTransform(transformToTopLeft);
|
||||
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, false, false, true);
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, transparent, false, false, true);
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, minCorner, maxCorner, backgroundColor);
|
||||
|
||||
float scale = _lineHeight / _textRenderer->getFontSize();
|
||||
|
|
|
@ -164,6 +164,8 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
|
|||
}
|
||||
|
||||
void RenderableWebEntityItem::render(RenderArgs* args) {
|
||||
checkFading();
|
||||
|
||||
#ifdef WANT_EXTRA_DEBUGGING
|
||||
{
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
|
@ -181,6 +183,7 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
if (!buildWebSurface(static_cast<EntityTreeRenderer*>(args->_renderer))) {
|
||||
return;
|
||||
}
|
||||
_fadeStartTime = usecTimestampNow();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -207,8 +210,11 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture);
|
||||
}
|
||||
|
||||
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
||||
batch._glColor4f(1.0f, 1.0f, 1.0f, fadeRatio);
|
||||
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleSRGBTexturedUnlitNoTexAlphaProgram(batch);
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio));
|
||||
}
|
||||
|
||||
void RenderableWebEntityItem::setSourceUrl(const QString& value) {
|
||||
|
|
|
@ -39,7 +39,7 @@ void main(void) {
|
|||
vec3 color = varColor.rgb;
|
||||
packDeferredFragmentTranslucent(
|
||||
interpolatedNormal * frontCondition,
|
||||
texel.a,
|
||||
texel.a * varColor.a,
|
||||
polyline.color * texel.rgb,
|
||||
vec3(0.01, 0.01, 0.01),
|
||||
10.0);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<@include gpu/Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
// model.frag
|
||||
// polyvox.frag
|
||||
// fragment shader
|
||||
//
|
||||
// Created by Seth Alves on 2015-8-3
|
||||
|
@ -41,5 +41,13 @@ void main(void) {
|
|||
vec3 yzDiffuseScaled = yzDiffuse.rgb * abs(worldNormal.x);
|
||||
vec4 diffuse = vec4(xyDiffuseScaled + xzDiffuseScaled + yzDiffuseScaled, 1.0);
|
||||
|
||||
packDeferredFragment(_normal, 1.0, vec3(diffuse), DEFAULT_ROUGHNESS, DEFAULT_METALLIC, DEFAULT_EMISSIVE, DEFAULT_OCCLUSION, DEFAULT_SCATTERING);
|
||||
packDeferredFragment(
|
||||
_normal,
|
||||
1.0,
|
||||
vec3(diffuse),
|
||||
DEFAULT_ROUGHNESS,
|
||||
DEFAULT_METALLIC,
|
||||
DEFAULT_EMISSIVE,
|
||||
DEFAULT_OCCLUSION,
|
||||
DEFAULT_SCATTERING);
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ out vec4 varColor;
|
|||
out vec2 varTexcoord;
|
||||
|
||||
const int NUM_VERTICES_PER_PARTICLE = 4;
|
||||
// This ordering ensures that un-rotated particles render upright in the wiewer.
|
||||
// This ordering ensures that un-rotated particles render upright in the viewer.
|
||||
const vec4 UNIT_QUAD[NUM_VERTICES_PER_PARTICLE] = vec4[NUM_VERTICES_PER_PARTICLE](
|
||||
vec4(-1.0, 1.0, 0.0, 0.0),
|
||||
vec4(-1.0, -1.0, 0.0, 0.0),
|
||||
|
|
|
@ -2212,4 +2212,4 @@ void EntityItem::globalizeProperties(EntityItemProperties& properties, const QSt
|
|||
}
|
||||
QUuid empty;
|
||||
properties.setParentID(empty);
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@
|
|||
#include <Transform.h>
|
||||
#include <Sound.h>
|
||||
#include <SpatiallyNestable.h>
|
||||
#include <Interpolate.h>
|
||||
|
||||
#include "EntityItemID.h"
|
||||
#include "EntityItemPropertiesDefaults.h"
|
||||
|
@ -435,6 +436,7 @@ public:
|
|||
QUuid getOwningAvatarID() const { return _owningAvatarID; }
|
||||
void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; }
|
||||
|
||||
virtual bool isTransparent() { return _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f : false; }
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -565,7 +567,8 @@ protected:
|
|||
quint64 _lastUpdatedAngularVelocityTimestamp { 0 };
|
||||
quint64 _lastUpdatedAccelerationTimestamp { 0 };
|
||||
|
||||
|
||||
quint64 _fadeStartTime { usecTimestampNow() };
|
||||
bool _isFading { true };
|
||||
};
|
||||
|
||||
#endif // hifi_EntityItem_h
|
||||
|
|
|
@ -634,8 +634,8 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con
|
|||
}
|
||||
} else {
|
||||
// if the entity type doesn't support a detailed intersection, then just return the non-AABox results
|
||||
// Never intersect with particle effect entities
|
||||
if (localDistance < distance && EntityTypes::getEntityTypeName(entity->getType()) != "ParticleEffect") {
|
||||
// Never intersect with particle entities
|
||||
if (localDistance < distance && entity->getType() != EntityTypes::ParticleEffect) {
|
||||
distance = localDistance;
|
||||
face = localFace;
|
||||
surfaceNormal = localSurfaceNormal;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <NumericalConstants.h>
|
||||
#include <Finally.h>
|
||||
#include <PathUtils.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
|
||||
#include "OffscreenGLCanvas.h"
|
||||
#include "GLEscrow.h"
|
||||
|
@ -55,6 +56,22 @@ private:
|
|||
friend class OffscreenQmlSurface;
|
||||
};
|
||||
|
||||
class QmlNetworkAccessManager : public NetworkAccessManager {
|
||||
public:
|
||||
friend class QmlNetworkAccessManagerFactory;
|
||||
protected:
|
||||
QmlNetworkAccessManager(QObject* parent) : NetworkAccessManager(parent) { }
|
||||
};
|
||||
|
||||
class QmlNetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory {
|
||||
public:
|
||||
QNetworkAccessManager* create(QObject* parent);
|
||||
};
|
||||
|
||||
QNetworkAccessManager* QmlNetworkAccessManagerFactory::create(QObject* parent) {
|
||||
return new QmlNetworkAccessManager(parent);
|
||||
}
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(offscreenFocus)
|
||||
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
|
||||
|
||||
|
@ -402,6 +419,8 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
|||
// Create a QML engine.
|
||||
_qmlEngine = new QQmlEngine;
|
||||
|
||||
_qmlEngine->setNetworkAccessManagerFactory(new QmlNetworkAccessManagerFactory);
|
||||
|
||||
auto importList = _qmlEngine->importPathList();
|
||||
importList.insert(importList.begin(), PathUtils::resourcesPath());
|
||||
_qmlEngine->setImportPathList(importList);
|
||||
|
|
|
@ -596,7 +596,7 @@ int makeUniformBlockSlots(GLuint glprogram, const Shader::BindingSet& slotBindin
|
|||
}
|
||||
|
||||
Element element(SCALAR, gpu::UINT32, gpu::UNIFORM_BUFFER);
|
||||
buffers.insert(Shader::Slot(name, binding, element, Resource::BUFFER));
|
||||
buffers.insert(Shader::Slot(name, binding, element, Resource::BUFFER, size));
|
||||
}
|
||||
return buffersCount;
|
||||
}
|
||||
|
|
|
@ -53,19 +53,22 @@ public:
|
|||
int32 _location{INVALID_LOCATION};
|
||||
Element _element;
|
||||
uint16 _resourceType{Resource::BUFFER};
|
||||
uint32 _size { 0 };
|
||||
|
||||
Slot(const Slot& s) : _name(s._name), _location(s._location), _element(s._element), _resourceType(s._resourceType) {}
|
||||
Slot(Slot&& s) : _name(s._name), _location(s._location), _element(s._element), _resourceType(s._resourceType) {}
|
||||
Slot(const std::string& name, int32 location, const Element& element, uint16 resourceType = Resource::BUFFER) :
|
||||
_name(name), _location(location), _element(element), _resourceType(resourceType) {}
|
||||
Slot(const Slot& s) : _name(s._name), _location(s._location), _element(s._element), _resourceType(s._resourceType), _size(s._size) {}
|
||||
Slot(Slot&& s) : _name(s._name), _location(s._location), _element(s._element), _resourceType(s._resourceType), _size(s._size) {}
|
||||
Slot(const std::string& name, int32 location, const Element& element, uint16 resourceType = Resource::BUFFER, uint32 size = 0) :
|
||||
_name(name), _location(location), _element(element), _resourceType(resourceType), _size(size) {}
|
||||
Slot(const std::string& name) : _name(name) {}
|
||||
|
||||
|
||||
Slot& operator= (const Slot& s) {
|
||||
_name = s._name;
|
||||
_location = s._location;
|
||||
_element = s._element;
|
||||
_resourceType = s._resourceType;
|
||||
return (*this); }
|
||||
_size = s._size;
|
||||
return (*this);
|
||||
}
|
||||
};
|
||||
|
||||
class Binding {
|
||||
|
|
|
@ -700,8 +700,6 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
|
|||
return false;
|
||||
}
|
||||
|
||||
const float UCHAR_TO_FLOAT = 1.0f / float(std::numeric_limits<unsigned char>::max());
|
||||
|
||||
// for each face of cube texture
|
||||
for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) {
|
||||
|
||||
|
@ -788,12 +786,9 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
|
|||
uint pixOffsetIndex = (x + y * width) * numComponents;
|
||||
|
||||
// get color from texture and map to range [0, 1]
|
||||
glm::vec3 clr(float(data[pixOffsetIndex]) * UCHAR_TO_FLOAT,
|
||||
float(data[pixOffsetIndex+1]) * UCHAR_TO_FLOAT,
|
||||
float(data[pixOffsetIndex+2]) * UCHAR_TO_FLOAT);
|
||||
|
||||
// Gamma correct
|
||||
clr = ColorUtils::sRGBToLinearVec3(clr);
|
||||
glm::vec3 clr(ColorUtils::sRGB8ToLinearFloat(data[pixOffsetIndex]),
|
||||
ColorUtils::sRGB8ToLinearFloat(data[pixOffsetIndex + 1]),
|
||||
ColorUtils::sRGB8ToLinearFloat(data[pixOffsetIndex + 2]));
|
||||
|
||||
// scale color and add to previously accumulated coefficients
|
||||
sphericalHarmonicsScale(shBuffB.data(), order,
|
||||
|
|
|
@ -404,6 +404,7 @@ void GeometryResourceWatcher::resourceFinished(bool success) {
|
|||
if (success) {
|
||||
_geometryRef = std::make_shared<Geometry>(*_resource);
|
||||
}
|
||||
emit finished(success);
|
||||
}
|
||||
|
||||
void GeometryResourceWatcher::resourceRefreshed() {
|
||||
|
|
|
@ -111,6 +111,9 @@ public:
|
|||
|
||||
QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); }
|
||||
|
||||
signals:
|
||||
void finished(bool success);
|
||||
|
||||
private:
|
||||
void startWatching();
|
||||
void stopWatching();
|
||||
|
|
|
@ -171,7 +171,8 @@ NetworkTexturePointer TextureCache::getTexture(const QUrl& url, Type type, const
|
|||
}
|
||||
|
||||
|
||||
NetworkTexture::TextureLoaderFunc getTextureLoaderForType(NetworkTexture::Type type) {
|
||||
NetworkTexture::TextureLoaderFunc getTextureLoaderForType(NetworkTexture::Type type,
|
||||
const QVariantMap& options = QVariantMap()) {
|
||||
using Type = NetworkTexture;
|
||||
|
||||
switch (type) {
|
||||
|
@ -188,7 +189,11 @@ NetworkTexture::TextureLoaderFunc getTextureLoaderForType(NetworkTexture::Type t
|
|||
break;
|
||||
}
|
||||
case Type::CUBE_TEXTURE: {
|
||||
return model::TextureUsage::createCubeTextureFromImage;
|
||||
if (options.value("generateIrradiance", true).toBool()) {
|
||||
return model::TextureUsage::createCubeTextureFromImage;
|
||||
} else {
|
||||
return model::TextureUsage::createCubeTextureFromImageWithoutIrradiance;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type::BUMP_TEXTURE: {
|
||||
|
@ -225,9 +230,9 @@ NetworkTexture::TextureLoaderFunc getTextureLoaderForType(NetworkTexture::Type t
|
|||
}
|
||||
|
||||
/// Returns a texture version of an image file
|
||||
gpu::TexturePointer TextureCache::getImageTexture(const QString& path, Type type) {
|
||||
gpu::TexturePointer TextureCache::getImageTexture(const QString& path, Type type, QVariantMap options) {
|
||||
QImage image = QImage(path);
|
||||
auto loader = getTextureLoaderForType(type);
|
||||
auto loader = getTextureLoaderForType(type, options);
|
||||
return gpu::TexturePointer(loader(image, QUrl::fromLocalFile(path).fileName().toStdString()));
|
||||
}
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ public:
|
|||
const gpu::TexturePointer& getNormalFittingTexture();
|
||||
|
||||
/// Returns a texture version of an image file
|
||||
static gpu::TexturePointer getImageTexture(const QString& path, Type type = Type::DEFAULT_TEXTURE);
|
||||
static gpu::TexturePointer getImageTexture(const QString& path, Type type = Type::DEFAULT_TEXTURE, QVariantMap options = QVariantMap());
|
||||
|
||||
/// Loads a texture from the specified URL.
|
||||
NetworkTexturePointer getTexture(const QUrl& url, Type type = Type::DEFAULT_TEXTURE,
|
||||
|
|
|
@ -59,31 +59,28 @@ const QImage TextureUsage::process2DImageColor(const QImage& srcImage, bool& val
|
|||
const uint8 OPAQUE_ALPHA = 255;
|
||||
const uint8 TRANSPARENT_ALPHA = 0;
|
||||
if (image.hasAlphaChannel()) {
|
||||
std::map<uint8, uint32> alphaHistogram;
|
||||
|
||||
if (image.format() != QImage::Format_ARGB32) {
|
||||
image = image.convertToFormat(QImage::Format_ARGB32);
|
||||
}
|
||||
|
||||
// Actual alpha channel? create the histogram
|
||||
for (int y = 0; y < image.height(); ++y) {
|
||||
const QRgb* data = reinterpret_cast<const QRgb*>(image.constScanLine(y));
|
||||
for (int x = 0; x < image.width(); ++x) {
|
||||
auto alpha = qAlpha(data[x]);
|
||||
alphaHistogram[alpha] ++;
|
||||
validAlpha = validAlpha || (alpha != OPAQUE_ALPHA);
|
||||
// Figure out if we can use a mask for alpha or not
|
||||
int numOpaques = 0;
|
||||
int numTranslucents = 0;
|
||||
const int NUM_PIXELS = image.width() * image.height();
|
||||
const int MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK = (int)(0.05f * (float)(NUM_PIXELS));
|
||||
const QRgb* data = reinterpret_cast<const QRgb*>(image.constBits());
|
||||
for (int i = 0; i < NUM_PIXELS; ++i) {
|
||||
auto alpha = qAlpha(data[i]);
|
||||
if (alpha == OPAQUE_ALPHA) {
|
||||
numOpaques++;
|
||||
} else if (alpha != TRANSPARENT_ALPHA) {
|
||||
if (++numTranslucents > MAX_TRANSLUCENT_PIXELS_FOR_ALPHAMASK) {
|
||||
alphaAsMask = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If alpha was meaningfull refine
|
||||
if (validAlpha && (alphaHistogram.size() > 1)) {
|
||||
auto totalNumPixels = image.height() * image.width();
|
||||
auto numOpaques = alphaHistogram[OPAQUE_ALPHA];
|
||||
auto numTransparents = alphaHistogram[TRANSPARENT_ALPHA];
|
||||
auto numTranslucents = totalNumPixels - numOpaques - numTransparents;
|
||||
|
||||
alphaAsMask = ((numTranslucents / (double)totalNumPixels) < 0.05);
|
||||
}
|
||||
validAlpha = (numOpaques != NUM_PIXELS);
|
||||
}
|
||||
|
||||
if (!validAlpha && image.format() != QImage::Format_RGB888) {
|
||||
|
@ -660,13 +657,12 @@ const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = {
|
|||
const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout);
|
||||
|
||||
gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isLinear, bool doCompress, bool generateMips, bool generateIrradiance) {
|
||||
|
||||
bool validAlpha = false;
|
||||
bool alphaAsMask = true;
|
||||
QImage image = process2DImageColor(srcImage, validAlpha, alphaAsMask);
|
||||
|
||||
gpu::Texture* theTexture = nullptr;
|
||||
if ((image.width() > 0) && (image.height() > 0)) {
|
||||
if ((srcImage.width() > 0) && (srcImage.height() > 0)) {
|
||||
QImage image = srcImage;
|
||||
if (image.format() != QImage::Format_RGB888) {
|
||||
image = image.convertToFormat(QImage::Format_RGB888);
|
||||
}
|
||||
|
||||
gpu::Element formatGPU;
|
||||
gpu::Element formatMip;
|
||||
|
@ -674,7 +670,7 @@ gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcIm
|
|||
|
||||
// Find the layout of the cubemap in the 2D image
|
||||
int foundLayout = CubeLayout::findLayout(image.width(), image.height());
|
||||
|
||||
|
||||
std::vector<QImage> faces;
|
||||
// If found, go extract the faces as separate images
|
||||
if (foundLayout >= 0) {
|
||||
|
@ -729,3 +725,7 @@ gpu::Texture* TextureUsage::processCubeTextureColorFromImage(const QImage& srcIm
|
|||
gpu::Texture* TextureUsage::createCubeTextureFromImage(const QImage& srcImage, const std::string& srcImageName) {
|
||||
return processCubeTextureColorFromImage(srcImage, srcImageName, false, true, true, true);
|
||||
}
|
||||
|
||||
gpu::Texture* TextureUsage::createCubeTextureFromImageWithoutIrradiance(const QImage& srcImage, const std::string& srcImageName) {
|
||||
return processCubeTextureColorFromImage(srcImage, srcImageName, false, true, true, false);
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ public:
|
|||
static gpu::Texture* createRoughnessTextureFromGlossImage(const QImage& image, const std::string& srcImageName);
|
||||
static gpu::Texture* createMetallicTextureFromImage(const QImage& image, const std::string& srcImageName);
|
||||
static gpu::Texture* createCubeTextureFromImage(const QImage& image, const std::string& srcImageName);
|
||||
static gpu::Texture* createCubeTextureFromImageWithoutIrradiance(const QImage& image, const std::string& srcImageName);
|
||||
static gpu::Texture* createLightmapTextureFromImage(const QImage& image, const std::string& srcImageName);
|
||||
|
||||
|
||||
|
|
|
@ -200,8 +200,9 @@ void AccountManager::sendRequest(const QString& path,
|
|||
const JSONCallbackParameters& callbackParams,
|
||||
const QByteArray& dataByteArray,
|
||||
QHttpMultiPart* dataMultiPart,
|
||||
const QVariantMap& propertyMap) {
|
||||
|
||||
const QVariantMap& propertyMap,
|
||||
QUrlQuery query) {
|
||||
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "sendRequest",
|
||||
Q_ARG(const QString&, path),
|
||||
|
@ -213,9 +214,9 @@ void AccountManager::sendRequest(const QString& path,
|
|||
Q_ARG(QVariantMap, propertyMap));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
|
||||
QNetworkRequest networkRequest;
|
||||
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter());
|
||||
|
@ -224,13 +225,17 @@ void AccountManager::sendRequest(const QString& path,
|
|||
uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit());
|
||||
|
||||
QUrl requestURL = _authURL;
|
||||
|
||||
|
||||
if (path.startsWith("/")) {
|
||||
requestURL.setPath(path);
|
||||
} else {
|
||||
requestURL.setPath("/" + path);
|
||||
}
|
||||
|
||||
|
||||
if (!query.isEmpty()) {
|
||||
requestURL.setQuery(query);
|
||||
}
|
||||
|
||||
if (authType != AccountManagerAuth::None ) {
|
||||
if (hasValidAccessToken()) {
|
||||
networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER,
|
||||
|
@ -241,22 +246,21 @@ void AccountManager::sendRequest(const QString& path,
|
|||
<< path << "that requires authentication";
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
|
||||
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
|
||||
qCDebug(networking) << "Making a request to" << qPrintable(requestURL.toString());
|
||||
|
||||
|
||||
if (!dataByteArray.isEmpty()) {
|
||||
qCDebug(networking) << "The POST/PUT body -" << QString(dataByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QNetworkReply* networkReply = NULL;
|
||||
|
||||
|
||||
switch (operation) {
|
||||
case QNetworkAccessManager::GetOperation:
|
||||
networkReply = networkAccessManager.get(networkRequest);
|
||||
|
@ -269,7 +273,7 @@ void AccountManager::sendRequest(const QString& path,
|
|||
} else {
|
||||
networkReply = networkAccessManager.put(networkRequest, dataMultiPart);
|
||||
}
|
||||
|
||||
|
||||
// make sure dataMultiPart is destroyed when the reply is
|
||||
connect(networkReply, &QNetworkReply::destroyed, dataMultiPart, &QHttpMultiPart::deleteLater);
|
||||
} else {
|
||||
|
@ -280,7 +284,7 @@ void AccountManager::sendRequest(const QString& path,
|
|||
networkReply = networkAccessManager.put(networkRequest, dataByteArray);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
case QNetworkAccessManager::DeleteOperation:
|
||||
networkReply = networkAccessManager.sendCustomRequest(networkRequest, "DELETE");
|
||||
|
@ -289,7 +293,7 @@ void AccountManager::sendRequest(const QString& path,
|
|||
// other methods not yet handled
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (networkReply) {
|
||||
if (!propertyMap.isEmpty()) {
|
||||
// we have properties to set on the reply so the user can check them after
|
||||
|
@ -297,18 +301,18 @@ void AccountManager::sendRequest(const QString& path,
|
|||
networkReply->setProperty(qPrintable(propertyKey), propertyMap.value(propertyKey));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (!callbackParams.isEmpty()) {
|
||||
// if we have information for a callback, insert the callbackParams into our local map
|
||||
_pendingCallbackMap.insert(networkReply, callbackParams);
|
||||
|
||||
|
||||
if (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) {
|
||||
callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)),
|
||||
callbackParams.updateSlot.toStdString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if we ended up firing of a request, hook up to it now
|
||||
connect(networkReply, SIGNAL(finished()), SLOT(processReply()));
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include "NetworkAccessManager.h"
|
||||
|
||||
|
@ -67,7 +68,8 @@ public:
|
|||
const JSONCallbackParameters& callbackParams = JSONCallbackParameters(),
|
||||
const QByteArray& dataByteArray = QByteArray(),
|
||||
QHttpMultiPart* dataMultiPart = NULL,
|
||||
const QVariantMap& propertyMap = QVariantMap());
|
||||
const QVariantMap& propertyMap = QVariantMap(),
|
||||
QUrlQuery query = QUrlQuery());
|
||||
|
||||
void setIsAgent(bool isAgent) { _isAgent = isAgent; }
|
||||
|
||||
|
|
|
@ -33,12 +33,7 @@ bool AssetResourceRequest::urlIsAssetHash() const {
|
|||
}
|
||||
|
||||
void AssetResourceRequest::doSend() {
|
||||
auto parts = _url.path().split(".", QString::SkipEmptyParts);
|
||||
auto hash = parts.length() > 0 ? parts[0] : "";
|
||||
auto extension = parts.length() > 1 ? parts[1] : "";
|
||||
|
||||
// We'll either have a hash or an ATP path to a file (that maps to a hash)
|
||||
|
||||
if (urlIsAssetHash()) {
|
||||
// We've detected that this is a hash - simply use AssetClient to request that asset
|
||||
auto parts = _url.path().split(".", QString::SkipEmptyParts);
|
||||
|
|
88
libraries/networking/src/AtpReply.cpp
Normal file
88
libraries/networking/src/AtpReply.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// AtpReply.cpp
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Zander Otavka on 8/4/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "ResourceManager.h"
|
||||
#include "AtpReply.h"
|
||||
|
||||
AtpReply::AtpReply(const QUrl& url, QObject* parent) :
|
||||
_resourceRequest(ResourceManager::createResourceRequest(parent, url)) {
|
||||
setOperation(QNetworkAccessManager::GetOperation);
|
||||
|
||||
connect(_resourceRequest, &AssetResourceRequest::progress, this, &AtpReply::downloadProgress);
|
||||
connect(_resourceRequest, &AssetResourceRequest::finished, this, &AtpReply::handleRequestFinish);
|
||||
|
||||
_resourceRequest->send();
|
||||
}
|
||||
|
||||
AtpReply::~AtpReply() {
|
||||
if (_resourceRequest) {
|
||||
_resourceRequest->deleteLater();
|
||||
_resourceRequest = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
qint64 AtpReply::bytesAvailable() const {
|
||||
return _content.size() - _readOffset + QIODevice::bytesAvailable();
|
||||
}
|
||||
|
||||
qint64 AtpReply::readData(char* data, qint64 maxSize) {
|
||||
if (_readOffset < _content.size()) {
|
||||
qint64 readSize = qMin(maxSize, _content.size() - _readOffset);
|
||||
memcpy(data, _content.constData() + _readOffset, readSize);
|
||||
_readOffset += readSize;
|
||||
return readSize;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void AtpReply::handleRequestFinish() {
|
||||
Q_ASSERT(_resourceRequest->getState() == ResourceRequest::State::Finished);
|
||||
|
||||
switch (_resourceRequest->getResult()) {
|
||||
case ResourceRequest::Result::Success:
|
||||
setError(NoError, "Success");
|
||||
_content = _resourceRequest->getData();
|
||||
break;
|
||||
case ResourceRequest::Result::InvalidURL:
|
||||
setError(ContentNotFoundError, "Invalid URL");
|
||||
break;
|
||||
case ResourceRequest::Result::NotFound:
|
||||
setError(ContentNotFoundError, "Not found");
|
||||
break;
|
||||
case ResourceRequest::Result::ServerUnavailable:
|
||||
setError(ServiceUnavailableError, "Service unavailable");
|
||||
break;
|
||||
case ResourceRequest::Result::AccessDenied:
|
||||
setError(ContentAccessDenied, "Access denied");
|
||||
break;
|
||||
case ResourceRequest::Result::Timeout:
|
||||
setError(TimeoutError, "Timeout");
|
||||
break;
|
||||
default:
|
||||
setError(UnknownNetworkError, "Unknown error");
|
||||
break;
|
||||
}
|
||||
|
||||
open(ReadOnly | Unbuffered);
|
||||
setHeader(QNetworkRequest::ContentLengthHeader, QVariant(_content.size()));
|
||||
|
||||
if (error() != NoError) {
|
||||
emit error(error());
|
||||
}
|
||||
|
||||
setFinished(true);
|
||||
emit readyRead();
|
||||
emit finished();
|
||||
|
||||
_resourceRequest->deleteLater();
|
||||
_resourceRequest = nullptr;
|
||||
}
|
40
libraries/networking/src/AtpReply.h
Normal file
40
libraries/networking/src/AtpReply.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// AtpReply.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Zander Otavka on 8/4/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AtpReply_h
|
||||
#define hifi_AtpReply_h
|
||||
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QUrl>
|
||||
|
||||
#include "AssetResourceRequest.h"
|
||||
|
||||
class AtpReply : public QNetworkReply {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AtpReply(const QUrl& url, QObject* parent = Q_NULLPTR);
|
||||
~AtpReply();
|
||||
qint64 bytesAvailable() const override;
|
||||
void abort() override { }
|
||||
bool isSequential() const override { return true; }
|
||||
|
||||
protected:
|
||||
qint64 readData(char* data, qint64 maxSize) override;
|
||||
|
||||
private:
|
||||
void handleRequestFinish();
|
||||
|
||||
ResourceRequest* _resourceRequest { nullptr };
|
||||
QByteArray _content;
|
||||
qint64 _readOffset { 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_AtpReply_h
|
36
libraries/networking/src/GroupRank.h
Normal file
36
libraries/networking/src/GroupRank.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// GroupRank.h
|
||||
// libraries/networking/src/
|
||||
//
|
||||
// Created by Seth Alves on 2016-7-21.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_GroupRank_h
|
||||
#define hifi_GroupRank_h
|
||||
|
||||
class GroupRank {
|
||||
public:
|
||||
GroupRank() {}
|
||||
GroupRank(QUuid id, unsigned int order, QString name, unsigned int membersCount) :
|
||||
id(id), order(order), name(name), membersCount(membersCount) {}
|
||||
|
||||
QUuid id;
|
||||
int order { -1 };
|
||||
QString name;
|
||||
int membersCount { -1 };
|
||||
};
|
||||
|
||||
inline bool operator==(const GroupRank& lhs, const GroupRank& rhs) {
|
||||
return
|
||||
lhs.id == rhs.id &&
|
||||
lhs.order == rhs.order &&
|
||||
lhs.name == rhs.name &&
|
||||
lhs.membersCount == rhs.membersCount;
|
||||
}
|
||||
inline bool operator!=(const GroupRank& lhs, const GroupRank& rhs) { return !(lhs == rhs); }
|
||||
|
||||
#endif // hifi_GroupRank_h
|
|
@ -135,17 +135,25 @@ void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) {
|
|||
|
||||
_permissions = newPermissions;
|
||||
|
||||
if (originalPermissions.canAdjustLocks != newPermissions.canAdjustLocks) {
|
||||
emit isAllowedEditorChanged(_permissions.canAdjustLocks);
|
||||
if (originalPermissions.can(NodePermissions::Permission::canAdjustLocks) !=
|
||||
newPermissions.can(NodePermissions::Permission::canAdjustLocks)) {
|
||||
emit isAllowedEditorChanged(_permissions.can(NodePermissions::Permission::canAdjustLocks));
|
||||
}
|
||||
if (originalPermissions.canRezPermanentEntities != newPermissions.canRezPermanentEntities) {
|
||||
emit canRezChanged(_permissions.canRezPermanentEntities);
|
||||
if (originalPermissions.can(NodePermissions::Permission::canRezPermanentEntities) !=
|
||||
newPermissions.can(NodePermissions::Permission::canRezPermanentEntities)) {
|
||||
emit canRezChanged(_permissions.can(NodePermissions::Permission::canRezPermanentEntities));
|
||||
}
|
||||
if (originalPermissions.canRezTemporaryEntities != newPermissions.canRezTemporaryEntities) {
|
||||
emit canRezTmpChanged(_permissions.canRezTemporaryEntities);
|
||||
if (originalPermissions.can(NodePermissions::Permission::canRezTemporaryEntities) !=
|
||||
newPermissions.can(NodePermissions::Permission::canRezTemporaryEntities)) {
|
||||
emit canRezTmpChanged(_permissions.can(NodePermissions::Permission::canRezTemporaryEntities));
|
||||
}
|
||||
if (originalPermissions.canWriteToAssetServer != newPermissions.canWriteToAssetServer) {
|
||||
emit canWriteAssetsChanged(_permissions.canWriteToAssetServer);
|
||||
if (originalPermissions.can(NodePermissions::Permission::canWriteToAssetServer) !=
|
||||
newPermissions.can(NodePermissions::Permission::canWriteToAssetServer)) {
|
||||
emit canWriteAssetsChanged(_permissions.can(NodePermissions::Permission::canWriteToAssetServer));
|
||||
}
|
||||
if (originalPermissions.can(NodePermissions::Permission::canKick) !=
|
||||
newPermissions.can(NodePermissions::Permission::canKick)) {
|
||||
emit canKickChanged(_permissions.can(NodePermissions::Permission::canKick));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -106,10 +106,11 @@ public:
|
|||
void setSessionUUID(const QUuid& sessionUUID);
|
||||
|
||||
void setPermissions(const NodePermissions& newPermissions);
|
||||
bool isAllowedEditor() const { return _permissions.canAdjustLocks; }
|
||||
bool getThisNodeCanRez() const { return _permissions.canRezPermanentEntities; }
|
||||
bool getThisNodeCanRezTmp() const { return _permissions.canRezTemporaryEntities; }
|
||||
bool getThisNodeCanWriteAssets() const { return _permissions.canWriteToAssetServer; }
|
||||
bool isAllowedEditor() const { return _permissions.can(NodePermissions::Permission::canAdjustLocks); }
|
||||
bool getThisNodeCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); }
|
||||
bool getThisNodeCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); }
|
||||
bool getThisNodeCanWriteAssets() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); }
|
||||
bool getThisNodeCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); }
|
||||
|
||||
quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); }
|
||||
QUdpSocket& getDTLSSocket();
|
||||
|
@ -258,6 +259,7 @@ signals:
|
|||
void canRezChanged(bool canRez);
|
||||
void canRezTmpChanged(bool canRezTmp);
|
||||
void canWriteAssetsChanged(bool canWriteAssets);
|
||||
void canKickChanged(bool canKick);
|
||||
|
||||
protected slots:
|
||||
void connectedForLocalSocketTest();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// NetworkAccessManager.cpp
|
||||
//
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Clement on 7/1/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <QThreadStorage>
|
||||
|
||||
#include "AtpReply.h"
|
||||
#include "NetworkAccessManager.h"
|
||||
|
||||
QThreadStorage<QNetworkAccessManager*> networkAccessManagers;
|
||||
|
@ -23,3 +24,13 @@ QNetworkAccessManager& NetworkAccessManager::getInstance() {
|
|||
|
||||
return *networkAccessManagers.localData();
|
||||
}
|
||||
|
||||
QNetworkReply* NetworkAccessManager::createRequest(Operation operation, const QNetworkRequest& request, QIODevice* device) {
|
||||
if (request.url().scheme() == "atp" && operation == GetOperation) {
|
||||
return new AtpReply(request.url());
|
||||
//auto url = request.url().toString();
|
||||
//return QNetworkAccessManager::createRequest(operation, request, device);
|
||||
} else {
|
||||
return QNetworkAccessManager::createRequest(operation, request, device);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// NetworkAccessManager.h
|
||||
//
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Clement on 7/1/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
@ -13,12 +13,17 @@
|
|||
#define hifi_NetworkAccessManager_h
|
||||
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtQml/QQmlNetworkAccessManagerFactory>
|
||||
|
||||
/// Wrapper around QNetworkAccessManager to restrict at one instance by thread
|
||||
class NetworkAccessManager : public QObject {
|
||||
class NetworkAccessManager : public QNetworkAccessManager {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static QNetworkAccessManager& getInstance();
|
||||
protected:
|
||||
NetworkAccessManager(QObject* parent = Q_NULLPTR) : QNetworkAccessManager(parent) {}
|
||||
virtual QNetworkReply* createRequest(Operation op, const QNetworkRequest& request, QIODevice* device = Q_NULLPTR) override;
|
||||
};
|
||||
|
||||
#endif // hifi_NetworkAccessManager_h
|
|
@ -65,10 +65,11 @@ public:
|
|||
|
||||
void setPermissions(const NodePermissions& newPermissions) { _permissions = newPermissions; }
|
||||
NodePermissions getPermissions() const { return _permissions; }
|
||||
bool isAllowedEditor() const { return _permissions.canAdjustLocks; }
|
||||
bool getCanRez() const { return _permissions.canRezPermanentEntities; }
|
||||
bool getCanRezTmp() const { return _permissions.canRezTemporaryEntities; }
|
||||
bool getCanWriteToAssetServer() const { return _permissions.canWriteToAssetServer; }
|
||||
bool isAllowedEditor() const { return _permissions.can(NodePermissions::Permission::canAdjustLocks); }
|
||||
bool getCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); }
|
||||
bool getCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); }
|
||||
bool getCanWriteToAssetServer() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); }
|
||||
bool getCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); }
|
||||
|
||||
void parseIgnoreRequestMessage(QSharedPointer<ReceivedMessage> message);
|
||||
void addIgnoredNode(const QUuid& otherNodeID);
|
||||
|
|
|
@ -283,12 +283,7 @@ void NodeList::sendDomainServerCheckIn() {
|
|||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
const QUuid& connectionToken = _domainHandler.getConnectionToken();
|
||||
|
||||
// we assume that we're on the same box as the DS if it has the same local address and
|
||||
// it didn't present us with a connection token to use for username signature
|
||||
bool localhostDomain = _domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost
|
||||
|| (_domainHandler.getSockAddr().getAddress() == _localSockAddr.getAddress() && connectionToken.isNull());
|
||||
|
||||
bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull() && !localhostDomain;
|
||||
bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull();
|
||||
|
||||
if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) {
|
||||
qWarning() << "A keypair is required to present a username signature to the domain-server"
|
||||
|
@ -732,7 +727,7 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) {
|
|||
emit ignoredNode(nodeID);
|
||||
|
||||
} else {
|
||||
qWarning() << "UsersScriptingInterface::ignore called with an invalid ID or an ID which matches the current session ID.";
|
||||
qWarning() << "NodeList::ignoreNodeBySessionID called with an invalid ID or an ID which matches the current session ID.";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -764,3 +759,28 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeList::kickNodeBySessionID(const QUuid& nodeID) {
|
||||
// send a request to domain-server to kick the node with the given session ID
|
||||
// the domain-server will handle the persistence of the kick (via username or IP)
|
||||
|
||||
if (!nodeID.isNull() && _sessionUUID != nodeID ) {
|
||||
if (getThisNodeCanKick()) {
|
||||
// setup the packet
|
||||
auto kickPacket = NLPacket::create(PacketType::NodeKickRequest, NUM_BYTES_RFC4122_UUID, true);
|
||||
|
||||
// write the node ID to the packet
|
||||
kickPacket->write(nodeID.toRfc4122());
|
||||
|
||||
qDebug() << "Sending packet to kick node" << uuidStringWithoutCurlyBraces(nodeID);
|
||||
|
||||
sendPacket(std::move(kickPacket), _domainHandler.getSockAddr());
|
||||
} else {
|
||||
qWarning() << "You do not have permissions to kick in this domain."
|
||||
<< "Request to kick node" << uuidStringWithoutCurlyBraces(nodeID) << "will not be sent";
|
||||
}
|
||||
} else {
|
||||
qWarning() << "NodeList::kickNodeBySessionID called with an invalid ID or an ID which matches the current session ID.";
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue