mirror of
https://github.com/overte-org/overte.git
synced 2025-04-09 07:12:45 +02:00
Merge branch 'master' into M22147
This commit is contained in:
commit
997dc963d3
187 changed files with 3486 additions and 1746 deletions
|
@ -53,6 +53,10 @@ Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio.
|
|||
|
||||
Change the Solution Configuration (menu ribbon under the menu bar, next to the green play button) from "Debug" to "Release" for best performance.
|
||||
|
||||
Create another environment variable (see Step #4)
|
||||
* Set "Variable name": `PreferredToolArchitecture`
|
||||
* Set "Variable value": `x64`
|
||||
|
||||
Run from the menu bar `Build > Build Solution`.
|
||||
|
||||
### Step 7. Testing Interface
|
||||
|
|
|
@ -62,7 +62,7 @@ endif()
|
|||
# Use default time server if none defined in environment
|
||||
set_from_env(TIMESERVER_URL TIMESERVER_URL "http://sha256timestamp.ws.symantec.com/sha256/timestamp")
|
||||
|
||||
set(HIFI_USE_OPTIMIZED_IK OFF)
|
||||
set(HIFI_USE_OPTIMIZED_IK_OPTION OFF)
|
||||
set(BUILD_CLIENT_OPTION ON)
|
||||
set(BUILD_SERVER_OPTION ON)
|
||||
set(BUILD_TESTS_OPTION OFF)
|
||||
|
@ -126,7 +126,7 @@ if (USE_GLES AND (NOT ANDROID))
|
|||
set(DISABLE_QML_OPTION ON)
|
||||
endif()
|
||||
|
||||
option(HIFI_USE_OPTIMIZED_IK "USE OPTIMIZED IK" ${HIFI_USE_OPTIMIZED_IK_OPTION})
|
||||
option(HIFI_USE_OPTIMIZED_IK "Use optimized IK" ${HIFI_USE_OPTIMIZED_IK_OPTION})
|
||||
option(BUILD_CLIENT "Build client components" ${BUILD_CLIENT_OPTION})
|
||||
option(BUILD_SERVER "Build server components" ${BUILD_SERVER_OPTION})
|
||||
option(BUILD_TESTS "Build tests" ${BUILD_TESTS_OPTION})
|
||||
|
@ -157,7 +157,7 @@ foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS})
|
|||
list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}")
|
||||
endforeach()
|
||||
|
||||
MESSAGE(STATUS "USE OPTIMIZED IK: " ${HIFI_USE_OPTIMIZED_IK})
|
||||
MESSAGE(STATUS "Use optimized IK: " ${HIFI_USE_OPTIMIZED_IK})
|
||||
MESSAGE(STATUS "Build server: " ${BUILD_SERVER})
|
||||
MESSAGE(STATUS "Build client: " ${BUILD_CLIENT})
|
||||
MESSAGE(STATUS "Build tests: " ${BUILD_TESTS})
|
||||
|
|
|
@ -307,7 +307,7 @@ void AssignmentClient::assignmentCompleted() {
|
|||
|
||||
// reset our NodeList by switching back to unassigned and clearing the list
|
||||
nodeList->setOwnerType(NodeType::Unassigned);
|
||||
nodeList->reset();
|
||||
nodeList->reset("Assignment completed");
|
||||
nodeList->resetNodeInterestSet();
|
||||
|
||||
_isAssigned = false;
|
||||
|
|
|
@ -45,6 +45,7 @@ int AvatarAudioStream::parseStreamProperties(PacketType type, const QByteArray&
|
|||
: AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
// restart the codec
|
||||
if (_codec) {
|
||||
QMutexLocker lock(&_decoderMutex);
|
||||
if (_decoder) {
|
||||
_codec->releaseDecoder(_decoder);
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ void EntityTreeHeadlessViewer::update() {
|
|||
if (_tree) {
|
||||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||
tree->withTryWriteLock([&] {
|
||||
tree->preUpdate();
|
||||
tree->update();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -372,7 +372,7 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
|
|||
// Record explicitly filtered-in entity so that extra entities can be flagged.
|
||||
entityNodeData->insertSentFilteredEntity(entityID);
|
||||
}
|
||||
OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData);
|
||||
OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData, entityNode->getCanGetAndSetPrivateUserData());
|
||||
|
||||
if (appendEntityState != OctreeElement::COMPLETED) {
|
||||
if (appendEntityState == OctreeElement::PARTIAL) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": 2.2,
|
||||
"version": 2.3,
|
||||
"settings": [
|
||||
{
|
||||
"name": "metaverse",
|
||||
|
@ -224,7 +224,7 @@
|
|||
"name": "standard_permissions",
|
||||
"type": "table",
|
||||
"label": "Domain-Wide User Permissions",
|
||||
"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>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified 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><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</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>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified 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><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</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": [
|
||||
|
@ -233,8 +233,8 @@
|
|||
"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>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified 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><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</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": 10
|
||||
"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>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified 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><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</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": 11
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -311,6 +311,13 @@
|
|||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_get_and_set_private_user_data",
|
||||
"label": "Can Get and Set Private User Data",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
],
|
||||
"non-deletable-row-key": "permissions_id",
|
||||
|
@ -337,6 +344,7 @@
|
|||
"id_can_rez_tmp": true,
|
||||
"id_can_rez_tmp_certified": true,
|
||||
"id_can_write_to_asset_server": true,
|
||||
"id_can_get_and_set_private_user_data": true,
|
||||
"permissions_id": "localhost"
|
||||
},
|
||||
{
|
||||
|
@ -361,8 +369,8 @@
|
|||
"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>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified 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><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</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": 10
|
||||
"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>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified 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><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</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": 11
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -464,6 +472,13 @@
|
|||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_get_and_set_private_user_data",
|
||||
"label": "Can Get and Set Private User Data",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -482,8 +497,8 @@
|
|||
"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>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified 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><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</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": 10
|
||||
"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>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified 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><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</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": 11
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -582,6 +597,13 @@
|
|||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_get_and_set_private_user_data",
|
||||
"label": "Can Get and Set Private User Data",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -596,8 +618,8 @@
|
|||
"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>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified 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><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</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": 10
|
||||
"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>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified 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><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</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": 11
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -674,6 +696,13 @@
|
|||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_get_and_set_private_user_data",
|
||||
"label": "Can Get and Set Private User Data",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -688,8 +717,8 @@
|
|||
"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>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users from specific IPs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users from specific IPs can create new certified 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><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</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": 10
|
||||
"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>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users from specific IPs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users from specific IPs can create new certified 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><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</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": 11
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -766,6 +795,13 @@
|
|||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_get_and_set_private_user_data",
|
||||
"label": "Can Get and Set Private User Data",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -780,8 +816,8 @@
|
|||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs 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 with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific MACs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific MACs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
|
||||
"span": 10
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs 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 with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific MACs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific MACs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
|
||||
"span": 11
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -858,7 +894,14 @@
|
|||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "id_can_get_and_set_private_user_data",
|
||||
"label": "Can Get and Set Private User Data",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -872,8 +915,8 @@
|
|||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints 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 with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
|
||||
"span": 10
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints 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 with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
|
||||
"span": 11
|
||||
}
|
||||
],
|
||||
"columns": [
|
||||
|
@ -950,6 +993,13 @@
|
|||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_get_and_set_private_user_data",
|
||||
"label": "Can Get and Set Private User Data",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -282,6 +282,7 @@ void DomainGatekeeper::updateNodePermissions() {
|
|||
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryCertifiedEntities;
|
||||
userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer;
|
||||
userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent;
|
||||
userPerms.permissions |= NodePermissions::Permission::canGetAndSetPrivateUserData;
|
||||
} else {
|
||||
// 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
|
||||
|
@ -374,6 +375,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
|
|||
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryCertifiedEntities;
|
||||
userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer;
|
||||
userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent;
|
||||
userPerms.permissions |= NodePermissions::Permission::canGetAndSetPrivateUserData;
|
||||
newNode->setPermissions(userPerms);
|
||||
return newNode;
|
||||
}
|
||||
|
@ -811,26 +813,23 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer<ReceivedMe
|
|||
// any peer we don't have we add to the hash, otherwise we update
|
||||
QDataStream iceResponseStream(message->getMessage());
|
||||
|
||||
NetworkPeer* receivedPeer = new NetworkPeer;
|
||||
auto receivedPeer = SharedNetworkPeer::create();
|
||||
iceResponseStream >> *receivedPeer;
|
||||
|
||||
if (!_icePeers.contains(receivedPeer->getUUID())) {
|
||||
qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer;
|
||||
SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer);
|
||||
_icePeers[receivedPeer->getUUID()] = newPeer;
|
||||
qCDebug(domain_server_ice) << "New peer requesting ICE connection being added to hash -" << *receivedPeer;
|
||||
_icePeers[receivedPeer->getUUID()] = receivedPeer;
|
||||
|
||||
// make sure we know when we should ping this peer
|
||||
connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainGatekeeper::handlePeerPingTimeout);
|
||||
connect(receivedPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainGatekeeper::handlePeerPingTimeout);
|
||||
|
||||
// immediately ping the new peer, and start a timer to continue pinging it until we connect to it
|
||||
newPeer->startPingTimer();
|
||||
receivedPeer->startPingTimer();
|
||||
|
||||
qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID"
|
||||
<< newPeer->getUUID();
|
||||
qCDebug(domain_server_ice) << "Sending ping packets to establish connectivity with ICE peer with ID"
|
||||
<< receivedPeer->getUUID();
|
||||
|
||||
pingPunchForConnectingPeer(newPeer);
|
||||
} else {
|
||||
delete receivedPeer;
|
||||
pingPunchForConnectingPeer(receivedPeer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
#include <OctreeDataUtils.h>
|
||||
|
||||
Q_LOGGING_CATEGORY(domain_server, "hifi.domain_server")
|
||||
Q_LOGGING_CATEGORY(domain_server_ice, "hifi.domain_server.ice")
|
||||
|
||||
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
|
||||
const QString DomainServer::REPLACEMENT_FILE_EXTENSION = ".replace";
|
||||
|
@ -374,7 +375,7 @@ void DomainServer::parseCommandLine(int argc, char* argv[]) {
|
|||
}
|
||||
|
||||
if (_iceServerAddr.isEmpty()) {
|
||||
qWarning() << "Could not parse an IP address and port combination from" << hostnamePortString;
|
||||
qCWarning(domain_server_ice) << "Could not parse an IP address and port combination from" << hostnamePortString;
|
||||
::exit(0);
|
||||
}
|
||||
}
|
||||
|
@ -1570,12 +1571,8 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
|||
callbackParameters.errorCallbackMethod = "handleFailedICEServerAddressUpdate";
|
||||
callbackParameters.jsonCallbackMethod = "handleSuccessfulICEServerAddressUpdate";
|
||||
|
||||
static bool printedIceServerMessage = false;
|
||||
if (!printedIceServerMessage) {
|
||||
printedIceServerMessage = true;
|
||||
qDebug() << "Updating ice-server address in High Fidelity Metaverse API to"
|
||||
<< (_iceServerSocket.isNull() ? "" : _iceServerSocket.getAddress().toString());
|
||||
}
|
||||
qCDebug(domain_server_ice) << "Updating ice-server address in High Fidelity Metaverse API to"
|
||||
<< (_iceServerSocket.isNull() ? "" : _iceServerSocket.getAddress().toString());
|
||||
|
||||
static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address";
|
||||
|
||||
|
@ -1589,11 +1586,11 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
|||
void DomainServer::handleSuccessfulICEServerAddressUpdate(QNetworkReply* requestReply) {
|
||||
_sendICEServerAddressToMetaverseAPIInProgress = false;
|
||||
if (_sendICEServerAddressToMetaverseAPIRedo) {
|
||||
qDebug() << "ice-server address updated with metaverse, but has since changed. redoing update...";
|
||||
qCDebug(domain_server_ice) << "ice-server address (" << _iceServerSocket << ") updated with metaverse, but has since changed. redoing update...";
|
||||
_sendICEServerAddressToMetaverseAPIRedo = false;
|
||||
sendICEServerAddressToMetaverseAPI();
|
||||
} else {
|
||||
qDebug() << "ice-server address updated with metaverse.";
|
||||
qCDebug(domain_server_ice) << "ice-server address (" << _iceServerSocket << ") updated with metaverse.";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1606,9 +1603,9 @@ void DomainServer::handleFailedICEServerAddressUpdate(QNetworkReply* requestRepl
|
|||
} else {
|
||||
const int ICE_SERVER_UPDATE_RETRY_MS = 2 * 1000;
|
||||
|
||||
qWarning() << "Failed to update ice-server address with High Fidelity Metaverse - error was"
|
||||
qCWarning(domain_server_ice) << "Failed to update ice-server address (" << _iceServerSocket << ") with High Fidelity Metaverse - error was"
|
||||
<< requestReply->errorString();
|
||||
qWarning() << "\tRe-attempting in" << ICE_SERVER_UPDATE_RETRY_MS / 1000 << "seconds";
|
||||
qCWarning(domain_server_ice) << "\tRe-attempting in" << ICE_SERVER_UPDATE_RETRY_MS / 1000 << "seconds";
|
||||
|
||||
QTimer::singleShot(ICE_SERVER_UPDATE_RETRY_MS, this, SLOT(sendICEServerAddressToMetaverseAPI()));
|
||||
}
|
||||
|
@ -1621,26 +1618,26 @@ void DomainServer::sendHeartbeatToIceServer() {
|
|||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
if (!accountManager->getAccountInfo().hasPrivateKey()) {
|
||||
qWarning() << "Cannot send an ice-server heartbeat without a private key for signature.";
|
||||
qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat.";
|
||||
qCWarning(domain_server_ice) << "Cannot send an ice-server heartbeat without a private key for signature.";
|
||||
qCWarning(domain_server_ice) << "Waiting for keypair generation to complete before sending ICE heartbeat.";
|
||||
|
||||
if (!limitedNodeList->getSessionUUID().isNull()) {
|
||||
accountManager->generateNewDomainKeypair(limitedNodeList->getSessionUUID());
|
||||
} else {
|
||||
qWarning() << "Attempting to send ICE server heartbeat with no domain ID. This is not supported";
|
||||
qCWarning(domain_server_ice) << "Attempting to send ICE server heartbeat with no domain ID. This is not supported";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const int FAILOVER_NO_REPLY_ICE_HEARTBEATS { 3 };
|
||||
const int FAILOVER_NO_REPLY_ICE_HEARTBEATS { 6 };
|
||||
|
||||
// increase the count of no reply ICE heartbeats and check the current value
|
||||
++_noReplyICEHeartbeats;
|
||||
|
||||
if (_noReplyICEHeartbeats > FAILOVER_NO_REPLY_ICE_HEARTBEATS) {
|
||||
qWarning() << "There have been" << _noReplyICEHeartbeats - 1 << "heartbeats sent with no reply from the ice-server";
|
||||
qWarning() << "Clearing the current ice-server socket and selecting a new candidate ice-server";
|
||||
qCWarning(domain_server_ice) << "There have been" << _noReplyICEHeartbeats - 1 << "heartbeats sent with no reply from the ice-server";
|
||||
qCWarning(domain_server_ice) << "Clearing the current ice-server socket and selecting a new candidate ice-server";
|
||||
|
||||
// add the current address to our list of failed addresses
|
||||
_failedIceServerAddresses << _iceServerSocket.getAddress();
|
||||
|
@ -1713,8 +1710,8 @@ void DomainServer::sendHeartbeatToIceServer() {
|
|||
limitedNodeList->sendUnreliablePacket(*_iceServerHeartbeatPacket, _iceServerSocket);
|
||||
|
||||
} else {
|
||||
qDebug() << "Not sending ice-server heartbeat since there is no selected ice-server.";
|
||||
qDebug() << "Waiting for" << _iceServerAddr << "host lookup response";
|
||||
qCDebug(domain_server_ice) << "Not sending ice-server heartbeat since there is no selected ice-server.";
|
||||
qCDebug(domain_server_ice) << "Waiting for" << _iceServerAddr << "host lookup response";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3294,7 +3291,7 @@ void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer<Received
|
|||
static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3;
|
||||
|
||||
if (++_numHeartbeatDenials > NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN) {
|
||||
qDebug() << "Received" << NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN << "heartbeat denials from ice-server"
|
||||
qCDebug(domain_server_ice) << "Received" << NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN << "heartbeat denials from ice-server"
|
||||
<< "- re-generating keypair now";
|
||||
|
||||
// we've hit our threshold of heartbeat denials, trigger a keypair re-generation
|
||||
|
@ -3316,7 +3313,7 @@ void DomainServer::processICEServerHeartbeatACK(QSharedPointer<ReceivedMessage>
|
|||
if (!_connectedToICEServer) {
|
||||
_connectedToICEServer = true;
|
||||
sendICEServerAddressToMetaverseAPI();
|
||||
qInfo() << "Connected to ice-server at" << _iceServerSocket;
|
||||
qCInfo(domain_server_ice) << "Connected to ice-server at" << _iceServerSocket;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3347,7 +3344,7 @@ void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
|
|||
}
|
||||
|
||||
if (hostInfo.error() != QHostInfo::NoError || sanitizedAddresses.empty()) {
|
||||
qWarning() << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString();
|
||||
qCWarning(domain_server_ice) << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString();
|
||||
|
||||
// if we don't have an ICE server to use yet, trigger a retry
|
||||
if (_iceServerSocket.isNull()) {
|
||||
|
@ -3362,7 +3359,7 @@ void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
|
|||
_iceServerAddresses = sanitizedAddresses;
|
||||
|
||||
if (countBefore == 0) {
|
||||
qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr;
|
||||
qCInfo(domain_server_ice) << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr;
|
||||
}
|
||||
|
||||
if (_iceServerSocket.isNull()) {
|
||||
|
@ -3396,7 +3393,7 @@ void DomainServer::randomizeICEServerAddress(bool shouldTriggerHostLookup) {
|
|||
// we ended up with an empty list since everything we've tried has failed
|
||||
// so clear the set of failed addresses and start going through them again
|
||||
|
||||
qWarning() << "All current ice-server addresses have failed - re-attempting all current addresses for"
|
||||
qCWarning(domain_server_ice) << "All current ice-server addresses have failed - re-attempting all current addresses for"
|
||||
<< _iceServerAddr;
|
||||
|
||||
_failedIceServerAddresses.clear();
|
||||
|
@ -3416,7 +3413,7 @@ void DomainServer::randomizeICEServerAddress(bool shouldTriggerHostLookup) {
|
|||
}
|
||||
|
||||
_iceServerSocket = HifiSockAddr { candidateICEAddresses[indexToTry], ICE_SERVER_DEFAULT_PORT };
|
||||
qInfo() << "Set candidate ice-server socket to" << _iceServerSocket;
|
||||
qCInfo(domain_server_ice) << "Set candidate ice-server socket to" << _iceServerSocket;
|
||||
|
||||
// clear our number of hearbeat denials, this should be re-set on ice-server change
|
||||
_numHeartbeatDenials = 0;
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(domain_server)
|
||||
Q_DECLARE_LOGGING_CATEGORY(domain_server_ice)
|
||||
|
||||
typedef QSharedPointer<Assignment> SharedAssignmentPointer;
|
||||
typedef QMultiHash<QUuid, WalletTransaction*> TransactionHash;
|
||||
|
|
|
@ -441,6 +441,12 @@ void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilena
|
|||
}
|
||||
}
|
||||
|
||||
if (oldVersion < 2.3) {
|
||||
unpackPermissions();
|
||||
_standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canGetAndSetPrivateUserData);
|
||||
packPermissions();
|
||||
}
|
||||
|
||||
|
||||
// write the current description version to our settings
|
||||
*versionVariant = _descriptionVersion;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import time
|
||||
|
@ -7,6 +9,15 @@ try:
|
|||
except ImportError:
|
||||
fcntl = None
|
||||
|
||||
try:
|
||||
import msvcrt
|
||||
except ImportError:
|
||||
msvcrt = None
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Used to ensure only one instance of the script runs at a time
|
||||
class Singleton:
|
||||
def __init__(self, path):
|
||||
|
@ -33,7 +44,10 @@ class Singleton:
|
|||
else:
|
||||
self.fh.close()
|
||||
self.fh = None
|
||||
print("Couldn't aquire lock, retrying in 10 seconds")
|
||||
# print is horked here so write directly to stdout.
|
||||
with open(1, mode="w", closefd=False) as _stdout:
|
||||
_stdout.write("Couldn't aquire lock, retrying in 10 seconds\n")
|
||||
_stdout.flush()
|
||||
time.sleep(10)
|
||||
return self
|
||||
|
||||
|
@ -43,4 +57,104 @@ class Singleton:
|
|||
else:
|
||||
fcntl.lockf(self.fh, fcntl.LOCK_UN)
|
||||
self.fh.close()
|
||||
os.unlink(self.path)
|
||||
os.unlink(self.path)
|
||||
|
||||
|
||||
class FLock:
|
||||
"""
|
||||
File locking context manager
|
||||
|
||||
>> with FLock("/tmp/foo.lock"):
|
||||
>> do_something_that_must_be_synced()
|
||||
|
||||
The lock file must stick around forever. The author is not aware of a no cross platform way to clean it up w/o introducting race conditions.
|
||||
"""
|
||||
def __init__(self, path):
|
||||
self.fh = os.open(path, os.O_CREAT | os.O_RDWR)
|
||||
self.path = path
|
||||
|
||||
def _lock_posix(self):
|
||||
try:
|
||||
fcntl.lockf(self.fh, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except BlockingIOError:
|
||||
# Windows sleeps for 10 seconds before giving up on a lock.
|
||||
# Lets mimic that behavior.
|
||||
time.sleep(10)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def _lock_windows(self):
|
||||
try:
|
||||
msvcrt.locking(self.fh, msvcrt.LK_LOCK, 1)
|
||||
except OSError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
if fcntl is not None:
|
||||
_lock = _lock_posix
|
||||
elif msvcrt is not None:
|
||||
_lock = _lock_windows
|
||||
else:
|
||||
raise RuntimeError("No locking library found")
|
||||
|
||||
def read_stats(self):
|
||||
data = {}
|
||||
with open(self.fh, mode="r", closefd=False) as stats_file:
|
||||
stats_file.seek(0)
|
||||
try:
|
||||
data = json.loads(stats_file.read())
|
||||
except json.decoder.JSONDecodeError:
|
||||
logger.warning("couldn't decode json in lock file")
|
||||
except PermissionError:
|
||||
# Can't read a locked file on Windows :(
|
||||
pass
|
||||
|
||||
lock_age = time.time() - os.fstat(self.fh).st_mtime
|
||||
if lock_age > 0:
|
||||
data["Age"] = "%0.2f" % lock_age
|
||||
|
||||
with open(1, mode="w", closefd=False) as _stdout:
|
||||
_stdout.write("Lock stats:\n")
|
||||
for key, value in sorted(data.items()):
|
||||
_stdout.write("* %s: %s\n" % (key, value))
|
||||
_stdout.flush()
|
||||
|
||||
def write_stats(self):
|
||||
stats = {
|
||||
"Owner PID": os.getpid(),
|
||||
}
|
||||
flock_env_vars = os.getenv("FLOCK_ENV_VARS")
|
||||
if flock_env_vars:
|
||||
for env_var_name in flock_env_vars.split(":"):
|
||||
stats[env_var_name] = os.getenv(env_var_name)
|
||||
|
||||
with open(self.fh, mode="w", closefd=False) as stats_file:
|
||||
stats_file.truncate()
|
||||
return stats_file.write(json.dumps(stats, indent=2))
|
||||
|
||||
def __enter__(self):
|
||||
while not self._lock():
|
||||
try:
|
||||
self.read_stats()
|
||||
except (IOError, ValueError) as exc:
|
||||
logger.exception("couldn't read stats")
|
||||
time.sleep(3.33) # don't hammer the file
|
||||
|
||||
self.write_stats()
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
os.close(self.fh)
|
||||
# WARNING: `os.close` gives up the lock on `fh` then we attempt the `os.unlink`. On posix platforms this can lead to us deleting a lock file that another process owns. This step is required to maintain compatablity with Singleton. When and if FLock is completely rolled out to the build fleet this unlink should be removed.
|
||||
try:
|
||||
os.unlink(self.path)
|
||||
except (FileNotFoundError, PermissionError):
|
||||
logger.exception("couldn't unlink lock file")
|
||||
|
||||
|
||||
if os.getenv("USE_FLOCK_CLS") is not None:
|
||||
logger.warning("Using FLock locker")
|
||||
Singleton = FLock
|
||||
|
|
|
@ -245,11 +245,8 @@
|
|||
#include "webbrowser/WebBrowserSuggestionsEngine.h"
|
||||
#include <DesktopPreviewProvider.h>
|
||||
|
||||
|
||||
#include "AboutUtil.h"
|
||||
|
||||
#include <DisableDeferred.h>
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <VersionHelpers.h>
|
||||
|
||||
|
@ -331,6 +328,7 @@ static const unsigned int THROTTLED_SIM_FRAMERATE = 15;
|
|||
static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SIM_FRAMERATE;
|
||||
static const int ENTITY_SERVER_ADDED_TIMEOUT = 5000;
|
||||
static const int ENTITY_SERVER_CONNECTION_TIMEOUT = 5000;
|
||||
static const int WATCHDOG_TIMER_TIMEOUT = 100;
|
||||
|
||||
static const float INITIAL_QUERY_RADIUS = 10.0f; // priority radius for entities before physics enabled
|
||||
|
||||
|
@ -443,6 +441,7 @@ public:
|
|||
auto elapsedMovingAverage = _movingAverage.getAverage();
|
||||
|
||||
if (elapsedMovingAverage > _maxElapsedAverage) {
|
||||
#if !defined(NDEBUG)
|
||||
qCDebug(interfaceapp_deadlock) << "DEADLOCK WATCHDOG WARNING:"
|
||||
<< "lastHeartbeatAge:" << lastHeartbeatAge
|
||||
<< "elapsedMovingAverage:" << elapsedMovingAverage
|
||||
|
@ -450,9 +449,11 @@ public:
|
|||
<< "PREVIOUS maxElapsedAverage:" << _maxElapsedAverage
|
||||
<< "NEW maxElapsedAverage:" << elapsedMovingAverage << "** NEW MAX ELAPSED AVERAGE **"
|
||||
<< "samples:" << _movingAverage.getSamples();
|
||||
#endif
|
||||
_maxElapsedAverage = elapsedMovingAverage;
|
||||
}
|
||||
if (lastHeartbeatAge > _maxElapsed) {
|
||||
#if !defined(NDEBUG)
|
||||
qCDebug(interfaceapp_deadlock) << "DEADLOCK WATCHDOG WARNING:"
|
||||
<< "lastHeartbeatAge:" << lastHeartbeatAge
|
||||
<< "elapsedMovingAverage:" << elapsedMovingAverage
|
||||
|
@ -460,8 +461,11 @@ public:
|
|||
<< "NEW maxElapsed:" << lastHeartbeatAge << "** NEW MAX ELAPSED **"
|
||||
<< "maxElapsedAverage:" << _maxElapsedAverage
|
||||
<< "samples:" << _movingAverage.getSamples();
|
||||
#endif
|
||||
_maxElapsed = lastHeartbeatAge;
|
||||
}
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
if (elapsedMovingAverage > WARNING_ELAPSED_HEARTBEAT) {
|
||||
qCDebug(interfaceapp_deadlock) << "DEADLOCK WATCHDOG WARNING:"
|
||||
<< "lastHeartbeatAge:" << lastHeartbeatAge
|
||||
|
@ -470,6 +474,7 @@ public:
|
|||
<< "maxElapsedAverage:" << _maxElapsedAverage
|
||||
<< "samples:" << _movingAverage.getSamples();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (lastHeartbeatAge > MAX_HEARTBEAT_AGE_USECS) {
|
||||
qCDebug(interfaceapp_deadlock) << "DEADLOCK DETECTED -- "
|
||||
|
@ -987,6 +992,7 @@ const bool DEFAULT_PREFER_STYLUS_OVER_LASER = false;
|
|||
const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false;
|
||||
const QString DEFAULT_CURSOR_NAME = "DEFAULT";
|
||||
const bool DEFAULT_MINI_TABLET_ENABLED = true;
|
||||
const bool DEFAULT_AWAY_STATE_WHEN_FOCUS_LOST_IN_VR_ENABLED = true;
|
||||
|
||||
QSharedPointer<OffscreenUi> getOffscreenUI() {
|
||||
#if !defined(DISABLE_QML)
|
||||
|
@ -1017,6 +1023,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_preferStylusOverLaserSetting("preferStylusOverLaser", DEFAULT_PREFER_STYLUS_OVER_LASER),
|
||||
_preferAvatarFingerOverStylusSetting("preferAvatarFingerOverStylus", DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS),
|
||||
_constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true),
|
||||
_awayStateWhenFocusLostInVREnabled("awayStateWhenFocusLostInVREnabled", DEFAULT_AWAY_STATE_WHEN_FOCUS_LOST_IN_VR_ENABLED),
|
||||
_preferredCursor("preferredCursor", DEFAULT_CURSOR_NAME),
|
||||
_miniTabletEnabledSetting("miniTabletEnabled", DEFAULT_MINI_TABLET_ENABLED),
|
||||
_scaleMirror(1.0f),
|
||||
|
@ -1069,6 +1076,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
// identify gpu as early as possible to help identify OpenGL initialization errors.
|
||||
auto gpuIdent = GPUIdent::getInstance();
|
||||
setCrashAnnotation("gpu_name", gpuIdent->getName().toStdString());
|
||||
setCrashAnnotation("gpu_driver", gpuIdent->getDriver().toStdString());
|
||||
setCrashAnnotation("gpu_memory", std::to_string(gpuIdent->getMemory()));
|
||||
}
|
||||
|
||||
// make sure the debug draw singleton is initialized on the main thread.
|
||||
DebugDraw::getInstance().removeMarker("");
|
||||
|
||||
|
@ -1130,6 +1145,18 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
auto deadlockWatchdogThread = new DeadlockWatchdogThread();
|
||||
deadlockWatchdogThread->setMainThreadID(QThread::currentThreadId());
|
||||
deadlockWatchdogThread->start();
|
||||
|
||||
|
||||
// Main thread timer to keep the watchdog updated
|
||||
QTimer* watchdogUpdateTimer = new QTimer(this);
|
||||
connect(watchdogUpdateTimer, &QTimer::timeout, [this] { updateHeartbeat(); });
|
||||
connect(this, &QCoreApplication::aboutToQuit, [watchdogUpdateTimer] {
|
||||
watchdogUpdateTimer->stop();
|
||||
watchdogUpdateTimer->deleteLater();
|
||||
});
|
||||
watchdogUpdateTimer->setSingleShot(false);
|
||||
watchdogUpdateTimer->setInterval(WATCHDOG_TIMER_TIMEOUT); // 100ms, Qt::CoarseTimer acceptable
|
||||
watchdogUpdateTimer->start();
|
||||
}
|
||||
|
||||
// Set File Logger Session UUID
|
||||
|
@ -1280,8 +1307,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
|
||||
|
||||
// you might think we could just do this in NodeList but we only want this connection for Interface
|
||||
connect(&nodeList->getDomainHandler(), SIGNAL(limitOfSilentDomainCheckInsReached()),
|
||||
nodeList.data(), SLOT(reset()));
|
||||
connect(&nodeList->getDomainHandler(), &DomainHandler::limitOfSilentDomainCheckInsReached,
|
||||
nodeList.data(), [nodeList]() {nodeList->reset("Domain checkin limit"); });
|
||||
|
||||
auto dialogsManager = DependencyManager::get<DialogsManager>();
|
||||
#if defined(Q_OS_ANDROID)
|
||||
|
@ -2688,7 +2715,7 @@ void Application::cleanupBeforeQuit() {
|
|||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// send the domain a disconnect packet, force stoppage of domain-server check-ins
|
||||
nodeList->getDomainHandler().disconnect();
|
||||
nodeList->getDomainHandler().disconnect("Quitting");
|
||||
nodeList->setIsShuttingDown(true);
|
||||
|
||||
// tell the packet receiver we're shutting down, so it can drop packets
|
||||
|
@ -2782,21 +2809,15 @@ Application::~Application() {
|
|||
// remove avatars from physics engine
|
||||
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||
avatarManager->clearOtherAvatars();
|
||||
auto myCharacterController = getMyAvatar()->getCharacterController();
|
||||
myCharacterController->clearDetailedMotionStates();
|
||||
|
||||
PhysicsEngine::Transaction transaction;
|
||||
avatarManager->buildPhysicsTransaction(transaction);
|
||||
_physicsEngine->processTransaction(transaction);
|
||||
avatarManager->handleProcessedPhysicsTransaction(transaction);
|
||||
|
||||
avatarManager->deleteAllAvatars();
|
||||
|
||||
auto myCharacterController = getMyAvatar()->getCharacterController();
|
||||
myCharacterController->clearDetailedMotionStates();
|
||||
|
||||
myCharacterController->buildPhysicsTransaction(transaction);
|
||||
_physicsEngine->processTransaction(transaction);
|
||||
myCharacterController->handleProcessedPhysicsTransaction(transaction);
|
||||
|
||||
_physicsEngine->setCharacterController(nullptr);
|
||||
|
||||
// the _shapeManager should have zero references
|
||||
|
@ -3015,7 +3036,7 @@ void Application::initializeDisplayPlugins() {
|
|||
void Application::initializeRenderEngine() {
|
||||
// FIXME: on low end systems os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread.
|
||||
DeadlockWatchdogThread::withPause([&] {
|
||||
_graphicsEngine.initializeRender(DISABLE_DEFERRED);
|
||||
_graphicsEngine.initializeRender();
|
||||
DependencyManager::get<Keyboard>()->registerKeyboardHighlighting();
|
||||
});
|
||||
}
|
||||
|
@ -3630,6 +3651,11 @@ void Application::setSettingConstrainToolbarPosition(bool setting) {
|
|||
getOffscreenUI()->setConstrainToolbarToCenterX(setting);
|
||||
}
|
||||
|
||||
void Application::setAwayStateWhenFocusLostInVREnabled(bool enabled) {
|
||||
_awayStateWhenFocusLostInVREnabled.set(enabled);
|
||||
emit awayStateWhenFocusLostInVRChanged(enabled);
|
||||
}
|
||||
|
||||
void Application::setMiniTabletEnabled(bool enabled) {
|
||||
_miniTabletEnabledSetting.set(enabled);
|
||||
emit miniTabletEnabledChanged(enabled);
|
||||
|
@ -4988,9 +5014,6 @@ void setupCpuMonitorThread() {
|
|||
void Application::idle() {
|
||||
PerformanceTimer perfTimer("idle");
|
||||
|
||||
// Update the deadlock watchdog
|
||||
updateHeartbeat();
|
||||
|
||||
#if !defined(DISABLE_QML)
|
||||
auto offscreenUi = getOffscreenUI();
|
||||
|
||||
|
@ -5537,7 +5560,7 @@ void Application::pauseUntilLoginDetermined() {
|
|||
cameraModeChanged();
|
||||
|
||||
// disconnect domain handler.
|
||||
nodeList->getDomainHandler().disconnect();
|
||||
nodeList->getDomainHandler().disconnect("Pause until login determined");
|
||||
|
||||
// From now on, it's permissible to call resumeAfterLoginDialogActionTaken()
|
||||
_resumeAfterLoginDialogActionTaken_SafeToRun = true;
|
||||
|
@ -5917,7 +5940,7 @@ void Application::reloadResourceCaches() {
|
|||
DependencyManager::get<TextureCache>()->refreshAll();
|
||||
DependencyManager::get<recording::ClipCache>()->refreshAll();
|
||||
|
||||
DependencyManager::get<NodeList>()->reset(); // Force redownload of .fst models
|
||||
DependencyManager::get<NodeList>()->reset("Reloading resources"); // Force redownload of .fst models
|
||||
|
||||
DependencyManager::get<ScriptEngines>()->reloadAllScripts();
|
||||
getOffscreenUI()->clearCache();
|
||||
|
@ -6406,64 +6429,42 @@ void Application::update(float deltaTime) {
|
|||
PROFILE_RANGE(simulation_physics, "Simulation");
|
||||
PerformanceTimer perfTimer("simulation");
|
||||
|
||||
if (_physicsEnabled) {
|
||||
auto t0 = std::chrono::high_resolution_clock::now();
|
||||
auto t1 = t0;
|
||||
getEntities()->preUpdate();
|
||||
|
||||
auto t0 = std::chrono::high_resolution_clock::now();
|
||||
auto t1 = t0;
|
||||
{
|
||||
PROFILE_RANGE(simulation_physics, "PrePhysics");
|
||||
PerformanceTimer perfTimer("prePhysics)");
|
||||
{
|
||||
PROFILE_RANGE(simulation_physics, "PrePhysics");
|
||||
PerformanceTimer perfTimer("prePhysics)");
|
||||
{
|
||||
PROFILE_RANGE(simulation_physics, "RemoveEntities");
|
||||
const VectorOfMotionStates& motionStates = _entitySimulation->getObjectsToRemoveFromPhysics();
|
||||
{
|
||||
PROFILE_RANGE_EX(simulation_physics, "NumObjs", 0xffff0000, (uint64_t)motionStates.size());
|
||||
_physicsEngine->removeObjects(motionStates);
|
||||
}
|
||||
_entitySimulation->deleteObjectsRemovedFromPhysics();
|
||||
}
|
||||
PROFILE_RANGE(simulation_physics, "Entities");
|
||||
PhysicsEngine::Transaction transaction;
|
||||
_entitySimulation->buildPhysicsTransaction(transaction);
|
||||
_physicsEngine->processTransaction(transaction);
|
||||
_entitySimulation->handleProcessedPhysicsTransaction(transaction);
|
||||
}
|
||||
|
||||
{
|
||||
PROFILE_RANGE(simulation_physics, "AddEntities");
|
||||
VectorOfMotionStates motionStates;
|
||||
getEntities()->getTree()->withReadLock([&] {
|
||||
_entitySimulation->getObjectsToAddToPhysics(motionStates);
|
||||
PROFILE_RANGE_EX(simulation_physics, "NumObjs", 0xffff0000, (uint64_t)motionStates.size());
|
||||
_physicsEngine->addObjects(motionStates);
|
||||
});
|
||||
}
|
||||
{
|
||||
VectorOfMotionStates motionStates;
|
||||
PROFILE_RANGE(simulation_physics, "ChangeEntities");
|
||||
getEntities()->getTree()->withReadLock([&] {
|
||||
_entitySimulation->getObjectsToChange(motionStates);
|
||||
VectorOfMotionStates stillNeedChange = _physicsEngine->changeObjects(motionStates);
|
||||
_entitySimulation->setObjectsToChange(stillNeedChange);
|
||||
});
|
||||
}
|
||||
t1 = std::chrono::high_resolution_clock::now();
|
||||
|
||||
{
|
||||
PROFILE_RANGE(simulation_physics, "Avatars");
|
||||
PhysicsEngine::Transaction transaction;
|
||||
avatarManager->buildPhysicsTransaction(transaction);
|
||||
_physicsEngine->processTransaction(transaction);
|
||||
avatarManager->handleProcessedPhysicsTransaction(transaction);
|
||||
|
||||
myAvatar->prepareForPhysicsSimulation();
|
||||
_physicsEngine->enableGlobalContactAddedCallback(myAvatar->isFlying());
|
||||
}
|
||||
}
|
||||
|
||||
if (_physicsEnabled) {
|
||||
{
|
||||
PROFILE_RANGE(simulation_physics, "PrepareActions");
|
||||
_entitySimulation->applyDynamicChanges();
|
||||
|
||||
t1 = std::chrono::high_resolution_clock::now();
|
||||
|
||||
{
|
||||
PROFILE_RANGE(simulation_physics, "Avatars");
|
||||
PhysicsEngine::Transaction transaction;
|
||||
avatarManager->buildPhysicsTransaction(transaction);
|
||||
_physicsEngine->processTransaction(transaction);
|
||||
avatarManager->handleProcessedPhysicsTransaction(transaction);
|
||||
myAvatar->getCharacterController()->buildPhysicsTransaction(transaction);
|
||||
_physicsEngine->processTransaction(transaction);
|
||||
myAvatar->getCharacterController()->handleProcessedPhysicsTransaction(transaction);
|
||||
myAvatar->prepareForPhysicsSimulation();
|
||||
_physicsEngine->enableGlobalContactAddedCallback(myAvatar->isFlying());
|
||||
}
|
||||
|
||||
{
|
||||
PROFILE_RANGE(simulation_physics, "PrepareActions");
|
||||
_physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
|
||||
dynamic->prepareForPhysicsSimulation();
|
||||
});
|
||||
}
|
||||
_physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
|
||||
dynamic->prepareForPhysicsSimulation();
|
||||
});
|
||||
}
|
||||
auto t2 = std::chrono::high_resolution_clock::now();
|
||||
{
|
||||
|
@ -6717,7 +6718,7 @@ void Application::updateRenderArgs(float deltaTime) {
|
|||
}
|
||||
appRenderArgs._renderArgs = RenderArgs(_graphicsEngine.getGPUContext(), lodManager->getOctreeSizeScale(),
|
||||
lodManager->getBoundaryLevelAdjust(), lodManager->getLODAngleHalfTan(), RenderArgs::DEFAULT_RENDER_MODE,
|
||||
RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE);
|
||||
RenderArgs::MONO, RenderArgs::DEFERRED, RenderArgs::RENDER_DEBUG_NONE);
|
||||
appRenderArgs._renderArgs._scene = getMain3DScene();
|
||||
|
||||
{
|
||||
|
@ -9390,7 +9391,7 @@ void Application::showUrlHandler(const QUrl& url) {
|
|||
void Application::beforeEnterBackground() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->setSendDomainServerCheckInEnabled(false);
|
||||
nodeList->reset(true);
|
||||
nodeList->reset("Entering background", true);
|
||||
clearDomainOctreeDetails();
|
||||
}
|
||||
|
||||
|
|
|
@ -211,6 +211,8 @@ public:
|
|||
float getNumCollisionObjects() const;
|
||||
float getTargetRenderFrameRate() const; // frames/second
|
||||
|
||||
static void setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties);
|
||||
|
||||
float getFieldOfView() { return _fieldOfView.get(); }
|
||||
void setFieldOfView(float fov);
|
||||
|
||||
|
@ -239,6 +241,9 @@ public:
|
|||
float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); }
|
||||
void setSettingConstrainToolbarPosition(bool setting);
|
||||
|
||||
float getAwayStateWhenFocusLostInVREnabled() { return _awayStateWhenFocusLostInVREnabled.get(); }
|
||||
void setAwayStateWhenFocusLostInVREnabled(bool setting);
|
||||
|
||||
Q_INVOKABLE void setMinimumGPUTextureMemStabilityCount(int stabilityCount) { _minimumGPUTextureMemSizeStabilityCount = stabilityCount; }
|
||||
|
||||
NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; }
|
||||
|
@ -369,6 +374,7 @@ signals:
|
|||
void loginDialogFocusDisabled();
|
||||
|
||||
void miniTabletEnabledChanged(bool enabled);
|
||||
void awayStateWhenFocusLostInVRChanged(bool enabled);
|
||||
|
||||
public slots:
|
||||
QVector<EntityItemID> pasteEntities(float x, float y, float z);
|
||||
|
@ -604,7 +610,6 @@ private:
|
|||
void maybeToggleMenuVisible(QMouseEvent* event) const;
|
||||
void toggleTabletUI(bool shouldOpen = false) const;
|
||||
|
||||
static void setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties);
|
||||
void userKickConfirmation(const QUuid& nodeID);
|
||||
|
||||
MainWindow* _window;
|
||||
|
@ -672,6 +677,7 @@ private:
|
|||
Setting::Handle<bool> _preferStylusOverLaserSetting;
|
||||
Setting::Handle<bool> _preferAvatarFingerOverStylusSetting;
|
||||
Setting::Handle<bool> _constrainToolbarPosition;
|
||||
Setting::Handle<bool> _awayStateWhenFocusLostInVREnabled;
|
||||
Setting::Handle<QString> _preferredCursor;
|
||||
Setting::Handle<bool> _miniTabletEnabledSetting;
|
||||
Setting::Handle<bool> _keepLogWindowOnTop { "keepLogWindowOnTop", false };
|
||||
|
|
|
@ -20,6 +20,7 @@ class FancyCamera : public Camera {
|
|||
|
||||
/**jsdoc
|
||||
* The <code>Camera</code> API provides access to the "camera" that defines your view in desktop and HMD display modes.
|
||||
* The High Fidelity camera has axes <code>x</code> = right, <code>y</code> = up, </code>-z</code> = forward.
|
||||
*
|
||||
* @namespace Camera
|
||||
*
|
||||
|
|
|
@ -272,10 +272,10 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) {
|
||||
void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor) {
|
||||
const auto cachedArg = task.addJob<SecondaryCameraJob>("SecondaryCamera");
|
||||
|
||||
task.addJob<RenderViewTask>("RenderSecondView", cullFunctor, isDeferred, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1);
|
||||
task.addJob<RenderViewTask>("RenderSecondView", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1);
|
||||
|
||||
task.addJob<EndSecondaryCameraFrame>("EndSecondaryCamera", cachedArg);
|
||||
}
|
|
@ -65,7 +65,7 @@ public:
|
|||
using JobModel = render::Task::Model<SecondaryCameraRenderTask, Config>;
|
||||
SecondaryCameraRenderTask() {}
|
||||
void configure(const Config& config) {}
|
||||
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred = true);
|
||||
void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -101,7 +101,7 @@ AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWe
|
|||
}
|
||||
|
||||
AvatarManager::~AvatarManager() {
|
||||
assert(_avatarsToChangeInPhysics.empty());
|
||||
assert(_otherAvatarsToChangeInPhysics.empty());
|
||||
}
|
||||
|
||||
void AvatarManager::init() {
|
||||
|
@ -295,7 +295,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
|
||||
render::Transaction renderTransaction;
|
||||
workload::Transaction workloadTransaction;
|
||||
|
||||
|
||||
for (int p = kHero; p < NumVariants; p++) {
|
||||
auto& priorityQueue = avatarPriorityQueues[p];
|
||||
// Sorting the current queue HERE as part of the measured timing.
|
||||
|
@ -314,7 +314,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
// remove the orb if it is there
|
||||
avatar->removeOrb();
|
||||
if (avatar->needsPhysicsUpdate()) {
|
||||
_avatarsToChangeInPhysics.insert(avatar);
|
||||
_otherAvatarsToChangeInPhysics.insert(avatar);
|
||||
}
|
||||
} else {
|
||||
avatar->updateOrbPosition();
|
||||
|
@ -419,69 +419,111 @@ AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) {
|
|||
}
|
||||
|
||||
void AvatarManager::queuePhysicsChange(const OtherAvatarPointer& avatar) {
|
||||
_avatarsToChangeInPhysics.insert(avatar);
|
||||
_otherAvatarsToChangeInPhysics.insert(avatar);
|
||||
}
|
||||
|
||||
DetailedMotionState* AvatarManager::createDetailedMotionState(OtherAvatarPointer avatar, int32_t jointIndex) {
|
||||
bool isBound = false;
|
||||
std::vector<int32_t> boundJoints;
|
||||
const btCollisionShape* shape = avatar->createCollisionShape(jointIndex, isBound, boundJoints);
|
||||
if (shape) {
|
||||
DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, jointIndex);
|
||||
motionState->setMass(0.0f); // DetailedMotionState has KINEMATIC MotionType, so zero mass is ok
|
||||
motionState->setIsBound(isBound, boundJoints);
|
||||
return motionState;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AvatarManager::rebuildAvatarPhysics(PhysicsEngine::Transaction& transaction, OtherAvatarPointer avatar) {
|
||||
if (!avatar->_motionState) {
|
||||
avatar->_motionState = new AvatarMotionState(avatar, nullptr);
|
||||
}
|
||||
AvatarMotionState* motionState = avatar->_motionState;
|
||||
ShapeInfo shapeInfo;
|
||||
avatar->computeShapeInfo(shapeInfo);
|
||||
const btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
|
||||
assert(shape);
|
||||
motionState->setShape(shape);
|
||||
motionState->setMass(avatar->computeMass());
|
||||
if (motionState->getRigidBody()) {
|
||||
transaction.objectsToReinsert.push_back(motionState);
|
||||
} else {
|
||||
transaction.objectsToAdd.push_back(motionState);
|
||||
}
|
||||
motionState->clearIncomingDirtyFlags();
|
||||
|
||||
// Rather than reconcile numbers of joints after change to model or LOD
|
||||
// we blow away old detailedMotionStates and create anew all around.
|
||||
|
||||
// delete old detailedMotionStates
|
||||
auto& detailedMotionStates = avatar->getDetailedMotionStates();
|
||||
if (detailedMotionStates.size() != 0) {
|
||||
for (auto& detailedMotionState : detailedMotionStates) {
|
||||
transaction.objectsToRemove.push_back(detailedMotionState);
|
||||
}
|
||||
avatar->resetDetailedMotionStates();
|
||||
}
|
||||
|
||||
// build new detailedMotionStates
|
||||
OtherAvatar::BodyLOD lod = avatar->getBodyLOD();
|
||||
if (lod == OtherAvatar::BodyLOD::Sphere) {
|
||||
auto dMotionState = createDetailedMotionState(avatar, -1);
|
||||
if (dMotionState) {
|
||||
detailedMotionStates.push_back(dMotionState);
|
||||
transaction.objectsToAdd.push_back(dMotionState);
|
||||
}
|
||||
} else {
|
||||
int32_t numJoints = avatar->getJointCount();
|
||||
for (int32_t i = 0; i < numJoints; i++) {
|
||||
auto dMotionState = createDetailedMotionState(avatar, i);
|
||||
if (dMotionState) {
|
||||
detailedMotionStates.push_back(dMotionState);
|
||||
transaction.objectsToAdd.push_back(dMotionState);
|
||||
}
|
||||
}
|
||||
}
|
||||
avatar->_needsReinsertion = false;
|
||||
}
|
||||
|
||||
void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) {
|
||||
SetOfOtherAvatars failedShapeBuilds;
|
||||
for (auto avatar : _avatarsToChangeInPhysics) {
|
||||
_myAvatar->getCharacterController()->buildPhysicsTransaction(transaction);
|
||||
for (auto avatar : _otherAvatarsToChangeInPhysics) {
|
||||
bool isInPhysics = avatar->isInPhysicsSimulation();
|
||||
if (isInPhysics != avatar->shouldBeInPhysicsSimulation()) {
|
||||
if (isInPhysics) {
|
||||
transaction.objectsToRemove.push_back(avatar->_motionState);
|
||||
avatar->_motionState = nullptr;
|
||||
auto& detailedMotionStates = avatar->getDetailedMotionStates();
|
||||
for (auto& mState : detailedMotionStates) {
|
||||
transaction.objectsToRemove.push_back(mState);
|
||||
for (auto& motionState : detailedMotionStates) {
|
||||
transaction.objectsToRemove.push_back(motionState);
|
||||
}
|
||||
avatar->resetDetailedMotionStates();
|
||||
} else {
|
||||
if (avatar->getDetailedMotionStates().size() == 0) {
|
||||
avatar->createDetailedMotionStates(avatar);
|
||||
for (auto dMotionState : avatar->getDetailedMotionStates()) {
|
||||
transaction.objectsToAdd.push_back(dMotionState);
|
||||
}
|
||||
}
|
||||
if (avatar->getDetailedMotionStates().size() > 0) {
|
||||
ShapeInfo shapeInfo;
|
||||
avatar->computeShapeInfo(shapeInfo);
|
||||
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
|
||||
if (shape) {
|
||||
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);
|
||||
motionState->setMass(avatar->computeMass());
|
||||
avatar->_motionState = motionState;
|
||||
transaction.objectsToAdd.push_back(motionState);
|
||||
} else {
|
||||
failedShapeBuilds.insert(avatar);
|
||||
}
|
||||
} else {
|
||||
failedShapeBuilds.insert(avatar);
|
||||
}
|
||||
rebuildAvatarPhysics(transaction, avatar);
|
||||
}
|
||||
} else if (isInPhysics) {
|
||||
transaction.objectsToChange.push_back(avatar->_motionState);
|
||||
|
||||
auto& detailedMotionStates = avatar->getDetailedMotionStates();
|
||||
for (auto& mState : detailedMotionStates) {
|
||||
if (mState) {
|
||||
transaction.objectsToChange.push_back(mState);
|
||||
}
|
||||
AvatarMotionState* motionState = avatar->_motionState;
|
||||
uint32_t flags = motionState->getIncomingDirtyFlags();
|
||||
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
|
||||
motionState->handleEasyChanges(flags);
|
||||
}
|
||||
// NOTE: we don't call detailedMotionState->handleEasyChanges() here because they are KINEMATIC
|
||||
// and Bullet will automagically call DetailedMotionState::getWorldTransform() on all that are active.
|
||||
|
||||
if (motionState->needsNewShape()) {
|
||||
rebuildAvatarPhysics(transaction, avatar);
|
||||
} else {
|
||||
if (flags & (Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_COLLISION_GROUP)) {
|
||||
transaction.objectsToReinsert.push_back(motionState);
|
||||
}
|
||||
motionState->clearIncomingDirtyFlags();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
_avatarsToChangeInPhysics.swap(failedShapeBuilds);
|
||||
}
|
||||
|
||||
void AvatarManager::handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction) {
|
||||
// things on objectsToChange correspond to failed changes
|
||||
// so we push them back onto _avatarsToChangeInPhysics
|
||||
for (auto object : transaction.objectsToChange) {
|
||||
AvatarMotionState* motionState = static_cast<AvatarMotionState*>(object);
|
||||
assert(motionState);
|
||||
assert(motionState->_avatar);
|
||||
_avatarsToChangeInPhysics.insert(motionState->_avatar);
|
||||
}
|
||||
// things on objectsToRemove are ready for delete
|
||||
for (auto object : transaction.objectsToRemove) {
|
||||
delete object;
|
||||
|
@ -490,13 +532,32 @@ void AvatarManager::handleProcessedPhysicsTransaction(PhysicsEngine::Transaction
|
|||
}
|
||||
|
||||
void AvatarManager::removeDeadAvatarEntities(const SetOfEntities& deadEntities) {
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
for (auto entity : deadEntities) {
|
||||
QUuid sessionID = entity->getOwningAvatarID();
|
||||
AvatarSharedPointer avatar = getAvatarBySessionID(sessionID);
|
||||
QUuid entityOwnerID = entity->getOwningAvatarID();
|
||||
AvatarSharedPointer avatar = getAvatarBySessionID(entityOwnerID);
|
||||
const bool REQUIRES_REMOVAL_FROM_TREE = false;
|
||||
if (avatar) {
|
||||
const bool REQUIRES_REMOVAL_FROM_TREE = false;
|
||||
avatar->clearAvatarEntity(entity->getID(), REQUIRES_REMOVAL_FROM_TREE);
|
||||
}
|
||||
if (entityTree && entity->isMyAvatarEntity()) {
|
||||
entityTree->withWriteLock([&] {
|
||||
// We only need to delete the direct children (rather than the descendants) because
|
||||
// when the child is deleted, it will take care of its own children. If the child
|
||||
// is also an avatar-entity, we'll end up back here. If it's not, the entity-server
|
||||
// will take care of it in the usual way.
|
||||
entity->forEachChild([&](SpatiallyNestablePointer child) {
|
||||
EntityItemPointer childEntity = std::dynamic_pointer_cast<EntityItem>(child);
|
||||
if (childEntity) {
|
||||
entityTree->deleteEntity(childEntity->getID(), true, true);
|
||||
if (avatar) {
|
||||
avatar->clearAvatarEntity(childEntity->getID(), REQUIRES_REMOVAL_FROM_TREE);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -536,14 +597,20 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
|
|||
|
||||
workload::SpacePointer space = _space;
|
||||
transaction.transitionFinishedOperator(avatar->getRenderItemID(), [space, avatar]() {
|
||||
const render::ScenePointer& scene = qApp->getMain3DScene();
|
||||
render::Transaction transaction;
|
||||
avatar->removeFromScene(avatar, scene, transaction);
|
||||
scene->enqueueTransaction(transaction);
|
||||
if (avatar->getLastFadeRequested() != render::Transition::Type::USER_LEAVE_DOMAIN) {
|
||||
// The avatar is using another transition besides the fade-out transition, which means it is still in use.
|
||||
// Deleting the avatar now could cause state issues, so abort deletion and show message.
|
||||
qCWarning(interfaceapp) << "An ending fade-out transition wants to delete an avatar, but the avatar is still in use. Avatar deletion has aborted. (avatar ID: " << avatar->getSessionUUID() << ")";
|
||||
} else {
|
||||
const render::ScenePointer& scene = qApp->getMain3DScene();
|
||||
render::Transaction transaction;
|
||||
avatar->removeFromScene(avatar, scene, transaction);
|
||||
scene->enqueueTransaction(transaction);
|
||||
|
||||
workload::Transaction workloadTransaction;
|
||||
workloadTransaction.remove(avatar->getSpaceIndex());
|
||||
space->enqueueTransaction(workloadTransaction);
|
||||
workload::Transaction workloadTransaction;
|
||||
workloadTransaction.remove(avatar->getSpaceIndex());
|
||||
space->enqueueTransaction(workloadTransaction);
|
||||
}
|
||||
});
|
||||
scene->enqueueTransaction(transaction);
|
||||
}
|
||||
|
@ -570,7 +637,7 @@ void AvatarManager::clearOtherAvatars() {
|
|||
++avatarIterator;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& av : removedAvatars) {
|
||||
handleRemovedAvatar(av);
|
||||
|
@ -578,7 +645,7 @@ void AvatarManager::clearOtherAvatars() {
|
|||
}
|
||||
|
||||
void AvatarManager::deleteAllAvatars() {
|
||||
assert(_avatarsToChangeInPhysics.empty());
|
||||
assert(_otherAvatarsToChangeInPhysics.empty());
|
||||
QReadLocker locker(&_hashLock);
|
||||
AvatarHash::iterator avatarIterator = _avatarHash.begin();
|
||||
while (avatarIterator != _avatarHash.end()) {
|
||||
|
@ -588,7 +655,7 @@ void AvatarManager::deleteAllAvatars() {
|
|||
if (avatar != _myAvatar) {
|
||||
auto otherAvatar = std::static_pointer_cast<OtherAvatar>(avatar);
|
||||
assert(!otherAvatar->_motionState);
|
||||
assert(otherAvatar->getDetailedMotionStates().size() == 0);
|
||||
assert(otherAvatar->getDetailedMotionStates().size() == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -273,6 +273,8 @@ public slots:
|
|||
|
||||
protected:
|
||||
AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) override;
|
||||
DetailedMotionState* createDetailedMotionState(OtherAvatarPointer avatar, int32_t jointIndex);
|
||||
void rebuildAvatarPhysics(PhysicsEngine::Transaction& transaction, OtherAvatarPointer avatar);
|
||||
|
||||
private:
|
||||
explicit AvatarManager(QObject* parent = 0);
|
||||
|
@ -288,7 +290,7 @@ private:
|
|||
void handleTransitAnimations(AvatarTransit::Status status);
|
||||
|
||||
using SetOfOtherAvatars = std::set<OtherAvatarPointer>;
|
||||
SetOfOtherAvatars _avatarsToChangeInPhysics;
|
||||
SetOfOtherAvatars _otherAvatarsToChangeInPhysics;
|
||||
|
||||
std::shared_ptr<MyAvatar> _myAvatar;
|
||||
quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate.
|
||||
|
|
|
@ -29,23 +29,19 @@ void AvatarMotionState::handleEasyChanges(uint32_t& flags) {
|
|||
}
|
||||
}
|
||||
|
||||
bool AvatarMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
|
||||
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
|
||||
}
|
||||
|
||||
AvatarMotionState::~AvatarMotionState() {
|
||||
assert(_avatar);
|
||||
_avatar = nullptr;
|
||||
}
|
||||
|
||||
// virtual
|
||||
uint32_t AvatarMotionState::getIncomingDirtyFlags() {
|
||||
uint32_t AvatarMotionState::getIncomingDirtyFlags() const {
|
||||
return _body ? _dirtyFlags : 0;
|
||||
}
|
||||
|
||||
void AvatarMotionState::clearIncomingDirtyFlags() {
|
||||
void AvatarMotionState::clearIncomingDirtyFlags(uint32_t mask) {
|
||||
if (_body) {
|
||||
_dirtyFlags = 0;
|
||||
_dirtyFlags &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,13 +50,6 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const {
|
|||
return MOTION_TYPE_DYNAMIC;
|
||||
}
|
||||
|
||||
// virtual and protected
|
||||
const btCollisionShape* AvatarMotionState::computeNewShape() {
|
||||
ShapeInfo shapeInfo;
|
||||
_avatar->computeShapeInfo(shapeInfo);
|
||||
return getShapeManager()->getShape(shapeInfo);
|
||||
}
|
||||
|
||||
// virtual
|
||||
bool AvatarMotionState::isMoving() const {
|
||||
return false;
|
||||
|
|
|
@ -23,44 +23,44 @@ class AvatarMotionState : public ObjectMotionState {
|
|||
public:
|
||||
AvatarMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape);
|
||||
|
||||
virtual void handleEasyChanges(uint32_t& flags) override;
|
||||
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
|
||||
void handleEasyChanges(uint32_t& flags) override;
|
||||
|
||||
virtual PhysicsMotionType getMotionType() const override { return _motionType; }
|
||||
PhysicsMotionType getMotionType() const override { return _motionType; }
|
||||
|
||||
virtual uint32_t getIncomingDirtyFlags() override;
|
||||
virtual void clearIncomingDirtyFlags() override;
|
||||
uint32_t getIncomingDirtyFlags() const override;
|
||||
void clearIncomingDirtyFlags(uint32_t mask = DIRTY_PHYSICS_FLAGS) override;
|
||||
|
||||
virtual PhysicsMotionType computePhysicsMotionType() const override;
|
||||
PhysicsMotionType computePhysicsMotionType() const override;
|
||||
|
||||
virtual bool isMoving() const override;
|
||||
bool isMoving() const override;
|
||||
|
||||
// this relays incoming position/rotation to the RigidBody
|
||||
virtual void getWorldTransform(btTransform& worldTrans) const override;
|
||||
void getWorldTransform(btTransform& worldTrans) const override;
|
||||
|
||||
// this relays outgoing position/rotation to the EntityItem
|
||||
virtual void setWorldTransform(const btTransform& worldTrans) override;
|
||||
void setWorldTransform(const btTransform& worldTrans) override;
|
||||
|
||||
|
||||
// These pure virtual methods must be implemented for each MotionState type
|
||||
// and make it possible to implement more complicated methods in this base class.
|
||||
|
||||
// pure virtual overrides from ObjectMotionState
|
||||
virtual float getObjectRestitution() const override;
|
||||
virtual float getObjectFriction() const override;
|
||||
virtual float getObjectLinearDamping() const override;
|
||||
virtual float getObjectAngularDamping() const override;
|
||||
float getObjectRestitution() const override;
|
||||
float getObjectFriction() const override;
|
||||
float getObjectLinearDamping() const override;
|
||||
float getObjectAngularDamping() const override;
|
||||
|
||||
virtual glm::vec3 getObjectPosition() const override;
|
||||
virtual glm::quat getObjectRotation() const override;
|
||||
virtual glm::vec3 getObjectLinearVelocity() const override;
|
||||
virtual glm::vec3 getObjectAngularVelocity() const override;
|
||||
virtual glm::vec3 getObjectGravity() const override;
|
||||
glm::vec3 getObjectPosition() const override;
|
||||
glm::quat getObjectRotation() const override;
|
||||
glm::vec3 getObjectLinearVelocity() const override;
|
||||
glm::vec3 getObjectAngularVelocity() const override;
|
||||
glm::vec3 getObjectGravity() const override;
|
||||
|
||||
virtual const QUuid getObjectID() const override;
|
||||
const QUuid getObjectID() const override;
|
||||
|
||||
virtual QString getName() const override;
|
||||
virtual QUuid getSimulatorID() const override;
|
||||
QString getName() const override;
|
||||
ShapeType getShapeType() const override { return SHAPE_TYPE_CAPSULE_Y; }
|
||||
QUuid getSimulatorID() const override;
|
||||
|
||||
void setBoundingBox(const glm::vec3& corner, const glm::vec3& diagonal);
|
||||
|
||||
|
@ -69,9 +69,9 @@ public:
|
|||
void setCollisionGroup(int32_t group) { _collisionGroup = group; }
|
||||
int32_t getCollisionGroup() { return _collisionGroup; }
|
||||
|
||||
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
|
||||
void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
|
||||
|
||||
virtual float getMass() const override;
|
||||
float getMass() const override;
|
||||
|
||||
friend class AvatarManager;
|
||||
friend class Avatar;
|
||||
|
@ -85,9 +85,6 @@ protected:
|
|||
// ever called by the Avatar class dtor.
|
||||
~AvatarMotionState();
|
||||
|
||||
virtual bool isReadyToComputeShape() const override { return true; }
|
||||
virtual const btCollisionShape* computeNewShape() override;
|
||||
|
||||
OtherAvatarPointer _avatar;
|
||||
float _diameter { 0.0f };
|
||||
int32_t _collisionGroup;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include "MyAvatar.h"
|
||||
|
||||
|
||||
DetailedMotionState::DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int jointIndex) :
|
||||
DetailedMotionState::DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int32_t jointIndex) :
|
||||
ObjectMotionState(shape), _avatar(avatar), _jointIndex(jointIndex) {
|
||||
assert(_avatar);
|
||||
if (!_avatar->isMyAvatar()) {
|
||||
|
@ -33,47 +33,26 @@ void DetailedMotionState::handleEasyChanges(uint32_t& flags) {
|
|||
}
|
||||
}
|
||||
|
||||
bool DetailedMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
|
||||
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
|
||||
}
|
||||
|
||||
DetailedMotionState::~DetailedMotionState() {
|
||||
assert(_avatar);
|
||||
_avatar = nullptr;
|
||||
}
|
||||
|
||||
// virtual
|
||||
uint32_t DetailedMotionState::getIncomingDirtyFlags() {
|
||||
uint32_t DetailedMotionState::getIncomingDirtyFlags() const {
|
||||
return _body ? _dirtyFlags : 0;
|
||||
}
|
||||
|
||||
void DetailedMotionState::clearIncomingDirtyFlags() {
|
||||
void DetailedMotionState::clearIncomingDirtyFlags(uint32_t mask) {
|
||||
if (_body) {
|
||||
_dirtyFlags = 0;
|
||||
_dirtyFlags &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
PhysicsMotionType DetailedMotionState::computePhysicsMotionType() const {
|
||||
// TODO?: support non-DYNAMIC motion for avatars? (e.g. when sitting)
|
||||
return MOTION_TYPE_KINEMATIC;
|
||||
}
|
||||
|
||||
// virtual and protected
|
||||
const btCollisionShape* DetailedMotionState::computeNewShape() {
|
||||
btCollisionShape* shape = nullptr;
|
||||
if (!_avatar->isMyAvatar()) {
|
||||
if (_otherAvatar != nullptr) {
|
||||
shape = _otherAvatar->createCollisionShape(_jointIndex, _isBound, _boundJoints);
|
||||
}
|
||||
} else {
|
||||
std::shared_ptr<MyAvatar> myAvatar = std::static_pointer_cast<MyAvatar>(_avatar);
|
||||
if (myAvatar) {
|
||||
shape = myAvatar->getCharacterController()->createDetailedCollisionShapeForJoint(_jointIndex);
|
||||
}
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
// virtual
|
||||
bool DetailedMotionState::isMoving() const {
|
||||
return false;
|
||||
|
@ -178,11 +157,23 @@ void DetailedMotionState::setRigidBody(btRigidBody* body) {
|
|||
}
|
||||
|
||||
void DetailedMotionState::setShape(const btCollisionShape* shape) {
|
||||
ObjectMotionState::setShape(shape);
|
||||
if (_shape != shape) {
|
||||
if (_shape) {
|
||||
getShapeManager()->releaseShape(_shape);
|
||||
}
|
||||
_shape = shape;
|
||||
if (_body) {
|
||||
assert(_shape);
|
||||
_body->setCollisionShape(const_cast<btCollisionShape*>(_shape));
|
||||
}
|
||||
} else if (shape) {
|
||||
// we need to release unused reference to shape
|
||||
getShapeManager()->releaseShape(shape);
|
||||
}
|
||||
}
|
||||
|
||||
void DetailedMotionState::forceActive() {
|
||||
if (_body && !_body->isActive()) {
|
||||
_body->setActivationState(ACTIVE_TAG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,55 +23,55 @@ class DetailedMotionState : public ObjectMotionState {
|
|||
public:
|
||||
DetailedMotionState(AvatarPointer avatar, const btCollisionShape* shape, int jointIndex);
|
||||
|
||||
virtual void handleEasyChanges(uint32_t& flags) override;
|
||||
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
|
||||
void handleEasyChanges(uint32_t& flags) override;
|
||||
|
||||
virtual PhysicsMotionType getMotionType() const override { return _motionType; }
|
||||
PhysicsMotionType getMotionType() const override { return _motionType; }
|
||||
|
||||
virtual uint32_t getIncomingDirtyFlags() override;
|
||||
virtual void clearIncomingDirtyFlags() override;
|
||||
uint32_t getIncomingDirtyFlags() const override;
|
||||
void clearIncomingDirtyFlags(uint32_t mask = DIRTY_PHYSICS_FLAGS) override;
|
||||
|
||||
virtual PhysicsMotionType computePhysicsMotionType() const override;
|
||||
PhysicsMotionType computePhysicsMotionType() const override;
|
||||
|
||||
virtual bool isMoving() const override;
|
||||
bool isMoving() const override;
|
||||
|
||||
// this relays incoming position/rotation to the RigidBody
|
||||
virtual void getWorldTransform(btTransform& worldTrans) const override;
|
||||
void getWorldTransform(btTransform& worldTrans) const override;
|
||||
|
||||
// this relays outgoing position/rotation to the EntityItem
|
||||
virtual void setWorldTransform(const btTransform& worldTrans) override;
|
||||
void setWorldTransform(const btTransform& worldTrans) override;
|
||||
|
||||
|
||||
// These pure virtual methods must be implemented for each MotionState type
|
||||
// and make it possible to implement more complicated methods in this base class.
|
||||
|
||||
// pure virtual overrides from ObjectMotionState
|
||||
virtual float getObjectRestitution() const override;
|
||||
virtual float getObjectFriction() const override;
|
||||
virtual float getObjectLinearDamping() const override;
|
||||
virtual float getObjectAngularDamping() const override;
|
||||
float getObjectRestitution() const override;
|
||||
float getObjectFriction() const override;
|
||||
float getObjectLinearDamping() const override;
|
||||
float getObjectAngularDamping() const override;
|
||||
|
||||
virtual glm::vec3 getObjectPosition() const override;
|
||||
virtual glm::quat getObjectRotation() const override;
|
||||
virtual glm::vec3 getObjectLinearVelocity() const override;
|
||||
virtual glm::vec3 getObjectAngularVelocity() const override;
|
||||
virtual glm::vec3 getObjectGravity() const override;
|
||||
glm::vec3 getObjectPosition() const override;
|
||||
glm::quat getObjectRotation() const override;
|
||||
glm::vec3 getObjectLinearVelocity() const override;
|
||||
glm::vec3 getObjectAngularVelocity() const override;
|
||||
glm::vec3 getObjectGravity() const override;
|
||||
|
||||
virtual const QUuid getObjectID() const override;
|
||||
const QUuid getObjectID() const override;
|
||||
|
||||
virtual QString getName() const override;
|
||||
virtual QUuid getSimulatorID() const override;
|
||||
QString getName() const override;
|
||||
ShapeType getShapeType() const override { return SHAPE_TYPE_HULL; }
|
||||
QUuid getSimulatorID() const override;
|
||||
|
||||
void addDirtyFlags(uint32_t flags) { _dirtyFlags |= flags; }
|
||||
|
||||
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
|
||||
void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
|
||||
|
||||
virtual float getMass() const override;
|
||||
float getMass() const override;
|
||||
void forceActive();
|
||||
QUuid getAvatarID() const { return _avatar->getID(); }
|
||||
int getJointIndex() const { return _jointIndex; }
|
||||
void setIsBound(bool isBound, std::vector<int> boundJoints) { _isBound = isBound; _boundJoints = boundJoints; }
|
||||
bool getIsBound(std::vector<int>& boundJoints) const { boundJoints = _boundJoints; return _isBound; }
|
||||
int32_t getJointIndex() const { return _jointIndex; }
|
||||
void setIsBound(bool isBound, const std::vector<int32_t>& boundJoints) { _isBound = isBound; _boundJoints = boundJoints; }
|
||||
bool getIsBound(std::vector<int32_t>& boundJoints) const { boundJoints = _boundJoints; return _isBound; }
|
||||
|
||||
friend class AvatarManager;
|
||||
friend class Avatar;
|
||||
|
@ -84,17 +84,14 @@ protected:
|
|||
// ever called by the Avatar class dtor.
|
||||
~DetailedMotionState();
|
||||
|
||||
virtual bool isReadyToComputeShape() const override { return true; }
|
||||
virtual const btCollisionShape* computeNewShape() override;
|
||||
|
||||
AvatarPointer _avatar;
|
||||
float _diameter { 0.0f };
|
||||
|
||||
uint32_t _dirtyFlags;
|
||||
int _jointIndex { -1 };
|
||||
int32_t _jointIndex { -1 };
|
||||
OtherAvatarPointer _otherAvatar { nullptr };
|
||||
bool _isBound { false };
|
||||
std::vector<int> _boundJoints;
|
||||
std::vector<int32_t> _boundJoints;
|
||||
};
|
||||
|
||||
#endif // hifi_DetailedMotionState_h
|
||||
|
|
|
@ -3755,6 +3755,7 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings
|
|||
void MyAvatar::leaveDomain() {
|
||||
clearScaleRestriction();
|
||||
saveAvatarScale();
|
||||
prepareResetTraitInstances();
|
||||
}
|
||||
|
||||
void MyAvatar::saveAvatarScale() {
|
||||
|
|
|
@ -377,21 +377,18 @@ void MyCharacterController::updateMassProperties() {
|
|||
_rigidBody->setMassProps(mass, inertia);
|
||||
}
|
||||
|
||||
btCollisionShape* MyCharacterController::createDetailedCollisionShapeForJoint(int jointIndex) {
|
||||
const btCollisionShape* MyCharacterController::createDetailedCollisionShapeForJoint(int32_t jointIndex) {
|
||||
ShapeInfo shapeInfo;
|
||||
_avatar->computeDetailedShapeInfo(shapeInfo, jointIndex);
|
||||
if (shapeInfo.getType() != SHAPE_TYPE_NONE) {
|
||||
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
|
||||
if (shape) {
|
||||
shape->setMargin(0.001f);
|
||||
}
|
||||
const btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
|
||||
return shape;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DetailedMotionState* MyCharacterController::createDetailedMotionStateForJoint(int jointIndex) {
|
||||
auto shape = createDetailedCollisionShapeForJoint(jointIndex);
|
||||
DetailedMotionState* MyCharacterController::createDetailedMotionStateForJoint(int32_t jointIndex) {
|
||||
const btCollisionShape* shape = createDetailedCollisionShapeForJoint(jointIndex);
|
||||
if (shape) {
|
||||
DetailedMotionState* motionState = new DetailedMotionState(_avatar, shape, jointIndex);
|
||||
motionState->setMass(_avatar->computeMass());
|
||||
|
@ -423,25 +420,16 @@ void MyCharacterController::buildPhysicsTransaction(PhysicsEngine::Transaction&
|
|||
}
|
||||
if (_pendingFlags & PENDING_FLAG_ADD_DETAILED_TO_SIMULATION) {
|
||||
_pendingFlags &= ~PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
|
||||
for (int i = 0; i < _avatar->getJointCount(); i++) {
|
||||
for (int32_t i = 0; i < _avatar->getJointCount(); i++) {
|
||||
auto dMotionState = createDetailedMotionStateForJoint(i);
|
||||
if (dMotionState) {
|
||||
_detailedMotionStates.push_back(dMotionState);
|
||||
transaction.objectsToAdd.push_back(dMotionState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyCharacterController::handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction) {
|
||||
// things on objectsToRemove are ready for delete
|
||||
for (auto object : transaction.objectsToRemove) {
|
||||
delete object;
|
||||
}
|
||||
transaction.clear();
|
||||
}
|
||||
|
||||
|
||||
class DetailedRayResultCallback : public btCollisionWorld::AllHitsRayResultCallback {
|
||||
public:
|
||||
DetailedRayResultCallback()
|
||||
|
@ -467,7 +455,7 @@ std::vector<MyCharacterController::RayAvatarResult> MyCharacterController::rayTe
|
|||
_dynamicsWorld->rayTest(origin, end, rayCallback);
|
||||
if (rayCallback.m_hitFractions.size() > 0) {
|
||||
foundAvatars.reserve(rayCallback.m_hitFractions.size());
|
||||
for (int i = 0; i < rayCallback.m_hitFractions.size(); i++) {
|
||||
for (int32_t i = 0; i < rayCallback.m_hitFractions.size(); i++) {
|
||||
auto object = rayCallback.m_collisionObjects[i];
|
||||
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(object->getUserPointer());
|
||||
if (motionState && motionState->getType() == MOTIONSTATE_TYPE_DETAILED) {
|
||||
|
@ -493,4 +481,4 @@ std::vector<MyCharacterController::RayAvatarResult> MyCharacterController::rayTe
|
|||
}
|
||||
}
|
||||
return foundAvatars;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,27 +44,25 @@ public:
|
|||
|
||||
void setDensity(btScalar density) { _density = density; }
|
||||
|
||||
btCollisionShape* createDetailedCollisionShapeForJoint(int jointIndex);
|
||||
DetailedMotionState* createDetailedMotionStateForJoint(int jointIndex);
|
||||
const btCollisionShape* createDetailedCollisionShapeForJoint(int32_t jointIndex);
|
||||
DetailedMotionState* createDetailedMotionStateForJoint(int32_t jointIndex);
|
||||
std::vector<DetailedMotionState*>& getDetailedMotionStates() { return _detailedMotionStates; }
|
||||
void clearDetailedMotionStates();
|
||||
void resetDetailedMotionStates();
|
||||
|
||||
void buildPhysicsTransaction(PhysicsEngine::Transaction& transaction);
|
||||
void handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction);
|
||||
|
||||
|
||||
struct RayAvatarResult {
|
||||
bool _intersect { false };
|
||||
bool _isBound { false };
|
||||
QUuid _intersectWithAvatar;
|
||||
int _intersectWithJoint { -1 };
|
||||
int32_t _intersectWithJoint { -1 };
|
||||
float _distance { 0.0f };
|
||||
float _maxDistance { 0.0f };
|
||||
QVariantMap _extraInfo;
|
||||
glm::vec3 _intersectionPoint;
|
||||
glm::vec3 _intersectionNormal;
|
||||
std::vector<int> _boundJoints;
|
||||
std::vector<int32_t> _boundJoints;
|
||||
};
|
||||
std::vector<RayAvatarResult> rayTest(const btVector3& origin, const btVector3& direction, const btScalar& length,
|
||||
const QVector<uint>& jointsToExclude) const;
|
||||
|
|
|
@ -116,6 +116,8 @@ void OtherAvatar::updateSpaceProxy(workload::Transaction& transaction) const {
|
|||
int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) {
|
||||
int32_t bytesRead = Avatar::parseDataFromBuffer(buffer);
|
||||
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
|
||||
// NOTE: we activate _detailedMotionStates is because they are KINEMATIC
|
||||
// and Bullet will automagically call DetailedMotionState::getWorldTransform() when active.
|
||||
_detailedMotionStates[i]->forceActive();
|
||||
}
|
||||
if (_moving && _motionState) {
|
||||
|
@ -124,11 +126,11 @@ int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
btCollisionShape* OtherAvatar::createCollisionShape(int jointIndex, bool& isBound, std::vector<int>& boundJoints) {
|
||||
const btCollisionShape* OtherAvatar::createCollisionShape(int32_t jointIndex, bool& isBound, std::vector<int32_t>& boundJoints) {
|
||||
ShapeInfo shapeInfo;
|
||||
isBound = false;
|
||||
QString jointName = "";
|
||||
if (jointIndex > -1 && jointIndex < (int)_multiSphereShapes.size()) {
|
||||
QString jointName = "";
|
||||
if (jointIndex > -1 && jointIndex < (int32_t)_multiSphereShapes.size()) {
|
||||
jointName = _multiSphereShapes[jointIndex].getJointName();
|
||||
}
|
||||
switch (_bodyLOD) {
|
||||
|
@ -163,39 +165,21 @@ btCollisionShape* OtherAvatar::createCollisionShape(int jointIndex, bool& isBoun
|
|||
}
|
||||
break;
|
||||
}
|
||||
// Note: MultiSphereLow case really means: "skip fingers and use spheres for hands,
|
||||
// else fall through to MultiSphereHigh case"
|
||||
case BodyLOD::MultiSphereHigh:
|
||||
computeDetailedShapeInfo(shapeInfo, jointIndex);
|
||||
break;
|
||||
default:
|
||||
assert(false); // should never reach here
|
||||
break;
|
||||
}
|
||||
if (shapeInfo.getType() != SHAPE_TYPE_NONE) {
|
||||
auto shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
|
||||
if (shape) {
|
||||
shape->setMargin(0.001f);
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DetailedMotionState* OtherAvatar::createMotionState(std::shared_ptr<OtherAvatar> avatar, int jointIndex) {
|
||||
bool isBound = false;
|
||||
std::vector<int> boundJoints;
|
||||
btCollisionShape* shape = createCollisionShape(jointIndex, isBound, boundJoints);
|
||||
if (shape) {
|
||||
DetailedMotionState* motionState = new DetailedMotionState(avatar, shape, jointIndex);
|
||||
motionState->setMass(computeMass());
|
||||
motionState->setIsBound(isBound, boundJoints);
|
||||
return motionState;
|
||||
}
|
||||
return nullptr;
|
||||
return ObjectMotionState::getShapeManager()->getShape(shapeInfo);
|
||||
}
|
||||
|
||||
void OtherAvatar::resetDetailedMotionStates() {
|
||||
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
|
||||
_detailedMotionStates[i] = nullptr;
|
||||
}
|
||||
// NOTE: the DetailedMotionStates are deleted after being added to PhysicsEngine::Transaction::_objectsToRemove
|
||||
// See AvatarManager::handleProcessedPhysicsTransaction()
|
||||
_detailedMotionStates.clear();
|
||||
}
|
||||
|
||||
|
@ -231,11 +215,11 @@ void OtherAvatar::computeShapeLOD() {
|
|||
}
|
||||
|
||||
bool OtherAvatar::isInPhysicsSimulation() const {
|
||||
return _motionState != nullptr && _detailedMotionStates.size() > 0;
|
||||
return _motionState && _motionState->getRigidBody();
|
||||
}
|
||||
|
||||
bool OtherAvatar::shouldBeInPhysicsSimulation() const {
|
||||
return !isDead() && !(isInPhysicsSimulation() && _needsReinsertion);
|
||||
return !isDead() && _workloadRegion < workload::Region::R3;
|
||||
}
|
||||
|
||||
bool OtherAvatar::needsPhysicsUpdate() const {
|
||||
|
@ -245,12 +229,9 @@ bool OtherAvatar::needsPhysicsUpdate() const {
|
|||
|
||||
void OtherAvatar::rebuildCollisionShape() {
|
||||
if (_motionState) {
|
||||
// do not actually rebuild here, instead flag for later
|
||||
_motionState->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
|
||||
}
|
||||
for (size_t i = 0; i < _detailedMotionStates.size(); i++) {
|
||||
if (_detailedMotionStates[i]) {
|
||||
_detailedMotionStates[i]->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
|
||||
}
|
||||
_needsReinsertion = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,25 +241,6 @@ void OtherAvatar::setCollisionWithOtherAvatarsFlags() {
|
|||
}
|
||||
}
|
||||
|
||||
void OtherAvatar::createDetailedMotionStates(const std::shared_ptr<OtherAvatar>& avatar) {
|
||||
auto& detailedMotionStates = getDetailedMotionStates();
|
||||
assert(detailedMotionStates.empty());
|
||||
if (_bodyLOD == BodyLOD::Sphere) {
|
||||
auto dMotionState = createMotionState(avatar, -1);
|
||||
if (dMotionState) {
|
||||
detailedMotionStates.push_back(dMotionState);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < getJointCount(); i++) {
|
||||
auto dMotionState = createMotionState(avatar, i);
|
||||
if (dMotionState) {
|
||||
detailedMotionStates.push_back(dMotionState);
|
||||
}
|
||||
}
|
||||
}
|
||||
_needsReinsertion = false;
|
||||
}
|
||||
|
||||
void OtherAvatar::simulate(float deltaTime, bool inView) {
|
||||
PROFILE_RANGE(simulation, "simulate");
|
||||
|
||||
|
|
|
@ -52,9 +52,7 @@ public:
|
|||
bool shouldBeInPhysicsSimulation() const;
|
||||
bool needsPhysicsUpdate() const;
|
||||
|
||||
btCollisionShape* createCollisionShape(int jointIndex, bool& isBound, std::vector<int>& boundJoints);
|
||||
DetailedMotionState* createMotionState(std::shared_ptr<OtherAvatar> avatar, int jointIndex);
|
||||
void createDetailedMotionStates(const std::shared_ptr<OtherAvatar>& avatar);
|
||||
const btCollisionShape* createCollisionShape(int32_t jointIndex, bool& isBound, std::vector<int32_t>& boundJoints);
|
||||
std::vector<DetailedMotionState*>& getDetailedMotionStates() { return _detailedMotionStates; }
|
||||
void resetDetailedMotionStates();
|
||||
BodyLOD getBodyLOD() { return _bodyLOD; }
|
||||
|
|
|
@ -65,15 +65,15 @@ void GraphicsEngine::initializeGPU(GLWidget* glwidget) {
|
|||
DependencyManager::get<TextureCache>()->setGPUContext(_gpuContext);
|
||||
}
|
||||
|
||||
void GraphicsEngine::initializeRender(bool disableDeferred) {
|
||||
void GraphicsEngine::initializeRender() {
|
||||
|
||||
// Set up the render engine
|
||||
render::CullFunctor cullFunctor = LODManager::shouldRender;
|
||||
_renderEngine->addJob<UpdateSceneTask>("UpdateScene");
|
||||
#ifndef Q_OS_ANDROID
|
||||
_renderEngine->addJob<SecondaryCameraRenderTask>("SecondaryCameraJob", cullFunctor, !disableDeferred);
|
||||
_renderEngine->addJob<SecondaryCameraRenderTask>("SecondaryCameraJob", cullFunctor);
|
||||
#endif
|
||||
_renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, !disableDeferred, render::ItemKey::TAG_BITS_0, render::ItemKey::TAG_BITS_0);
|
||||
_renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, render::ItemKey::TAG_BITS_0, render::ItemKey::TAG_BITS_0);
|
||||
_renderEngine->load();
|
||||
_renderEngine->registerScene(_renderScene);
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ public:
|
|||
~GraphicsEngine();
|
||||
|
||||
void initializeGPU(GLWidget*);
|
||||
void initializeRender(bool disableDeferred);
|
||||
void initializeRender();
|
||||
void startup();
|
||||
void shutdown();
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace render {
|
|||
PerformanceTimer perfTimer("worldBox");
|
||||
|
||||
auto& batch = *args->_batch;
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch);
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, false, true, false, false, true, args->_renderMethod == Args::RenderMethod::FORWARD);
|
||||
WorldBoxRenderData::renderWorldBox(args, batch);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,15 @@ int main(int argc, const char* argv[]) {
|
|||
// https://i.kym-cdn.com/entries/icons/original/000/008/342/ihave.jpg
|
||||
QSurfaceFormat::setDefaultFormat(format);
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
// Check the minimum version of
|
||||
if (gl::getAvailableVersion() < gl::getRequiredVersion()) {
|
||||
MessageBoxA(nullptr, "Interface requires OpenGL 4.1 or higher", "Unsupported", MB_OK);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
setupHifiApplication(BuildInfo::INTERFACE_NAME);
|
||||
|
||||
QStringList arguments;
|
||||
|
|
|
@ -168,7 +168,7 @@ bool isEntityPhysicsReady(const EntityItemPointer& entity) {
|
|||
bool hasAABox;
|
||||
entity->getAABox(hasAABox);
|
||||
if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) {
|
||||
return (!entity->shouldBePhysical() || entity->isReadyToComputeShape() || modelEntity->computeShapeFailedToLoad());
|
||||
return (!entity->shouldBePhysical() || entity->isInPhysicsSimulation() || modelEntity->computeShapeFailedToLoad());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,37 @@ void buildObjectIntersectionsMap(IntersectionType intersectionType, const std::v
|
|||
}
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* An intersection result for a collision pick.
|
||||
*
|
||||
* @typedef {object} CollisionPickResult
|
||||
* @property {boolean} intersects - <code>true</code> if there is at least one intersection, <code>false</code> if there isn't.
|
||||
* @property {IntersectingObject[]} intersectingObjects - All objects which intersect with the <code>collisionRegion</code>.
|
||||
* @property {CollisionRegion} collisionRegion - The collision region that was used. Valid even if there was no intersection.
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Information about a {@link CollisionPick}'s intersection with an object.
|
||||
*
|
||||
* @typedef {object} IntersectingObject
|
||||
* @property {Uuid} id - The ID of the object.
|
||||
* @property {IntersectionType} type - The type of the object, either <code>1</code> for INTERSECTED_ENTITY or <code>3</code>
|
||||
* for INTERSECTED_AVATAR.
|
||||
* @property {CollisionContact[]} collisionContacts - Information on the penetration between the pick and the object.
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* A pair of points that represents part of an overlap between a {@link CollisionPick} and an object in the physics engine.
|
||||
* Points which are further apart represent deeper overlap.
|
||||
*
|
||||
* @typedef {object} CollisionContact
|
||||
* @property {Vec3} pointOnPick - A point representing a penetration of the object's surface into the volume of the pick, in
|
||||
* world coordinates.
|
||||
* @property {Vec3} pointOnObject - A point representing a penetration of the pick's surface into the volume of the object, in
|
||||
* world coordinates.
|
||||
* @property {Vec3} normalOnPick - The normal vector pointing away from the pick, representing the direction of collision.
|
||||
*/
|
||||
|
||||
QVariantMap CollisionPickResult::toVariantMap() const {
|
||||
QVariantMap variantMap;
|
||||
|
||||
|
|
|
@ -42,6 +42,23 @@ public:
|
|||
float parabolicDistance { FLT_MAX };
|
||||
bool intersects { false };
|
||||
|
||||
/**jsdoc
|
||||
* An intersection result for a parabola pick.
|
||||
*
|
||||
* @typedef {object} ParabolaPickResult
|
||||
* @property {number} type - The intersection type.
|
||||
* @property {boolean} intersects - <code>true</code> if there's a valid intersection, <code>false</code> if there isn't.
|
||||
* @property {Uuid} objectID - The ID of the intersected object. <code>null</code> for HUD or invalid intersections.
|
||||
* @property {number} distance - The distance from the parabola origin to the intersection point in a straight line.
|
||||
* @property {number} parabolicDistance - The distance from the parabola origin to the intersection point along the arc of
|
||||
* the parabola.
|
||||
* @property {Vec3} intersection - The intersection point in world coordinates.
|
||||
* @property {Vec3} surfaceNormal - The surface normal at the intersected point. All <code>NaN</code>s if <code>type ==
|
||||
* Picks.INTERSECTED_HUD</code>.
|
||||
* @property {SubmeshIntersection} extraInfo - Additional intersection details for model objects, otherwise
|
||||
* <code>{ }</code>.
|
||||
* @property {PickParabola} parabola - The pick parabola that was used. Valid even if there is no intersection.
|
||||
*/
|
||||
virtual QVariantMap toVariantMap() const override {
|
||||
QVariantMap toReturn;
|
||||
toReturn["type"] = type;
|
||||
|
|
|
@ -20,8 +20,7 @@ const float ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_W
|
|||
const bool ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA { false };
|
||||
const bool ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_DRAWINFRONT { false };
|
||||
|
||||
gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_parabolaPipeline { nullptr };
|
||||
gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_transparentParabolaPipeline { nullptr };
|
||||
std::map<std::pair<bool, bool>, gpu::PipelinePointer> ParabolaPointer::RenderState::ParabolaRenderItem::_parabolaPipelines;
|
||||
|
||||
ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover,
|
||||
const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd,
|
||||
|
@ -401,33 +400,34 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::updateBounds() {
|
|||
_bound = AABox(min, max - min);
|
||||
}
|
||||
|
||||
const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline() {
|
||||
if (!_parabolaPipeline || !_transparentParabolaPipeline) {
|
||||
{
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola);
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline(bool forward) const {
|
||||
if (_parabolaPipelines.empty()) {
|
||||
using namespace shader::render_utils::program;
|
||||
|
||||
static const std::vector<std::tuple<bool, bool, uint32_t>> keys = {
|
||||
std::make_tuple(false, false, parabola), std::make_tuple(false, true, forward_parabola), std::make_tuple(true, false, parabola_translucent)/*, std::make_tuple(true, true, forward_parabola_translucent)*/
|
||||
};
|
||||
|
||||
for (auto& key : keys) {
|
||||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
state->setBlendFunction(false,
|
||||
if (std::get<0>(key)) {
|
||||
PrepareStencil::testMask(*state);
|
||||
} else {
|
||||
PrepareStencil::testMaskDrawShape(*state);
|
||||
}
|
||||
state->setBlendFunction(std::get<0>(key),
|
||||
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);
|
||||
PrepareStencil::testMaskDrawShape(*state);
|
||||
state->setCullMode(gpu::State::CULL_NONE);
|
||||
_parabolaPipeline = gpu::Pipeline::create(program, state);
|
||||
|
||||
_parabolaPipelines[{std::get<0>(key), std::get<1>(key)}] = gpu::Pipeline::create(gpu::Shader::createProgram(std::get<2>(key)), state);
|
||||
}
|
||||
|
||||
{
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::parabola_translucent);
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
state->setBlendFunction(true,
|
||||
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);
|
||||
PrepareStencil::testMask(*state);
|
||||
state->setCullMode(gpu::State::CULL_NONE);
|
||||
_transparentParabolaPipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
// The forward opaque/translucent pipelines are the same for now
|
||||
_parabolaPipelines[{ true, true }] = _parabolaPipelines[{ false, true}];
|
||||
}
|
||||
return (_parabolaData.color.a < 1.0f ? _transparentParabolaPipeline : _parabolaPipeline);
|
||||
return _parabolaPipelines[{ _parabolaData.color.a < 1.0f, forward }];
|
||||
}
|
||||
|
||||
void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args) {
|
||||
|
@ -441,7 +441,7 @@ void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args)
|
|||
transform.setTranslation(_origin);
|
||||
batch.setModelTransform(transform);
|
||||
|
||||
batch.setPipeline(getParabolaPipeline());
|
||||
batch.setPipeline(getParabolaPipeline(args->_renderMethod == render::Args::RenderMethod::FORWARD));
|
||||
|
||||
const int MAX_SECTIONS = 100;
|
||||
if (glm::length2(_parabolaData.acceleration) < EPSILON) {
|
||||
|
|
|
@ -26,9 +26,8 @@ public:
|
|||
bool isVisibleInSecondaryCamera, bool drawInFront, bool enabled);
|
||||
~ParabolaRenderItem() {}
|
||||
|
||||
static gpu::PipelinePointer _parabolaPipeline;
|
||||
static gpu::PipelinePointer _transparentParabolaPipeline;
|
||||
const gpu::PipelinePointer getParabolaPipeline();
|
||||
static std::map<std::pair<bool, bool>, gpu::PipelinePointer> _parabolaPipelines;
|
||||
gpu::PipelinePointer getParabolaPipeline(bool forward) const;
|
||||
|
||||
void render(RenderArgs* args);
|
||||
render::Item::Bound& editBound() { return _bound; }
|
||||
|
|
|
@ -55,23 +55,37 @@ PickFilter getPickFilter(unsigned int filter) {
|
|||
}
|
||||
|
||||
/**jsdoc
|
||||
* A set of properties that can be passed to {@link Picks.createPick} to create a new Ray Pick.
|
||||
* A set of properties that can be passed to {@link Picks.createPick} when creating a new ray pick.
|
||||
*
|
||||
* @typedef {object} Picks.RayPickProperties
|
||||
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
|
||||
* @property {number} [filter=0] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
|
||||
* @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or a pick.
|
||||
* @property {number} [parentJointIndex=0] - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint)
|
||||
* @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar.
|
||||
* @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Ray Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral
|
||||
* @property {Vec3} [dirOffset=Vec3.UP] Only for Joint Ray Picks. A local joint direction offset. x = upward, y = forward, z = lateral
|
||||
* @property {Vec3} [position] Only for Static Ray Picks. The world-space origin of the ray.
|
||||
* @property {Vec3} [direction=-Vec3.UP] Only for Static Ray Picks. The world-space direction of the ray.
|
||||
* @property {boolean} [enabled=false] - <code>true</code> if this pick should start enabled, <code>false</code> if it should
|
||||
* start disabled. Disabled picks do not update their pick results.
|
||||
* @property {FilterFlags} [filter=0] - The filter for this pick to use. Construct using {@link Picks} FilterFlags property
|
||||
* values (e.g., <code>Picks.PICK_DOMAIN_ENTTITIES</code>) combined with <code>|</code> (bitwise OR) operators.
|
||||
* @property {number} [maxDistance=0.0] - The maximum distance at which this pick will intersect. A value of <code>0.0</code>
|
||||
* means no maximum.
|
||||
* @property {Uuid} [parentID] - The ID of the parent: an avatar, an entity, or another pick.
|
||||
* @property {number} [parentJointIndex=0] - The joint of the parent to parent to, for example, an avatar joint.
|
||||
* A value of <code>0</code> means no joint.<br />
|
||||
* <em>Used only if <code>parentID</code> is specified.</em>
|
||||
* @property {string} [joint] - <code>"Mouse"</code> parents the pick to the mouse; <code>"Avatar"</code> parents the pick to
|
||||
* the user's avatar head; a joint name parents to the joint in the user's avatar; otherwise, the pick is "static", not
|
||||
* parented to anything.<br />
|
||||
* <em>Used only if <code>parentID</code> is not specified.</em>
|
||||
* @property {Vec3} [position=Vec3.ZERO] - The offset of the ray origin from its parent if parented, otherwise the ray origin
|
||||
* in world coordinates.
|
||||
* @property {Vec3} [posOffset] - Synonym for <code>position</code>.
|
||||
* @property {Vec3} [direction] - The offset of the ray direction from its parent's y-axis if parented, otherwise the ray
|
||||
* direction in world coordinates.
|
||||
* <p><strong>Default Value:</strong> <code>Vec3.UP</code> direction if <code>joint</code> is specified, otherwise
|
||||
* <code>-Vec3.UP</code>.</p>
|
||||
* @property {Vec3} [dirOffset] - Synonym for <code>direction</code>.
|
||||
* @property {Quat} [orientation] - Alternative property for specifying <code>direction</code>. The value is applied to the
|
||||
* default <code>direction</code> value.
|
||||
*/
|
||||
unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) {
|
||||
QVariantMap propMap = properties.toMap();
|
||||
|
||||
|
||||
#if defined (Q_OS_ANDROID)
|
||||
QString jointName { "" };
|
||||
if (propMap["joint"].isValid()) {
|
||||
|
@ -124,12 +138,20 @@ unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) {
|
|||
}
|
||||
|
||||
/**jsdoc
|
||||
* A set of properties that can be passed to {@link Picks.createPick} to create a new Stylus Pick.
|
||||
* A set of properties that can be passed to {@link Picks.createPick} when creating a new stylus pick.
|
||||
*
|
||||
* @typedef {object} Picks.StylusPickProperties
|
||||
* @property {number} [hand=-1] An integer. 0 == left, 1 == right. Invalid otherwise.
|
||||
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
|
||||
* @property {number} [filter=0] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
|
||||
* @property {number} [hand=-1] <code>0</code> for the left hand, <code>1</code> for the right hand, invalid (<code>-1</code>)
|
||||
* otherwise.
|
||||
* @property {boolean} [enabled=false] - <code>true</code> if this pick should start enabled, <code>false</code> if it should
|
||||
* start disabled. Disabled picks do not update their pick results.
|
||||
* @property {number} [filter=0] - The filter for this pick to use. Construct using {@link Picks} FilterFlags property
|
||||
* values (e.g., <code>Picks.PICK_DOMAIN_ENTTITIES</code>) combined with <code>|</code> (bitwise OR) operators.
|
||||
* <p><strong>Note:</strong> Stylus picks do not intersect avatars or the HUD.</p>
|
||||
* @property {number} [maxDistance=0.0] - The maximum distance at which this pick will intersect. A value of <code>0.0</code>
|
||||
* means no maximum.
|
||||
* @property {Vec3} [tipOffset=0,0.095,0] - The position of the stylus tip relative to the hand position at default avatar
|
||||
* scale.
|
||||
*/
|
||||
unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties) {
|
||||
QVariantMap propMap = properties.toMap();
|
||||
|
@ -167,23 +189,45 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties
|
|||
|
||||
// NOTE: Laser pointer still uses scaleWithAvatar. Until scaleWithAvatar is also deprecated for pointers, scaleWithAvatar should not be removed from the pick API.
|
||||
/**jsdoc
|
||||
* A set of properties that can be passed to {@link Picks.createPick} to create a new Parabola Pick.
|
||||
* A set of properties that can be passed to {@link Picks.createPick} when creating a new parabola pick.
|
||||
*
|
||||
* @typedef {object} Picks.ParabolaPickProperties
|
||||
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
|
||||
* @property {number} [filter=0] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
|
||||
* @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or a pick.
|
||||
* @property {number} [parentJointIndex=0] - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint)
|
||||
* @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar.
|
||||
* @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Parabola Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral
|
||||
* @property {Vec3} [dirOffset=Vec3.UP] Only for Joint Parabola Picks. A local joint direction offset. x = upward, y = forward, z = lateral
|
||||
* @property {Vec3} [position] Only for Static Parabola Picks. The world-space origin of the parabola segment.
|
||||
* @property {Vec3} [direction=-Vec3.FRONT] Only for Static Parabola Picks. The world-space direction of the parabola segment.
|
||||
* @property {number} [speed=1] The initial speed of the parabola, i.e. the initial speed of the projectile whose trajectory defines the parabola.
|
||||
* @property {Vec3} [accelerationAxis=-Vec3.UP] The acceleration of the parabola, i.e. the acceleration of the projectile whose trajectory defines the parabola, both magnitude and direction.
|
||||
* @property {boolean} [rotateAccelerationWithAvatar=true] Whether or not the acceleration axis should rotate with the avatar's local Y axis.
|
||||
* @property {boolean} [rotateAccelerationWithParent=false] Whether or not the acceleration axis should rotate with the parent's local Y axis, if available.
|
||||
* @property {boolean} [scaleWithParent=true] If true, the velocity and acceleration of the Pick will scale linearly with the parent, if available. scaleWithAvatar is an alias but is deprecated.
|
||||
* @property {boolean} [enabled=false] - <code>true</code> if this pick should start enabled, <code>false</code> if it should
|
||||
* start disabled. Disabled picks do not update their pick results.
|
||||
* @property {number} [filter=0] - The filter for this pick to use. Construct using {@link Picks} FilterFlags property
|
||||
* values (e.g., <code>Picks.PICK_DOMAIN_ENTTITIES</code>) combined with <code>|</code> (bitwise OR) operators.
|
||||
* @property {number} [maxDistance=0.0] - The maximum distance at which this pick will intersect. A value of <code>0.0</code>
|
||||
* means no maximum.
|
||||
* @property {Uuid} [parentID] - The ID of the parent: an avatar, an entity, or another pick.
|
||||
* @property {number} [parentJointIndex=0] - The joint of the parent to parent to, for example, an avatar joint.
|
||||
* A value of <code>0</code> means no joint.<br />
|
||||
* <em>Used only if <code>parentID</code> is specified.</em>
|
||||
* @property {string} [joint] - <code>"Mouse"</code> parents the pick to the mouse; <code>"Avatar"</code> parents the pick to
|
||||
* the user's avatar head; a joint name parents to the joint in the user's avatar; otherwise, the pick is "static", not
|
||||
* parented to anything.
|
||||
* <em>Used only if <code>parentID</code> is not specified.</em>
|
||||
* @property {Vec3} [position=Vec3.ZERO] - The offset of the parabola origin from its parent if parented, otherwise the
|
||||
* parabola origin in world coordinates.
|
||||
* @property {Vec3} [posOffset] - Synonym for <code>position</code>.
|
||||
* @property {Vec3} [direction] - The offset of the parabola direction from its parent's y-axis if parented, otherwise the
|
||||
* parabola direction in world coordinates.
|
||||
* <p><strong>Default Value:</strong> <code>Vec3.UP</code> direction if <code>joint</code> is specified, otherwise
|
||||
* <code>Vec3.FRONT</code>.</p>
|
||||
* @property {Vec3} [dirOffset] - Synonym for <code>direction</code>.
|
||||
* @property {Quat} [orientation] - Alternative property for specifying <code>direction</code>. The value is applied to the
|
||||
* default <code>direction</code> value.
|
||||
* @property {number} [speed=1] - The initial speed of the parabola in m/s, i.e., the initial speed of a virtual projectile
|
||||
* whose trajectory defines the parabola.
|
||||
* @property {Vec3} [accelerationAxis=-Vec3.UP] - The acceleration of the parabola in m/s<sup>2</sup>, i.e., the acceleration
|
||||
* of a virtual projectile whose trajectory defines the parabola, both magnitude and direction.
|
||||
* @property {boolean} [rotateAccelerationWithAvatar=true] - <code>true</code> if the acceleration axis should rotate with the
|
||||
* avatar about the avatar's y-axis, <code>false</code> if it shouldn't.
|
||||
* @property {boolean} [rotateAccelerationWithParent=false] - <code>true</code> if the acceleration axis should rotate with the
|
||||
* parent about the parent's y-axis, if available.
|
||||
* @property {boolean} [scaleWithParent=true] - <code>true</code> if the velocity and acceleration of the pick should scale
|
||||
* with the avatar or other parent.
|
||||
* @property {boolean} [scaleWithAvatar=true] - Synonym for <code>scalewithParent</code>.
|
||||
* <p class="important">Deprecated: This property is deprecated and will be removed.</p>
|
||||
*/
|
||||
unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properties) {
|
||||
QVariantMap propMap = properties.toMap();
|
||||
|
@ -251,35 +295,38 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti
|
|||
return DependencyManager::get<PickManager>()->addPick(PickQuery::Parabola, parabolaPick);
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* A Shape defines a physical volume.
|
||||
*
|
||||
* @typedef {object} Shape
|
||||
* @property {string} shapeType The type of shape to use. Can be one of the following: "box", "sphere", "capsule-x", "capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"
|
||||
* @property {Vec3} dimensions - The size to scale the shape to.
|
||||
*/
|
||||
|
||||
// TODO: Add this property to the Shape jsdoc above once model picks work properly
|
||||
// * @property {string} modelURL - If shapeType is one of: "compound", "simple-hull", "simple-compound", or "static-mesh", this defines the model to load to generate the collision volume.
|
||||
|
||||
/**jsdoc
|
||||
* A set of properties that can be passed to {@link Picks.createPick} to create a new Collision Pick.
|
||||
|
||||
* @typedef {object} Picks.CollisionPickProperties
|
||||
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
|
||||
* @property {number} [filter=0] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {Shape} shape - The information about the collision region's size and shape. Dimensions are in world space, but will scale with the parent if defined.
|
||||
* @property {Vec3} position - The position of the collision region, relative to a parent if defined.
|
||||
* @property {Quat} orientation - The orientation of the collision region, relative to a parent if defined.
|
||||
* @property {float} threshold - The approximate minimum penetration depth for a test object to be considered in contact with the collision region.
|
||||
* The depth is measured in world space, but will scale with the parent if defined.
|
||||
* @property {CollisionMask} [collisionGroup=8] - The type of object this collision pick collides as. Objects whose collision masks overlap with the pick's collision group
|
||||
* will be considered colliding with the pick.
|
||||
* @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, or a pick.
|
||||
* @property {number} [parentJointIndex=0] - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint)
|
||||
* @property {string} joint - If "Mouse," parents the pick to the mouse. If "Avatar," parents the pick to MyAvatar's head. Otherwise, parents to the joint of the given name on MyAvatar.
|
||||
* @property {boolean} [scaleWithParent=true] If true, the collision pick's dimensions and threshold will adjust according to the scale of the parent.
|
||||
*/
|
||||
* A set of properties that can be passed to {@link Picks.createPick} when creating a new collision pick.
|
||||
*
|
||||
* @typedef {object} Picks.CollisionPickProperties
|
||||
* @property {boolean} [enabled=false] - <code>true</code> if this pick should start enabled, <code>false</code> if it should
|
||||
* start disabled. Disabled picks do not update their pick results.
|
||||
* @property {FilterFlags} [filter=0] - The filter for this pick to use. Construct using {@link Picks} FilterFlags property
|
||||
* values (e.g., <code>Picks.PICK_DOMAIN_ENTTITIES</code>) combined with <code>|</code> (bitwise OR) operators.
|
||||
* <p><strong>Note:</strong> Collision picks do not intersect the HUD.</p>
|
||||
* @property {number} [maxDistance=0.0] - The maximum distance at which this pick will intersect. A value of <code>0.0</code>
|
||||
* means no maximum.
|
||||
* @property {Uuid} [parentID] - The ID of the parent: an avatar, an entity, or another pick.
|
||||
* @property {number} [parentJointIndex=0] - The joint of the parent to parent to, for example, an avatar joint.
|
||||
* A value of <code>0</code> means no joint.<br />
|
||||
* <em>Used only if <code>parentID</code> is specified.</em>
|
||||
* @property {string} [joint] - <code>"Mouse"</code> parents the pick to the mouse; <code>"Avatar"</code> parents the pick to
|
||||
* the user's avatar head; a joint name parents to the joint in the user's avatar; otherwise, the pick is "static", not
|
||||
* parented to anything.<br />
|
||||
* <em>Used only if <code>parentID</code> is not specified.</em>
|
||||
* @property {boolean} [scaleWithParent=true] - <code>true</code> to scale the pick's dimensions and threshold according to the
|
||||
* scale of the parent.
|
||||
*
|
||||
* @property {Shape} shape - The collision region's shape and size. Dimensions are in world coordinates but scale with the
|
||||
* parent if defined.
|
||||
* @property {Vec3} position - The position of the collision region, relative to the parent if defined.
|
||||
* @property {Quat} orientation - The orientation of the collision region, relative to the parent if defined.
|
||||
* @property {number} threshold - The approximate minimum penetration depth for a test object to be considered in contact with
|
||||
* the collision region. The depth is in world coordinates but scales with the parent if defined.
|
||||
* @property {CollisionMask} [collisionGroup=8] - The type of objects the collision region collides as. Objects whose collision
|
||||
* masks overlap with the region's collision group are considered to be colliding with the region.
|
||||
*/
|
||||
unsigned int PickScriptingInterface::createCollisionPick(const QVariant& properties) {
|
||||
QVariantMap propMap = properties.toMap();
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include <PickFilter.h>
|
||||
|
||||
/**jsdoc
|
||||
* The Picks API lets you create and manage objects for repeatedly calculating intersections in different ways.
|
||||
* The <code>Picks</code> API lets you create and manage objects for repeatedly calculating intersections.
|
||||
*
|
||||
* @namespace Picks
|
||||
*
|
||||
|
@ -25,33 +25,45 @@
|
|||
* @hifi-client-entity
|
||||
* @hifi-avatar
|
||||
*
|
||||
* @property {number} PICK_ENTITIES A filter flag. Include domain and avatar entities when intersecting. <em>Read-only.</em>. Deprecated.
|
||||
* @property {number} PICK_OVERLAYS A filter flag. Include local entities when intersecting. <em>Read-only.</em>. Deprecated.
|
||||
* @property {FilterFlags} PICK_DOMAIN_ENTITIES - Include domain entities when intersecting. <em>Read-only.</em>
|
||||
* @property {FilterFlags} PICK_AVATAR_ENTITIES - Include avatar entities when intersecting. <em>Read-only.</em>
|
||||
* @property {FilterFlags} PICK_LOCAL_ENTITIES - Include local entities when intersecting. <em>Read-only.</em>
|
||||
* @property {FilterFlags} PICK_AVATARS - Include avatars when intersecting. <em>Read-only.</em>
|
||||
* @property {FilterFlags} PICK_HUD - Include the HUD sphere when intersecting in HMD mode. <em>Read-only.</em>
|
||||
*
|
||||
* @property {number} PICK_DOMAIN_ENTITIES A filter flag. Include domain entities when intersecting. <em>Read-only.</em>.
|
||||
* @property {number} PICK_AVATAR_ENTITIES A filter flag. Include avatar entities when intersecting. <em>Read-only.</em>.
|
||||
* @property {number} PICK_LOCAL_ENTITIES A filter flag. Include local entities when intersecting. <em>Read-only.</em>.
|
||||
* @property {number} PICK_AVATARS A filter flag. Include avatars when intersecting. <em>Read-only.</em>.
|
||||
* @property {number} PICK_HUD A filter flag. Include the HUD sphere when intersecting in HMD mode. <em>Read-only.</em>.
|
||||
* @property {FilterFlags} PICK_ENTITIES - Include domain and avatar entities when intersecting. <em>Read-only.</em>
|
||||
* <p class="important">Deprecated: This property is deprecated and will be removed. Use <code>PICK_DOMAIN_ENTITIES |
|
||||
* PICK_AVATAR_ENTITIES</code> instead.</p>
|
||||
* @property {FilterFlags} PICK_OVERLAYS - Include local entities when intersecting. <em>Read-only.</em>
|
||||
* <p class="important">Deprecated: This property is deprecated and will be removed. Use <code>PICK_LOCAL_ENTITIES</code>
|
||||
* instead.</p>
|
||||
*
|
||||
* @property {number} PICK_INCLUDE_VISIBLE A filter flag. Include visible objects when intersecting. <em>Read-only.</em>.
|
||||
* @property {number} PICK_INCLUDE_INVISIBLE A filter flag. Include invisible objects when intersecting. <em>Read-only.</em>.
|
||||
* @property {FilterFlags} PICK_INCLUDE_VISIBLE - Include visible objects when intersecting. <em>Read-only.</em>
|
||||
* <p><strong>Warning:</strong> Is currently always enabled by default but may not be in the future.</p>
|
||||
* @property {FilterFlags} PICK_INCLUDE_INVISIBLE - Include invisible objects when intersecting. <em>Read-only.</em>
|
||||
*
|
||||
* @property {number} PICK_INCLUDE_COLLIDABLE A filter flag. Include collidable objects when intersecting. <em>Read-only.</em>.
|
||||
* @property {number} PICK_INCLUDE_NONCOLLIDABLE A filter flag. Include non-collidable objects when intersecting. <em>Read-only.</em>.
|
||||
* @property {FilterFlags} PICK_INCLUDE_COLLIDABLE - Include collidable objects when intersecting. <em>Read-only.</em>
|
||||
* <p><strong>Warning:</strong> Is currently always enabled by default but may not be in the future.</p>
|
||||
* @property {FilterFlags} PICK_INCLUDE_NONCOLLIDABLE - Include non-collidable objects when intersecting. <em>Read-only.</em>
|
||||
*
|
||||
* @property {number} PICK_PRECISE A filter flag. Pick against exact meshes. <em>Read-only.</em>.
|
||||
* @property {number} PICK_COARSE A filter flag. Pick against coarse meshes. <em>Read-only.</em>.
|
||||
* @property {FilterFlags} PICK_PRECISE - Pick against exact meshes. <em>Read-only.</em>
|
||||
* @property {FilterFlags} PICK_COARSE - Pick against coarse meshes. <em>Read-only.</em>
|
||||
*
|
||||
* @property {number} PICK_ALL_INTERSECTIONS <em>Read-only.</em>.
|
||||
* @property {FilterFlags} PICK_ALL_INTERSECTIONS - If set, returns all intersections instead of just the closest.
|
||||
* <em>Read-only.</em>
|
||||
* <p><strong>Warning:</strong> Not yet implemented.</p>
|
||||
*
|
||||
* @property {number} INTERSECTED_NONE An intersection type. Intersected nothing with the given filter flags. <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_ENTITY An intersection type. Intersected an entity. <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_LOCAL_ENTITY An intersection type. Intersected a local entity.</em>
|
||||
* @property {number} INTERSECTED_OVERLAY An intersection type. Intersected an entity (3D Overlays no longer exist). <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_AVATAR An intersection type. Intersected an avatar. <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_HUD An intersection type. Intersected the HUD sphere. <em>Read-only.</em>
|
||||
* @property {number} perFrameTimeBudget - The max number of usec to spend per frame updating Pick results.
|
||||
* @property {IntersectionType} INTERSECTED_NONE - Intersected nothing. <em>Read-only.</em>
|
||||
* @property {IntersectionType} INTERSECTED_ENTITY - Intersected an entity. <em>Read-only.</em>
|
||||
* @property {IntersectionType} INTERSECTED_LOCAL_ENTITY - Intersected a local entity. <em>Read-only.</em>
|
||||
* @property {IntersectionType} INTERSECTED_OVERLAY - Intersected a local entity. (3D overlays no longer exist.)
|
||||
* <em>Read-only.</em>
|
||||
* <p class="important">Deprecated: This property is deprecated and will be removed. Use
|
||||
* <code>INTERSECTED_LOCAL_ENTITY</code> instead.</p>
|
||||
* @property {IntersectionType} INTERSECTED_AVATAR - Intersected an avatar. <em>Read-only.</em>
|
||||
* @property {IntersectionType} INTERSECTED_HUD - Intersected the HUD sphere. <em>Read-only.</em>
|
||||
*
|
||||
* @property {number} perFrameTimeBudget - The maximum time, in microseconds, to spend per frame updating pick results.
|
||||
*/
|
||||
|
||||
class PickScriptingInterface : public QObject, public Dependency {
|
||||
|
@ -94,163 +106,145 @@ public:
|
|||
void registerMetaTypes(QScriptEngine* engine);
|
||||
|
||||
/**jsdoc
|
||||
* Adds a new Pick.
|
||||
* Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
|
||||
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick.
|
||||
* Picks created with this method always intersect at least visible and collidable things
|
||||
* Creates a new pick. Different {@link PickType}s use different properties, and within one PickType the properties you
|
||||
* choose can lead to a wide range of behaviors. For example, with <code>PickType.Ray</code>, the properties could
|
||||
* configure a mouse ray pick, an avatar head ray pick, or a joint ray pick.
|
||||
* <p><strong>Warning:</strong> Picks created using this method currently always intersect at least visible and collidable
|
||||
* things but this may not always be the case.</p>
|
||||
* @function Picks.createPick
|
||||
* @param {PickType} type A PickType that specifies the method of picking to use
|
||||
* @param {Picks.RayPickProperties|Picks.StylusPickProperties|Picks.ParabolaPickProperties|Picks.CollisionPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
|
||||
* @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid.
|
||||
* @param {PickType} type - The type of picking to use.
|
||||
* @param {Picks.RayPickProperties|Picks.ParabolaPickProperties|Picks.StylusPickProperties|Picks.CollisionPickProperties}
|
||||
* properties - Properties of the pick, per the pick <code>type</code>.
|
||||
* @returns {number} The ID of the pick created. <code>0</code> if invalid.
|
||||
*/
|
||||
// TODO: expand Pointers to be able to be fully configurable with PickFilters
|
||||
Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties);
|
||||
|
||||
/**jsdoc
|
||||
* Enables a Pick.
|
||||
* Enables a pick. Enabled picks update their pick results.
|
||||
* @function Picks.enablePick
|
||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||
* @param {number} id - The ID of the pick.
|
||||
*/
|
||||
Q_INVOKABLE void enablePick(unsigned int uid);
|
||||
|
||||
/**jsdoc
|
||||
* Disables a Pick.
|
||||
* Disables a pick. Disabled picks do not update their pick results.
|
||||
* @function Picks.disablePick
|
||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||
* @param {number} id - The ID of the pick.
|
||||
*/
|
||||
Q_INVOKABLE void disablePick(unsigned int uid);
|
||||
|
||||
/**jsdoc
|
||||
* Removes a Pick.
|
||||
* Removes (deletes) a pick.
|
||||
* @function Picks.removePick
|
||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||
* @param {number} id - The ID of the pick.
|
||||
*/
|
||||
Q_INVOKABLE void removePick(unsigned int uid);
|
||||
|
||||
/**jsdoc
|
||||
* An intersection result for a Ray Pick.
|
||||
*
|
||||
* @typedef {object} RayPickResult
|
||||
* @property {number} type The intersection type.
|
||||
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
|
||||
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
|
||||
* @property {number} distance The distance to the intersection point from the origin of the ray.
|
||||
* @property {Vec3} intersection The intersection point in world-space.
|
||||
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
|
||||
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
|
||||
* @property {PickRay} searchRay The PickRay that was used. Valid even if there was no intersection.
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* An intersection result for a Stylus Pick.
|
||||
*
|
||||
* @typedef {object} StylusPickResult
|
||||
* @property {number} type The intersection type.
|
||||
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
|
||||
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
|
||||
* @property {number} distance The distance to the intersection point from the origin of the ray.
|
||||
* @property {Vec3} intersection The intersection point in world-space.
|
||||
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
|
||||
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
|
||||
* @property {StylusTip} stylusTip The StylusTip that was used. Valid even if there was no intersection.
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* An intersection result for a Parabola Pick.
|
||||
*
|
||||
* @typedef {object} ParabolaPickResult
|
||||
* @property {number} type The intersection type.
|
||||
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
|
||||
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
|
||||
* @property {number} distance The distance to the intersection point from the origin of the parabola, not along the parabola.
|
||||
* @property {number} parabolicDistance The distance to the intersection point from the origin of the parabola, along the parabola.
|
||||
* @property {Vec3} intersection The intersection point in world-space.
|
||||
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
|
||||
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
|
||||
* @property {PickParabola} parabola The PickParabola that was used. Valid even if there was no intersection.
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* An intersection result for a Collision Pick.
|
||||
*
|
||||
* @typedef {object} CollisionPickResult
|
||||
* @property {boolean} intersects If there was at least one valid intersection (intersectingObjects.length > 0)
|
||||
* @property {IntersectingObject[]} intersectingObjects The collision information of each object which intersect with the CollisionRegion.
|
||||
* @property {CollisionRegion} collisionRegion The CollisionRegion that was used. Valid even if there was no intersection.
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Information about the Collision Pick's intersection with an object
|
||||
*
|
||||
* @typedef {object} IntersectingObject
|
||||
* @property {QUuid} id The ID of the object.
|
||||
* @property {number} type The type of the object, either Picks.INTERSECTED_ENTITY() or Picks.INTERSECTED_AVATAR()
|
||||
* @property {CollisionContact[]} collisionContacts Pairs of points representing penetration information between the pick and the object
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* A pair of points that represents part of an overlap between a Collision Pick and an object in the physics engine. Points which are further apart represent deeper overlap
|
||||
*
|
||||
* @typedef {object} CollisionContact
|
||||
* @property {Vec3} pointOnPick A point representing a penetration of the object's surface into the volume of the pick, in world space.
|
||||
* @property {Vec3} pointOnObject A point representing a penetration of the pick's surface into the volume of the found object, in world space.
|
||||
* @property {Vec3} normalOnPick The normalized vector pointing away from the pick, representing the direction of collision.
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Get the most recent pick result from this Pick. This will be updated as long as the Pick is enabled.
|
||||
* Gets the most recent result from a pick. A pick continues to be updated ready to return a result, as long as it is
|
||||
* enabled.
|
||||
* <p><strong>Note:</strong> Stylus picks only intersect with objects in their include list, set using
|
||||
* {@link Picks.setIncludeItems|setIncludeItems}.</p>
|
||||
* @function Picks.getPrevPickResult
|
||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||
* @returns {RayPickResult|StylusPickResult|ParabolaPickResult|CollisionPickResult} The most recent intersection result. This will be different for different PickTypes.
|
||||
* @param {number} id - The ID of the pick.
|
||||
* @returns {RayPickResult|ParabolaPickResult|StylusPickResult|CollisionPickResult} The most recent intersection result.
|
||||
* @example <caption>Highlight entities under your mouse in desktop mode or that you're looking at in HMD mode.</caption>
|
||||
* // Highlight.
|
||||
* var HIGHLIGHT_LIST_NAME = "highlightEntitiesExampleList";
|
||||
* var HIGHLIGHT_LIST_TYPE = "entity";
|
||||
* Selection.enableListHighlight(HIGHLIGHT_LIST_NAME, {});
|
||||
*
|
||||
* // Ray pick.
|
||||
* var PICK_FILTER = Picks.PICK_DOMAIN_ENTITIES | Picks.PICK_AVATAR_ENTITIES
|
||||
* | Picks.PICK_INCLUDE_COLLIDABLE | Picks.PICK_INCLUDE_NONCOLLIDABLE;
|
||||
* var rayPick = Picks.createPick(PickType.Ray, {
|
||||
* enabled: true,
|
||||
* filter: PICK_FILTER,
|
||||
* joint: HMD.active ? "Avatar" : "Mouse"
|
||||
* });
|
||||
*
|
||||
* // Highlight intersected entity.
|
||||
* var highlightedEntityID = null;
|
||||
* Script.update.connect(function () {
|
||||
* var rayPickResult = Picks.getPrevPickResult(rayPick);
|
||||
* if (rayPickResult.intersects) {
|
||||
* if (rayPickResult.objectID !== highlightedEntityID) {
|
||||
* if (highlightedEntityID) {
|
||||
* Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, HIGHLIGHT_LIST_TYPE, highlightedEntityID);
|
||||
* }
|
||||
* highlightedEntityID = rayPickResult.objectID;
|
||||
* Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, HIGHLIGHT_LIST_TYPE, highlightedEntityID);
|
||||
* }
|
||||
* } else {
|
||||
* if (highlightedEntityID) {
|
||||
* Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, HIGHLIGHT_LIST_TYPE, highlightedEntityID);
|
||||
* highlightedEntityID = null;
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // Clean up.
|
||||
* Script.scriptEnding.connect(function () {
|
||||
* if (highlightedEntityID) {
|
||||
* Selection.removeFromSelectedItemsList(HIGHLIGHT_LIST_NAME, HIGHLIGHT_LIST_TYPE, highlightedEntityID);
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid);
|
||||
|
||||
/**jsdoc
|
||||
* Sets whether or not to use precision picking.
|
||||
* Sets whether or not to use precision picking, i.e., whether to pick against precise meshes or coarse meshes.
|
||||
* This has the same effect as using the <code>PICK_PRECISE</code> or <code>PICK_COARSE</code> filter flags.
|
||||
* @function Picks.setPrecisionPicking
|
||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||
* @param {boolean} precisionPicking Whether or not to use precision picking
|
||||
* @param {number} id - The ID of the pick.
|
||||
* @param {boolean} precisionPicking - <code>true</code> to use precision picking, <code>false</code> to use coarse picking.
|
||||
*/
|
||||
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking);
|
||||
|
||||
/**jsdoc
|
||||
* Sets a list of Entity IDs and/or Avatar IDs to ignore during intersection. Not used by Stylus Picks.
|
||||
* Sets a list of entity and avatar IDs to ignore during intersection.
|
||||
* <p><strong>Note:</strong> Not used by stylus picks.</p>
|
||||
* @function Picks.setIgnoreItems
|
||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||
* @param {Uuid[]} ignoreItems A list of IDs to ignore.
|
||||
* @param {number} id - The ID of the pick.
|
||||
* @param {Uuid[]} ignoreItems - The list of IDs to ignore.
|
||||
*/
|
||||
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems);
|
||||
|
||||
/**jsdoc
|
||||
* Sets a list of Entity IDs and/or Avatar IDs to include during intersection, instead of intersecting with everything. Stylus
|
||||
* Picks <b>only</b> intersect with objects in their include list.
|
||||
* Sets a list of entity IDs and/or avatar IDs to include during intersection, instead of intersecting with everything.
|
||||
* <p><strong>Note:</strong> Stylus picks only intersect with objects in their include list.</p>
|
||||
* @function Picks.setIncludeItems
|
||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||
* @param {Uuid[]} includeItems A list of IDs to include.
|
||||
* @param {number} id - The ID of the pick.
|
||||
* @param {Uuid[]} includeItems - The list of IDs to include.
|
||||
*/
|
||||
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeItems);
|
||||
|
||||
/**jsdoc
|
||||
* Check if a Pick is associated with the left hand.
|
||||
* Checks if a pick is associated with the left hand: a ray or parabola pick with joint set to
|
||||
* <code>"_CONTROLLER_LEFTHAND"</code> or <code>"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"</code>, or a stylus pick with hand
|
||||
* set to <code>0</code>.
|
||||
* @function Picks.isLeftHand
|
||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||
* @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0.
|
||||
* @param {number} id - The ID of the pick.
|
||||
* @returns {boolean} <code>true</code> if the pick is associated with the left hand, <code>false</code> if it isn't.
|
||||
*/
|
||||
Q_INVOKABLE bool isLeftHand(unsigned int uid);
|
||||
|
||||
/**jsdoc
|
||||
* Check if a Pick is associated with the right hand.
|
||||
* Checks if a pick is associated with the right hand: a ray or parabola pick with joint set to
|
||||
* <code>"_CONTROLLER_RIGHTHAND"</code> or <code>"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"</code>, or a stylus pick with hand
|
||||
* set to <code>1</code>.
|
||||
* @function Picks.isRightHand
|
||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||
* @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1.
|
||||
* @param {number} id - The ID of the pick.
|
||||
* @returns {boolean} <code>true</code> if the pick is associated with the right hand, <code>false</code> if it isn't.
|
||||
*/
|
||||
Q_INVOKABLE bool isRightHand(unsigned int uid);
|
||||
|
||||
/**jsdoc
|
||||
* Check if a Pick is associated with the system mouse.
|
||||
* Checks if a pick is associated with the system mouse: a ray or parabola pick with joint set to <code>"Mouse"</code>.
|
||||
* @function Picks.isMouse
|
||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||
* @returns {boolean} True if the Pick is a Mouse Ray or Parabola Pick, false otherwise.
|
||||
* @param {number} id - The ID of the pick.
|
||||
* @returns {boolean} <code>true</code> if the pick is associated with the system mouse, <code>false</code> if it isn't.
|
||||
*/
|
||||
Q_INVOKABLE bool isMouse(unsigned int uid);
|
||||
|
||||
|
@ -261,112 +255,162 @@ public slots:
|
|||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_ENTITIES
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_DOMAIN_ENTITIES |
|
||||
* Picks.PICK_AVATAR_ENTITIES</cpode> properties expression instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_OVERLAYS
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_LOCAL_ENTITIES</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_OVERLAYS() { return PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); }
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_DOMAIN_ENTITIES
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_DOMAIN_ENTITIES</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_DOMAIN_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_AVATAR_ENTITIES
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_AVATAR_ENTITIES</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_AVATAR_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_LOCAL_ENTITIES
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_LOCAL_ENTITIES</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_LOCAL_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_AVATARS
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_AVATARS</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_AVATARS() { return PickFilter::getBitMask(PickFilter::FlagBit::AVATARS); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_HUD
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_HUD</code> property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_HUD() { return PickFilter::getBitMask(PickFilter::FlagBit::HUD); }
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_INCLUDE_VISIBLE
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_INCLUDE_VISIBLE</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_INCLUDE_VISIBLE() { return PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_INCLUDE_INVISIBLE
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_INCLUDE_INVISIBLE</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_INCLUDE_INVISIBLE() { return PickFilter::getBitMask(PickFilter::FlagBit::INVISIBLE); }
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_INCLUDE_COLLIDABLE
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_INCLUDE_COLLIDABLE</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_INCLUDE_COLLIDABLE() { return PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_INCLUDE_NONCOLLIDABLE
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_INCLUDE_NONCOLLIDABLE</code>
|
||||
* property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return PickFilter::getBitMask(PickFilter::FlagBit::NONCOLLIDABLE); }
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_PRECISE
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_PRECISE</code> property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_PRECISE() { return PickFilter::getBitMask(PickFilter::FlagBit::PRECISE); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_COARSE
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_COARSE</code> property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_COARSE() { return PickFilter::getBitMask(PickFilter::FlagBit::COARSE); }
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_ALL_INTERSECTIONS
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.PICK_ALL_INTERSECTIONS</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_ALL_INTERSECTIONS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ALL_INTERSECTIONS); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.INTERSECTED_NONE
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.INTERSECTED_NONE</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int INTERSECTED_NONE() { return IntersectionType::NONE; }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.INTERSECTED_ENTITY
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.INTERSECTED_ENTITY</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int INTERSECTED_ENTITY() { return IntersectionType::ENTITY; }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.INTERSECTED_OVERLAY
|
||||
* @function Picks.INTERSECTED_LOCAL_ENTITY
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.INTERSECTED_LOCAL_ENTITY</code>
|
||||
* property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int INTERSECTED_LOCAL_ENTITY() { return IntersectionType::LOCAL_ENTITY; }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.INTERSECTED_OVERLAY
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.INTERSECTED_LOCAL_ENTITY</code>
|
||||
* property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int INTERSECTED_OVERLAY() { return INTERSECTED_LOCAL_ENTITY(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.INTERSECTED_AVATAR
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.INTERSECTED_AVATAR</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int INTERSECTED_AVATAR() { return IntersectionType::AVATAR; }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.INTERSECTED_HUD
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Picks.INTERSECTED_HUD</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; }
|
||||
|
|
|
@ -49,7 +49,7 @@ public:
|
|||
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pointer, a Mouse Ray Pointer, or a Joint Ray Pointer.
|
||||
* Pointers created with this method always intersect at least visible and collidable things
|
||||
* @function Pointers.createPointer
|
||||
* @param {PickType} type A PickType that specifies the method of picking to use
|
||||
* @param {PickType} type A PickType that specifies the method of picking to use. Cannot be {@link PickType|PickType.Collision}.
|
||||
* @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer <b>and</b> the {@link Picks.PickProperties} for the Pick that
|
||||
* this Pointer will use to do its picking.
|
||||
* @returns {number} The ID of the created Pointer. Used for managing the Pointer. 0 if invalid.
|
||||
|
|
|
@ -39,6 +39,21 @@ public:
|
|||
float distance { FLT_MAX };
|
||||
bool intersects { false };
|
||||
|
||||
/**jsdoc
|
||||
* An intersection result for a ray pick.
|
||||
*
|
||||
* @typedef {object} RayPickResult
|
||||
* @property {IntersectionType} type - The intersection type.
|
||||
* @property {boolean} intersects - <code>true</code> if there's a valid intersection, <code>false</code> if there isn't.
|
||||
* @property {Uuid} objectID - The ID of the intersected object. <code>null</code> for HUD or invalid intersections.
|
||||
* @property {number} distance - The distance from the ray origin to the intersection point.
|
||||
* @property {Vec3} intersection - The intersection point in world coordinates.
|
||||
* @property {Vec3} surfaceNormal - The surface normal at the intersected point. All <code>NaN</code>s if <code>type ==
|
||||
* Picks.INTERSECTED_HUD</code>.
|
||||
* @property {SubmeshIntersection} extraInfo - Additional intersection details for model objects, otherwise
|
||||
* <code>{ }</code>.
|
||||
* @property {PickRay} searchRay - The pick ray that was used. Valid even if there is no intersection.
|
||||
*/
|
||||
virtual QVariantMap toVariantMap() const override {
|
||||
QVariantMap toReturn;
|
||||
toReturn["type"] = type;
|
||||
|
|
|
@ -19,29 +19,32 @@
|
|||
#include "PickScriptingInterface.h"
|
||||
|
||||
/**jsdoc
|
||||
* Synonym for {@link Picks} as used for ray picks. Deprecated.
|
||||
* The <code>RayPick</code> API is a subset of the {@link Picks} API, as used for ray picks.
|
||||
*
|
||||
* @namespace RayPick
|
||||
*
|
||||
* @deprecated This API is deprecated and will be removed. Use the {@link Picks} API instead.
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
* @hifi-avatar
|
||||
*
|
||||
* @property {number} PICK_ENTITIES <em>Read-only.</em>
|
||||
* @property {number} PICK_OVERLAYS <em>Read-only.</em>
|
||||
* @property {number} PICK_AVATARS <em>Read-only.</em>
|
||||
* @property {number} PICK_HUD <em>Read-only.</em>
|
||||
* @property {number} PICK_COARSE <em>Read-only.</em>
|
||||
* @property {number} PICK_INCLUDE_INVISIBLE <em>Read-only.</em>
|
||||
* @property {number} PICK_INCLUDE_NONCOLLIDABLE <em>Read-only.</em>
|
||||
* @property {number} PICK_ALL_INTERSECTIONS <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_NONE <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_ENTITY <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_OVERLAY <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_AVATAR <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_HUD <em>Read-only.</em>
|
||||
* @property {FilterFlags} PICK_ENTITIES - Include domain and avatar entities when intersecting.
|
||||
* <em>Read-only.</em>
|
||||
* @property {FilterFlags} PICK_OVERLAYS - Include local entities when intersecting. <em>Read-only.</em>
|
||||
* @property {FilterFlags} PICK_AVATARS - Include avatars when intersecting. <em>Read-only.</em>
|
||||
* @property {FilterFlags} PICK_HUD - Include the HUD sphere when intersecting in HMD mode. <em>Read-only.</em>
|
||||
* @property {FilterFlags} PICK_PRECISE - Pick against exact meshes. <em>Read-only.</em>
|
||||
* @property {FilterFlags} PICK_INCLUDE_INVISIBLE - Include invisible objects when intersecting. <em>Read-only.</em>
|
||||
* @property {FilterFlags} PICK_INCLUDE_NONCOLLIDABLE - Include non-collidable objects when intersecting. <em>Read-only.</em>
|
||||
* @property {FilterFlags} PICK_ALL_INTERSECTIONS - Return all intersections instead of just the closest. <em>Read-only.</em>
|
||||
* @property {IntersectionType} INTERSECTED_NONE - Intersected nothing with the given filter flags. <em>Read-only.</em>
|
||||
* @property {IntersectionType} INTERSECTED_ENTITY - Intersected an entity. <em>Read-only.</em>
|
||||
* @property {IntersectionType} INTERSECTED_LOCAL_ENTITY - Intersected a local entity. <em>Read-only.</em>
|
||||
* @property {IntersectionType} INTERSECTED_OVERLAY - Intersected an entity (3D Overlays no longer exist). <em>Read-only.</em>
|
||||
* @property {IntersectionType} INTERSECTED_AVATAR - Intersected an avatar. <em>Read-only.</em>
|
||||
* @property {IntersectionType} INTERSECTED_HUD - Intersected the HUD sphere. <em>Read-only.</em>
|
||||
*/
|
||||
|
||||
class RayPickScriptingInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(unsigned int PICK_ENTITIES READ PICK_ENTITIES CONSTANT)
|
||||
|
@ -63,78 +66,96 @@ class RayPickScriptingInterface : public QObject, public Dependency {
|
|||
public:
|
||||
|
||||
/**jsdoc
|
||||
* Creates a new ray pick.
|
||||
* <p><strong>Warning:</strong> Picks created using this method currently always intersect at least visible and collidable
|
||||
* things but this may not always be the case.</p>
|
||||
* @function RayPick.createRayPick
|
||||
* @param {Picks.RayPickProperties}
|
||||
* @returns {number}
|
||||
* @param {Picks.RayPickProperties} properties - Properties of the pick.
|
||||
* @returns {number} The ID of the pick created. <code>0</code> if invalid.
|
||||
*/
|
||||
Q_INVOKABLE unsigned int createRayPick(const QVariant& properties);
|
||||
|
||||
/**jsdoc
|
||||
* Enables a ray pick.
|
||||
* @function RayPick.enableRayPick
|
||||
* @param {number} id
|
||||
* @param {number} id - The ID of the ray pick.
|
||||
*/
|
||||
Q_INVOKABLE void enableRayPick(unsigned int uid);
|
||||
|
||||
/**jsdoc
|
||||
* Disables a ray pick.
|
||||
* @function RayPick.disableRayPick
|
||||
* @param {number} id
|
||||
* @param {number} id - The ID of the ray pick.
|
||||
*/
|
||||
Q_INVOKABLE void disableRayPick(unsigned int uid);
|
||||
|
||||
/**jsdoc
|
||||
* Removes (deletes) a ray pick.
|
||||
* @function RayPick.removeRayPick
|
||||
* @param {number} id
|
||||
* @param {number} id - The ID of the ray pick.
|
||||
*/
|
||||
Q_INVOKABLE void removeRayPick(unsigned int uid);
|
||||
|
||||
/**jsdoc
|
||||
* Gets the most recent pick result from a ray pick. A ray pick continues to be updated ready to return a result, as long
|
||||
* as it is enabled.
|
||||
* @function RayPick.getPrevRayPickResult
|
||||
* @param {number} id
|
||||
* @param {number} id - The ID of the ray pick.
|
||||
* @returns {RayPickResult}
|
||||
*/
|
||||
Q_INVOKABLE QVariantMap getPrevRayPickResult(unsigned int uid);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Sets whether or not to use precision picking, i.e., whether to pick against precise meshes or coarse meshes.
|
||||
* @function RayPick.setPrecisionPicking
|
||||
* @param {number} id
|
||||
* @param {boolean} precisionPicking
|
||||
* @param {number} id - The ID of the ray pick.
|
||||
* @param {boolean} precisionPicking - <code>true</code> to use precision picking, <code>false</code> to use coarse picking.
|
||||
*/
|
||||
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking);
|
||||
|
||||
/**jsdoc
|
||||
* Sets a list of entity and avatar IDs to ignore during intersection.
|
||||
* @function RayPick.setIgnoreItems
|
||||
* @param {number} id
|
||||
* @param {Uuid[]) ignoreEntities
|
||||
* @param {number} id - The ID of the ray pick.
|
||||
* @param {Uuid[]} ignoreItems - The list of IDs to ignore.
|
||||
*/
|
||||
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities);
|
||||
|
||||
/**jsdoc
|
||||
* Sets a list of entity IDs and/or avatar IDs to include during intersection, instead of intersecting with everything.
|
||||
* @function RayPick.setIncludeItems
|
||||
* @param {number} id
|
||||
* @param {Uuid[]) includeEntities
|
||||
* @param {number} id - The ID of the ray pick.
|
||||
* @param {Uuid[]} includeItems - The list of IDs to include.
|
||||
*/
|
||||
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Checks if a pick is associated with the left hand: a ray or parabola pick with joint set to
|
||||
* <code>"_CONTROLLER_LEFTHAND"</code> or <code>"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"</code>, or a stylus pick with hand
|
||||
* set to <code>0</code>.
|
||||
* @function RayPick.isLeftHand
|
||||
* @param {number} id
|
||||
* @returns {boolean}
|
||||
* @param {number} id - The ID of the ray pick.
|
||||
* @returns {boolean} <code>true</code> if the pick is associated with the left hand, <code>false</code> if it isn't.
|
||||
*/
|
||||
Q_INVOKABLE bool isLeftHand(unsigned int uid);
|
||||
|
||||
/**jsdoc
|
||||
* Checks if a pick is associated with the right hand: a ray or parabola pick with joint set to
|
||||
* <code>"_CONTROLLER_RIGHTHAND"</code> or <code>"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"</code>, or a stylus pick with hand
|
||||
* set to <code>1</code>.
|
||||
* @function RayPick.isRightHand
|
||||
* @param {number} id
|
||||
* @returns {boolean}
|
||||
* @param {number} id - The ID of the ray pick.
|
||||
* @returns {boolean} <code>true</code> if the pick is associated with the right hand, <code>false</code> if it isn't.
|
||||
*/
|
||||
Q_INVOKABLE bool isRightHand(unsigned int uid);
|
||||
|
||||
/**jsdoc
|
||||
* Checks if a pick is associated with the system mouse: a ray or parabola pick with joint set to <code>"Mouse"</code>.
|
||||
* @function RayPick.isMouse
|
||||
* @param {number} id
|
||||
* @returns {boolean}
|
||||
* @param {number} id - The ID of the ray pick.
|
||||
* @returns {boolean} <code>true</code> if the pick is associated with the system mouse, <code>false</code> if it isn't.
|
||||
*/
|
||||
Q_INVOKABLE bool isMouse(unsigned int uid);
|
||||
|
||||
|
@ -142,84 +163,107 @@ public slots:
|
|||
|
||||
/**jsdoc
|
||||
* @function RayPick.PICK_ENTITIES
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>Raypick.PICK_ENTITIES</code> property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int PICK_ENTITIES() { return PickScriptingInterface::PICK_ENTITIES(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.PICK_OVERLAYS
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>RayPick.PICK_OVERLAYS</code> property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int PICK_OVERLAYS() { return PickScriptingInterface::PICK_OVERLAYS(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.PICK_AVATARS
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>RayPick.PICK_AVATARS</code> property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int PICK_AVATARS() { return PickScriptingInterface::PICK_AVATARS(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.PICK_HUD
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>RayPick.PICK_HUD</code> property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int PICK_HUD() { return PickScriptingInterface::PICK_HUD(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.PICK_COARSE
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>RayPick.PICK_COARSE</code> property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int PICK_COARSE() { return PickScriptingInterface::PICK_COARSE(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.PICK_INCLUDE_INVISIBLE
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>RayPick.PICK_INCLUDE_INVISIBLE</code>
|
||||
* property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int PICK_INCLUDE_INVISIBLE() { return PickScriptingInterface::PICK_INCLUDE_INVISIBLE(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.PICK_INCLUDE_NONCOLLIDABLE
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>RayPick.PICK_INCLUDE_NONCOLLIDABLE</code>
|
||||
* property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return PickScriptingInterface::PICK_INCLUDE_NONCOLLIDABLE(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.PICK_ALL_INTERSECTIONS
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>RayPick.PICK_ALL_INTERSECTIONS</code>
|
||||
* property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int PICK_ALL_INTERSECTIONS() { return PickScriptingInterface::PICK_ALL_INTERSECTIONS(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.INTERSECTED_NONE
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>RayPick.INTERSECTED_NONE</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int INTERSECTED_NONE() { return PickScriptingInterface::INTERSECTED_NONE(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.INTERSECTED_ENTITY
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>RayPick.INTERSECTED_ENTITY</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int INTERSECTED_ENTITY() { return PickScriptingInterface::INTERSECTED_ENTITY(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.INTERSECTED_OVERLAY
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>RayPick.INTERSECTED_LOCAL_ENTITY</code>
|
||||
* property instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int INTERSECTED_LOCAL_ENTITY() { return PickScriptingInterface::INTERSECTED_LOCAL_ENTITY(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.INTERSECTED_OVERLAY
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>RayPick.INTERSECTED_OVERLAY</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int INTERSECTED_OVERLAY() { return PickScriptingInterface::INTERSECTED_LOCAL_ENTITY(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.INTERSECTED_AVATAR
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>RayPick.INTERSECTED_AVATAR</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int INTERSECTED_AVATAR() { return PickScriptingInterface::INTERSECTED_AVATAR(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.INTERSECTED_HUD
|
||||
* @deprecated This function is deprecated and will be removed. Use the <code>RayPick.INTERSECTED_HUD</code> property
|
||||
* instead.
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int INTERSECTED_HUD() { return PickScriptingInterface::INTERSECTED_HUD(); }
|
||||
|
|
|
@ -38,6 +38,18 @@ public:
|
|||
glm::vec3 intersection { NAN };
|
||||
glm::vec3 surfaceNormal { NAN };
|
||||
|
||||
/**jsdoc
|
||||
* An intersection result for a stylus pick.
|
||||
*
|
||||
* @typedef {object} StylusPickResult
|
||||
* @property {number} type - The intersection type.
|
||||
* @property {boolean} intersects - <code>true</code> if there's a valid intersection, <code>false</code> if there isn't.
|
||||
* @property {Uuid} objectID - The ID of the intersected object. <code>null</code> for invalid intersections.
|
||||
* @property {number} distance - The distance to the intersection point from the stylus tip.
|
||||
* @property {Vec3} intersection - The intersection point in world coordinates.
|
||||
* @property {Vec3} surfaceNormal - The surface normal at the intersected point.
|
||||
* @property {StylusTip} stylusTip - The stylus tip at the time of the result. Valid even if there is no intersection.
|
||||
*/
|
||||
virtual QVariantMap toVariantMap() const override {
|
||||
QVariantMap toReturn;
|
||||
toReturn["type"] = type;
|
||||
|
|
|
@ -366,8 +366,10 @@ void Audio::onContextChanged() {
|
|||
void Audio::handlePushedToTalk(bool enabled) {
|
||||
if (getPTT()) {
|
||||
if (enabled) {
|
||||
DependencyManager::get<AudioClient>()->setOutputGain(0.1f); // duck the output by 20dB
|
||||
setMuted(false);
|
||||
} else {
|
||||
DependencyManager::get<AudioClient>()->setOutputGain(1.0f);
|
||||
setMuted(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,9 @@ HMDScriptingInterface::HMDScriptingInterface() {
|
|||
connect(qApp, &Application::miniTabletEnabledChanged, [this](bool enabled) {
|
||||
emit miniTabletEnabledChanged(enabled);
|
||||
});
|
||||
connect(qApp, &Application::awayStateWhenFocusLostInVRChanged, [this](bool enabled) {
|
||||
emit awayStateWhenFocusLostInVRChanged(enabled);
|
||||
});
|
||||
}
|
||||
|
||||
glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const {
|
||||
|
@ -137,6 +140,14 @@ bool HMDScriptingInterface::getMiniTabletEnabled() {
|
|||
return qApp->getMiniTabletEnabled();
|
||||
}
|
||||
|
||||
void HMDScriptingInterface::setAwayStateWhenFocusLostInVREnabled(bool enabled) {
|
||||
qApp->setAwayStateWhenFocusLostInVREnabled(enabled);
|
||||
}
|
||||
|
||||
bool HMDScriptingInterface::getAwayStateWhenFocusLostInVREnabled() {
|
||||
return qApp->getAwayStateWhenFocusLostInVREnabled();
|
||||
}
|
||||
|
||||
|
||||
QScriptValue HMDScriptingInterface::getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine) {
|
||||
glm::vec3 hudIntersection;
|
||||
|
|
|
@ -375,6 +375,14 @@ signals:
|
|||
*/
|
||||
bool miniTabletEnabledChanged(bool enabled);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the altering the mode for going into an away state when the interface focus is lost in VR.
|
||||
* @function HMD.awayStateWhenFocusLostInVRChanged
|
||||
* @param {boolean} enabled - <code>true</code> if the setting to go into an away state in VR when the interface focus is lost is enabled, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
bool awayStateWhenFocusLostInVRChanged(bool enabled);
|
||||
|
||||
public:
|
||||
HMDScriptingInterface();
|
||||
|
||||
|
@ -423,6 +431,9 @@ public:
|
|||
void setMiniTabletEnabled(bool enabled);
|
||||
bool getMiniTabletEnabled();
|
||||
|
||||
void setAwayStateWhenFocusLostInVREnabled(bool enabled);
|
||||
bool getAwayStateWhenFocusLostInVREnabled();
|
||||
|
||||
QVariant getPlayAreaRect();
|
||||
QVector<glm::vec3> getSensorPositions();
|
||||
|
||||
|
|
|
@ -109,6 +109,8 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
|
|||
|
||||
auto mainWindow = qApp->getWindow();
|
||||
_dockWidget = std::shared_ptr<DockWidget>(new DockWidget(title, mainWindow), dockWidgetDeleter);
|
||||
auto quickView = _dockWidget->getQuickView();
|
||||
Application::setupQmlSurface(quickView->rootContext(), true);
|
||||
|
||||
/**jsdoc
|
||||
* Configures how a <code>NATIVE</code> window is displayed.
|
||||
|
@ -141,7 +143,6 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
|
|||
}
|
||||
}
|
||||
|
||||
auto quickView = _dockWidget->getQuickView();
|
||||
QObject::connect(quickView.get(), &QQuickView::statusChanged, [&, this] (QQuickView::Status status) {
|
||||
if (status == QQuickView::Ready) {
|
||||
QQuickItem* rootItem = _dockWidget->getRootItem();
|
||||
|
|
|
@ -111,6 +111,12 @@ void setupPreferences() {
|
|||
auto setter = [](bool value) { qApp->setSettingConstrainToolbarPosition(value); };
|
||||
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Constrain Toolbar Position to Horizontal Center", getter, setter));
|
||||
}
|
||||
|
||||
{
|
||||
auto getter = []()->bool { return qApp->getAwayStateWhenFocusLostInVREnabled(); };
|
||||
auto setter = [](bool value) { qApp->setAwayStateWhenFocusLostInVREnabled(value); };
|
||||
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Go into away state when interface window loses focus in VR", getter, setter));
|
||||
}
|
||||
|
||||
{
|
||||
auto getter = []()->float { return qApp->getDesktopTabletScale(); };
|
||||
|
|
|
@ -145,6 +145,7 @@ void GameWorkloadRenderItem::setAllViews(const workload::Views& views) {
|
|||
}
|
||||
|
||||
const gpu::PipelinePointer GameWorkloadRenderItem::getProxiesPipeline() {
|
||||
// FIXME: this needs a forward pipeline, or to only write to one output
|
||||
if (!_drawAllProxiesPipeline) {
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::drawWorkloadProxy);
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
|
@ -162,6 +163,7 @@ const gpu::PipelinePointer GameWorkloadRenderItem::getProxiesPipeline() {
|
|||
|
||||
|
||||
const gpu::PipelinePointer GameWorkloadRenderItem::getViewsPipeline() {
|
||||
// FIXME: this needs a forward pipeline, or to only write to one output
|
||||
if (!_drawAllViewsPipeline) {
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::drawWorkloadView);
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
|
|
|
@ -221,6 +221,35 @@ static float computeLoudness(int16_t* samples, int numSamples, int numChannels,
|
|||
return (float)loudness * scale;
|
||||
}
|
||||
|
||||
template <int NUM_CHANNELS>
|
||||
static void applyGainSmoothing(float* buffer, int numFrames, float gain0, float gain1) {
|
||||
|
||||
// fast path for unity gain
|
||||
if (gain0 == 1.0f && gain1 == 1.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
// cubic poly from gain0 to gain1
|
||||
float c3 = -2.0f * (gain1 - gain0);
|
||||
float c2 = 3.0f * (gain1 - gain0);
|
||||
float c0 = gain0;
|
||||
|
||||
float t = 0.0f;
|
||||
float tStep = 1.0f / numFrames;
|
||||
|
||||
for (int i = 0; i < numFrames; i++) {
|
||||
|
||||
// evaluate poly over t=[0,1)
|
||||
float gain = (c3 * t + c2) * t * t + c0;
|
||||
t += tStep;
|
||||
|
||||
// apply gain to all channels
|
||||
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
|
||||
buffer[NUM_CHANNELS*i + ch] *= gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline float convertToFloat(int16_t sample) {
|
||||
return (float)sample * (1 / 32768.0f);
|
||||
}
|
||||
|
@ -2109,6 +2138,14 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
|
|||
int framesPopped = samplesPopped / AudioConstants::STEREO;
|
||||
int bytesWritten;
|
||||
if (samplesPopped > 0) {
|
||||
|
||||
// apply output gain
|
||||
float newGain = _audio->_outputGain.load(std::memory_order_acquire);
|
||||
float oldGain = _audio->_lastOutputGain;
|
||||
_audio->_lastOutputGain = newGain;
|
||||
|
||||
applyGainSmoothing<OUTPUT_CHANNEL_COUNT>(mixBuffer, framesPopped, oldGain, newGain);
|
||||
|
||||
if (deviceChannelCount == OUTPUT_CHANNEL_COUNT) {
|
||||
// limit the audio
|
||||
_audio->_audioLimiter.render(mixBuffer, (int16_t*)data, framesPopped);
|
||||
|
|
|
@ -241,8 +241,10 @@ public slots:
|
|||
void setInputVolume(float volume, bool emitSignal = true);
|
||||
void setReverb(bool reverb);
|
||||
void setReverbOptions(const AudioEffectOptions* options);
|
||||
|
||||
void setLocalInjectorGain(float gain) { _localInjectorGain = gain; };
|
||||
void setSystemInjectorGain(float gain) { _systemInjectorGain = gain; };
|
||||
void setOutputGain(float gain) { _outputGain = gain; };
|
||||
|
||||
void outputNotify();
|
||||
|
||||
|
@ -395,6 +397,8 @@ private:
|
|||
int _outputPeriod { 0 };
|
||||
float* _outputMixBuffer { NULL };
|
||||
int16_t* _outputScratchBuffer { NULL };
|
||||
std::atomic<float> _outputGain { 1.0f };
|
||||
float _lastOutputGain { 1.0f };
|
||||
|
||||
// for local audio (used by audio injectors thread)
|
||||
std::atomic<float> _localInjectorGain { 1.0f };
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
|
||||
#include "InboundAudioStream.h"
|
||||
#include "TryLocker.h"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
|
@ -215,7 +216,7 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
|
|||
if (framesAvailable > _desiredJitterBufferFrames + MAX_FRAMES_OVER_DESIRED) {
|
||||
int framesToDrop = framesAvailable - (_desiredJitterBufferFrames + DESIRED_JITTER_BUFFER_FRAMES_PADDING);
|
||||
_ringBuffer.shiftReadPosition(framesToDrop * _ringBuffer.getNumFrameSamples());
|
||||
|
||||
|
||||
_framesAvailableStat.reset();
|
||||
_currentJitterBufferFrames = 0;
|
||||
|
||||
|
@ -247,10 +248,18 @@ int InboundAudioStream::lostAudioData(int numPackets) {
|
|||
QByteArray decodedBuffer;
|
||||
|
||||
while (numPackets--) {
|
||||
MutexTryLocker lock(_decoderMutex);
|
||||
if (!lock.isLocked()) {
|
||||
// an incoming packet is being processed,
|
||||
// and will likely be on the ring buffer shortly,
|
||||
// so don't bother generating more data
|
||||
qCInfo(audiostream, "Packet currently being unpacked or lost frame already being generated. Not generating lost frame.");
|
||||
return 0;
|
||||
}
|
||||
if (_decoder) {
|
||||
_decoder->lostFrame(decodedBuffer);
|
||||
} else {
|
||||
decodedBuffer.resize(AudioConstants::NETWORK_FRAME_BYTES_STEREO);
|
||||
decodedBuffer.resize(AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL * _numChannels);
|
||||
memset(decodedBuffer.data(), 0, decodedBuffer.size());
|
||||
}
|
||||
_ringBuffer.writeData(decodedBuffer.data(), decodedBuffer.size());
|
||||
|
@ -260,6 +269,12 @@ int InboundAudioStream::lostAudioData(int numPackets) {
|
|||
|
||||
int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) {
|
||||
QByteArray decodedBuffer;
|
||||
|
||||
// may block on the real-time thread, which is acceptible as
|
||||
// parseAudioData is only called by the packet processing
|
||||
// thread which, while high performance, is not as sensitive to
|
||||
// delays as the real-time thread.
|
||||
QMutexLocker lock(&_decoderMutex);
|
||||
if (_decoder) {
|
||||
_decoder->decode(packetAfterStreamProperties, decodedBuffer);
|
||||
} else {
|
||||
|
@ -278,16 +293,23 @@ int InboundAudioStream::writeDroppableSilentFrames(int silentFrames) {
|
|||
// case we will call the decoder's lostFrame() method, which indicates
|
||||
// that it should interpolate from its last known state down toward
|
||||
// silence.
|
||||
if (_decoder) {
|
||||
// FIXME - We could potentially use the output from the codec, in which
|
||||
// case we might get a cleaner fade toward silence. NOTE: The below logic
|
||||
// attempts to catch up in the event that the jitter buffers have grown.
|
||||
// The better long term fix is to use the output from the decode, detect
|
||||
// when it actually reaches silence, and then delete the silent portions
|
||||
// of the jitter buffers. Or petentially do a cross fade from the decode
|
||||
// output to silence.
|
||||
QByteArray decodedBuffer;
|
||||
_decoder->lostFrame(decodedBuffer);
|
||||
{
|
||||
// may block on the real-time thread, which is acceptible as
|
||||
// writeDroppableSilentFrames is only called by the packet processing
|
||||
// thread which, while high performance, is not as sensitive to
|
||||
// delays as the real-time thread.
|
||||
QMutexLocker lock(&_decoderMutex);
|
||||
if (_decoder) {
|
||||
// FIXME - We could potentially use the output from the codec, in which
|
||||
// case we might get a cleaner fade toward silence. NOTE: The below logic
|
||||
// attempts to catch up in the event that the jitter buffers have grown.
|
||||
// The better long term fix is to use the output from the decode, detect
|
||||
// when it actually reaches silence, and then delete the silent portions
|
||||
// of the jitter buffers. Or petentially do a cross fade from the decode
|
||||
// output to silence.
|
||||
QByteArray decodedBuffer;
|
||||
_decoder->lostFrame(decodedBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
// calculate how many silent frames we should drop.
|
||||
|
@ -338,10 +360,23 @@ int InboundAudioStream::popSamples(int maxSamples, bool allOrNothing) {
|
|||
popSamplesNoCheck(samplesAvailable);
|
||||
samplesPopped = samplesAvailable;
|
||||
} else {
|
||||
// we can't pop any samples, set this stream to starved
|
||||
// we can't pop any samples, set this stream to starved for jitter
|
||||
// buffer calculations.
|
||||
setToStarved();
|
||||
_consecutiveNotMixedCount++;
|
||||
_lastPopSucceeded = false;
|
||||
//Kick PLC to generate a filler frame, reducing 'click'
|
||||
lostAudioData(allOrNothing ? (maxSamples - samplesAvailable) / _ringBuffer.getNumFrameSamples() : 1);
|
||||
samplesPopped = _ringBuffer.samplesAvailable();
|
||||
if (samplesPopped) {
|
||||
popSamplesNoCheck(samplesPopped);
|
||||
} else {
|
||||
// No samples available means a packet is currently being
|
||||
// processed, so we don't generate lost audio data, and instead
|
||||
// just wait for the packet to come in. This prevents locking
|
||||
// the real-time audio thread at the cost of a potential (but rare)
|
||||
// 'click'
|
||||
_lastPopSucceeded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return samplesPopped;
|
||||
|
@ -528,6 +563,7 @@ void InboundAudioStream::setupCodec(CodecPluginPointer codec, const QString& cod
|
|||
_codec = codec;
|
||||
_selectedCodecName = codecName;
|
||||
if (_codec) {
|
||||
QMutexLocker lock(&_decoderMutex);
|
||||
_decoder = codec->createDecoder(AudioConstants::SAMPLE_RATE, numChannels);
|
||||
}
|
||||
}
|
||||
|
@ -535,6 +571,7 @@ void InboundAudioStream::setupCodec(CodecPluginPointer codec, const QString& cod
|
|||
void InboundAudioStream::cleanupCodec() {
|
||||
// release any old codec encoder/decoder first...
|
||||
if (_codec) {
|
||||
QMutexLocker lock(&_decoderMutex);
|
||||
if (_decoder) {
|
||||
_codec->releaseDecoder(_decoder);
|
||||
_decoder = nullptr;
|
||||
|
|
|
@ -187,6 +187,7 @@ protected:
|
|||
|
||||
CodecPluginPointer _codec;
|
||||
QString _selectedCodecName;
|
||||
QMutex _decoderMutex;
|
||||
Decoder* _decoder { nullptr };
|
||||
int _mismatchedAudioCodecCount { 0 };
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "MixedProcessedAudioStream.h"
|
||||
#include "AudioLogging.h"
|
||||
#include "TryLocker.h"
|
||||
|
||||
MixedProcessedAudioStream::MixedProcessedAudioStream(int numFramesCapacity, int numStaticJitterFrames)
|
||||
: InboundAudioStream(AudioConstants::STEREO, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL,
|
||||
|
@ -36,13 +37,20 @@ int MixedProcessedAudioStream::lostAudioData(int numPackets) {
|
|||
QByteArray outputBuffer;
|
||||
|
||||
while (numPackets--) {
|
||||
MutexTryLocker lock(_decoderMutex);
|
||||
if (!lock.isLocked()) {
|
||||
// an incoming packet is being processed,
|
||||
// and will likely be on the ring buffer shortly,
|
||||
// so don't bother generating more data
|
||||
qCInfo(audiostream, "Packet currently being unpacked or lost frame already being generated. Not generating lost frame.");
|
||||
return 0;
|
||||
}
|
||||
if (_decoder) {
|
||||
_decoder->lostFrame(decodedBuffer);
|
||||
} else {
|
||||
decodedBuffer.resize(AudioConstants::NETWORK_FRAME_BYTES_STEREO);
|
||||
memset(decodedBuffer.data(), 0, decodedBuffer.size());
|
||||
}
|
||||
|
||||
emit addedStereoSamples(decodedBuffer);
|
||||
|
||||
emit processSamples(decodedBuffer, outputBuffer);
|
||||
|
@ -55,6 +63,12 @@ int MixedProcessedAudioStream::lostAudioData(int numPackets) {
|
|||
|
||||
int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) {
|
||||
QByteArray decodedBuffer;
|
||||
|
||||
// may block on the real-time thread, which is acceptible as
|
||||
// parseAudioData is only called by the packet processing
|
||||
// thread which, while high performance, is not as sensitive to
|
||||
// delays as the real-time thread.
|
||||
QMutexLocker lock(&_decoderMutex);
|
||||
if (_decoder) {
|
||||
_decoder->decode(packetAfterStreamProperties, decodedBuffer);
|
||||
} else {
|
||||
|
|
|
@ -690,6 +690,11 @@ void Avatar::fade(render::Transaction& transaction, render::Transition::Type typ
|
|||
transaction.addTransitionToItem(itemId, type, _renderItemID);
|
||||
}
|
||||
}
|
||||
_lastFadeRequested = type;
|
||||
}
|
||||
|
||||
render::Transition::Type Avatar::getLastFadeRequested() const {
|
||||
return _lastFadeRequested;
|
||||
}
|
||||
|
||||
void Avatar::removeFromScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) {
|
||||
|
@ -779,7 +784,7 @@ void Avatar::render(RenderArgs* renderArgs) {
|
|||
pointerTransform.setTranslation(position);
|
||||
pointerTransform.setRotation(rotation);
|
||||
batch.setModelTransform(pointerTransform);
|
||||
geometryCache->bindSimpleProgram(batch);
|
||||
geometryCache->bindSimpleProgram(batch, false, false, true, false, false, true, renderArgs->_renderMethod == render::Args::FORWARD);
|
||||
geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f), laserColor, _leftPointerGeometryID);
|
||||
}
|
||||
}
|
||||
|
@ -803,7 +808,7 @@ void Avatar::render(RenderArgs* renderArgs) {
|
|||
pointerTransform.setTranslation(position);
|
||||
pointerTransform.setRotation(rotation);
|
||||
batch.setModelTransform(pointerTransform);
|
||||
geometryCache->bindSimpleProgram(batch);
|
||||
geometryCache->bindSimpleProgram(batch, false, false, true, false, false, true, renderArgs->_renderMethod == render::Args::FORWARD);
|
||||
geometryCache->renderLine(batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f), laserColor, _rightPointerGeometryID);
|
||||
}
|
||||
}
|
||||
|
@ -829,7 +834,7 @@ void Avatar::render(RenderArgs* renderArgs) {
|
|||
auto& frustum = renderArgs->getViewFrustum();
|
||||
auto textPosition = getDisplayNamePosition();
|
||||
if (frustum.pointIntersectsFrustum(textPosition)) {
|
||||
renderDisplayName(batch, frustum, textPosition);
|
||||
renderDisplayName(batch, frustum, textPosition, renderArgs->_renderMethod == render::Args::FORWARD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1034,7 +1039,7 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& view, const g
|
|||
return result;
|
||||
}
|
||||
|
||||
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const {
|
||||
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition, bool forward) const {
|
||||
PROFILE_RANGE_BATCH(batch, __FUNCTION__);
|
||||
|
||||
bool shouldShowReceiveStats = showReceiveStats && !isMyAvatar();
|
||||
|
@ -1090,7 +1095,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const
|
|||
|
||||
{
|
||||
PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderBevelCornersRect");
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, false, true, true, true);
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, false, true, true, true, true, forward);
|
||||
DependencyManager::get<GeometryCache>()->renderBevelCornersRect(batch, left, bottom, width, height,
|
||||
bevelDistance, backgroundColor, _nameRectGeometryID);
|
||||
}
|
||||
|
@ -1103,7 +1108,7 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const
|
|||
batch.setModelTransform(textTransform);
|
||||
{
|
||||
PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderText");
|
||||
renderer->draw(batch, text_x, -text_y, nameUTF8.data(), textColor);
|
||||
renderer->draw(batch, text_x, -text_y, nameUTF8.data(), textColor, glm::vec2(-1.0f), forward);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -523,6 +523,7 @@ public:
|
|||
|
||||
void fadeIn(render::ScenePointer scene);
|
||||
void fadeOut(render::Transaction& transaction, KillAvatarReason reason);
|
||||
render::Transition::Type getLastFadeRequested() const;
|
||||
|
||||
// JSDoc is in AvatarData.h.
|
||||
Q_INVOKABLE virtual float getEyeHeight() const override;
|
||||
|
@ -694,13 +695,14 @@ protected:
|
|||
glm::vec3 getDisplayNamePosition() const;
|
||||
|
||||
Transform calculateDisplayNameTransform(const ViewFrustum& view, const glm::vec3& textPosition) const;
|
||||
void renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition) const;
|
||||
void renderDisplayName(gpu::Batch& batch, const ViewFrustum& view, const glm::vec3& textPosition, bool forward) const;
|
||||
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const;
|
||||
virtual void fixupModelsInScene(const render::ScenePointer& scene);
|
||||
|
||||
virtual void updatePalms();
|
||||
|
||||
render::ItemID _renderItemID{ render::Item::INVALID_ITEM_ID };
|
||||
render::Transition::Type _lastFadeRequested { render::Transition::Type::NONE }; // Used for sanity checking
|
||||
|
||||
ThreadSafeValueCache<glm::vec3> _leftPalmPositionCache { glm::vec3() };
|
||||
ThreadSafeValueCache<glm::quat> _leftPalmRotationCache { glm::quat() };
|
||||
|
|
|
@ -220,6 +220,7 @@ void EntityTreeRenderer::stopDomainAndNonOwnedEntities() {
|
|||
void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
|
||||
stopDomainAndNonOwnedEntities();
|
||||
|
||||
auto sessionUUID = getTree()->getMyAvatarSessionUUID();
|
||||
std::unordered_map<EntityItemID, EntityRendererPointer> savedEntities;
|
||||
// remove all entities from the scene
|
||||
auto scene = _viewState->getMain3DScene();
|
||||
|
@ -227,7 +228,7 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
|
|||
for (const auto& entry : _entitiesInScene) {
|
||||
const auto& renderer = entry.second;
|
||||
const EntityItemPointer& entityItem = renderer->getEntity();
|
||||
if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) {
|
||||
if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == sessionUUID))) {
|
||||
fadeOutRenderable(renderer);
|
||||
} else {
|
||||
savedEntities[entry.first] = entry.second;
|
||||
|
@ -238,7 +239,9 @@ void EntityTreeRenderer::clearDomainAndNonOwnedEntities() {
|
|||
_renderablesToUpdate = savedEntities;
|
||||
_entitiesInScene = savedEntities;
|
||||
|
||||
_layeredZones.clearNonLocalLayeredZones();
|
||||
if (_layeredZones.clearDomainAndNonOwnedZones(sessionUUID)) {
|
||||
applyLayeredZones();
|
||||
}
|
||||
|
||||
OctreeProcessor::clearDomainAndNonOwnedEntities();
|
||||
}
|
||||
|
@ -271,6 +274,9 @@ void EntityTreeRenderer::clear() {
|
|||
|
||||
// reset the zone to the default (while we load the next scene)
|
||||
_layeredZones.clear();
|
||||
if (!_shuttingDown) {
|
||||
applyLayeredZones();
|
||||
}
|
||||
|
||||
OctreeProcessor::clear();
|
||||
}
|
||||
|
@ -363,6 +369,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r
|
|||
for (const auto& processedId : processedIds) {
|
||||
_entitiesToAdd.erase(processedId);
|
||||
}
|
||||
forceRecheckEntities();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -474,6 +481,12 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene
|
|||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::preUpdate() {
|
||||
if (_tree && !_shuttingDown) {
|
||||
_tree->preUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::update(bool simulate) {
|
||||
PROFILE_RANGE(simulation_physics, "ETR::update");
|
||||
PerformanceTimer perfTimer("ETRupdate");
|
||||
|
@ -537,8 +550,7 @@ void EntityTreeRenderer::handleSpaceUpdate(std::pair<int32_t, glm::vec4> proxyUp
|
|||
_spaceUpdates.emplace_back(proxyUpdate.first, proxyUpdate.second);
|
||||
}
|
||||
|
||||
bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QSet<EntityItemID>& entitiesContainingAvatar) {
|
||||
bool didUpdate = false;
|
||||
void EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QSet<EntityItemID>& entitiesContainingAvatar) {
|
||||
float radius = 0.01f; // for now, assume 0.01 meter radius, because we actually check the point inside later
|
||||
QVector<QUuid> entityIDs;
|
||||
|
||||
|
@ -550,7 +562,7 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QSet<EntityItemI
|
|||
// FIXME - if EntityTree had a findEntitiesContainingPoint() this could theoretically be a little faster
|
||||
entityTree->evalEntitiesInSphere(_avatarPosition, radius, PickFilter(), entityIDs);
|
||||
|
||||
LayeredZones oldLayeredZones(std::move(_layeredZones));
|
||||
LayeredZones oldLayeredZones(_layeredZones);
|
||||
_layeredZones.clear();
|
||||
|
||||
// create a list of entities that actually contain the avatar's position
|
||||
|
@ -578,8 +590,8 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QSet<EntityItemI
|
|||
|
||||
if (contains) {
|
||||
// if this entity is a zone and visible, add it to our layered zones
|
||||
if (isZone && entity->getVisible() && renderableForEntity(entity)) {
|
||||
_layeredZones.insert(std::dynamic_pointer_cast<ZoneEntityItem>(entity));
|
||||
if (isZone && entity->getVisible() && renderableIdForEntity(entity) != render::Item::INVALID_ITEM_ID) {
|
||||
_layeredZones.emplace_back(std::dynamic_pointer_cast<ZoneEntityItem>(entity));
|
||||
}
|
||||
|
||||
if ((!hasScript && isZone) || scriptHasLoaded) {
|
||||
|
@ -588,24 +600,17 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QSet<EntityItemI
|
|||
}
|
||||
}
|
||||
|
||||
// check if our layered zones have changed
|
||||
if ((_layeredZones.empty() && oldLayeredZones.empty()) || (!oldLayeredZones.empty() && _layeredZones.contains(oldLayeredZones))) {
|
||||
return;
|
||||
_layeredZones.sort();
|
||||
if (!_layeredZones.equals(oldLayeredZones)) {
|
||||
applyLayeredZones();
|
||||
}
|
||||
|
||||
applyLayeredZones();
|
||||
|
||||
didUpdate = true;
|
||||
});
|
||||
|
||||
return didUpdate;
|
||||
}
|
||||
|
||||
bool EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||
void EntityTreeRenderer::checkEnterLeaveEntities() {
|
||||
PROFILE_RANGE(simulation_physics, "EnterLeave");
|
||||
PerformanceTimer perfTimer("enterLeave");
|
||||
auto now = usecTimestampNow();
|
||||
bool didUpdate = false;
|
||||
|
||||
if (_tree && !_shuttingDown) {
|
||||
glm::vec3 avatarPosition = _viewState->getAvatarPosition();
|
||||
|
@ -623,7 +628,7 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() {
|
|||
_forceRecheckEntities = false;
|
||||
|
||||
QSet<EntityItemID> entitiesContainingAvatar;
|
||||
didUpdate = findBestZoneAndMaybeContainingEntities(entitiesContainingAvatar);
|
||||
findBestZoneAndMaybeContainingEntities(entitiesContainingAvatar);
|
||||
|
||||
// Note: at this point we don't need to worry about the tree being locked, because we only deal with
|
||||
// EntityItemIDs from here. The callEntityScriptMethod() method is robust against attempting to call scripts
|
||||
|
@ -649,7 +654,6 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() {
|
|||
}
|
||||
}
|
||||
}
|
||||
return didUpdate;
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::leaveDomainAndNonOwnedEntities() {
|
||||
|
@ -696,18 +700,12 @@ bool EntityTreeRenderer::applyLayeredZones() {
|
|||
// in the expected layered order and update the scene with it
|
||||
auto scene = _viewState->getMain3DScene();
|
||||
if (scene) {
|
||||
render::Transaction transaction;
|
||||
render::ItemIDs list;
|
||||
for (auto& zone : _layeredZones) {
|
||||
auto id = renderableIdForEntity(zone.zone);
|
||||
// The zone may not have been rendered yet.
|
||||
if (id != render::Item::INVALID_ITEM_ID) {
|
||||
list.push_back(id);
|
||||
}
|
||||
}
|
||||
render::Selection selection("RankedZones", list);
|
||||
transaction.resetSelection(selection);
|
||||
_layeredZones.appendRenderIDs(list, this);
|
||||
|
||||
render::Selection selection("RankedZones", list);
|
||||
render::Transaction transaction;
|
||||
transaction.resetSelection(selection);
|
||||
scene->enqueueTransaction(transaction);
|
||||
} else {
|
||||
qCWarning(entitiesrenderer) << "EntityTreeRenderer::applyLayeredZones(), Unexpected null scene, possibly during application shutdown";
|
||||
|
@ -1018,7 +1016,6 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::addingEntity(const EntityItemID& entityID) {
|
||||
forceRecheckEntities(); // reset our state to force checking our inside/outsideness of entities
|
||||
checkAndCallPreload(entityID);
|
||||
auto entity = std::static_pointer_cast<EntityTree>(_tree)->findEntityByID(entityID);
|
||||
if (entity) {
|
||||
|
@ -1190,107 +1187,103 @@ void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) {
|
|||
}
|
||||
|
||||
void EntityTreeRenderer::updateZone(const EntityItemID& id) {
|
||||
// Get in the zone!
|
||||
auto zone = std::dynamic_pointer_cast<ZoneEntityItem>(getTree()->findEntityByEntityItemID(id));
|
||||
if (zone && zone->contains(_avatarPosition)) {
|
||||
_layeredZones.update(zone);
|
||||
if (auto zone = std::dynamic_pointer_cast<ZoneEntityItem>(getTree()->findEntityByEntityItemID(id))) {
|
||||
if (_layeredZones.update(zone, _avatarPosition, this)) {
|
||||
applyLayeredZones();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EntityTreeRenderer::LayeredZones::LayeredZones(LayeredZones&& other) {
|
||||
// In a swap:
|
||||
// > All iterators and references remain valid. The past-the-end iterator is invalidated.
|
||||
bool isSkyboxLayerValid = (other._skyboxLayer != other.end());
|
||||
bool EntityTreeRenderer::LayeredZones::clearDomainAndNonOwnedZones(const QUuid& sessionUUID) {
|
||||
bool zonesChanged = false;
|
||||
|
||||
swap(other);
|
||||
_map.swap(other._map);
|
||||
_skyboxLayer = other._skyboxLayer;
|
||||
|
||||
if (!isSkyboxLayerValid) {
|
||||
_skyboxLayer = end();
|
||||
auto it = begin();
|
||||
while (it != end()) {
|
||||
auto zone = it->zone.lock();
|
||||
if (!zone || !(zone->isLocalEntity() || (zone->isAvatarEntity() && zone->getOwningAvatarID() == sessionUUID))) {
|
||||
zonesChanged = true;
|
||||
it = erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
if (zonesChanged) {
|
||||
sort();
|
||||
}
|
||||
return zonesChanged;
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::LayeredZones::clearNonLocalLayeredZones() {
|
||||
std::set<LayeredZone> localLayeredZones;
|
||||
std::map<QUuid, iterator> newMap;
|
||||
std::pair<bool, bool> EntityTreeRenderer::LayeredZones::getZoneInteractionProperties() const {
|
||||
for (auto it = cbegin(); it != cend(); it++) {
|
||||
auto zone = it->zone.lock();
|
||||
if (zone && zone->isDomainEntity()) {
|
||||
return { zone->getFlyingAllowed(), zone->getGhostingAllowed() };
|
||||
}
|
||||
}
|
||||
return { true, true };
|
||||
}
|
||||
|
||||
for (auto iter = begin(); iter != end(); iter++) {
|
||||
LayeredZone layeredZone = *iter;
|
||||
bool EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zone, const glm::vec3& position, EntityTreeRenderer* entityTreeRenderer) {
|
||||
// When a zone's position or visibility changes, we call this method
|
||||
// In order to resort our zones, we first remove the changed zone, and then re-insert it if necessary
|
||||
|
||||
if (layeredZone.zone->isLocalEntity()) {
|
||||
bool success;
|
||||
iterator it;
|
||||
std::tie(it, success) = localLayeredZones.insert(layeredZone);
|
||||
bool needsResort = false;
|
||||
|
||||
if (success) {
|
||||
newMap.emplace(layeredZone.id, it);
|
||||
{
|
||||
auto it = begin();
|
||||
while (it != end()) {
|
||||
if (it->zone.lock() == zone) {
|
||||
break;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
if (it != end()) {
|
||||
erase(it);
|
||||
needsResort = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Only call contains if the zone is rendering
|
||||
if (zone->isVisible() && entityTreeRenderer->renderableIdForEntity(zone) != render::Item::INVALID_ITEM_ID && zone->contains(position)) {
|
||||
emplace_back(zone);
|
||||
needsResort = true;
|
||||
}
|
||||
|
||||
if (needsResort) {
|
||||
sort();
|
||||
}
|
||||
|
||||
return needsResort;
|
||||
}
|
||||
|
||||
bool EntityTreeRenderer::LayeredZones::equals(const LayeredZones& other) const {
|
||||
if (size() != other.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = cbegin();
|
||||
auto otherIt = other.cbegin();
|
||||
while (it != cend()) {
|
||||
if (*it != *otherIt) {
|
||||
return false;
|
||||
}
|
||||
it++;
|
||||
otherIt++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::LayeredZones::appendRenderIDs(render::ItemIDs& list, EntityTreeRenderer* entityTreeRenderer) const {
|
||||
for (auto it = cbegin(); it != cend(); it++) {
|
||||
if (it->zone.lock()) {
|
||||
auto id = entityTreeRenderer->renderableIdForEntityId(it->id);
|
||||
if (id != render::Item::INVALID_ITEM_ID) {
|
||||
list.push_back(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::set<LayeredZone>::operator=(localLayeredZones);
|
||||
_map = newMap;
|
||||
_skyboxLayer = empty() ? end() : begin();
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::LayeredZones::clear() {
|
||||
std::set<LayeredZone>::clear();
|
||||
_map.clear();
|
||||
_skyboxLayer = end();
|
||||
}
|
||||
|
||||
std::pair<EntityTreeRenderer::LayeredZones::iterator, bool> EntityTreeRenderer::LayeredZones::insert(const LayeredZone& layer) {
|
||||
iterator it;
|
||||
bool success;
|
||||
std::tie(it, success) = std::set<LayeredZone>::insert(layer);
|
||||
|
||||
if (success) {
|
||||
_map.emplace(it->id, it);
|
||||
}
|
||||
|
||||
return { it, success };
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zone) {
|
||||
bool isVisible = zone->isVisible();
|
||||
|
||||
if (empty() && isVisible) {
|
||||
// there are no zones: set this one
|
||||
insert(zone);
|
||||
return;
|
||||
} else {
|
||||
LayeredZone zoneLayer(zone);
|
||||
|
||||
// find this zone's layer, if it exists
|
||||
iterator layer = end();
|
||||
auto it = _map.find(zoneLayer.id);
|
||||
if (it != _map.end()) {
|
||||
layer = it->second;
|
||||
// if the volume changed, we need to resort the layer (reinsertion)
|
||||
// if the visibility changed, we need to erase the layer
|
||||
if (zoneLayer.volume != layer->volume || !isVisible) {
|
||||
erase(layer);
|
||||
_map.erase(it);
|
||||
layer = end();
|
||||
}
|
||||
}
|
||||
|
||||
// (re)insert this zone's layer if necessary
|
||||
if (layer == end() && isVisible) {
|
||||
std::tie(layer, std::ignore) = insert(zoneLayer);
|
||||
_map.emplace(layer->id, layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool EntityTreeRenderer::LayeredZones::contains(const LayeredZones& other) {
|
||||
bool result = std::equal(other.begin(), other._skyboxLayer, begin());
|
||||
if (result) {
|
||||
// if valid, set the _skyboxLayer from the other LayeredZones
|
||||
_skyboxLayer = std::next(begin(), std::distance(other.begin(), other._skyboxLayer));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CalculateEntityLoadingPriority EntityTreeRenderer::_calculateEntityLoadingPriorityFunc = [](const EntityItem& item) -> float {
|
||||
|
@ -1298,14 +1291,7 @@ CalculateEntityLoadingPriority EntityTreeRenderer::_calculateEntityLoadingPriori
|
|||
};
|
||||
|
||||
std::pair<bool, bool> EntityTreeRenderer::getZoneInteractionProperties() {
|
||||
for (auto& zone : _layeredZones) {
|
||||
// Only domain entities control flying allowed and ghosting allowed
|
||||
if (zone.zone && zone.zone->isDomainEntity()) {
|
||||
return { zone.zone->getFlyingAllowed(), zone.zone->getGhostingAllowed() };
|
||||
}
|
||||
}
|
||||
|
||||
return { true, true };
|
||||
return _layeredZones.getZoneInteractionProperties();
|
||||
}
|
||||
|
||||
bool EntityTreeRenderer::wantsKeyboardFocus(const EntityItemID& id) const {
|
||||
|
|
|
@ -78,6 +78,7 @@ public:
|
|||
void setSetPrecisionPickingOperator(std::function<void(unsigned int, bool)> setPrecisionPickingOperator) { _setPrecisionPickingOperator = setPrecisionPickingOperator; }
|
||||
|
||||
void shutdown();
|
||||
void preUpdate();
|
||||
void update(bool simulate);
|
||||
|
||||
EntityTreePointer getTree() { return std::static_pointer_cast<EntityTree>(_tree); }
|
||||
|
@ -169,7 +170,7 @@ private:
|
|||
|
||||
void resetEntitiesScriptEngine();
|
||||
|
||||
bool findBestZoneAndMaybeContainingEntities(QSet<EntityItemID>& entitiesContainingAvatar);
|
||||
void findBestZoneAndMaybeContainingEntities(QSet<EntityItemID>& entitiesContainingAvatar);
|
||||
|
||||
bool applyLayeredZones();
|
||||
void stopDomainAndNonOwnedEntities();
|
||||
|
@ -180,7 +181,7 @@ private:
|
|||
EntityItemID _currentClickingOnEntityID;
|
||||
|
||||
QScriptValueList createEntityArgs(const EntityItemID& entityID);
|
||||
bool checkEnterLeaveEntities();
|
||||
void checkEnterLeaveEntities();
|
||||
void leaveDomainAndNonOwnedEntities();
|
||||
void leaveAllEntities();
|
||||
void forceRecheckEntities();
|
||||
|
@ -210,48 +211,38 @@ private:
|
|||
|
||||
class LayeredZone {
|
||||
public:
|
||||
LayeredZone(std::shared_ptr<ZoneEntityItem> zone, QUuid id, float volume) : zone(zone), id(id), volume(volume) {}
|
||||
LayeredZone(std::shared_ptr<ZoneEntityItem> zone) : LayeredZone(zone, zone->getID(), zone->getVolumeEstimate()) {}
|
||||
LayeredZone(std::shared_ptr<ZoneEntityItem> zone) : zone(zone), id(zone->getID()), volume(zone->getVolumeEstimate()) {}
|
||||
|
||||
bool operator<(const LayeredZone& r) const { return std::tie(volume, id) < std::tie(r.volume, r.id); }
|
||||
bool operator==(const LayeredZone& r) const { return id == r.id; }
|
||||
// We need to sort on volume AND id so that different clients sort zones with identical volumes the same way
|
||||
bool operator<(const LayeredZone& r) const { return volume < r.volume || (volume == r.volume && id < r.id); }
|
||||
bool operator==(const LayeredZone& r) const { return zone.lock() && zone.lock() == r.zone.lock(); }
|
||||
bool operator!=(const LayeredZone& r) const { return !(*this == r); }
|
||||
bool operator<=(const LayeredZone& r) const { return (*this < r) || (*this == r); }
|
||||
|
||||
std::shared_ptr<ZoneEntityItem> zone;
|
||||
std::weak_ptr<ZoneEntityItem> zone;
|
||||
QUuid id;
|
||||
float volume;
|
||||
};
|
||||
|
||||
class LayeredZones : public std::set<LayeredZone> {
|
||||
class LayeredZones : public std::vector<LayeredZone> {
|
||||
public:
|
||||
LayeredZones() {};
|
||||
LayeredZones(LayeredZones&& other);
|
||||
bool clearDomainAndNonOwnedZones(const QUuid& sessionUUID);
|
||||
|
||||
// avoid accidental misconstruction
|
||||
LayeredZones(const LayeredZones&) = delete;
|
||||
LayeredZones& operator=(const LayeredZones&) = delete;
|
||||
LayeredZones& operator=(LayeredZones&&) = delete;
|
||||
void sort() { std::sort(begin(), end(), std::less<LayeredZone>()); }
|
||||
bool equals(const LayeredZones& other) const;
|
||||
bool update(std::shared_ptr<ZoneEntityItem> zone, const glm::vec3& position, EntityTreeRenderer* entityTreeRenderer);
|
||||
|
||||
void clear();
|
||||
void clearNonLocalLayeredZones();
|
||||
std::pair<iterator, bool> insert(const LayeredZone& layer);
|
||||
void update(std::shared_ptr<ZoneEntityItem> zone);
|
||||
bool contains(const LayeredZones& other);
|
||||
|
||||
std::shared_ptr<ZoneEntityItem> getZone() { return empty() ? nullptr : begin()->zone; }
|
||||
|
||||
private:
|
||||
std::map<QUuid, iterator> _map;
|
||||
iterator _skyboxLayer { end() };
|
||||
void appendRenderIDs(render::ItemIDs& list, EntityTreeRenderer* entityTreeRenderer) const;
|
||||
std::pair<bool, bool> getZoneInteractionProperties() const;
|
||||
};
|
||||
|
||||
LayeredZones _layeredZones;
|
||||
float _avgRenderableUpdateCost { 0.0f };
|
||||
|
||||
uint64_t _lastZoneCheck { 0 };
|
||||
const uint64_t ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz
|
||||
const float ZONE_CHECK_DISTANCE = 0.001f;
|
||||
|
||||
float _avgRenderableUpdateCost { 0.0f };
|
||||
|
||||
ReadWriteLockable _changedEntitiesGuard;
|
||||
std::unordered_set<EntityItemID> _changedEntities;
|
||||
|
||||
|
|
|
@ -261,15 +261,17 @@ void GizmoEntityRenderer::doRender(RenderArgs* args) {
|
|||
Transform transform;
|
||||
bool hasTickMarks;
|
||||
glm::vec4 tickProperties;
|
||||
bool forward;
|
||||
withReadLock([&] {
|
||||
transform = _renderTransform;
|
||||
hasTickMarks = _ringProperties.getHasTickMarks();
|
||||
tickProperties = glm::vec4(_ringProperties.getMajorTickMarksAngle(), _ringProperties.getMajorTickMarksLength(),
|
||||
_ringProperties.getMinorTickMarksAngle(), _ringProperties.getMinorTickMarksLength());
|
||||
forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD;
|
||||
});
|
||||
|
||||
bool wireframe = render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES;
|
||||
geometryCache->bindSimpleProgram(batch, false, isTransparent(), false, wireframe, true, true, _renderLayer != RenderLayer::WORLD);
|
||||
geometryCache->bindSimpleProgram(batch, false, isTransparent(), false, wireframe, true, true, forward);
|
||||
|
||||
batch.setModelTransform(transform);
|
||||
|
||||
|
|
|
@ -113,11 +113,13 @@ void GridEntityRenderer::doRender(RenderArgs* args) {
|
|||
glm::vec4 color;
|
||||
glm::vec3 dimensions;
|
||||
Transform renderTransform;
|
||||
bool forward;
|
||||
withReadLock([&] {
|
||||
color = glm::vec4(toGlm(_color), _alpha);
|
||||
color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created);
|
||||
dimensions = _dimensions;
|
||||
renderTransform = _renderTransform;
|
||||
forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD;
|
||||
});
|
||||
|
||||
if (!_visible) {
|
||||
|
@ -153,5 +155,5 @@ void GridEntityRenderer::doRender(RenderArgs* args) {
|
|||
DependencyManager::get<GeometryCache>()->renderGrid(*batch, minCorner, maxCorner,
|
||||
minorGridRowDivisions, minorGridColDivisions, MINOR_GRID_EDGE,
|
||||
majorGridRowDivisions, majorGridColDivisions, MAJOR_GRID_EDGE,
|
||||
color, _geometryId);
|
||||
color, forward, _geometryId);
|
||||
}
|
|
@ -55,7 +55,8 @@ void LineEntityRenderer::doRender(RenderArgs* args) {
|
|||
transform.setRotation(modelTransform.getRotation());
|
||||
batch.setModelTransform(transform);
|
||||
if (_linePoints.size() > 1) {
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch);
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, false, true, false, false, true,
|
||||
_renderLayer != RenderLayer::WORLD || args->_renderMethod == Args::RenderMethod::FORWARD);
|
||||
DependencyManager::get<GeometryCache>()->renderVertices(batch, gpu::LINE_STRIP, _lineVerticesID);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -735,13 +735,15 @@ bool RenderableModelEntityItem::shouldBePhysical() const {
|
|||
auto model = getModel();
|
||||
// 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 || getShapeType() == SHAPE_TYPE_SIMPLE_COMPOUND) && model->didCollisionGeometryRequestFail()) {
|
||||
return false;
|
||||
} else if (model && getShapeType() != SHAPE_TYPE_NONE && model->didVisualGeometryRequestFail()) {
|
||||
return false;
|
||||
} else {
|
||||
return ModelEntityItem::shouldBePhysical();
|
||||
ShapeType shapeType = getShapeType();
|
||||
if (model) {
|
||||
if ((shapeType == SHAPE_TYPE_COMPOUND || shapeType == SHAPE_TYPE_SIMPLE_COMPOUND) && model->didCollisionGeometryRequestFail()) {
|
||||
return false;
|
||||
} else if (shapeType != SHAPE_TYPE_NONE && model->didVisualGeometryRequestFail()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !isDead() && shapeType != SHAPE_TYPE_NONE && QUrl(_modelURL).isValid();
|
||||
}
|
||||
|
||||
int RenderableModelEntityItem::getJointParent(int index) const {
|
||||
|
@ -1520,7 +1522,7 @@ void ModelEntityRenderer::doRender(RenderArgs* args) {
|
|||
model = _model;
|
||||
});
|
||||
if (model) {
|
||||
model->renderDebugMeshBoxes(batch);
|
||||
model->renderDebugMeshBoxes(batch, args->_renderMethod == Args::RenderMethod::FORWARD);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -19,15 +19,12 @@
|
|||
#include <PerfStat.h>
|
||||
#include <shaders/Shaders.h>
|
||||
|
||||
#include <DisableDeferred.h>
|
||||
|
||||
#include "paintStroke_Shared.slh"
|
||||
|
||||
using namespace render;
|
||||
using namespace render::entities;
|
||||
|
||||
gpu::PipelinePointer PolyLineEntityRenderer::_pipeline = nullptr;
|
||||
gpu::PipelinePointer PolyLineEntityRenderer::_glowPipeline = nullptr;
|
||||
std::map<std::pair<render::Args::RenderMethod, bool>, gpu::PipelinePointer> PolyLineEntityRenderer::_pipelines;
|
||||
|
||||
static const QUrl DEFAULT_POLYLINE_TEXTURE = PathUtils::resourcesUrl("images/paintStroke.png");
|
||||
|
||||
|
@ -44,29 +41,24 @@ PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity)
|
|||
}
|
||||
}
|
||||
|
||||
void PolyLineEntityRenderer::buildPipeline() {
|
||||
// FIXME: opaque pipeline
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(DISABLE_DEFERRED ? shader::entities_renderer::program::paintStroke_forward : shader::entities_renderer::program::paintStroke);
|
||||
void PolyLineEntityRenderer::buildPipelines() {
|
||||
// FIXME: opaque pipelines
|
||||
|
||||
static const std::vector<std::pair<render::Args::RenderMethod, bool>> keys = {
|
||||
{ render::Args::DEFERRED, false }, { render::Args::DEFERRED, true }, { render::Args::FORWARD, false }, { render::Args::FORWARD, true },
|
||||
};
|
||||
|
||||
for (auto& key : keys) {
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(key.first == render::Args::DEFERRED ? shader::entities_renderer::program::paintStroke : shader::entities_renderer::program::paintStroke_forward);
|
||||
|
||||
{
|
||||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
state->setCullMode(gpu::State::CullMode::CULL_NONE);
|
||||
state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
state->setDepthTest(true, !key.second, gpu::LESS_EQUAL);
|
||||
PrepareStencil::testMask(*state);
|
||||
state->setBlendFunction(true,
|
||||
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);
|
||||
_pipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
{
|
||||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
state->setCullMode(gpu::State::CullMode::CULL_NONE);
|
||||
state->setDepthTest(true, false, gpu::LESS_EQUAL);
|
||||
PrepareStencil::testMask(*state);
|
||||
state->setBlendFunction(true,
|
||||
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);
|
||||
_glowPipeline = gpu::Pipeline::create(program, state);
|
||||
_pipelines[key] = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -299,11 +291,11 @@ void PolyLineEntityRenderer::doRender(RenderArgs* args) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!_pipeline) {
|
||||
buildPipeline();
|
||||
if (_pipelines.empty()) {
|
||||
buildPipelines();
|
||||
}
|
||||
|
||||
batch.setPipeline(_glow ? _glowPipeline : _pipeline);
|
||||
batch.setPipeline(_pipelines[{args->_renderMethod, _glow}]);
|
||||
batch.setModelTransform(transform);
|
||||
batch.setResourceTexture(0, texture);
|
||||
batch.draw(gpu::TRIANGLE_STRIP, (gpu::uint32)(2 * numVertices), 0);
|
||||
|
|
|
@ -37,7 +37,7 @@ protected:
|
|||
virtual ShapeKey getShapeKey() override;
|
||||
virtual void doRender(RenderArgs* args) override;
|
||||
|
||||
void buildPipeline();
|
||||
static void buildPipelines();
|
||||
void updateGeometry();
|
||||
void updateData();
|
||||
|
||||
|
@ -58,8 +58,7 @@ protected:
|
|||
size_t _numVertices;
|
||||
gpu::BufferPointer _polylineDataBuffer;
|
||||
gpu::BufferPointer _polylineGeometryBuffer;
|
||||
static gpu::PipelinePointer _pipeline;
|
||||
static gpu::PipelinePointer _glowPipeline;
|
||||
static std::map<std::pair<render::Args::RenderMethod, bool>, gpu::PipelinePointer> _pipelines;
|
||||
};
|
||||
|
||||
} } // namespace
|
||||
|
|
|
@ -72,7 +72,6 @@ public:
|
|||
glm::mat4 localToVoxelMatrix() const;
|
||||
|
||||
virtual ShapeType getShapeType() const override;
|
||||
virtual bool shouldBePhysical() const override { return !isDead(); }
|
||||
virtual bool isReadyToComputeShape() const override;
|
||||
virtual void computeShapeInfo(ShapeInfo& info) override;
|
||||
|
||||
|
|
|
@ -280,7 +280,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
|
|||
// FIXME, support instanced multi-shape rendering using multidraw indirect
|
||||
outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
||||
render::ShapePipelinePointer pipeline;
|
||||
if (renderLayer == RenderLayer::WORLD) {
|
||||
if (renderLayer == RenderLayer::WORLD && args->_renderMethod != Args::RenderMethod::FORWARD) {
|
||||
pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline();
|
||||
} else {
|
||||
pipeline = outColor.a < 1.0f ? geometryCache->getForwardTransparentShapePipeline() : geometryCache->getForwardOpaqueShapePipeline();
|
||||
|
|
|
@ -163,7 +163,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
Transform modelTransform;
|
||||
glm::vec3 dimensions;
|
||||
BillboardMode billboardMode;
|
||||
bool layered;
|
||||
bool forward;
|
||||
withReadLock([&] {
|
||||
modelTransform = _renderTransform;
|
||||
dimensions = _dimensions;
|
||||
|
@ -174,7 +174,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
textColor = EntityRenderer::calculatePulseColor(textColor, _pulseProperties, _created);
|
||||
backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha);
|
||||
backgroundColor = EntityRenderer::calculatePulseColor(backgroundColor, _pulseProperties, _created);
|
||||
layered = _renderLayer != RenderLayer::WORLD;
|
||||
forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD;
|
||||
});
|
||||
|
||||
// Render background
|
||||
|
@ -187,7 +187,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
gpu::Batch& batch = *args->_batch;
|
||||
|
||||
// FIXME: we need to find a better way of rendering text so we don't have to do this
|
||||
if (layered) {
|
||||
if (forward) {
|
||||
DependencyManager::get<DeferredLightingEffect>()->setupKeyLightBatch(args, batch);
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
if (backgroundColor.a > 0.0f) {
|
||||
batch.setModelTransform(transformToTopLeft);
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false, true, layered);
|
||||
geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false, true, forward);
|
||||
geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID);
|
||||
}
|
||||
|
||||
|
@ -210,7 +210,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
batch.setModelTransform(transformToTopLeft);
|
||||
|
||||
glm::vec2 bounds = glm::vec2(dimensions.x - (_leftMargin + _rightMargin), dimensions.y - (_topMargin + _bottomMargin));
|
||||
_textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale, layered);
|
||||
_textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale, forward);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -92,9 +92,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
|
|||
}
|
||||
|
||||
{ // Sun
|
||||
// Need an update ?
|
||||
if (_needSunUpdate) {
|
||||
// Do we need to allocate the light in the stage ?
|
||||
if (LightStage::isIndexInvalid(_sunIndex)) {
|
||||
_sunIndex = _stage->addLight(_sunLight);
|
||||
} else {
|
||||
|
@ -107,9 +105,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
|
|||
{ // Ambient
|
||||
updateAmbientMap();
|
||||
|
||||
// Need an update ?
|
||||
if (_needAmbientUpdate) {
|
||||
// Do we need to allocate the light in the stage ?
|
||||
if (LightStage::isIndexInvalid(_ambientIndex)) {
|
||||
_ambientIndex = _stage->addLight(_ambientLight);
|
||||
} else {
|
||||
|
@ -123,7 +119,7 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
|
|||
updateSkyboxMap();
|
||||
|
||||
if (_needBackgroundUpdate) {
|
||||
if (_skyboxMode == COMPONENT_MODE_ENABLED && BackgroundStage::isIndexInvalid(_backgroundIndex)) {
|
||||
if (BackgroundStage::isIndexInvalid(_backgroundIndex)) {
|
||||
_backgroundIndex = _backgroundStage->addBackground(_background);
|
||||
}
|
||||
_needBackgroundUpdate = false;
|
||||
|
@ -186,24 +182,19 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
|
|||
}
|
||||
}
|
||||
|
||||
void ZoneEntityRenderer::removeFromScene(const ScenePointer& scene, Transaction& transaction) {
|
||||
#if 0
|
||||
if (_model) {
|
||||
_model->removeFromScene(scene, transaction);
|
||||
}
|
||||
#endif
|
||||
Parent::removeFromScene(scene, transaction);
|
||||
}
|
||||
|
||||
void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
|
||||
DependencyManager::get<EntityTreeRenderer>()->updateZone(entity->getID());
|
||||
|
||||
auto position = entity->getWorldPosition();
|
||||
auto rotation = entity->getWorldOrientation();
|
||||
auto dimensions = entity->getScaledDimensions();
|
||||
bool rotationChanged = rotation != _lastRotation;
|
||||
bool transformChanged = rotationChanged || position != _lastPosition || dimensions != _lastDimensions;
|
||||
|
||||
auto visible = entity->getVisible();
|
||||
if (transformChanged || visible != _lastVisible) {
|
||||
_lastVisible = visible;
|
||||
DependencyManager::get<EntityTreeRenderer>()->updateZone(entity->getID());
|
||||
}
|
||||
|
||||
auto proceduralUserData = entity->getUserData();
|
||||
bool proceduralUserDataChanged = _proceduralUserData != proceduralUserData;
|
||||
|
||||
|
@ -226,25 +217,6 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen
|
|||
_proceduralUserData = entity->getUserData();
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (_lastShapeURL != _typedEntity->getCompoundShapeURL()) {
|
||||
_lastShapeURL = _typedEntity->getCompoundShapeURL();
|
||||
_model.reset();
|
||||
_model = std::make_shared<Model>();
|
||||
_model->setIsWireframe(true);
|
||||
_model->init();
|
||||
_model->setURL(_lastShapeURL);
|
||||
}
|
||||
|
||||
if (_model && _model->isActive()) {
|
||||
_model->setScaleToFit(true, _lastDimensions);
|
||||
_model->setSnapModelToRegistrationPoint(true, _entity->getRegistrationPoint());
|
||||
_model->setRotation(_lastRotation);
|
||||
_model->setTranslation(_lastPosition);
|
||||
_model->simulate(0.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
updateKeyZoneItemFromEntity(entity);
|
||||
|
||||
if (keyLightChanged) {
|
||||
|
@ -296,6 +268,10 @@ ItemKey ZoneEntityRenderer::getKey() {
|
|||
}
|
||||
|
||||
bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const {
|
||||
if (entity->getVisible() != _lastVisible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (entity->keyLightPropertiesChanged() ||
|
||||
entity->ambientLightPropertiesChanged() ||
|
||||
entity->hazePropertiesChanged() ||
|
||||
|
@ -323,25 +299,7 @@ bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint
|
|||
return true;
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (_typedEntity->getCompoundShapeURL() != _lastShapeURL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_model) {
|
||||
if (!_model->needsFixupInScene() && (!ZoneEntityItem::getDrawZoneBoundaries() || _entity->getShapeType() != SHAPE_TYPE_COMPOUND)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_model->needsFixupInScene() && (ZoneEntityItem::getDrawZoneBoundaries() || _entity->getShapeType() == SHAPE_TYPE_COMPOUND)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_lastModelActive != _model->isActive()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// FIXME: do we need to trigger an update when shapeType changes? see doRenderUpdateAsynchronousTyped
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -450,7 +408,6 @@ void ZoneEntityRenderer::updateKeyZoneItemFromEntity(const TypedEntityPointer& e
|
|||
}
|
||||
|
||||
void ZoneEntityRenderer::setAmbientURL(const QString& ambientUrl) {
|
||||
// nothing change if nothing change
|
||||
if (_ambientTextureURL == ambientUrl) {
|
||||
return;
|
||||
}
|
||||
|
@ -466,8 +423,6 @@ void ZoneEntityRenderer::setAmbientURL(const QString& ambientUrl) {
|
|||
_pendingAmbientTexture = true;
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
_ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::AMBIENT_TEXTURE);
|
||||
|
||||
// keep whatever is assigned on the ambient map/sphere until texture is loaded
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -492,7 +447,6 @@ void ZoneEntityRenderer::updateAmbientMap() {
|
|||
}
|
||||
|
||||
void ZoneEntityRenderer::setSkyboxURL(const QString& skyboxUrl) {
|
||||
// nothing change if nothing change
|
||||
if (_skyboxTextureURL == skyboxUrl) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -24,9 +24,6 @@
|
|||
#include "RenderableEntityItem.h"
|
||||
#include <ComponentMode.h>
|
||||
|
||||
#if 0
|
||||
#include <Model.h>
|
||||
#endif
|
||||
namespace render { namespace entities {
|
||||
|
||||
class ZoneEntityRenderer : public TypedEntityRenderer<ZoneEntityItem> {
|
||||
|
@ -40,7 +37,6 @@ protected:
|
|||
virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override;
|
||||
virtual ItemKey getKey() override;
|
||||
virtual void doRender(RenderArgs* args) override;
|
||||
virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction) override;
|
||||
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
|
||||
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
|
||||
virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override;
|
||||
|
@ -76,14 +72,7 @@ private:
|
|||
glm::vec3 _lastPosition;
|
||||
glm::vec3 _lastDimensions;
|
||||
glm::quat _lastRotation;
|
||||
|
||||
// FIXME compount shapes are currently broken
|
||||
// FIXME draw zone boundaries are currently broken (also broken in master)
|
||||
#if 0
|
||||
ModelPointer _model;
|
||||
bool _lastModelActive { false };
|
||||
QString _lastShapeURL;
|
||||
#endif
|
||||
bool _lastVisible;
|
||||
|
||||
LightStagePointer _stage;
|
||||
const graphics::LightPointer _sunLight { std::make_shared<graphics::Light>() };
|
||||
|
@ -137,25 +126,4 @@ private:
|
|||
|
||||
} } // namespace
|
||||
|
||||
#if 0
|
||||
|
||||
class NetworkGeometry;
|
||||
class KeyLightPayload;
|
||||
|
||||
class RenderableZoneEntityItemMeta;
|
||||
|
||||
class RenderableZoneEntityItem : public ZoneEntityItem, public RenderableEntityInterface {
|
||||
public:
|
||||
virtual bool contains(const glm::vec3& point) const override;
|
||||
virtual bool addToScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) override;
|
||||
virtual void removeFromScene(const EntityItemPointer& self, const render::ScenePointer& scene, render::Transaction& transaction) override;
|
||||
private:
|
||||
virtual void locationChanged(bool tellPhysics = true, bool tellChildren = true) override { EntityItem::locationChanged(tellPhysics, tellChildren); notifyBoundChanged(); }
|
||||
virtual void dimensionsChanged() override { EntityItem::dimensionsChanged(); notifyBoundChanged(); }
|
||||
void notifyBoundChanged();
|
||||
void notifyChangedRenderItem();
|
||||
void sceneUpdateRenderItemFromEntity(render::Transaction& transaction);
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // hifi_RenderableZoneEntityItem_h
|
||||
|
|
|
@ -110,6 +110,10 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
|
|||
|
||||
EntityPropertyFlags requestedProperties = propertiesCopy.getChangedProperties();
|
||||
|
||||
if (!nodeList->getThisNodeCanGetAndSetPrivateUserData() && requestedProperties.getHasProperty(PROP_PRIVATE_USER_DATA)) {
|
||||
requestedProperties -= PROP_PRIVATE_USER_DATA;
|
||||
}
|
||||
|
||||
while (encodeResult == OctreeElement::PARTIAL) {
|
||||
encodeResult = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut, requestedProperties, didntFitProperties);
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
|||
requestedProperties += PROP_NAME;
|
||||
requestedProperties += PROP_LOCKED;
|
||||
requestedProperties += PROP_USER_DATA;
|
||||
requestedProperties += PROP_PRIVATE_USER_DATA;
|
||||
requestedProperties += PROP_HREF;
|
||||
requestedProperties += PROP_DESCRIPTION;
|
||||
requestedProperties += PROP_POSITION;
|
||||
|
@ -154,7 +155,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
|||
}
|
||||
|
||||
OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData) const {
|
||||
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
|
||||
const bool destinationNodeCanGetAndSetPrivateUserData) const {
|
||||
|
||||
// ALL this fits...
|
||||
// object ID [16 bytes]
|
||||
|
@ -198,6 +200,11 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
requestedProperties = entityTreeElementExtraEncodeData->entities.value(getEntityItemID());
|
||||
}
|
||||
|
||||
QString privateUserData = "";
|
||||
if (destinationNodeCanGetAndSetPrivateUserData) {
|
||||
privateUserData = getPrivateUserData();
|
||||
}
|
||||
|
||||
EntityPropertyFlags propertiesDidntFit = requestedProperties;
|
||||
|
||||
LevelDetails entityLevel = packetData->startLevel();
|
||||
|
@ -266,8 +273,8 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, _simulationOwner.toByteArray());
|
||||
// convert AVATAR_SELF_ID to actual sessionUUID.
|
||||
QUuid actualParentID = getParentID();
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
if (actualParentID == AVATAR_SELF_ID) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
actualParentID = nodeList->getSessionUUID();
|
||||
}
|
||||
APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, actualParentID);
|
||||
|
@ -276,6 +283,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
|||
APPEND_ENTITY_PROPERTY(PROP_NAME, getName());
|
||||
APPEND_ENTITY_PROPERTY(PROP_LOCKED, getLocked());
|
||||
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, getUserData());
|
||||
APPEND_ENTITY_PROPERTY(PROP_PRIVATE_USER_DATA, privateUserData);
|
||||
APPEND_ENTITY_PROPERTY(PROP_HREF, getHref());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription());
|
||||
APPEND_ENTITY_PROPERTY(PROP_POSITION, getLocalPosition());
|
||||
|
@ -812,6 +820,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
READ_ENTITY_PROPERTY(PROP_NAME, QString, setName);
|
||||
READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked);
|
||||
READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData);
|
||||
READ_ENTITY_PROPERTY(PROP_PRIVATE_USER_DATA, QString, setPrivateUserData);
|
||||
READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref);
|
||||
READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription);
|
||||
{ // When we own the simulation we don't accept updates to the entity's transform/velocities
|
||||
|
@ -1331,6 +1340,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire
|
|||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(locked, getLocked);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(userData, getUserData);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(privateUserData, getPrivateUserData);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getLocalPosition);
|
||||
|
@ -1479,6 +1489,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(privateUserData, setPrivateUserData);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPosition);
|
||||
|
@ -1828,42 +1839,42 @@ void EntityItem::setParentID(const QUuid& value) {
|
|||
if (!value.isNull() && tree) {
|
||||
EntityItemPointer entity = tree->findEntityByEntityItemID(value);
|
||||
if (entity) {
|
||||
newParentNoBootstrapping = entity->getSpecialFlags() & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
||||
newParentNoBootstrapping = entity->getSpecialFlags() & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
|
||||
}
|
||||
}
|
||||
|
||||
if (!oldParentID.isNull() && tree) {
|
||||
EntityItemPointer entity = tree->findEntityByEntityItemID(oldParentID);
|
||||
if (entity) {
|
||||
oldParentNoBootstrapping = entity->getDirtyFlags() & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
||||
oldParentNoBootstrapping = entity->getDirtyFlags() & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
|
||||
}
|
||||
}
|
||||
|
||||
if (!value.isNull() && (value == Physics::getSessionUUID() || value == AVATAR_SELF_ID)) {
|
||||
newParentNoBootstrapping |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
||||
newParentNoBootstrapping |= Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
|
||||
}
|
||||
|
||||
if (!oldParentID.isNull() && (oldParentID == Physics::getSessionUUID() || oldParentID == AVATAR_SELF_ID)) {
|
||||
oldParentNoBootstrapping |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
||||
oldParentNoBootstrapping |= Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
|
||||
}
|
||||
|
||||
if ((bool)(oldParentNoBootstrapping ^ newParentNoBootstrapping)) {
|
||||
if ((bool)(newParentNoBootstrapping & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) {
|
||||
markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
|
||||
if ((bool)(newParentNoBootstrapping & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING)) {
|
||||
markSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
|
||||
forEachDescendant([&](SpatiallyNestablePointer object) {
|
||||
if (object->getNestableType() == NestableType::Entity) {
|
||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
|
||||
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
||||
entity->markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
|
||||
entity->markSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
|
||||
clearSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
|
||||
forEachDescendant([&](SpatiallyNestablePointer object) {
|
||||
if (object->getNestableType() == NestableType::Entity) {
|
||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
|
||||
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
||||
entity->clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
|
||||
entity->clearSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2102,7 +2113,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int32_t& group, int32_t& mask
|
|||
}
|
||||
}
|
||||
|
||||
if ((bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) {
|
||||
if ((bool)(_flags & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING)) {
|
||||
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
|
||||
}
|
||||
mask = Physics::getDefaultCollisionMask(group) & (int32_t)(userMask);
|
||||
|
@ -2173,8 +2184,8 @@ bool EntityItem::addAction(EntitySimulationPointer simulation, EntityDynamicPoin
|
|||
}
|
||||
|
||||
void EntityItem::enableNoBootstrap() {
|
||||
if (!(bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) {
|
||||
_flags |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
||||
if (!(bool)(_flags & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING)) {
|
||||
_flags |= Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
|
||||
_flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||
|
||||
// NOTE: unlike disableNoBootstrap() below, we do not call simulation->changeEntity() here
|
||||
|
@ -2186,7 +2197,7 @@ void EntityItem::enableNoBootstrap() {
|
|||
if (child->getNestableType() == NestableType::Entity) {
|
||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
|
||||
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
||||
entity->markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
|
||||
entity->markSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2194,7 +2205,7 @@ void EntityItem::enableNoBootstrap() {
|
|||
|
||||
void EntityItem::disableNoBootstrap() {
|
||||
if (!stillHasMyGrabAction()) {
|
||||
_flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
||||
_flags &= ~Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
|
||||
_flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||
|
||||
EntityTreePointer entityTree = getTree();
|
||||
|
@ -2207,7 +2218,7 @@ void EntityItem::disableNoBootstrap() {
|
|||
if (child->getNestableType() == NestableType::Entity) {
|
||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
|
||||
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
||||
entity->clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
|
||||
entity->clearSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
|
||||
simulation->changeEntity(entity);
|
||||
}
|
||||
});
|
||||
|
@ -2326,7 +2337,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
|
|||
if (removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) {
|
||||
disableNoBootstrap();
|
||||
} else {
|
||||
// NO-OP: we assume SPECIAL_FLAGS_NO_BOOTSTRAPPING bits and collision group are correct
|
||||
// NO-OP: we assume SPECIAL_FLAG_NO_BOOTSTRAPPING bits and collision group are correct
|
||||
// because they should have been set correctly when the action was added
|
||||
// and/or when children were linked
|
||||
}
|
||||
|
@ -3121,6 +3132,20 @@ void EntityItem::setUserData(const QString& value) {
|
|||
});
|
||||
}
|
||||
|
||||
QString EntityItem::getPrivateUserData() const {
|
||||
QString result;
|
||||
withReadLock([&] {
|
||||
result = _privateUserData;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::setPrivateUserData(const QString& value) {
|
||||
withWriteLock([&] {
|
||||
_privateUserData = value;
|
||||
});
|
||||
}
|
||||
|
||||
// Certifiable Properties
|
||||
#define DEFINE_PROPERTY_GETTER(type, accessor, var) \
|
||||
type EntityItem::get##accessor() const { \
|
||||
|
@ -3154,21 +3179,21 @@ DEFINE_PROPERTY_ACCESSOR(quint32, StaticCertificateVersion, staticCertificateVer
|
|||
uint32_t EntityItem::getDirtyFlags() const {
|
||||
uint32_t result;
|
||||
withReadLock([&] {
|
||||
result = _flags & Simulation::DIRTY_FLAGS;
|
||||
result = _flags & Simulation::DIRTY_FLAGS_MASK;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::markDirtyFlags(uint32_t mask) {
|
||||
withWriteLock([&] {
|
||||
mask &= Simulation::DIRTY_FLAGS;
|
||||
mask &= Simulation::DIRTY_FLAGS_MASK;
|
||||
_flags |= mask;
|
||||
});
|
||||
}
|
||||
|
||||
void EntityItem::clearDirtyFlags(uint32_t mask) {
|
||||
withWriteLock([&] {
|
||||
mask &= Simulation::DIRTY_FLAGS;
|
||||
mask &= Simulation::DIRTY_FLAGS_MASK;
|
||||
_flags &= ~mask;
|
||||
});
|
||||
}
|
||||
|
@ -3176,21 +3201,21 @@ void EntityItem::clearDirtyFlags(uint32_t mask) {
|
|||
uint32_t EntityItem::getSpecialFlags() const {
|
||||
uint32_t result;
|
||||
withReadLock([&] {
|
||||
result = _flags & Simulation::SPECIAL_FLAGS;
|
||||
result = _flags & Simulation::SPECIAL_FLAGS_MASK;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityItem::markSpecialFlags(uint32_t mask) {
|
||||
withWriteLock([&] {
|
||||
mask &= Simulation::SPECIAL_FLAGS;
|
||||
mask &= Simulation::SPECIAL_FLAGS_MASK;
|
||||
_flags |= mask;
|
||||
});
|
||||
}
|
||||
|
||||
void EntityItem::clearSpecialFlags(uint32_t mask) {
|
||||
withWriteLock([&] {
|
||||
mask &= Simulation::SPECIAL_FLAGS;
|
||||
mask &= Simulation::SPECIAL_FLAGS_MASK;
|
||||
_flags &= ~mask;
|
||||
});
|
||||
}
|
||||
|
@ -3431,6 +3456,10 @@ bool EntityItem::isWearable() const {
|
|||
(getParentID() == DependencyManager::get<NodeList>()->getSessionUUID() || getParentID() == AVATAR_SELF_ID);
|
||||
}
|
||||
|
||||
bool EntityItem::isMyAvatarEntity() const {
|
||||
return _hostType == entity::HostType::AVATAR && Physics::getSessionUUID() == _owningAvatarID;
|
||||
};
|
||||
|
||||
void EntityItem::addGrab(GrabPointer grab) {
|
||||
enableNoBootstrap();
|
||||
SpatiallyNestable::addGrab(grab);
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <OctreeElement.h> // for OctreeElement::AppendState
|
||||
#include <OctreePacketData.h>
|
||||
#include <PhysicsCollisionGroups.h>
|
||||
#include <SimulationFlags.h>
|
||||
#include <ShapeInfo.h>
|
||||
#include <Transform.h>
|
||||
#include <SpatiallyNestable.h>
|
||||
|
@ -33,11 +34,11 @@
|
|||
#include "EntityPropertyFlags.h"
|
||||
#include "EntityTypes.h"
|
||||
#include "SimulationOwner.h"
|
||||
#include "SimulationFlags.h"
|
||||
#include "EntityDynamicInterface.h"
|
||||
#include "GrabPropertyGroup.h"
|
||||
|
||||
class EntitySimulation;
|
||||
using EntitySimulationPointer = std::shared_ptr<EntitySimulation>;
|
||||
class EntityTreeElement;
|
||||
class EntityTreeElementExtraEncodeData;
|
||||
class EntityDynamicInterface;
|
||||
|
@ -133,7 +134,8 @@ public:
|
|||
virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const;
|
||||
|
||||
virtual OctreeElement::AppendState appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData) const;
|
||||
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
|
||||
const bool destinationNodeCanGetAndSetPrivateUserData = false) const;
|
||||
|
||||
virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params,
|
||||
EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData,
|
||||
|
@ -322,7 +324,7 @@ public:
|
|||
bool getDynamic() const;
|
||||
void setDynamic(bool value);
|
||||
|
||||
virtual bool shouldBePhysical() const { return false; }
|
||||
virtual bool shouldBePhysical() const { return !isDead() && getShapeType() != SHAPE_TYPE_NONE; }
|
||||
bool isVisuallyReady() const { return _visuallyReady; }
|
||||
|
||||
bool getLocked() const;
|
||||
|
@ -331,6 +333,9 @@ public:
|
|||
QString getUserData() const;
|
||||
virtual void setUserData(const QString& value); // FIXME: This is suspicious
|
||||
|
||||
QString getPrivateUserData() const;
|
||||
void setPrivateUserData(const QString& value);
|
||||
|
||||
// FIXME not thread safe?
|
||||
const SimulationOwner& getSimulationOwner() const { return _simulationOwner; }
|
||||
void setSimulationOwner(const QUuid& id, uint8_t priority);
|
||||
|
@ -423,8 +428,9 @@ public:
|
|||
|
||||
bool isSimulated() const { return _simulated; }
|
||||
|
||||
void* getPhysicsInfo() const { return _physicsInfo; }
|
||||
bool isInPhysicsSimulation() const { return (bool)(_flags & Simulation::SPECIAL_FLAG_IN_PHYSICS_SIMULATION); }
|
||||
|
||||
void* getPhysicsInfo() const { return _physicsInfo; }
|
||||
void setPhysicsInfo(void* data) { _physicsInfo = data; }
|
||||
|
||||
EntityTreeElementPointer getElement() const { return _element; }
|
||||
|
@ -501,6 +507,7 @@ public:
|
|||
virtual bool isWearable() const;
|
||||
bool isDomainEntity() const { return _hostType == entity::HostType::DOMAIN; }
|
||||
bool isAvatarEntity() const { return _hostType == entity::HostType::AVATAR; }
|
||||
bool isMyAvatarEntity() const;
|
||||
bool isLocalEntity() const { return _hostType == entity::HostType::LOCAL; }
|
||||
entity::HostType getEntityHostType() const { return _hostType; }
|
||||
virtual void setEntityHostType(entity::HostType hostType) { _hostType = hostType; }
|
||||
|
@ -641,6 +648,7 @@ protected:
|
|||
bool _dynamic { ENTITY_ITEM_DEFAULT_DYNAMIC };
|
||||
bool _locked { ENTITY_ITEM_DEFAULT_LOCKED };
|
||||
QString _userData { ENTITY_ITEM_DEFAULT_USER_DATA };
|
||||
QString _privateUserData{ ENTITY_ITEM_DEFAULT_PRIVATE_USER_DATA };
|
||||
SimulationOwner _simulationOwner;
|
||||
bool _shouldHighlight { false };
|
||||
QString _name { ENTITY_ITEM_DEFAULT_NAME };
|
||||
|
|
|
@ -487,6 +487,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_NAME, name);
|
||||
CHECK_PROPERTY_CHANGE(PROP_LOCKED, locked);
|
||||
CHECK_PROPERTY_CHANGE(PROP_USER_DATA, userData);
|
||||
CHECK_PROPERTY_CHANGE(PROP_PRIVATE_USER_DATA, privateUserData);
|
||||
CHECK_PROPERTY_CHANGE(PROP_HREF, href);
|
||||
CHECK_PROPERTY_CHANGE(PROP_DESCRIPTION, description);
|
||||
CHECK_PROPERTY_CHANGE(PROP_POSITION, position);
|
||||
|
@ -801,7 +802,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
*
|
||||
* @property {boolean} collisionless=false - Whether or not the entity should collide with items per its
|
||||
* <code>collisionMask<code> property. If <code>true</code> then the entity does not collide. A synonym is <code>ignoreForCollisions</code>.
|
||||
* @property {Entities.CollisionMask} collisionMask=31 - What types of items the entity should collide with.
|
||||
* @property {CollisionMask} collisionMask=31 - What types of items the entity should collide with.
|
||||
* @property {string} collidesWith="static,dynamic,kinematic,myAvatar,otherAvatar," - Synonym for <code>collisionMask</code>,
|
||||
* in text format.
|
||||
* @property {string} collisionSoundURL="" - The sound to play when the entity experiences a collision. Valid file formats are
|
||||
|
@ -818,6 +819,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
* which you can manipulate the properties of, and use <code>JSON.stringify()</code> to convert the object into a string to
|
||||
* put in the property.
|
||||
*
|
||||
* @property {string} privateUserData="" - Like userData, but only accessible by Entity Server Scripts, AC scripts, and users
|
||||
* who are given "Can Get and Set Private User Data" permissions from the ACL matrix on the Domain Settings page.
|
||||
*
|
||||
* @property {string} script="" - The URL of the client entity script, if any, that is attached to the entity.
|
||||
* @property {number} scriptTimestamp=0 - Intended to be used to indicate when the client entity script was loaded. Should be
|
||||
* an integer number of milliseconds since midnight GMT on January 1, 1970 (e.g., as supplied by <code>Date.now()</code>.
|
||||
|
@ -1591,6 +1595,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NAME, name);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCKED, locked);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USER_DATA, userData);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PRIVATE_USER_DATA, privateUserData);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_HREF, href);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DESCRIPTION, description);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POSITION, position);
|
||||
|
@ -1999,6 +2004,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE(name, QString, setName);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(userData, QString, setUserData);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(privateUserData, QString, setPrivateUserData);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(href, QString, setHref);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(description, QString, setDescription);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(position, vec3, setPosition);
|
||||
|
@ -2288,6 +2294,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
|
|||
COPY_PROPERTY_IF_CHANGED(name);
|
||||
COPY_PROPERTY_IF_CHANGED(locked);
|
||||
COPY_PROPERTY_IF_CHANGED(userData);
|
||||
COPY_PROPERTY_IF_CHANGED(privateUserData);
|
||||
COPY_PROPERTY_IF_CHANGED(href);
|
||||
COPY_PROPERTY_IF_CHANGED(description);
|
||||
COPY_PROPERTY_IF_CHANGED(position);
|
||||
|
@ -2573,6 +2580,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
|
|||
ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_LOCKED, Locked, locked, bool);
|
||||
ADD_PROPERTY_TO_MAP(PROP_USER_DATA, UserData, userData, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_PRIVATE_USER_DATA, PrivateUserData, privateUserData, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_HREF, Href, href, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_DESCRIPTION, Description, description, QString);
|
||||
ADD_PROPERTY_TO_MAP(PROP_POSITION, Position, position, vec3);
|
||||
|
@ -3047,6 +3055,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
|
|||
APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName());
|
||||
APPEND_ENTITY_PROPERTY(PROP_LOCKED, properties.getLocked());
|
||||
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, properties.getUserData());
|
||||
APPEND_ENTITY_PROPERTY(PROP_PRIVATE_USER_DATA, properties.getPrivateUserData());
|
||||
APPEND_ENTITY_PROPERTY(PROP_HREF, properties.getHref());
|
||||
APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, properties.getDescription());
|
||||
APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition());
|
||||
|
@ -3530,6 +3539,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCKED, bool, setLocked);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USER_DATA, QString, setUserData);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PRIVATE_USER_DATA, QString, setPrivateUserData);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HREF, QString, setHref);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DESCRIPTION, QString, setDescription);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, vec3, setPosition);
|
||||
|
@ -3941,6 +3951,7 @@ void EntityItemProperties::markAllChanged() {
|
|||
_nameChanged = true;
|
||||
_lockedChanged = true;
|
||||
_userDataChanged = true;
|
||||
_privateUserDataChanged = true;
|
||||
_hrefChanged = true;
|
||||
_descriptionChanged = true;
|
||||
_positionChanged = true;
|
||||
|
@ -4303,6 +4314,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
if (userDataChanged()) {
|
||||
out += "userData";
|
||||
}
|
||||
if (privateUserDataChanged()) {
|
||||
out += "privateUserData";
|
||||
}
|
||||
if (hrefChanged()) {
|
||||
out += "href";
|
||||
}
|
||||
|
|
|
@ -180,6 +180,7 @@ public:
|
|||
DEFINE_PROPERTY_REF(PROP_NAME, Name, name, QString, ENTITY_ITEM_DEFAULT_NAME);
|
||||
DEFINE_PROPERTY(PROP_LOCKED, Locked, locked, bool, ENTITY_ITEM_DEFAULT_LOCKED);
|
||||
DEFINE_PROPERTY_REF(PROP_USER_DATA, UserData, userData, QString, ENTITY_ITEM_DEFAULT_USER_DATA);
|
||||
DEFINE_PROPERTY_REF(PROP_PRIVATE_USER_DATA, PrivateUserData, privateUserData, QString, ENTITY_ITEM_DEFAULT_PRIVATE_USER_DATA);
|
||||
DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString, "");
|
||||
DEFINE_PROPERTY_REF(PROP_DESCRIPTION, Description, description, QString, "");
|
||||
DEFINE_PROPERTY_REF_WITH_SETTER(PROP_POSITION, Position, position, glm::vec3, ENTITY_ITEM_ZERO_VEC3);
|
||||
|
@ -607,6 +608,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
|
|||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Locked, locked, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Textures, textures, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, UserData, userData, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, PrivateUserData, privateUserData, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, SimulationOwner, simulationOwner, SimulationOwner());
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Text, text, "");
|
||||
DEBUG_PROPERTY_IF_CHANGED(debug, properties, LineHeight, lineHeight, "");
|
||||
|
|
|
@ -28,6 +28,7 @@ const QVector<glm::vec3> ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC = QVector<glm::vec3
|
|||
|
||||
const bool ENTITY_ITEM_DEFAULT_LOCKED = false;
|
||||
const QString ENTITY_ITEM_DEFAULT_USER_DATA = QString("");
|
||||
const QString ENTITY_ITEM_DEFAULT_PRIVATE_USER_DATA = QString("");
|
||||
const QUuid ENTITY_ITEM_DEFAULT_SIMULATOR_ID = QUuid();
|
||||
|
||||
// Certifiable Properties
|
||||
|
|
|
@ -26,6 +26,7 @@ enum EntityPropertyList {
|
|||
PROP_NAME,
|
||||
PROP_LOCKED,
|
||||
PROP_USER_DATA,
|
||||
PROP_PRIVATE_USER_DATA,
|
||||
PROP_HREF,
|
||||
PROP_DESCRIPTION,
|
||||
PROP_POSITION,
|
||||
|
|
|
@ -56,6 +56,7 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership
|
|||
connect(nodeList.data(), &NodeList::canRezCertifiedChanged, this, &EntityScriptingInterface::canRezCertifiedChanged);
|
||||
connect(nodeList.data(), &NodeList::canRezTmpCertifiedChanged, this, &EntityScriptingInterface::canRezTmpCertifiedChanged);
|
||||
connect(nodeList.data(), &NodeList::canWriteAssetsChanged, this, &EntityScriptingInterface::canWriteAssetsChanged);
|
||||
connect(nodeList.data(), &NodeList::canGetAndSetPrivateUserDataChanged, this, &EntityScriptingInterface::canGetAndSetPrivateUserDataChanged);
|
||||
|
||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||
packetReceiver.registerListener(PacketType::EntityScriptCallMethod, this, "handleEntityScriptCallMethodPacket");
|
||||
|
@ -107,6 +108,11 @@ bool EntityScriptingInterface::canReplaceContent() {
|
|||
return nodeList->getThisNodeCanReplaceContent();
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::canGetAndSetPrivateUserData() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
return nodeList->getThisNodeCanGetAndSetPrivateUserData();
|
||||
}
|
||||
|
||||
void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) {
|
||||
if (_entityTree) {
|
||||
disconnect(_entityTree.get(), &EntityTree::addingEntityPointer, this, &EntityScriptingInterface::onAddingEntity);
|
||||
|
|
|
@ -236,6 +236,14 @@ public slots:
|
|||
*/
|
||||
Q_INVOKABLE bool canReplaceContent();
|
||||
|
||||
/**jsdoc
|
||||
* Check whether or not you can get and set private user data.
|
||||
* @function Entities.canGetAndSetPrivateUserData
|
||||
* @returns {boolean} <code>true</code> if the domain server will allow the user to get and set private user data,
|
||||
* otherwise <code>false</code>.
|
||||
*/
|
||||
Q_INVOKABLE bool canGetAndSetPrivateUserData();
|
||||
|
||||
/**jsdoc
|
||||
* <p>How an entity is sent over the wire.</p>
|
||||
* <table>
|
||||
|
@ -1861,6 +1869,15 @@ signals:
|
|||
*/
|
||||
void canWriteAssetsChanged(bool canWriteAssets);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when your ability to get and set private user data changes.
|
||||
* @function Entities.canGetAndSetPrivateUserDataChanged
|
||||
* @param {boolean} canGetAndSetPrivateUserData - <code>true</code> if you can change the <code>privateUserData</code> property of an entity,
|
||||
* otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void canGetAndSetPrivateUserDataChanged(bool canGetAndSetPrivateUserData);
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when a mouse button is clicked while the mouse cursor is on an entity, or a controller trigger is fully
|
||||
|
|
|
@ -176,47 +176,44 @@ void EntitySimulation::addEntity(EntityItemPointer entity) {
|
|||
void EntitySimulation::changeEntity(EntityItemPointer entity) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
assert(entity);
|
||||
if (!entity->isSimulated()) {
|
||||
// This entity was either never added to the simulation or has been removed
|
||||
// (probably for pending delete), so we don't want to keep a pointer to it
|
||||
// on any internal lists.
|
||||
return;
|
||||
}
|
||||
_changedEntities.insert(entity);
|
||||
}
|
||||
|
||||
// Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes
|
||||
// it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence
|
||||
// we must check for that case here, however we rely on the change event to have set DIRTY_POSITION flag.
|
||||
void EntitySimulation::processChangedEntities() {
|
||||
QMutexLocker lock(&_mutex);
|
||||
PROFILE_RANGE_EX(simulation_physics, "processChangedEntities", 0xffff00ff, (uint64_t)_changedEntities.size());
|
||||
for (auto& entity : _changedEntities) {
|
||||
if (entity->isSimulated()) {
|
||||
processChangedEntity(entity);
|
||||
}
|
||||
}
|
||||
_changedEntities.clear();
|
||||
}
|
||||
|
||||
void EntitySimulation::processChangedEntity(const EntityItemPointer& entity) {
|
||||
uint32_t dirtyFlags = entity->getDirtyFlags();
|
||||
if (dirtyFlags & Simulation::DIRTY_POSITION) {
|
||||
AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE);
|
||||
bool success;
|
||||
AACube newCube = entity->getQueryAACube(success);
|
||||
if (success && !domainBounds.touches(newCube)) {
|
||||
qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds.";
|
||||
entity->die();
|
||||
prepareEntityForDelete(entity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (dirtyFlags & Simulation::DIRTY_LIFETIME) {
|
||||
if (entity->isMortal()) {
|
||||
_mortalEntities.insert(entity);
|
||||
uint64_t expiry = entity->getExpiry();
|
||||
if (expiry < _nextExpiry) {
|
||||
_nextExpiry = expiry;
|
||||
if (dirtyFlags & (Simulation::DIRTY_LIFETIME | Simulation::DIRTY_UPDATEABLE)) {
|
||||
if (dirtyFlags & Simulation::DIRTY_LIFETIME) {
|
||||
if (entity->isMortal()) {
|
||||
_mortalEntities.insert(entity);
|
||||
uint64_t expiry = entity->getExpiry();
|
||||
if (expiry < _nextExpiry) {
|
||||
_nextExpiry = expiry;
|
||||
}
|
||||
} else {
|
||||
_mortalEntities.remove(entity);
|
||||
}
|
||||
} else {
|
||||
_mortalEntities.remove(entity);
|
||||
}
|
||||
entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME);
|
||||
if (dirtyFlags & Simulation::DIRTY_UPDATEABLE) {
|
||||
if (entity->needsToCallUpdate()) {
|
||||
_entitiesToUpdate.insert(entity);
|
||||
} else {
|
||||
_entitiesToUpdate.remove(entity);
|
||||
}
|
||||
}
|
||||
entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME | Simulation::DIRTY_UPDATEABLE);
|
||||
}
|
||||
if (entity->needsToCallUpdate()) {
|
||||
_entitiesToUpdate.insert(entity);
|
||||
} else {
|
||||
_entitiesToUpdate.remove(entity);
|
||||
}
|
||||
changeEntityInternal(entity);
|
||||
}
|
||||
|
||||
void EntitySimulation::clearEntities() {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#define hifi_EntitySimulation_h
|
||||
|
||||
#include <limits>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QSet>
|
||||
|
@ -82,13 +83,15 @@ public:
|
|||
/// \param entity pointer to EntityItem that needs to be put on the entitiesToDelete list and removed from others.
|
||||
virtual void prepareEntityForDelete(EntityItemPointer entity);
|
||||
|
||||
void processChangedEntities();
|
||||
|
||||
protected:
|
||||
// These pure virtual methods are protected because they are not to be called will-nilly. The base class
|
||||
// calls them in the right places.
|
||||
virtual void updateEntitiesInternal(uint64_t now) = 0;
|
||||
virtual void addEntityInternal(EntityItemPointer entity) = 0;
|
||||
virtual void removeEntityInternal(EntityItemPointer entity);
|
||||
virtual void changeEntityInternal(EntityItemPointer entity) = 0;
|
||||
virtual void processChangedEntity(const EntityItemPointer& entity);
|
||||
virtual void clearEntitiesInternal() = 0;
|
||||
|
||||
void expireMortalEntities(uint64_t now);
|
||||
|
@ -114,11 +117,11 @@ private:
|
|||
|
||||
// We maintain multiple lists, each for its distinct purpose.
|
||||
// An entity may be in more than one list.
|
||||
std::unordered_set<EntityItemPointer> _changedEntities; // all changes this frame
|
||||
SetOfEntities _allEntities; // tracks all entities added the simulation
|
||||
SetOfEntities _mortalEntities; // entities that have an expiry
|
||||
uint64_t _nextExpiry;
|
||||
|
||||
|
||||
SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update()
|
||||
};
|
||||
|
||||
|
|
|
@ -1286,6 +1286,14 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList<Q
|
|||
}
|
||||
}
|
||||
|
||||
if (properties.privateUserDataChanged()) {
|
||||
int index = changedProperties.indexOf("privateUserData");
|
||||
if (index >= 0) {
|
||||
QString changeHint = properties.getPrivateUserData();
|
||||
changedProperties[index] = QString("privateUserData:") + changeHint;
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.parentJointIndexChanged()) {
|
||||
int index = changedProperties.indexOf("parentJointIndex");
|
||||
if (index >= 0) {
|
||||
|
@ -1772,6 +1780,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
|
||||
bool suppressDisallowedClientScript = false;
|
||||
bool suppressDisallowedServerScript = false;
|
||||
bool suppressDisallowedPrivateUserData = false;
|
||||
bool isPhysics = message.getType() == PacketType::EntityPhysics;
|
||||
|
||||
_totalEditMessages++;
|
||||
|
@ -1860,7 +1869,22 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!properties.getPrivateUserData().isEmpty() && validEditPacket && !senderNode->getCanGetAndSetPrivateUserData()) {
|
||||
if (wantEditLogging()) {
|
||||
qCDebug(entities) << "User [" << senderNode->getUUID()
|
||||
<< "] is attempting to set private user data but user isn't allowed; edit rejected...";
|
||||
}
|
||||
|
||||
// If this was an add, we also want to tell the client that sent this edit that the entity was not added.
|
||||
if (isAdd) {
|
||||
QWriteLocker locker(&_recentlyDeletedEntitiesLock);
|
||||
_recentlyDeletedEntityItemIDs.insert(usecTimestampNow(), entityItemID);
|
||||
validEditPacket = false;
|
||||
} else {
|
||||
suppressDisallowedPrivateUserData = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isClone) {
|
||||
|
@ -1915,6 +1939,11 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
properties.setServerScripts(existingEntity->getServerScripts());
|
||||
}
|
||||
|
||||
if (suppressDisallowedPrivateUserData) {
|
||||
bumpTimestamp(properties);
|
||||
properties.setPrivateUserData(existingEntity->getPrivateUserData());
|
||||
}
|
||||
|
||||
// if the EntityItem exists, then update it
|
||||
startLogging = usecTimestampNow();
|
||||
if (wantEditLogging()) {
|
||||
|
@ -2079,7 +2108,6 @@ void EntityTree::entityChanged(EntityItemPointer entity) {
|
|||
}
|
||||
|
||||
void EntityTree::fixupNeedsParentFixups() {
|
||||
PROFILE_RANGE(simulation_physics, "FixupParents");
|
||||
MovingEntitiesOperator moveOperator;
|
||||
QVector<EntityItemWeakPointer> entitiesToFixup;
|
||||
{
|
||||
|
@ -2189,11 +2217,19 @@ void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) {
|
|||
_needsParentFixup.append(entity);
|
||||
}
|
||||
|
||||
void EntityTree::preUpdate() {
|
||||
withWriteLock([&] {
|
||||
fixupNeedsParentFixups();
|
||||
if (_simulation) {
|
||||
_simulation->processChangedEntities();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void EntityTree::update(bool simulate) {
|
||||
PROFILE_RANGE(simulation_physics, "UpdateTree");
|
||||
PerformanceTimer perfTimer("updateTree");
|
||||
withWriteLock([&] {
|
||||
fixupNeedsParentFixups();
|
||||
if (simulate && _simulation) {
|
||||
_simulation->updateEntities();
|
||||
{
|
||||
|
|
|
@ -109,9 +109,10 @@ public:
|
|||
|
||||
virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const override;
|
||||
|
||||
virtual void update() override { update(true); }
|
||||
|
||||
void update(bool simulate);
|
||||
// Why preUpdate() and update()?
|
||||
// Because sometimes we need to do stuff between the two.
|
||||
void preUpdate() override;
|
||||
void update(bool simulate = true) override;
|
||||
|
||||
// The newer API...
|
||||
void postAddEntity(EntityItemPointer entityItem);
|
||||
|
|
|
@ -421,11 +421,6 @@ void ModelEntityItem::setAnimationFPS(float value) {
|
|||
});
|
||||
}
|
||||
|
||||
// virtual
|
||||
bool ModelEntityItem::shouldBePhysical() const {
|
||||
return !isDead() && getShapeType() != SHAPE_TYPE_NONE && QUrl(_modelURL).isValid();
|
||||
}
|
||||
|
||||
void ModelEntityItem::resizeJointArrays(int newSize) {
|
||||
if (newSize < 0) {
|
||||
return;
|
||||
|
|
|
@ -118,8 +118,6 @@ public:
|
|||
const QString getTextures() const;
|
||||
void setTextures(const QString& textures);
|
||||
|
||||
virtual bool shouldBePhysical() const override;
|
||||
|
||||
virtual void setJointRotations(const QVector<glm::quat>& rotations);
|
||||
virtual void setJointRotationsSet(const QVector<bool>& rotationsSet);
|
||||
virtual void setJointTranslations(const QVector<glm::vec3>& translations);
|
||||
|
|
|
@ -231,6 +231,8 @@ public:
|
|||
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
|
||||
bool& somethingChanged) override;
|
||||
|
||||
bool shouldBePhysical() const override { return false; }
|
||||
|
||||
void setColor(const glm::u8vec3& value);
|
||||
glm::u8vec3 getColor() const { return _particleProperties.color.gradient.target; }
|
||||
|
||||
|
|
|
@ -164,7 +164,6 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
glm::vec3 getSurfacePositionAdjustment() const;
|
||||
|
||||
virtual ShapeType getShapeType() const override;
|
||||
virtual bool shouldBePhysical() const override { return !isDead(); }
|
||||
|
||||
bool isEdged() const;
|
||||
|
||||
|
|
|
@ -84,8 +84,6 @@ public:
|
|||
|
||||
void setUnscaledDimensions(const glm::vec3& value) override;
|
||||
|
||||
bool shouldBePhysical() const override { return !isDead(); }
|
||||
|
||||
bool supportsDetailedIntersection() const override;
|
||||
bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
OctreeElementPointer& element, float& distance,
|
||||
|
|
|
@ -85,7 +85,9 @@ void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
|
|||
_entitiesThatNeedSimulationOwner.remove(entity);
|
||||
}
|
||||
|
||||
void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
|
||||
void SimpleEntitySimulation::processChangedEntity(const EntityItemPointer& entity) {
|
||||
EntitySimulation::processChangedEntity(entity);
|
||||
|
||||
uint32_t flags = entity->getDirtyFlags();
|
||||
if ((flags & Simulation::DIRTY_SIMULATOR_ID) || (flags & Simulation::DIRTY_VELOCITIES)) {
|
||||
if (entity->getSimulatorID().isNull()) {
|
||||
|
|
|
@ -31,7 +31,7 @@ protected:
|
|||
void updateEntitiesInternal(uint64_t now) override;
|
||||
void addEntityInternal(EntityItemPointer entity) override;
|
||||
void removeEntityInternal(EntityItemPointer entity) override;
|
||||
void changeEntityInternal(EntityItemPointer entity) override;
|
||||
void processChangedEntity(const EntityItemPointer& entity) override;
|
||||
void clearEntitiesInternal() override;
|
||||
|
||||
void sortEntitiesThatMoved() override;
|
||||
|
|
|
@ -89,6 +89,8 @@
|
|||
// (14) When an entity's ownership priority drops to YIELD (=1, below VOLUNTEER) other participants may
|
||||
// bid for it immediately at VOLUNTEER.
|
||||
//
|
||||
/* These declarations temporarily moved to SimulationFlags.h while we unravel some spaghetti dependencies.
|
||||
* The intent is to move them back here once the dust settles.
|
||||
const uint8_t YIELD_SIMULATION_PRIORITY = 1;
|
||||
const uint8_t VOLUNTEER_SIMULATION_PRIORITY = YIELD_SIMULATION_PRIORITY + 1;
|
||||
const uint8_t RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
|
||||
|
@ -101,6 +103,7 @@ const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY
|
|||
// which really just means: things that collide with it will be bid at a priority level one lower
|
||||
const uint8_t PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY;
|
||||
const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = PERSONAL_SIMULATION_PRIORITY;
|
||||
*/
|
||||
|
||||
|
||||
class SimulationOwner {
|
||||
|
|
|
@ -62,6 +62,7 @@ public:
|
|||
virtual bool isReadyToComputeShape() const override { return false; }
|
||||
virtual void setShapeType(ShapeType type) override;
|
||||
virtual ShapeType getShapeType() const override;
|
||||
bool shouldBePhysical() const override { return false; }
|
||||
|
||||
QString getCompoundShapeURL() const;
|
||||
virtual void setCompoundShapeURL(const QString& url);
|
||||
|
|
|
@ -71,19 +71,159 @@ void gl::globalRelease(bool finish) {}
|
|||
#endif
|
||||
|
||||
|
||||
void gl::getTargetVersion(int& major, int& minor) {
|
||||
uint16_t gl::getTargetVersion() {
|
||||
uint8_t major = 0, minor = 0;
|
||||
|
||||
#if defined(USE_GLES)
|
||||
major = 3;
|
||||
minor = 2;
|
||||
#else
|
||||
#if defined(Q_OS_MAC)
|
||||
#elif defined(Q_OS_MAC)
|
||||
major = 4;
|
||||
minor = 1;
|
||||
#else
|
||||
major = 4;
|
||||
minor = disableGl45() ? 1 : 5;
|
||||
#endif
|
||||
return GL_MAKE_VERSION(major, minor);
|
||||
}
|
||||
|
||||
uint16_t gl::getRequiredVersion() {
|
||||
uint8_t major = 0, minor = 0;
|
||||
#if defined(USE_GLES)
|
||||
major = 3;
|
||||
minor = 2;
|
||||
#else
|
||||
major = 4;
|
||||
minor = 1;
|
||||
#endif
|
||||
return GL_MAKE_VERSION(major, minor);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
|
||||
typedef BOOL(APIENTRYP PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats);
|
||||
typedef HGLRC(APIENTRYP PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShareContext, const int *attribList);
|
||||
GLAPI PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;
|
||||
GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;
|
||||
|
||||
static bool setupPixelFormatSimple(HDC hdc) {
|
||||
// FIXME build the PFD based on the
|
||||
static const PIXELFORMATDESCRIPTOR pfd = // pfd Tells Windows How We Want Things To Be
|
||||
{
|
||||
sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor
|
||||
1, // Version Number
|
||||
PFD_DRAW_TO_WINDOW | // Format Must Support Window
|
||||
PFD_SUPPORT_OPENGL | // Format Must Support OpenGL
|
||||
PFD_DOUBLEBUFFER, // Must Support Double Buffering
|
||||
PFD_TYPE_RGBA, // Request An RGBA Format
|
||||
24, // Select Our Color Depth
|
||||
0, 0, 0, 0, 0, 0, // Color Bits Ignored
|
||||
1, // Alpha Buffer
|
||||
0, // Shift Bit Ignored
|
||||
0, // No Accumulation Buffer
|
||||
0, 0, 0, 0, // Accumulation Bits Ignored
|
||||
24, // 24 Bit Z-Buffer (Depth Buffer)
|
||||
8, // 8 Bit Stencil Buffer
|
||||
0, // No Auxiliary Buffer
|
||||
PFD_MAIN_PLANE, // Main Drawing Layer
|
||||
0, // Reserved
|
||||
0, 0, 0 // Layer Masks Ignored
|
||||
};
|
||||
auto pixelFormat = ChoosePixelFormat(hdc, &pfd);
|
||||
if (pixelFormat == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SetPixelFormat(hdc, pixelFormat, &pfd) == FALSE) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
uint16_t gl::getAvailableVersion() {
|
||||
static uint8_t major = 0, minor = 0;
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&] {
|
||||
#if defined(USE_GLES)
|
||||
// FIXME do runtime detection of the available GL version
|
||||
major = 3;
|
||||
minor = 2;
|
||||
#elif defined(Q_OS_MAC)
|
||||
// Damn it Apple.
|
||||
major = 4;
|
||||
minor = 1;
|
||||
#elif defined(Q_OS_WIN)
|
||||
//
|
||||
HINSTANCE hInstance = GetModuleHandle(nullptr);
|
||||
const auto windowClassName = "OpenGLVersionCheck";
|
||||
WNDCLASS wc = { };
|
||||
wc.lpfnWndProc = DefWindowProc;
|
||||
wc.hInstance = hInstance;
|
||||
wc.lpszClassName = windowClassName;
|
||||
RegisterClass(&wc);
|
||||
|
||||
using Handle = std::shared_ptr<void>;
|
||||
HWND rawHwnd = CreateWindowEx(
|
||||
WS_EX_APPWINDOW, // extended style
|
||||
windowClassName, // class name
|
||||
windowClassName, // title
|
||||
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CS_OWNDC | WS_POPUP, // style
|
||||
0, 0, 10, 10, // position and size
|
||||
NULL, NULL, hInstance, NULL);
|
||||
auto WindowDestroyer = [](void* handle) {
|
||||
DestroyWindow((HWND)handle);
|
||||
};
|
||||
Handle hWnd = Handle(rawHwnd, WindowDestroyer);
|
||||
if (!hWnd) {
|
||||
return;
|
||||
}
|
||||
HDC rawDC = GetDC(rawHwnd);
|
||||
auto DCDestroyer = [=](void* handle) {
|
||||
ReleaseDC(rawHwnd, (HDC)handle);
|
||||
};
|
||||
if (!rawDC) {
|
||||
return;
|
||||
}
|
||||
Handle hDC = Handle(rawDC, DCDestroyer);
|
||||
if (!setupPixelFormatSimple(rawDC)) {
|
||||
return;
|
||||
}
|
||||
auto GLRCDestroyer = [](void* handle) {
|
||||
wglDeleteContext((HGLRC)handle);
|
||||
};
|
||||
auto rawglrc = wglCreateContext(rawDC);
|
||||
if (!rawglrc) {
|
||||
return;
|
||||
}
|
||||
Handle hGLRC = Handle(rawglrc, GLRCDestroyer);
|
||||
if (!wglMakeCurrent(rawDC, rawglrc)) {
|
||||
return;
|
||||
}
|
||||
gl::initModuleGl();
|
||||
wglMakeCurrent(0, 0);
|
||||
hGLRC.reset();
|
||||
if (!wglChoosePixelFormatARB || !wglCreateContextAttribsARB) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The only two versions we care about on Windows
|
||||
// are 4.5 and 4.1
|
||||
if (GLAD_GL_VERSION_4_5) {
|
||||
major = 4;
|
||||
minor = disableGl45() ? 1 : 5;
|
||||
} else if (GLAD_GL_VERSION_4_1) {
|
||||
major = 4;
|
||||
minor = 1;
|
||||
}
|
||||
#else
|
||||
// FIXME do runtime detection of GL version on non-Mac/Windows/Mobile platforms
|
||||
major = 4;
|
||||
minor = disableGl45() ? 1 : 5;
|
||||
#endif
|
||||
});
|
||||
return GL_MAKE_VERSION(major, minor);
|
||||
}
|
||||
|
||||
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
|
||||
|
@ -105,10 +245,9 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
|
|||
// Qt Quick may need a depth and stencil buffer. Always make sure these are available.
|
||||
format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS);
|
||||
format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS);
|
||||
int major, minor;
|
||||
::gl::getTargetVersion(major, minor);
|
||||
format.setMajorVersion(major);
|
||||
format.setMinorVersion(minor);
|
||||
auto glversion = ::gl::getTargetVersion();
|
||||
format.setMajorVersion(GL_GET_MAJOR_VERSION(glversion));
|
||||
format.setMinorVersion(GL_GET_MINOR_VERSION(glversion));
|
||||
});
|
||||
return format;
|
||||
}
|
||||
|
|
|
@ -34,6 +34,10 @@ int glVersionToInteger(QString glVersion);
|
|||
|
||||
bool isRenderThread();
|
||||
|
||||
#define GL_MAKE_VERSION(major, minor) ((major << 8) | minor)
|
||||
#define GL_GET_MINOR_VERSION(glversion) (glversion & 0x00FF)
|
||||
#define GL_GET_MAJOR_VERSION(glversion) ((glversion & 0xFF00) >> 8)
|
||||
|
||||
namespace gl {
|
||||
void globalLock();
|
||||
void globalRelease(bool finish = true);
|
||||
|
@ -52,7 +56,11 @@ namespace gl {
|
|||
|
||||
bool disableGl45();
|
||||
|
||||
void getTargetVersion(int& major, int& minor);
|
||||
uint16_t getTargetVersion();
|
||||
|
||||
uint16_t getAvailableVersion();
|
||||
|
||||
uint16_t getRequiredVersion();
|
||||
} // namespace gl
|
||||
|
||||
#define CHECK_GL_ERROR() ::gl::checkGLErrorDebug(__FUNCTION__)
|
||||
|
|
|
@ -61,7 +61,7 @@ void CalculateBlendshapeNormalsTask::run(const baker::BakeContextPointer& contex
|
|||
outVertex = blendshape.vertices[lookupIndex];
|
||||
} else {
|
||||
// Index isn't in the blendshape, so return vertex from mesh
|
||||
outVertex = mesh.vertices[lookupIndex];
|
||||
outVertex = baker::safeGet(mesh.vertices, lookupIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ void CalculateMeshNormalsTask::run(const baker::BakeContextPointer& context, con
|
|||
return &normalsOut[normalIndex];
|
||||
},
|
||||
[&mesh](int vertexIndex, glm::vec3& outVertex) /* VertexSetter */ {
|
||||
outVertex = mesh.vertices[vertexIndex];
|
||||
outVertex = baker::safeGet(mesh.vertices, vertexIndex);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,17 @@ namespace baker {
|
|||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T& safeGet(const QVector<T>& data, int i) {
|
||||
static T t;
|
||||
|
||||
if (i >= 0 && data.size() > i) {
|
||||
return data[i];
|
||||
} else {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a reference to the normal at the specified index, or nullptr if it cannot be accessed
|
||||
using NormalAccessor = std::function<glm::vec3*(int index)>;
|
||||
|
||||
|
|
|
@ -542,5 +542,3 @@ void GeometryResourceWatcher::resourceRefreshed() {
|
|||
// FIXME: Model is not set up to handle a refresh
|
||||
// _instance.reset();
|
||||
}
|
||||
|
||||
#include "ModelCache.moc"
|
||||
|
|
|
@ -482,7 +482,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
|
|||
} else {
|
||||
QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString();
|
||||
|
||||
qCDebug(networking) << "Possible domain change required to connect to domain with ID" << domainID
|
||||
qCDebug(networking_ice) << "Possible domain change required to connect to domain with ID" << domainID
|
||||
<< "via ice-server at" << iceServerAddress;
|
||||
|
||||
emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID);
|
||||
|
|
|
@ -64,7 +64,7 @@ DomainHandler::DomainHandler(QObject* parent) :
|
|||
connect(this, &DomainHandler::redirectToErrorDomainURL, &_apiRefreshTimer, &QTimer::stop);
|
||||
}
|
||||
|
||||
void DomainHandler::disconnect() {
|
||||
void DomainHandler::disconnect(QString reason) {
|
||||
// if we're currently connected to a domain, send a disconnect packet on our way out
|
||||
if (_isConnected) {
|
||||
sendDisconnectPacket();
|
||||
|
@ -81,6 +81,8 @@ void DomainHandler::disconnect() {
|
|||
_sockAddr.clear();
|
||||
}
|
||||
|
||||
qCDebug(networking_ice) << "Disconnecting from domain server.";
|
||||
qCDebug(networking_ice) << "REASON:" << reason;
|
||||
setIsConnected(false);
|
||||
}
|
||||
|
||||
|
@ -100,9 +102,9 @@ void DomainHandler::clearSettings() {
|
|||
_settingsObject = QJsonObject();
|
||||
}
|
||||
|
||||
void DomainHandler::softReset() {
|
||||
void DomainHandler::softReset(QString reason) {
|
||||
qCDebug(networking) << "Resetting current domain connection information.";
|
||||
disconnect();
|
||||
disconnect(reason);
|
||||
|
||||
clearSettings();
|
||||
|
||||
|
@ -118,10 +120,10 @@ void DomainHandler::softReset() {
|
|||
}
|
||||
}
|
||||
|
||||
void DomainHandler::hardReset() {
|
||||
void DomainHandler::hardReset(QString reason) {
|
||||
emit resetting();
|
||||
|
||||
softReset();
|
||||
softReset(reason);
|
||||
_isInErrorState = false;
|
||||
emit redirectErrorStateChanged(_isInErrorState);
|
||||
|
||||
|
@ -166,7 +168,7 @@ void DomainHandler::setErrorDomainURL(const QUrl& url) {
|
|||
void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) {
|
||||
if (_sockAddr != sockAddr) {
|
||||
// we should reset on a sockAddr change
|
||||
hardReset();
|
||||
hardReset("Changing domain sockAddr");
|
||||
// change the sockAddr
|
||||
_sockAddr = sockAddr;
|
||||
}
|
||||
|
@ -209,7 +211,7 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) {
|
|||
// if it's in the error state, reset and try again.
|
||||
if ((_domainURL != domainURL || _sockAddr.getPort() != domainPort) || _isInErrorState) {
|
||||
// re-set the domain info so that auth information is reloaded
|
||||
hardReset();
|
||||
hardReset("Changing domain URL");
|
||||
|
||||
QString previousHost = _domainURL.host();
|
||||
_domainURL = domainURL;
|
||||
|
@ -242,10 +244,24 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) {
|
|||
|
||||
void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id) {
|
||||
|
||||
auto newIceServer = _iceServerSockAddr.getAddress().toString() != iceServerHostname;
|
||||
auto newDomainID = id != _pendingDomainID;
|
||||
|
||||
// if it's in the error state, reset and try again.
|
||||
if ((_iceServerSockAddr.getAddress().toString() != iceServerHostname || id != _pendingDomainID) || _isInErrorState) {
|
||||
if (newIceServer || newDomainID || _isInErrorState) {
|
||||
QString reason;
|
||||
if (newIceServer) {
|
||||
reason += "New ICE server;";
|
||||
}
|
||||
if (newDomainID) {
|
||||
reason += "New domain ID;";
|
||||
}
|
||||
if (_isInErrorState) {
|
||||
reason += "Domain in error state;";
|
||||
}
|
||||
|
||||
// re-set the domain info to connect to new domain
|
||||
hardReset();
|
||||
hardReset(reason);
|
||||
|
||||
// refresh our ICE client UUID to something new
|
||||
_iceClientID = QUuid::createUuid();
|
||||
|
@ -268,7 +284,7 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname,
|
|||
completedIceServerHostnameLookup();
|
||||
}
|
||||
|
||||
qCDebug(networking) << "ICE required to connect to domain via ice server at" << iceServerHostname;
|
||||
qCDebug(networking_ice) << "ICE required to connect to domain via ice server at" << iceServerHostname;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -322,7 +338,7 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) {
|
|||
}
|
||||
|
||||
void DomainHandler::completedIceServerHostnameLookup() {
|
||||
qCDebug(networking) << "ICE server socket is at" << _iceServerSockAddr;
|
||||
qCDebug(networking_ice) << "ICE server socket is at" << _iceServerSockAddr;
|
||||
|
||||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetICEServerSocket);
|
||||
|
||||
|
@ -409,7 +425,7 @@ void DomainHandler::processSettingsPacketList(QSharedPointer<ReceivedMessage> pa
|
|||
|
||||
void DomainHandler::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
const HifiSockAddr& senderSockAddr = message->getSenderSockAddr();
|
||||
qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr;
|
||||
qCDebug(networking_ice) << "Received reply from domain-server on" << senderSockAddr;
|
||||
|
||||
if (getIP().isNull()) {
|
||||
// we're hearing back from this domain-server, no need to refresh API information
|
||||
|
@ -417,13 +433,13 @@ void DomainHandler::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> me
|
|||
|
||||
// for now we're unsafely assuming this came back from the domain
|
||||
if (senderSockAddr == _icePeer.getLocalSocket()) {
|
||||
qCDebug(networking) << "Connecting to domain using local socket";
|
||||
qCDebug(networking_ice) << "Connecting to domain using local socket";
|
||||
activateICELocalSocket();
|
||||
} else if (senderSockAddr == _icePeer.getPublicSocket()) {
|
||||
qCDebug(networking) << "Conecting to domain using public socket";
|
||||
qCDebug(networking_ice) << "Conecting to domain using public socket";
|
||||
activateICEPublicSocket();
|
||||
} else {
|
||||
qCDebug(networking) << "Reply does not match either local or public socket for domain. Will not connect.";
|
||||
qCDebug(networking_ice) << "Reply does not match either local or public socket for domain. Will not connect.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -442,7 +458,7 @@ void DomainHandler::processDTLSRequirementPacket(QSharedPointer<ReceivedMessage>
|
|||
|
||||
void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> message) {
|
||||
if (_icePeer.hasSockets()) {
|
||||
qCDebug(networking) << "Received an ICE peer packet for domain-server but we already have sockets. Not processing.";
|
||||
qCDebug(networking_ice) << "Received an ICE peer packet for domain-server but we already have sockets. Not processing.";
|
||||
// bail on processing this packet if our ice peer already has sockets
|
||||
return;
|
||||
}
|
||||
|
@ -457,10 +473,10 @@ void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> mes
|
|||
DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation);
|
||||
|
||||
if (_icePeer.getUUID() != _pendingDomainID) {
|
||||
qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection.";
|
||||
qCDebug(networking_ice) << "Received a network peer with ID that does not match current domain. Will not attempt connection.";
|
||||
_icePeer.reset();
|
||||
} else {
|
||||
qCDebug(networking) << "Received network peer object for domain -" << _icePeer;
|
||||
qCDebug(networking_ice) << "Received network peer object for domain -" << _icePeer;
|
||||
|
||||
// ask the peer object to start its ping timer
|
||||
_icePeer.startPingTimer();
|
||||
|
@ -546,10 +562,14 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
|
|||
bool DomainHandler::checkInPacketTimeout() {
|
||||
++_checkInPacketsSinceLastReply;
|
||||
|
||||
if (_checkInPacketsSinceLastReply > 1) {
|
||||
qCDebug(networking_ice) << "Silent domain checkins:" << _checkInPacketsSinceLastReply;
|
||||
}
|
||||
|
||||
if (_checkInPacketsSinceLastReply > MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
|
||||
// we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS
|
||||
// so emit our signal that says that
|
||||
qCDebug(networking) << "Limit of silent domain checkins reached";
|
||||
qCDebug(networking_ice) << "Limit of silent domain checkins reached";
|
||||
emit limitOfSilentDomainCheckInsReached();
|
||||
return true;
|
||||
} else {
|
||||
|
|
|
@ -42,7 +42,7 @@ class DomainHandler : public QObject {
|
|||
public:
|
||||
DomainHandler(QObject* parent = 0);
|
||||
|
||||
void disconnect();
|
||||
void disconnect(QString reason);
|
||||
void clearSettings();
|
||||
|
||||
const QUuid& getUUID() const { return _uuid; }
|
||||
|
@ -105,7 +105,7 @@ public:
|
|||
|
||||
bool isSocketKnown() const { return !_sockAddr.getAddress().isNull(); }
|
||||
|
||||
void softReset();
|
||||
void softReset(QString reason);
|
||||
|
||||
int getCheckInPacketsSinceLastReply() const { return _checkInPacketsSinceLastReply; }
|
||||
bool checkInPacketTimeout();
|
||||
|
@ -210,7 +210,7 @@ signals:
|
|||
private:
|
||||
bool reasonSuggestsLogin(ConnectionRefusedReason reasonCode);
|
||||
void sendDisconnectPacket();
|
||||
void hardReset();
|
||||
void hardReset(QString reason);
|
||||
|
||||
bool isHardRefusal(int reasonCode);
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue