Merge branch 'master' of github.com:highfidelity/hifi into fixCameraMode

This commit is contained in:
NissimHadar 2018-05-24 17:40:21 -07:00
commit 9ed8c04097
113 changed files with 1513 additions and 554 deletions
BUILD_WIN.md
android
assignment-client/src/entities
cmake/externals
domain-server/src
interface/src
libraries
plugins
scripts

View file

@ -9,15 +9,17 @@ Note: The prerequisites will require about 10 GB of space on your drive. You wil
If you dont have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/).
When selecting components, check "Desktop development with C++." Also check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)" on the Summary toolbar on the right.
When selecting components, check "Desktop development with C++." Also on the right on the Summary toolbar, check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)".
### Step 2. Installing CMake
Download and install the latest version of CMake 3.9. Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). Make sure to check "Add CMake to system PATH for all users" when prompted during installation.
Download and install the latest version of CMake 3.9.
Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.9 Version page](https://cmake.org/files/v3.9/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted.
### Step 3. Installing Qt
Download and install the [Qt Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.10.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)".
Download and install the [Qt Open Source Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.10.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)".
Note: Installing the Sources is optional but recommended if you have room for them (~2GB).
@ -56,9 +58,9 @@ Where `%HIFI_DIR%` is the directory for the highfidelity repository.
Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio.
Change the Solution Configuration (next to the green play button) from "Debug" to "Release" for best performance.
Change the Solution Configuration (menu ribbon under the menu bar, next to the green play button) from "Debug" to "Release" for best performance.
Run `Build > Build Solution`.
Run from the menu bar `Build > Build Solution`.
### Step 9. Testing Interface
@ -66,7 +68,7 @@ Create another environment variable (see Step #4)
* Set "Variable name": `_NO_DEBUG_HEAP`
* Set "Variable value": `1`
In Visual Studio, right+click "interface" under the Apps folder in Solution Explorer and select "Set as Startup Project". Run `Debug > Start Debugging`.
In Visual Studio, right+click "interface" under the Apps folder in Solution Explorer and select "Set as Startup Project". Run from the menu bar `Debug > Start Debugging`.
Now, you should have a full build of High Fidelity and be able to run the Interface using Visual Studio. Please check our [Docs](https://wiki.highfidelity.com/wiki/Main_Page) for more information regarding the programming workflow.

View file

@ -66,17 +66,17 @@ ext {
def baseFolder = new File(HIFI_ANDROID_PRECOMPILED)
def appDir = new File(projectDir, 'app')
def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a')
def baseUrl = 'https://hifi-public.s3.amazonaws.com/austin/android/'
def baseUrl = ''
def qtFile='qt-5.9.3_linux_armv8-libcpp_openssl.tgz'
def qtFile='https://hifi-public.s3.amazonaws.com/austin/android/qt-5.9.3_linux_armv8-libcpp_openssl.tgz'
def qtChecksum='04599670ccca84bd2b15f6915568eb2d'
def qtVersionId='PeoqzN31n.YvLfs9JE2SgHgZ4.IaKAlt'
if (Os.isFamily(Os.FAMILY_MAC)) {
qtFile = 'qt-5.9.3_osx_armv8-libcpp_openssl.tgz'
qtFile = 'https://hifi-public.s3.amazonaws.com/austin/android/qt-5.9.3_osx_armv8-libcpp_openssl.tgz'
qtChecksum='4b02de9d67d6bfb202355a808d2d9c59'
qtVersionId='HygCmtMLPYioyil0DfXckGVzhw2SXZA9'
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
qtFile = 'qt-5.9.3_win_armv8-libcpp_openssl.tgz'
qtFile = 'https://hifi-public.s3.amazonaws.com/austin/android/qt-5.9.3_win_armv8-libcpp_openssl.tgz'
qtChecksum='c3e25db64002d0f43cf565e0ef708911'
qtVersionId='HeVObSVLCBoc7yY7He1oBMvPIH0VkClT'
}
@ -88,58 +88,58 @@ def packages = [
checksum: qtChecksum,
],
bullet: [
file: 'bullet-2.83_armv8-libcpp.tgz',
versionId: 'ljb7v.1IjVRqyopUKVDbVnLA4z88J8Eo',
checksum: '2c558d604fce337f5eba3eb7ec1252fd',
file: 'https://hifi-public.s3.amazonaws.com/dependencies/android/bullet-2.88_armv8-libcpp.tgz',
versionId: 'S8YaoED0Cl8sSb8fSV7Q2G1lQJSNDxqg',
checksum: '81642779ccb110f8c7338e8739ac38a0',
],
draco: [
file: 'draco_armv8-libcpp.tgz',
file: 'https://hifi-public.s3.amazonaws.com/austin/android/draco_armv8-libcpp.tgz',
versionId: 'cA3tVJSmkvb1naA3l6D_Jv2Noh.4yc4m',
checksum: '617a80d213a5ec69fbfa21a1f2f738cd',
],
glad: [
file: 'glad_armv8-libcpp.zip',
file: 'https://hifi-public.s3.amazonaws.com/austin/android/glad_armv8-libcpp.zip',
versionId: 'Q9szthzeye8fFyAA.cY26Lgn2B8kezEE',
checksum: 'a8ee8584cf1ccd34766c7ddd9d5e5449',
],
glm: [
file: 'glm-0.9.8.tgz',
versionId: 'BlkJNwaYV2Gfy5XwMeU7K0uzPDRKFMt2',
checksum: 'd2b42cee31d2bc17bab6ce69e6b3f30a',
file: 'https://hifi-public.s3.amazonaws.com/dependencies/android/glm-0.9.8.5-patched.tgz',
versionId: 'cskfMoJrFlAeqI3WPxemyO_Cxt7rT9EJ',
checksum: '067b5fe16b220b5b1a1039ba51b062ae',
],
gvr: [
file: 'gvrsdk_v1.101.0.tgz',
file: 'https://hifi-public.s3.amazonaws.com/austin/android/gvrsdk_v1.101.0.tgz',
versionId: 'UTberAIFraEfF9IVjoV66u1DTPTopgeY',
checksum: '57fd02baa069176ba18597a29b6b4fc7',
],
nvtt: [
file: 'nvtt_armv8-libcpp.zip',
file: 'https://hifi-public.s3.amazonaws.com/austin/android/nvtt_armv8-libcpp.zip',
versionId: 'vLqrqThvpq4gp75BHMAqO6HhfTXaa0An',
checksum: 'eb46d0b683e66987190ed124aabf8910',
sharedLibFolder: 'lib',
includeLibs: ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so'],
],
openssl: [
file: 'openssl-1.1.0g_armv8.tgz',
file: 'https://hifi-public.s3.amazonaws.com/austin/android/openssl-1.1.0g_armv8.tgz',
versionId: 'DmahmSGFS4ltpHyTdyQvv35WOeUOiib9',
checksum: 'cabb681fbccd79594f65fcc266e02f32',
],
polyvox: [
file: 'polyvox_armv8-libcpp.tgz',
file: 'https://hifi-public.s3.amazonaws.com/austin/android/polyvox_armv8-libcpp.tgz',
versionId: 'LDJtzMTvdm4SAc2KYg8Cg6uwWk4Vq3e3',
checksum: '349ad5b72aaf2749ca95d847e60c5314',
sharedLibFolder: 'lib',
includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'],
],
tbb: [
file: 'tbb-2018_U1_armv8_libcpp.tgz',
file: 'https://hifi-public.s3.amazonaws.com/austin/android/tbb-2018_U1_armv8_libcpp.tgz',
versionId: 'YZliDD8.Menh1IVXKEuLPeO3xAjJ1UdF',
checksum: '20768f298f53b195e71b414b0ae240c4',
sharedLibFolder: 'lib/release',
includeLibs: ['libtbb.so', 'libtbbmalloc.so'],
],
hifiAC: [
file: 'libplugins_libhifiCodec.zip',
file: 'https://hifi-public.s3.amazonaws.com/austin/android/libplugins_libhifiCodec.zip',
versionId: 'mzKhsRCgVmloqq5bvE.0IwYK1NjGQc_G',
checksum: '9412a8e12c88a4096c1fc843bb9fe52d',
sharedLibFolder: '',
@ -150,15 +150,15 @@ def packages = [
def scribeLocalFile='scribe' + EXEC_SUFFIX
def scribeFile='scribe_linux_x86_64'
def scribeFile='https://hifi-public.s3.amazonaws.com/austin/android/scribe_linux_x86_64'
def scribeChecksum='ca4b904f52f4f993c29175ba96798fa6'
def scribeVersion='wgpf4dB2Ltzg4Lb2jJ4nPFsHoDkmK_OO'
if (Os.isFamily(Os.FAMILY_MAC)) {
scribeFile = 'scribe_osx_x86_64'
scribeFile = 'https://hifi-public.s3.amazonaws.com/austin/android/scribe_osx_x86_64'
scribeChecksum='72db9d32d4e1e50add755570ac5eb749'
scribeVersion='o_NbPrktzEYtBkQf3Tn7zc1nZWzM52w6'
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
scribeFile = 'scribe_win32_x86_64.exe'
scribeFile = 'https://hifi-public.s3.amazonaws.com/austin/android/scribe_win32_x86_64.exe'
scribeChecksum='678e43d290c90fda670c6fefe038a06d'
scribeVersion='GCCJxlmd2irvNOFWfZR0U1UCLHndHQrC'
}
@ -608,4 +608,4 @@ task testElf (dependsOn: 'externalNativeBuildDebug') {
}
}
}
*/
*/

View file

@ -44,6 +44,7 @@ EntityServer::EntityServer(ReceivedMessage& message) :
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
PacketType::EntityClone,
PacketType::EntityEdit,
PacketType::EntityErase,
PacketType::EntityPhysics,

View file

@ -17,8 +17,8 @@ include(ExternalProject)
if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.83-ccd-and-cmake-fixes.tgz
URL_MD5 03051bf112dcc78ddd296f9cab38fd68
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.88.tgz
URL_MD5 0a6876607ebe83e227427215f15946fd
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_BULLET3=0 -DBUILD_OPENGL3_DEMOS=0 -DBUILD_BULLET2_DEMOS=0 -DBUILD_UNIT_TESTS=0 -DUSE_GLUT=0 -DUSE_DX11=0
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
@ -28,8 +28,8 @@ if (WIN32)
else ()
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.83-ccd-and-cmake-fixes.tgz
URL_MD5 03051bf112dcc78ddd296f9cab38fd68
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.88.tgz
URL_MD5 0a6876607ebe83e227427215f15946fd
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_BULLET3=0 -DBUILD_OPENGL3_DEMOS=0 -DBUILD_BULLET2_DEMOS=0 -DBUILD_UNIT_TESTS=0 -DUSE_GLUT=0
LOG_DOWNLOAD 1
LOG_CONFIGURE 1

View file

@ -3,8 +3,8 @@ set(EXTERNAL_NAME glm)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.8.zip
URL_MD5 579ac77a3110befa3244d68c0ceb7281
URL https://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.8.5-patched.zip
URL_MD5 7d39ecc1cea275427534c3cfd6dd63f0
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> ${EXTERNAL_ARGS}
LOG_DOWNLOAD 1
@ -18,4 +18,4 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glm include directories")
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glm include directories")

View file

@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC67-v4.zip
URL_MD5 ba32aed18bfeaac4ccaf5ebb8ea3e804
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC68.zip
URL_MD5 a068f74d4045e257cfa7926fe6e38ad5
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

View file

@ -479,7 +479,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
limitedNodeList->killNodeWithUUID(existingNodeID);
}
// add the connecting node (or re-use the matched one from eachNodeBreakable above)
// add the connecting node
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
// set the edit rights for this user
@ -508,26 +508,22 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
return newNode;
}
SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection,
QUuid nodeID) {
SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection) {
HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr;
SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID);
if (connectedPeer) {
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
nodeID = nodeConnection.connectUUID;
if (connectedPeer->getActiveSocket()) {
// set their discovered socket to whatever the activated socket on the network peer object was
discoveredSocket = *connectedPeer->getActiveSocket();
}
} else {
// we got a connectUUID we didn't recognize, either use the hinted node ID or randomly generate a new one
if (nodeID.isNull()) {
nodeID = QUuid::createUuid();
}
if (connectedPeer && connectedPeer->getActiveSocket()) {
// set their discovered socket to whatever the activated socket on the network peer object was
discoveredSocket = *connectedPeer->getActiveSocket();
}
// create a new node ID for the verified connecting node
auto nodeID = QUuid::createUuid();
// add a mapping from connection node ID to ICE peer ID
// so that we can remove the ICE peer once we see this node connect
_nodeToICEPeerIDs.insert(nodeID, nodeConnection.connectUUID);
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
Node::LocalID newLocalID = findOrCreateLocalID(nodeID);
@ -541,6 +537,15 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
return newNode;
}
void DomainGatekeeper::cleanupICEPeerForNode(const QUuid& nodeID) {
// remove this node ID from our node to ICE peer ID map
// and the associated ICE peer (if it still exists)
auto icePeerID = _nodeToICEPeerIDs.take(nodeID);
if (!icePeerID.isNull()) {
_icePeers.remove(icePeerID);
}
}
bool DomainGatekeeper::verifyUserSignature(const QString& username,
const QByteArray& usernameSignature,
const HifiSockAddr& senderSockAddr) {

View file

@ -39,8 +39,8 @@ public:
void addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID,
const QUuid& walletUUID, const QString& nodeVersion);
QUuid assignmentUUIDForPendingAssignment(const QUuid& tempUUID);
void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); }
void cleanupICEPeerForNode(const QUuid& nodeID);
Node::LocalID findOrCreateLocalID(const QUuid& uuid);
@ -77,8 +77,7 @@ private:
SharedNodePointer processAgentConnectRequest(const NodeConnectionData& nodeConnection,
const QString& username,
const QByteArray& usernameSignature);
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection,
QUuid nodeID = QUuid());
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection);
bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature,
const HifiSockAddr& senderSockAddr);
@ -101,6 +100,10 @@ private:
std::unordered_map<QUuid, PendingAssignedNodeData> _pendingAssignedNodes;
QHash<QUuid, SharedNetworkPeer> _icePeers;
using ConnectingNodeID = QUuid;
using ICEPeerID = QUuid;
QHash<ConnectingNodeID, ICEPeerID> _nodeToICEPeerIDs;
QHash<QString, QUuid> _connectionTokenHash;

View file

@ -1017,15 +1017,22 @@ void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> mess
sendingNode->setPublicSocket(nodeRequestData.publicSockAddr);
sendingNode->setLocalSocket(nodeRequestData.localSockAddr);
// update the NodeInterestSet in case there have been any changes
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
if (!nodeData->hasCheckedIn()) {
nodeData->setHasCheckedIn(true);
// on first check in, make sure we've cleaned up any ICE peer for this node
_gatekeeper.cleanupICEPeerForNode(sendingNode->getUUID());
}
// guard against patched agents asking to hear about other agents
auto safeInterestSet = nodeRequestData.interestList.toSet();
if (sendingNode->getType() == NodeType::Agent) {
safeInterestSet.remove(NodeType::Agent);
}
// update the NodeInterestSet in case there have been any changes
nodeData->setNodeInterestSet(safeInterestSet);
// update the connecting hostname in case it has changed
@ -2945,7 +2952,7 @@ void DomainServer::nodeAdded(SharedNodePointer node) {
void DomainServer::nodeKilled(SharedNodePointer node) {
// if this peer connected via ICE then remove them from our ICE peers hash
_gatekeeper.removeICEPeer(node->getUUID());
_gatekeeper.cleanupICEPeerForNode(node->getUUID());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
@ -2978,6 +2985,8 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
}
}
}
broadcastNodeDisconnect(node);
}
SharedAssignmentPointer DomainServer::dequeueMatchingAssignment(const QUuid& assignmentUUID, NodeType_t nodeType) {
@ -3163,18 +3172,23 @@ void DomainServer::handleKillNode(SharedNodePointer nodeToKill) {
const QUuid& nodeUUID = nodeToKill->getUUID();
limitedNodeList->killNodeWithUUID(nodeUUID);
}
static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID);
void DomainServer::broadcastNodeDisconnect(const SharedNodePointer& disconnectedNode) {
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID, true);
removedNodePacket->reset();
removedNodePacket->write(nodeUUID.toRfc4122());
removedNodePacket->write(disconnectedNode->getUUID().toRfc4122());
// broadcast out the DomainServerRemovedNode message
limitedNodeList->eachMatchingNode([this, &nodeToKill](const SharedNodePointer& otherNode) -> bool {
limitedNodeList->eachMatchingNode([this, &disconnectedNode](const SharedNodePointer& otherNode) -> bool {
// only send the removed node packet to nodes that care about the type of node this was
return isInInterestSet(otherNode, nodeToKill);
return isInInterestSet(otherNode, disconnectedNode);
}, [&limitedNodeList](const SharedNodePointer& otherNode){
limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode);
auto removedNodePacketCopy = NLPacket::createCopy(*removedNodePacket);
limitedNodeList->sendPacket(std::move(removedNodePacketCopy), *otherNode);
});
}

View file

@ -165,6 +165,7 @@ private:
unsigned int countConnectedUsers();
void handleKillNode(SharedNodePointer nodeToKill);
void broadcastNodeDisconnect(const SharedNodePointer& disconnnectedNode);
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr);

View file

@ -67,8 +67,11 @@ public:
const QString& getPlaceName() { return _placeName; }
void setPlaceName(const QString& placeName) { _placeName = placeName; }
bool wasAssigned() const { return _wasAssigned; };
bool wasAssigned() const { return _wasAssigned; }
void setWasAssigned(bool wasAssigned) { _wasAssigned = wasAssigned; }
bool hasCheckedIn() const { return _hasCheckedIn; }
void setHasCheckedIn(bool hasCheckedIn) { _hasCheckedIn = hasCheckedIn; }
private:
QJsonObject overrideValuesIfNeeded(const QJsonObject& newStats);
@ -94,6 +97,8 @@ private:
QString _placeName;
bool _wasAssigned { false };
bool _hasCheckedIn { false };
};
#endif // hifi_DomainServerNodeData_h

View file

@ -717,7 +717,7 @@ private:
* <tr><td><code>NavigationFocused</code></td><td>number</td><td>number</td><td><em>Not used.</em></td></tr>
* </tbody>
* </table>
* @typedef Controller.Hardware-Application
* @typedef {object} Controller.Hardware-Application
*/
static const QString STATE_IN_HMD = "InHMD";

View file

@ -651,7 +651,7 @@ private:
quint64 _lastFaceTrackerUpdate;
render::ScenePointer _main3DScene{ new render::Scene(glm::vec3(-0.5f * (float)TREE_SCALE), (float)TREE_SCALE) };
render::EnginePointer _renderEngine{ new render::Engine() };
render::EnginePointer _renderEngine{ new render::RenderEngine() };
gpu::ContextPointer _gpuContext; // initialized during window creation
mutable QMutex _renderArgsMutex{ QMutex::Recursive };

View file

@ -25,7 +25,7 @@ class FancyCamera : public Camera {
// FIXME: JSDoc 3.5.5 doesn't augment @property definitions. The following definition is repeated in Camera.h.
/**jsdoc
* @property cameraEntity {Uuid} The ID of the entity that the camera position and orientation follow when the camera is in
* @property {Uuid} cameraEntity The ID of the entity that the camera position and orientation follow when the camera is in
* entity mode.
*/
Q_PROPERTY(QUuid cameraEntity READ getCameraEntity WRITE setCameraEntity)

View file

@ -160,7 +160,7 @@ QUuid AvatarMotionState::getSimulatorID() const {
}
// virtual
void AvatarMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const {
void AvatarMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const {
group = BULLET_COLLISION_GROUP_OTHER_AVATAR;
mask = Physics::getDefaultCollisionMask(group);
}

View file

@ -65,7 +65,7 @@ public:
void addDirtyFlags(uint32_t flags) { _dirtyFlags |= flags; }
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
virtual float getMass() const override;

View file

@ -121,7 +121,7 @@ class MyAvatar : public Avatar {
* while flying.
* @property {number} hmdRollControlDeadZone=8 - The amount of HMD roll, in degrees, required before your avatar turns if
* <code>hmdRollControlEnabled</code> is enabled.
* @property hmdRollControlRate {number} If hmdRollControlEnabled is true, this value determines the maximum turn rate of
* @property {number} hmdRollControlRate If hmdRollControlEnabled is true, this value determines the maximum turn rate of
* your avatar when rolling your HMD in degrees per second.
* @property {number} userHeight=1.75 - The height of the user in sensor space.
* @property {number} userEyeHeight=1.65 - The estimated height of the user's eyes in sensor space. <em>Read-only.</em>
@ -1402,7 +1402,7 @@ private:
SharedSoundPointer _collisionSound;
MyCharacterController _characterController;
int16_t _previousCollisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR };
int32_t _previousCollisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR };
AvatarWeakPointer _lookAtTargetAvatar;
glm::vec3 _targetAvatarPosition;

View file

@ -22,21 +22,22 @@
* @hifi-interface
* @hifi-client-entity
*
* @property PICK_NOTHING {number} A filter flag. Don't intersect with anything. <em>Read-only.</em>
* @property PICK_ENTITIES {number} A filter flag. Include entities when intersecting. <em>Read-only.</em>
* @property PICK_OVERLAYS {number} A filter flag. Include overlays when intersecting. <em>Read-only.</em>
* @property PICK_AVATARS {number} A filter flag. Include avatars when intersecting. <em>Read-only.</em>
* @property PICK_HUD {number} A filter flag. Include the HUD sphere when intersecting in HMD mode. <em>Read-only.</em>
* @property PICK_COARSE {number} A filter flag. Pick against coarse meshes, instead of exact meshes. <em>Read-only.</em>
* @property PICK_INCLUDE_INVISIBLE {number} A filter flag. Include invisible objects when intersecting. <em>Read-only.</em>
* @property PICK_INCLUDE_NONCOLLIDABLE {number} A filter flag. Include non-collidable objects when intersecting.
* @property {number} PICK_NOTHING A filter flag. Don't intersect with anything. <em>Read-only.</em>
* @property {number} PICK_ENTITIES A filter flag. Include entities when intersecting. <em>Read-only.</em>
* @property {number} PICK_OVERLAYS A filter flag. Include overlays 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 {number} PICK_COARSE A filter flag. Pick against coarse meshes, instead of exact meshes. <em>Read-only.</em>
* @property {number} PICK_INCLUDE_INVISIBLE A filter flag. Include invisible 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 PICK_ALL_INTERSECTIONS {number} <em>Read-only.</em>
* @property INTERSECTED_NONE {number} An intersection type. Intersected nothing with the given filter flags. <em>Read-only.</em>
* @property INTERSECTED_ENTITY {number} An intersection type. Intersected an entity. <em>Read-only.</em>
* @property INTERSECTED_OVERLAY {number} An intersection type. Intersected an overlay. <em>Read-only.</em>
* @property INTERSECTED_AVATAR {number} An intersection type. Intersected an avatar. <em>Read-only.</em>
* @property INTERSECTED_HUD {number} An intersection type. Intersected the HUD sphere. <em>Read-only.</em>
* @property {number} PICK_ALL_INTERSECTIONS <em>Read-only.</em>
* @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_OVERLAY An intersection type. Intersected an overlay. <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. <em>Read-only.</em>
*/
@ -99,7 +100,7 @@ public:
/**jsdoc
* An intersection result for a Ray Pick.
*
* @typedef {Object} RayPickResult
* @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.
@ -113,7 +114,7 @@ public:
/**jsdoc
* An intersection result for a Stylus Pick.
*
* @typedef {Object} StylusPickResult
* @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.

View file

@ -68,14 +68,14 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties)
* A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.RayPointerRenderState},
* but with an additional distance field.
*
* @typedef {Object} Pointers.DefaultRayPointerRenderState
* @typedef {object} Pointers.DefaultRayPointerRenderState
* @augments Pointers.RayPointerRenderState
* @property {number} distance The distance at which to render the end of this Ray Pointer, if one is defined.
*/
/**jsdoc
* A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something.
*
* @typedef {Object} Pointers.RayPointerRenderState
* @typedef {object} Pointers.RayPointerRenderState
* @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState}
* @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field).
* An overlay to represent the beginning of the Ray Pointer, if desired.
@ -87,7 +87,7 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties)
/**jsdoc
* A trigger mechanism for Ray Pointers.
*
* @typedef {Object} Pointers.Trigger
* @typedef {object} Pointers.Trigger
* @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger <code>button</code>.
* @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
* it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".

View file

@ -522,7 +522,7 @@ int WindowScriptingInterface::openMessageBox(QString title, QString text, int bu
* <tr> <td><strong>RestoreDefaults</strong></td> <td><code>0x8000000</code></td> <td>"Restore Defaults"</td> </tr>
* </tbody>
* </table>
* @typedef Window.MessageBoxButton
* @typedef {number} Window.MessageBoxButton
*/
int WindowScriptingInterface::createMessageBox(QString title, QString text, int buttons, int defaultButton) {
auto messageBox = DependencyManager::get<OffscreenUi>()->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text,

View file

@ -470,7 +470,7 @@ public slots:
* </tr>
* </tbody>
* </table>
* @typedef Window.DisplayTexture
* @typedef {string} Window.DisplayTexture
*/
bool setDisplayTexture(const QString& name);
@ -523,16 +523,21 @@ public slots:
int openMessageBox(QString title, QString text, int buttons, int defaultButton);
/**jsdoc
* Open the given resource in the Interface window or in a web browser depending on the url scheme
* Open a URL in the Interface window or other application, depending on the URL's scheme. If the URL starts with
* <code>hifi://</code> then that URL is navigated to in Interface, otherwise the URL is opened in the application the OS
* associates with the URL's scheme (e.g., a Web browser for <code>http://</code>).
* @function Window.openUrl
* @param {string} url - The resource to open
* @param {string} url - The URL to open.
*/
void openUrl(const QUrl& url);
/**jsdoc
* (Android only) Open the requested Activity and optionally back to the scene when the activity is done
* Open an Android activity and optionally return back to the scene when the activity is completed. <em>Android only.</em>
* @function Window.openAndroidActivity
* @param {string} activityName - The name of the activity to open. One of "Home", "Login" or "Privacy Policy"
* @param {string} activityName - The name of the activity to open: one of <code>"Home"</code>, <code>"Login"</code>, or
* <code>"Privacy Policy"</code>.
* @param {boolean} backToScene - If <code>true</code>, the user is automatically returned back to the scene when the
* activity is completed.
*/
void openAndroidActivity(const QString& activityName, const bool backToScene);

View file

@ -53,7 +53,7 @@ const OverlayID UNKNOWN_OVERLAY_ID = OverlayID();
* @property {number} distance - The distance from the {@link PickRay} origin to the intersection point.
* @property {Vec3} surfaceNormal - The normal of the overlay surface at the intersection point.
* @property {Vec3} intersection - The position of the intersection point.
* @property {Object} extraInfo Additional intersection details, if available.
* @property {object} extraInfo Additional intersection details, if available.
*/
class RayToOverlayIntersectionResult {
public:
@ -482,7 +482,7 @@ public slots:
/**jsdoc
* Check if there is an overlay of a given ID.
* @function Overlays.isAddedOverly
* @function Overlays.isAddedOverlay
* @param {Uuid} overlayID - The ID to check.
* @returns {boolean} <code>true</code> if an overlay with the given ID exists, <code>false</code> otherwise.
*/

View file

@ -70,7 +70,7 @@ public:
* @function AnimationCache.prefetch
* @param {string} url - URL of the resource to prefetch.
* @param {object} [extra=null]
* @returns {Resource}
* @returns {ResourceObject}
*/
/**jsdoc
@ -79,7 +79,7 @@ public:
* @param {string} url - URL of the resource to load.
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
* @param {} [extra=null]
* @returns {Resource}
* @returns {object}
*/
@ -87,7 +87,7 @@ public:
* Returns animation resource for particular animation.
* @function AnimationCache.getAnimation
* @param {string} url - URL to load.
* @returns {Resource} animation
* @returns {AnimationObject} animation
*/
Q_INVOKABLE AnimationPointer getAnimation(const QString& url) { return getAnimation(QUrl(url)); }
Q_INVOKABLE AnimationPointer getAnimation(const QUrl& url);
@ -104,6 +104,17 @@ private:
Q_DECLARE_METATYPE(AnimationPointer)
/**jsdoc
* @class AnimationObject
*
* @hifi-interface
* @hifi-client-entity
* @hifi-server-entity
* @hifi-assignment-client
*
* @property {string[]} jointNames
* @property {FBXAnimationFrame[]} frames
*/
/// An animation loaded from the network.
class Animation : public Resource {
Q_OBJECT
@ -118,9 +129,16 @@ public:
virtual bool isLoaded() const override;
/**jsdoc
* @function AnimationObject.getJointNames
* @returns {string[]}
*/
Q_INVOKABLE QStringList getJointNames() const;
/**jsdoc
* @function AnimationObject.getFrames
* @returns {FBXAnimationFrame[]}
*/
Q_INVOKABLE QVector<FBXAnimationFrame> getFrames() const;
const QVector<FBXAnimationFrame>& getFramesReference() const;

View file

@ -77,6 +77,17 @@ private:
typedef QSharedPointer<Sound> SharedSoundPointer;
/**jsdoc
* @class SoundObject
*
* @hifi-interface
* @hifi-client-entity
* @hifi-server-entity
* @hifi-assignment-client
*
* @property {boolean} downloaded
* @property {number} duration
*/
class SoundScriptingInterface : public QObject {
Q_OBJECT
@ -90,6 +101,10 @@ public:
bool isReady() const { return _sound->isReady(); }
float getDuration() { return _sound->getDuration(); }
/**jsdoc
* @function SoundObject.ready
* @returns {Signal}
*/
signals:
void ready();

View file

@ -64,7 +64,7 @@ public:
* @function SoundCache.prefetch
* @param {string} url - URL of the resource to prefetch.
* @param {object} [extra=null]
* @returns {Resource}
* @returns {ResourceObject}
*/
/**jsdoc
@ -73,14 +73,14 @@ public:
* @param {string} url - URL of the resource to load.
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
* @param {} [extra=null]
* @returns {Resource}
* @returns {object}
*/
/**jsdoc
* @function SoundCache.getSound
* @param {string} url
* @returns {object}
* @returns {SoundObject}
*/
Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url);
protected:

View file

@ -292,7 +292,7 @@ public:
*/
/**jsdoc
* Information about a single joint in an Avatar's skeleton hierarchy.
* @typedef MyAvatar.SkeletonJoint
* @typedef {object} MyAvatar.SkeletonJoint
* @property {string} name - Joint name.
* @property {number} index - Joint index.
* @property {number} parentIndex - Index of this joint's parent (-1 if no parent).

View file

@ -2363,7 +2363,7 @@ glm::vec3 AvatarData::getAbsoluteJointTranslationInObjectFrame(int index) const
}
/**jsdoc
* @typedef AttachmentData
* @typedef {object} AttachmentData
* @property {string} modelUrl
* @property {string} jointName
* @property {Vec3} translation

View file

@ -578,8 +578,7 @@ public:
* @param {Quat} rotation - The rotation of the joint relative to its parent.
* @param {Vec3} translation - The translation of the joint relative to its parent.
* @example <caption>Set your avatar to it's default T-pose for a while.<br />
* <img alt="Avatar in T-pose" src="https://docs.highfidelity.com/user/pages/06.api-reference/25.myavatar/t-pose.png" />
* </caption>
* <img alt="Avatar in T-pose" src="https://docs.highfidelity.com/images/t-pose.png" /></caption>
* // Set all joint translations and rotations to defaults.
* var i, length, rotation, translation;
* for (i = 0, length = MyAvatar.getJointNames().length; i < length; i++) {
@ -680,8 +679,7 @@ public:
* @param {string} name - The name of the joint.
* @param {Quat} rotation - The rotation of the joint relative to its parent.
* @example <caption>Set your avatar to its default T-pose then rotate its right arm.<br />
* <img alt="Avatar in T-pose with arm rotated"
* src="https://docs.highfidelity.com/user/pages/06.api-reference/25.myavatar/armpose.png" /></caption>
* <img alt="Avatar in T-pose with arm rotated" src="https://docs.highfidelity.com/images/armpose.png" /></caption>
* // Set all joint translations and rotations to defaults.
* var i, length, rotation, translation;
* for (i = 0, length = MyAvatar.getJointNames().length; i < length; i++) {
@ -713,8 +711,7 @@ public:
* @param {Vec3} translation - The translation of the joint relative to its parent.
* @example <caption>Stretch your avatar's neck. Depending on the avatar you are using, you will either see a gap between
* the head and body or you will see the neck stretched.<br />
* <img alt="Avatar with neck stretched"
* src="https://docs.highfidelity.com/user/pages/06.api-reference/25.myavatar/stretched-neck.png" /></caption>
* <img alt="Avatar with neck stretched" src="https://docs.highfidelity.com/images/stretched-neck.png" /></caption>
* // Stretch your avatar's neck.
* MyAvatar.setJointTranslation("Neck", { x: 0, y: 25, z: 0 });
*
@ -798,8 +795,7 @@ public:
* @param {Quat[]} jointRotations - The rotations for all joints in the avatar. The values are in the same order as the
* array returned by {@link MyAvatar.getJointNames} or {@link Avatar.getJointNames}.
* @example <caption>Set your avatar to its default T-pose then rotate its right arm.<br />
* <img alt="Avatar in T-pose" src="https://docs.highfidelity.com/user/pages/06.api-reference/25.myavatar/armpose.png" />
* </caption>
* <img alt="Avatar in T-pose" src="https://docs.highfidelity.com/images/armpose.png" /></caption>
* // Set all joint translations and rotations to defaults.
* var i, length, rotation, translation;
* for (i = 0, length = MyAvatar.getJointNames().length; i < length; i++) {

View file

@ -307,7 +307,7 @@ namespace controller {
* action.</td></tr>
* </tbody>
* </table>
* @typedef Controller.Actions
* @typedef {object} Controller.Actions
*/
// Device functions
Input::NamedVector ActionsDevice::getAvailableInputs() const {

View file

@ -79,7 +79,7 @@ enum Hand {
* {@link Controller.Hardware-Vive}.</td></tr>
* </tbody>
* </table>
* @typedef Controller.Hardware
* @typedef {object} Controller.Hardware
* @example <caption>List all the currently available <code>Controller.Hardware</code> properties.</caption>
* function printProperties(string, item) {
* print(string);

View file

@ -231,7 +231,7 @@ void StandardController::focusOutEvent() {
*
* </tbody>
* </table>
* @typedef Controller.Standard
* @typedef {object} Controller.Standard
*/
Input::NamedVector StandardController::getAvailableInputs() const {
static Input::NamedVector availableInputs {

View file

@ -46,7 +46,7 @@ bool operator!=(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b
/**jsdoc
* The AnimationProperties are used to configure an animation.
* @typedef Entities.AnimationProperties
* @typedef {object} Entities.AnimationProperties
* @property {string} url="" - The URL of the FBX file that has the animation.
* @property {number} fps=30 - The speed in frames/s that the animation is played at.
* @property {number} firstFrame=0 - The first frame to play in the animation.

View file

@ -154,3 +154,11 @@ void EntityEditPacketSender::queueEraseEntityMessage(const EntityItemID& entityI
queueOctreeEditMessage(PacketType::EntityErase, bufferOut);
}
}
void EntityEditPacketSender::queueCloneEntityMessage(const EntityItemID& entityIDToClone, const EntityItemID& newEntityID) {
QByteArray bufferOut(NLPacket::maxPayloadSize(PacketType::EntityClone), 0);
if (EntityItemProperties::encodeCloneEntityMessage(entityIDToClone, newEntityID, bufferOut)) {
queueOctreeEditMessage(PacketType::EntityClone, bufferOut);
}
}

View file

@ -38,6 +38,7 @@ public:
void queueEraseEntityMessage(const EntityItemID& entityItemID);
void queueCloneEntityMessage(const EntityItemID& entityIDToClone, const EntityItemID& newEntityID);
// My server type is the model server
virtual char getMyNodeType() const override { return NodeType::EntityServer; }

View file

@ -124,6 +124,13 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_LAST_EDITED_BY;
requestedProperties += PROP_CLONEABLE;
requestedProperties += PROP_CLONE_LIFETIME;
requestedProperties += PROP_CLONE_LIMIT;
requestedProperties += PROP_CLONE_DYNAMIC;
requestedProperties += PROP_CLONE_AVATAR_ENTITY;
requestedProperties += PROP_CLONE_ORIGIN_ID;
return requestedProperties;
}
@ -288,6 +295,13 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, getQueryAACube());
APPEND_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, getLastEditedBy());
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE, getCloneable());
APPEND_ENTITY_PROPERTY(PROP_CLONE_LIFETIME, getCloneLifetime());
APPEND_ENTITY_PROPERTY(PROP_CLONE_LIMIT, getCloneLimit());
APPEND_ENTITY_PROPERTY(PROP_CLONE_DYNAMIC, getCloneDynamic());
APPEND_ENTITY_PROPERTY(PROP_CLONE_AVATAR_ENTITY, getCloneAvatarEntity());
APPEND_ENTITY_PROPERTY(PROP_CLONE_ORIGIN_ID, getCloneOriginID());
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
requestedProperties,
propertyFlags,
@ -803,7 +817,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible);
READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow);
READ_ENTITY_PROPERTY(PROP_COLLISIONLESS, bool, setCollisionless);
READ_ENTITY_PROPERTY(PROP_COLLISION_MASK, uint8_t, setCollisionMask);
READ_ENTITY_PROPERTY(PROP_COLLISION_MASK, uint16_t, setCollisionMask);
READ_ENTITY_PROPERTY(PROP_DYNAMIC, bool, setDynamic);
READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked);
READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData);
@ -848,6 +862,13 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, QUuid, setLastEditedBy);
READ_ENTITY_PROPERTY(PROP_CLONEABLE, bool, setCloneable);
READ_ENTITY_PROPERTY(PROP_CLONE_LIFETIME, float, setCloneLifetime);
READ_ENTITY_PROPERTY(PROP_CLONE_LIMIT, float, setCloneLimit);
READ_ENTITY_PROPERTY(PROP_CLONE_DYNAMIC, bool, setCloneDynamic);
READ_ENTITY_PROPERTY(PROP_CLONE_AVATAR_ENTITY, bool, setCloneAvatarEntity);
READ_ENTITY_PROPERTY(PROP_CLONE_ORIGIN_ID, QUuid, setCloneOriginID);
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args,
propertyFlags, overwriteLocalData, somethingChanged);
@ -1275,6 +1296,13 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lastEditedBy, getLastEditedBy);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneable, getCloneable);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneLifetime, getCloneLifetime);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneLimit, getCloneLimit);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneDynamic, getCloneDynamic);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneAvatarEntity, getCloneAvatarEntity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneOriginID, getCloneOriginID);
properties._defaultSettings = false;
return properties;
@ -1382,6 +1410,13 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneable, setCloneable);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneLifetime, setCloneLifetime);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneLimit, setCloneLimit);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneDynamic, setCloneDynamic);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneAvatarEntity, setCloneAvatarEntity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneOriginID, setCloneOriginID);
if (updateQueryAACube()) {
somethingChanged = true;
}
@ -1814,7 +1849,7 @@ void EntityItem::setCollisionless(bool value) {
});
}
void EntityItem::setCollisionMask(uint8_t value) {
void EntityItem::setCollisionMask(uint16_t value) {
withWriteLock([&] {
if ((_collisionMask & ENTITY_COLLISION_MASK_DEFAULT) != (value & ENTITY_COLLISION_MASK_DEFAULT)) {
_collisionMask = (value & ENTITY_COLLISION_MASK_DEFAULT);
@ -1879,7 +1914,7 @@ void EntityItem::setCreated(quint64 value) {
});
}
void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask) const {
void EntityItem::computeCollisionGroupAndFinalMask(int32_t& group, int32_t& mask) const {
if (_collisionless) {
group = BULLET_COLLISION_GROUP_COLLISIONLESS;
mask = 0;
@ -1892,7 +1927,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
group = BULLET_COLLISION_GROUP_STATIC;
}
uint8_t userMask = getCollisionMask();
uint16_t userMask = getCollisionMask();
if ((bool)(userMask & USER_COLLISION_GROUP_MY_AVATAR) !=
(bool)(userMask & USER_COLLISION_GROUP_OTHER_AVATAR)) {
@ -1906,7 +1941,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
if ((bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) {
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
}
mask = Physics::getDefaultCollisionMask(group) & (int16_t)(userMask);
mask = Physics::getDefaultCollisionMask(group) & (int32_t)(userMask);
}
}
@ -2760,8 +2795,8 @@ bool EntityItem::getCollisionless() const {
return result;
}
uint8_t EntityItem::getCollisionMask() const {
uint8_t result;
uint16_t EntityItem::getCollisionMask() const {
uint16_t result;
withReadLock([&] {
result = _collisionMask;
});
@ -2975,3 +3010,118 @@ std::unordered_map<std::string, graphics::MultiMaterial> EntityItem::getMaterial
}
return toReturn;
}
bool EntityItem::getCloneable() const {
bool result;
withReadLock([&] {
result = _cloneable;
});
return result;
}
void EntityItem::setCloneable(bool value) {
withWriteLock([&] {
_cloneable = value;
});
}
float EntityItem::getCloneLifetime() const {
float result;
withReadLock([&] {
result = _cloneLifetime;
});
return result;
}
void EntityItem::setCloneLifetime(float value) {
withWriteLock([&] {
_cloneLifetime = value;
});
}
float EntityItem::getCloneLimit() const {
float result;
withReadLock([&] {
result = _cloneLimit;
});
return result;
}
void EntityItem::setCloneLimit(float value) {
withWriteLock([&] {
_cloneLimit = value;
});
}
bool EntityItem::getCloneDynamic() const {
bool result;
withReadLock([&] {
result = _cloneDynamic;
});
return result;
}
void EntityItem::setCloneDynamic(bool value) {
withWriteLock([&] {
_cloneDynamic = value;
});
}
bool EntityItem::getCloneAvatarEntity() const {
bool result;
withReadLock([&] {
result = _cloneAvatarEntity;
});
return result;
}
void EntityItem::setCloneAvatarEntity(bool value) {
withWriteLock([&] {
_cloneAvatarEntity = value;
});
}
const QUuid EntityItem::getCloneOriginID() const {
QUuid result;
withReadLock([&] {
result = _cloneOriginID;
});
return result;
}
void EntityItem::setCloneOriginID(const QUuid& value) {
withWriteLock([&] {
_cloneOriginID = value;
});
}
void EntityItem::addCloneID(const QUuid& cloneID) {
withWriteLock([&] {
if (!_cloneIDs.contains(cloneID)) {
_cloneIDs.append(cloneID);
}
});
}
void EntityItem::removeCloneID(const QUuid& cloneID) {
withWriteLock([&] {
int index = _cloneIDs.indexOf(cloneID);
if (index >= 0) {
_cloneIDs.removeAt(index);
}
});
}
const QVector<QUuid> EntityItem::getCloneIDs() const {
QVector<QUuid> result;
withReadLock([&] {
result = _cloneIDs;
});
return result;
}
void EntityItem::setCloneIDs(const QVector<QUuid>& cloneIDs) {
withWriteLock([&] {
_cloneIDs = cloneIDs;
});
}

View file

@ -288,10 +288,10 @@ public:
bool getCollisionless() const;
void setCollisionless(bool value);
uint8_t getCollisionMask() const;
void setCollisionMask(uint8_t value);
uint16_t getCollisionMask() const;
void setCollisionMask(uint16_t value);
void computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask) const;
void computeCollisionGroupAndFinalMask(int32_t& group, int32_t& mask) const;
bool getDynamic() const;
void setDynamic(bool value);
@ -341,6 +341,19 @@ public:
quint32 getStaticCertificateVersion() const;
void setStaticCertificateVersion(const quint32&);
bool getCloneable() const;
void setCloneable(bool value);
float getCloneLifetime() const;
void setCloneLifetime(float value);
float getCloneLimit() const;
void setCloneLimit(float value);
bool getCloneDynamic() const;
void setCloneDynamic(bool value);
bool getCloneAvatarEntity() const;
void setCloneAvatarEntity(bool value);
const QUuid getCloneOriginID() const;
void setCloneOriginID(const QUuid& value);
// TODO: get rid of users of getRadius()...
float getRadius() const;
@ -494,6 +507,11 @@ public:
void setSimulationOwnershipExpiry(uint64_t expiry) { _simulationOwnershipExpiry = expiry; }
uint64_t getSimulationOwnershipExpiry() const { return _simulationOwnershipExpiry; }
void addCloneID(const QUuid& cloneID);
void removeCloneID(const QUuid& cloneID);
const QVector<QUuid> getCloneIDs() const;
void setCloneIDs(const QVector<QUuid>& cloneIDs);
signals:
void requestRenderUpdate();
@ -562,7 +580,7 @@ protected:
bool _visible { ENTITY_ITEM_DEFAULT_VISIBLE };
bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW };
bool _collisionless { ENTITY_ITEM_DEFAULT_COLLISIONLESS };
uint8_t _collisionMask { ENTITY_COLLISION_MASK_DEFAULT };
uint16_t _collisionMask { ENTITY_COLLISION_MASK_DEFAULT };
bool _dynamic { ENTITY_ITEM_DEFAULT_DYNAMIC };
bool _locked { ENTITY_ITEM_DEFAULT_LOCKED };
QString _userData { ENTITY_ITEM_DEFAULT_USER_DATA };
@ -648,6 +666,14 @@ protected:
bool _cauterized { false }; // if true, don't draw because it would obscure 1st-person camera
bool _cloneable { ENTITY_ITEM_DEFAULT_CLONEABLE };
float _cloneLifetime { ENTITY_ITEM_DEFAULT_CLONE_LIFETIME };
float _cloneLimit { ENTITY_ITEM_DEFAULT_CLONE_LIMIT };
bool _cloneDynamic { ENTITY_ITEM_DEFAULT_CLONE_DYNAMIC };
bool _cloneAvatarEntity { ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY };
QUuid _cloneOriginID;
QVector<QUuid> _cloneIDs;
private:
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
std::mutex _materialsLock;

View file

@ -130,7 +130,7 @@ void buildStringToMaterialMappingModeLookup() {
addMaterialMappingMode(PROJECTED);
}
QString getCollisionGroupAsString(uint8_t group) {
QString getCollisionGroupAsString(uint16_t group) {
switch (group) {
case USER_COLLISION_GROUP_DYNAMIC:
return "dynamic";
@ -146,7 +146,7 @@ QString getCollisionGroupAsString(uint8_t group) {
return "";
}
uint8_t getCollisionGroupAsBitMask(const QStringRef& name) {
uint16_t getCollisionGroupAsBitMask(const QStringRef& name) {
if (0 == name.compare(QString("dynamic"))) {
return USER_COLLISION_GROUP_DYNAMIC;
} else if (0 == name.compare(QString("static"))) {
@ -164,7 +164,7 @@ uint8_t getCollisionGroupAsBitMask(const QStringRef& name) {
QString EntityItemProperties::getCollisionMaskAsString() const {
QString maskString("");
for (int i = 0; i < NUM_USER_COLLISION_GROUPS; ++i) {
uint8_t group = 0x01 << i;
uint16_t group = 0x0001 << i;
if (group & _collisionMask) {
maskString.append(getCollisionGroupAsString(group));
maskString.append(',');
@ -175,7 +175,7 @@ QString EntityItemProperties::getCollisionMaskAsString() const {
void EntityItemProperties::setCollisionMaskFromString(const QString& maskString) {
QVector<QStringRef> groups = maskString.splitRef(',');
uint8_t mask = 0x00;
uint16_t mask = 0x0000;
for (auto groupName : groups) {
mask |= getCollisionGroupAsBitMask(groupName);
}
@ -436,6 +436,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_DPI, dpi);
CHECK_PROPERTY_CHANGE(PROP_RELAY_PARENT_JOINTS, relayParentJoints);
CHECK_PROPERTY_CHANGE(PROP_CLONEABLE, cloneable);
CHECK_PROPERTY_CHANGE(PROP_CLONE_LIFETIME, cloneLifetime);
CHECK_PROPERTY_CHANGE(PROP_CLONE_LIMIT, cloneLimit);
CHECK_PROPERTY_CHANGE(PROP_CLONE_DYNAMIC, cloneDynamic);
CHECK_PROPERTY_CHANGE(PROP_CLONE_AVATAR_ENTITY, cloneAvatarEntity);
CHECK_PROPERTY_CHANGE(PROP_CLONE_ORIGIN_ID, cloneOriginID);
changedProperties += _animation.getChangedProperties();
changedProperties += _keyLight.getChangedProperties();
changedProperties += _ambientLight.getChangedProperties();
@ -479,7 +486,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @property {boolean} locked=false - Whether or not the entity can be edited or deleted. If <code>true</code> then the
* entity's properties other than <code>locked</code> cannot be changed, and the entity cannot be deleted.
* @property {boolean} visible=true - Whether or not the entity is rendered. If <code>true</code> then the entity is rendered.
* @property {boolean} canCastShadows=true - Whether or not the entity casts shadows. Currently applicable only to
* @property {boolean} canCastShadow=true - Whether or not the entity can cast a shadow. Currently applicable only to
* {@link Entities.EntityType|Model} and {@link Entities.EntityType|Shape} entities. Shadows are cast if inside a
* {@link Entities.EntityType|Zone} entity with <code>castShadows</code> enabled in its
* {@link Entities.EntityProperties-Zone|keyLight} property.
@ -1391,7 +1398,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
/**jsdoc
* The axis-aligned bounding box of an entity.
* @typedef Entities.BoundingBox
* @typedef {object} Entities.BoundingBox
* @property {Vec3} brn - The bottom right near (minimum axes values) corner of the AA box.
* @property {Vec3} tfl - The top far left (maximum axes values) corner of the AA box.
* @property {Vec3} center - The center of the AA box.
@ -1430,6 +1437,13 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); // Gettable but not settable except at entity creation
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); // Gettable but not settable
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONEABLE, cloneable);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_LIFETIME, cloneLifetime);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_LIMIT, cloneLimit);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_DYNAMIC, cloneDynamic);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_AVATAR_ENTITY, cloneAvatarEntity);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_ORIGIN_ID, cloneOriginID);
// Rendering info
if (!skipDefaults && !strictSemantics) {
QScriptValue renderInfo = engine->newObject();
@ -1506,7 +1520,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(localRenderAlpha, float, setLocalRenderAlpha);
COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionless, bool, setCollisionless);
COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(ignoreForCollisions, bool, setCollisionless, getCollisionless); // legacy support
COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionMask, uint8_t, setCollisionMask);
COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionMask, uint16_t, setCollisionMask);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(collidesWith, CollisionMask);
COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(collisionsWillMove, bool, setDynamic, getDynamic); // legacy support
COPY_PROPERTY_FROM_QSCRIPTVALUE(dynamic, bool, setDynamic);
@ -1642,6 +1656,13 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(dpi, uint16_t, setDPI);
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneable, bool, setCloneable);
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneLifetime, float, setCloneLifetime);
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneLimit, float, setCloneLimit);
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneDynamic, bool, setCloneDynamic);
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneAvatarEntity, bool, setCloneAvatarEntity);
COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneOriginID, QUuid, setCloneOriginID);
_lastEdited = usecTimestampNow();
}
@ -1793,6 +1814,13 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(dpi);
COPY_PROPERTY_IF_CHANGED(cloneable);
COPY_PROPERTY_IF_CHANGED(cloneLifetime);
COPY_PROPERTY_IF_CHANGED(cloneLimit);
COPY_PROPERTY_IF_CHANGED(cloneDynamic);
COPY_PROPERTY_IF_CHANGED(cloneAvatarEntity);
COPY_PROPERTY_IF_CHANGED(cloneOriginID);
_lastEdited = usecTimestampNow();
}
@ -2017,6 +2045,13 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t);
ADD_PROPERTY_TO_MAP(PROP_CLONEABLE, Cloneable, cloneable, bool);
ADD_PROPERTY_TO_MAP(PROP_CLONE_LIFETIME, CloneLifetime, cloneLifetime, float);
ADD_PROPERTY_TO_MAP(PROP_CLONE_LIMIT, CloneLimit, cloneLimit, float);
ADD_PROPERTY_TO_MAP(PROP_CLONE_DYNAMIC, CloneDynamic, cloneDynamic, bool);
ADD_PROPERTY_TO_MAP(PROP_CLONE_AVATAR_ENTITY, CloneAvatarEntity, cloneAvatarEntity, bool);
ADD_PROPERTY_TO_MAP(PROP_CLONE_ORIGIN_ID, CloneOriginID, cloneOriginID, QUuid);
// FIXME - these are not yet handled
//ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64);
@ -2331,6 +2366,12 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, properties.getEntityInstanceNumber());
APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, properties.getCertificateID());
APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, properties.getStaticCertificateVersion());
APPEND_ENTITY_PROPERTY(PROP_CLONEABLE, properties.getCloneable());
APPEND_ENTITY_PROPERTY(PROP_CLONE_LIFETIME, properties.getCloneLifetime());
APPEND_ENTITY_PROPERTY(PROP_CLONE_LIMIT, properties.getCloneLimit());
APPEND_ENTITY_PROPERTY(PROP_CLONE_DYNAMIC, properties.getCloneDynamic());
APPEND_ENTITY_PROPERTY(PROP_CLONE_AVATAR_ENTITY, properties.getCloneAvatarEntity());
}
if (propertyCount > 0) {
@ -2535,7 +2576,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE, bool, setVisible);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISIONLESS, bool, setCollisionless);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_MASK, uint8_t, setCollisionMask);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_MASK, uint16_t, setCollisionMask);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DYNAMIC, bool, setDynamic);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCKED, bool, setLocked);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USER_DATA, QString, setUserData);
@ -2701,6 +2742,12 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_ID, QString, setCertificateID);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONEABLE, bool, setCloneable);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_LIFETIME, float, setCloneLifetime);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_LIMIT, float, setCloneLimit);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_DYNAMIC, bool, setCloneDynamic);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_AVATAR_ENTITY, bool, setCloneAvatarEntity);
return valid;
}
@ -2780,6 +2827,52 @@ bool EntityItemProperties::encodeEraseEntityMessage(const EntityItemID& entityIt
return true;
}
bool EntityItemProperties::encodeCloneEntityMessage(const EntityItemID& entityIDToClone, const EntityItemID& newEntityID, QByteArray& buffer) {
char* copyAt = buffer.data();
int outputLength = 0;
if (buffer.size() < (int)(NUM_BYTES_RFC4122_UUID * 2)) {
qCDebug(entities) << "ERROR - encodeCloneEntityMessage() called with buffer that is too small!";
return false;
}
memcpy(copyAt, entityIDToClone.toRfc4122().constData(), NUM_BYTES_RFC4122_UUID);
copyAt += NUM_BYTES_RFC4122_UUID;
outputLength += NUM_BYTES_RFC4122_UUID;
memcpy(copyAt, newEntityID.toRfc4122().constData(), NUM_BYTES_RFC4122_UUID);
copyAt += NUM_BYTES_RFC4122_UUID;
outputLength += NUM_BYTES_RFC4122_UUID;
buffer.resize(outputLength);
return true;
}
bool EntityItemProperties::decodeCloneEntityMessage(const QByteArray& buffer, int& processedBytes, EntityItemID& entityIDToClone, EntityItemID& newEntityID) {
const unsigned char* packetData = (const unsigned char*)buffer.constData();
const unsigned char* dataAt = packetData;
size_t packetLength = buffer.size();
processedBytes = 0;
if (NUM_BYTES_RFC4122_UUID * 2 > packetLength) {
qCDebug(entities) << "EntityItemProperties::processEraseMessageDetails().... bailing because not enough bytes in buffer";
return false; // bail to prevent buffer overflow
}
QByteArray encodedID = buffer.mid((int)processedBytes, NUM_BYTES_RFC4122_UUID);
entityIDToClone = QUuid::fromRfc4122(encodedID);
dataAt += encodedID.size();
processedBytes += encodedID.size();
encodedID = buffer.mid((int)processedBytes, NUM_BYTES_RFC4122_UUID);
newEntityID = QUuid::fromRfc4122(encodedID);
dataAt += encodedID.size();
processedBytes += encodedID.size();
return true;
}
void EntityItemProperties::markAllChanged() {
_lastEditedByChanged = true;
_simulationOwnerChanged = true;
@ -2941,6 +3034,13 @@ void EntityItemProperties::markAllChanged() {
_dpiChanged = true;
_relayParentJointsChanged = true;
_cloneableChanged = true;
_cloneLifetimeChanged = true;
_cloneLimitChanged = true;
_cloneDynamicChanged = true;
_cloneAvatarEntityChanged = true;
_cloneOriginIDChanged = true;
}
// The minimum bounding box for the entity.
@ -3373,6 +3473,25 @@ QList<QString> EntityItemProperties::listChangedProperties() {
out += "isUVModeStretch";
}
if (cloneableChanged()) {
out += "cloneable";
}
if (cloneLifetimeChanged()) {
out += "cloneLifetime";
}
if (cloneLimitChanged()) {
out += "cloneLimit";
}
if (cloneDynamicChanged()) {
out += "cloneDynamic";
}
if (cloneAvatarEntityChanged()) {
out += "cloneAvatarEntity";
}
if (cloneOriginIDChanged()) {
out += "cloneOriginID";
}
getAnimation().listChangedProperties(out);
getKeyLight().listChangedProperties(out);
getAmbientLight().listChangedProperties(out);
@ -3536,3 +3655,18 @@ bool EntityItemProperties::verifyStaticCertificateProperties() {
// I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash.
return verifySignature(EntityItem::_marketplacePublicKey, getStaticCertificateHash(), QByteArray::fromBase64(getCertificateID().toUtf8()));
}
void EntityItemProperties::convertToCloneProperties(const EntityItemID& entityIDToClone) {
setName(getName() + "-clone-" + entityIDToClone.toString());
setLocked(false);
setLifetime(getCloneLifetime());
setDynamic(getCloneDynamic());
setClientOnly(getCloneAvatarEntity());
setCreated(usecTimestampNow());
setLastEdited(usecTimestampNow());
setCloneable(ENTITY_ITEM_DEFAULT_CLONEABLE);
setCloneLifetime(ENTITY_ITEM_DEFAULT_CLONE_LIFETIME);
setCloneLimit(ENTITY_ITEM_DEFAULT_CLONE_LIMIT);
setCloneDynamic(ENTITY_ITEM_DEFAULT_CLONE_DYNAMIC);
setCloneAvatarEntity(ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY);
}

View file

@ -148,7 +148,7 @@ public:
DEFINE_PROPERTY_REF(PROP_ANGULAR_VELOCITY, AngularVelocity, angularVelocity, glm::vec3, ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY);
DEFINE_PROPERTY(PROP_ANGULAR_DAMPING, AngularDamping, angularDamping, float, ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING);
DEFINE_PROPERTY(PROP_COLLISIONLESS, Collisionless, collisionless, bool, ENTITY_ITEM_DEFAULT_COLLISIONLESS);
DEFINE_PROPERTY(PROP_COLLISION_MASK, CollisionMask, collisionMask, uint8_t, ENTITY_COLLISION_MASK_DEFAULT);
DEFINE_PROPERTY(PROP_COLLISION_MASK, CollisionMask, collisionMask, uint16_t, ENTITY_COLLISION_MASK_DEFAULT);
DEFINE_PROPERTY(PROP_DYNAMIC, Dynamic, dynamic, bool, ENTITY_ITEM_DEFAULT_DYNAMIC);
DEFINE_PROPERTY(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool, LightEntityItem::DEFAULT_IS_SPOTLIGHT);
DEFINE_PROPERTY(PROP_INTENSITY, Intensity, intensity, float, LightEntityItem::DEFAULT_INTENSITY);
@ -272,6 +272,13 @@ public:
DEFINE_PROPERTY_REF(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString, ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS);
DEFINE_PROPERTY(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool, ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS);
DEFINE_PROPERTY(PROP_CLONEABLE, Cloneable, cloneable, bool, ENTITY_ITEM_DEFAULT_CLONEABLE);
DEFINE_PROPERTY(PROP_CLONE_LIFETIME, CloneLifetime, cloneLifetime, float, ENTITY_ITEM_DEFAULT_CLONE_LIFETIME);
DEFINE_PROPERTY(PROP_CLONE_LIMIT, CloneLimit, cloneLimit, float, ENTITY_ITEM_DEFAULT_CLONE_LIMIT);
DEFINE_PROPERTY(PROP_CLONE_DYNAMIC, CloneDynamic, cloneDynamic, bool, ENTITY_ITEM_DEFAULT_CLONE_DYNAMIC);
DEFINE_PROPERTY(PROP_CLONE_AVATAR_ENTITY, CloneAvatarEntity, cloneAvatarEntity, bool, ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY);
DEFINE_PROPERTY_REF(PROP_CLONE_ORIGIN_ID, CloneOriginID, cloneOriginID, QUuid, ENTITY_ITEM_DEFAULT_CLONE_ORIGIN_ID);
static QString getComponentModeString(uint32_t mode);
static QString getComponentModeAsString(uint32_t mode);
@ -294,6 +301,8 @@ public:
QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties);
static bool encodeEraseEntityMessage(const EntityItemID& entityItemID, QByteArray& buffer);
static bool encodeCloneEntityMessage(const EntityItemID& entityIDToClone, const EntityItemID& newEntityID, QByteArray& buffer);
static bool decodeCloneEntityMessage(const QByteArray& buffer, int& processedBytes, EntityItemID& entityIDToClone, EntityItemID& newEntityID);
static bool decodeEntityEditPacket(const unsigned char* data, int bytesToRead, int& processedBytes,
EntityItemID& entityID, EntityItemProperties& properties);
@ -369,6 +378,8 @@ public:
bool verifyStaticCertificateProperties();
static bool verifySignature(const QString& key, const QByteArray& text, const QByteArray& signature);
void convertToCloneProperties(const EntityItemID& entityIDToClone);
protected:
QString getCollisionMaskAsString() const;
void setCollisionMaskFromString(const QString& maskString);
@ -515,6 +526,13 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
DEBUG_PROPERTY_IF_CHANGED(debug, properties, AmbientLightMode, ambientLightMode, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, SkyboxMode, skyboxMode, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Cloneable, cloneable, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CloneLifetime, cloneLifetime, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CloneLimit, cloneLimit, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CloneDynamic, cloneDynamic, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CloneAvatarEntity, cloneAvatarEntity, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CloneOriginID, cloneOriginID, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelVolumeSize, voxelVolumeSize, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelData, voxelData, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelSurfaceStyle, voxelSurfaceStyle, "");

View file

@ -97,4 +97,11 @@ const QUuid ENTITY_ITEM_DEFAULT_LAST_EDITED_BY = QUuid();
const bool ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS = false;
const bool ENTITY_ITEM_DEFAULT_CLONEABLE = false;
const float ENTITY_ITEM_DEFAULT_CLONE_LIFETIME = 300.0f;
const int ENTITY_ITEM_DEFAULT_CLONE_LIMIT = 0;
const bool ENTITY_ITEM_DEFAULT_CLONE_DYNAMIC = false;
const bool ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY = false;
const QUuid ENTITY_ITEM_DEFAULT_CLONE_ORIGIN_ID = QUuid();
#endif // hifi_EntityItemPropertiesDefaults_h

View file

@ -204,6 +204,13 @@ enum EntityPropertyList {
PROP_CERTIFICATE_ID,
PROP_STATIC_CERTIFICATE_VERSION,
PROP_CLONEABLE,
PROP_CLONE_LIFETIME,
PROP_CLONE_LIMIT,
PROP_CLONE_DYNAMIC,
PROP_CLONE_AVATAR_ENTITY,
PROP_CLONE_ORIGIN_ID,
PROP_HAZE_MODE,
PROP_KEYLIGHT_COLOR,

View file

@ -258,33 +258,9 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent);
propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged());
EntityItemID id = EntityItemID(QUuid::createUuid());
EntityItemID id;
// If we have a local entity tree set, then also update it.
bool success = true;
if (_entityTree) {
_entityTree->withWriteLock([&] {
EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID);
if (entity) {
if (propertiesWithSimID.queryAACubeRelatedPropertyChanged()) {
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
bool success;
AACube queryAACube = entity->getQueryAACube(success);
if (success) {
propertiesWithSimID.setQueryAACube(queryAACube);
}
}
entity->setLastBroadcast(usecTimestampNow());
// since we're creating this object we will immediately volunteer to own its simulation
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
propertiesWithSimID.setLastEdited(entity->getLastEdited());
} else {
qCDebug(entities) << "script failed to add new Entity to local Octree";
success = false;
}
});
}
bool success = addLocalEntityCopy(propertiesWithSimID, id);
// queue the packet
if (success) {
@ -295,6 +271,37 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
}
}
bool EntityScriptingInterface::addLocalEntityCopy(EntityItemProperties& properties, EntityItemID& id, bool isClone) {
bool success = true;
id = EntityItemID(QUuid::createUuid());
if (_entityTree) {
_entityTree->withWriteLock([&] {
EntityItemPointer entity = _entityTree->addEntity(id, properties, isClone);
if (entity) {
if (properties.queryAACubeRelatedPropertyChanged()) {
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
bool success;
AACube queryAACube = entity->getQueryAACube(success);
if (success) {
properties.setQueryAACube(queryAACube);
}
}
entity->setLastBroadcast(usecTimestampNow());
// since we're creating this object we will immediately volunteer to own its simulation
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
properties.setLastEdited(entity->getLastEdited());
} else {
qCDebug(entities) << "script failed to add new Entity to local Octree";
success = false;
}
});
}
return success;
}
QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& textures,
const QString& shapeType, bool dynamic, bool collisionless,
const glm::vec3& position, const glm::vec3& gravity) {
@ -320,6 +327,28 @@ QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QStrin
return addEntity(properties);
}
QUuid EntityScriptingInterface::cloneEntity(QUuid entityIDToClone) {
EntityItemID newEntityID;
EntityItemProperties properties = getEntityProperties(entityIDToClone);
bool cloneAvatarEntity = properties.getCloneAvatarEntity();
properties.convertToCloneProperties(entityIDToClone);
if (cloneAvatarEntity) {
return addEntity(properties, true);
} else {
// setLastEdited timestamp to 0 to ensure this entity gets updated with the properties
// from the server-created entity, don't change this unless you know what you are doing
properties.setLastEdited(0);
bool success = addLocalEntityCopy(properties, newEntityID, true);
if (success) {
getEntityPacketSender()->queueCloneEntityMessage(entityIDToClone, newEntityID);
return newEntityID;
} else {
return QUuid();
}
}
}
EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity) {
EntityPropertyFlags noSpecificProperties;
return getEntityProperties(identity, noSpecificProperties);

View file

@ -224,6 +224,16 @@ public slots:
Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic,
bool collisionless, const glm::vec3& position, const glm::vec3& gravity);
/**jsdoc
* Request a clone of an entity. Only entities that have been marked as 'cloneable' will be able to be cloned using this method.
* A cloned entity has most of the properties of the orignal entity, and can be requested from clients that do not have rez permissions.
* The client requests a clone from the entity server, which returns back the entityID of a valid clone if the operation was allowed.
* @function Entities.cloneEntity
* @param {Uuid} entityIDToClone - the ID of the entity to clone
* @returns {Entities.EntityID} The ID of the newly created clone
*/
Q_INVOKABLE QUuid cloneEntity(QUuid entityIDToClone);
/**jsdoc
* Get the properties of an entity.
* @function Entities.getEntityProperties
@ -1875,6 +1885,7 @@ private:
bool polyVoxWorker(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor);
bool setPoints(QUuid entityID, std::function<bool(LineEntityItem&)> actor);
void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties);
bool addLocalEntityCopy(EntityItemProperties& propertiesWithSimID, EntityItemID& id, bool isClone = false);
EntityItemPointer checkForTreeEntityAndTypeMatch(const QUuid& entityID,
EntityTypes::EntityType entityType = EntityTypes::Unknown);

View file

@ -65,6 +65,7 @@ void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) {
removeEntityInternal(entity);
if (entity->getElement()) {
_deadEntities.insert(entity);
_entityTree->cleanupCloneIDs(entity->getEntityItemID());
}
}
}

View file

@ -228,6 +228,7 @@ bool EntityTree::handlesEditPacketType(PacketType packetType) const {
// we handle these types of "edit" packets
switch (packetType) {
case PacketType::EntityAdd:
case PacketType::EntityClone:
case PacketType::EntityEdit:
case PacketType::EntityErase:
case PacketType::EntityPhysics:
@ -492,7 +493,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
return true;
}
EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool isClone) {
EntityItemPointer result = NULL;
EntityItemProperties props = properties;
@ -504,7 +505,7 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
if (!properties.getClientOnly() && getIsClient() &&
!nodeList->getThisNodeCanRez() && !nodeList->getThisNodeCanRezTmp() &&
!nodeList->getThisNodeCanRezCertified() && !nodeList->getThisNodeCanRezTmpCertified() && !_serverlessDomain) {
!nodeList->getThisNodeCanRezCertified() && !nodeList->getThisNodeCanRezTmpCertified() && !_serverlessDomain && !isClone) {
return nullptr;
}
@ -592,6 +593,7 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign
return;
}
cleanupCloneIDs(entityID);
unhookChildAvatar(entityID);
emit deletingEntity(entityID);
emit deletingEntityPointer(existingEntity.get());
@ -625,6 +627,28 @@ void EntityTree::unhookChildAvatar(const EntityItemID entityID) {
});
}
void EntityTree::cleanupCloneIDs(const EntityItemID& entityID) {
EntityItemPointer entity = findEntityByEntityItemID(entityID);
if (entity) {
// remove clone ID from it's clone origin's clone ID list if clone origin exists
const QUuid& cloneOriginID = entity->getCloneOriginID();
if (!cloneOriginID.isNull()) {
EntityItemPointer cloneOrigin = findEntityByID(cloneOriginID);
if (cloneOrigin) {
cloneOrigin->removeCloneID(entityID);
}
}
// clear the clone origin ID on any clones that this entity had
const QVector<QUuid>& cloneIDs = entity->getCloneIDs();
foreach(const QUuid& cloneChildID, cloneIDs) {
EntityItemPointer cloneChild = findEntityByEntityItemID(cloneChildID);
if (cloneChild) {
cloneChild->setCloneOriginID(QUuid());
}
}
}
}
void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool ignoreWarnings) {
// NOTE: callers must lock the tree before using this method
DeleteEntityOperator theOperator(getThisPointer());
@ -653,6 +677,7 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool i
}
// tell our delete operator about this entityID
cleanupCloneIDs(entityID);
unhookChildAvatar(entityID);
theOperator.addEntityIDToDeleteList(entityID);
emit deletingEntity(entityID);
@ -1392,6 +1417,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
int processedBytes = 0;
bool isAdd = false;
bool isClone = false;
// we handle these types of "edit" packets
switch (message.getType()) {
case PacketType::EntityErase: {
@ -1400,6 +1426,9 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
break;
}
case PacketType::EntityClone:
isClone = true; // fall through to next case
// FALLTHRU
case PacketType::EntityAdd:
isAdd = true; // fall through to next case
// FALLTHRU
@ -1422,8 +1451,22 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
EntityItemProperties properties;
startDecode = usecTimestampNow();
bool validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength, processedBytes,
entityItemID, properties);
bool validEditPacket = false;
EntityItemID entityIDToClone;
EntityItemPointer entityToClone;
if (isClone) {
QByteArray buffer = QByteArray::fromRawData(reinterpret_cast<const char*>(editData), maxLength);
validEditPacket = EntityItemProperties::decodeCloneEntityMessage(buffer, processedBytes, entityIDToClone, entityItemID);
if (validEditPacket) {
entityToClone = findEntityByEntityItemID(entityIDToClone);
if (entityToClone) {
properties = entityToClone->getProperties();
}
}
} else {
validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength, processedBytes, entityItemID, properties);
}
endDecode = usecTimestampNow();
EntityItemPointer existingEntity;
@ -1491,24 +1534,26 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
}
if ((isAdd || properties.lifetimeChanged()) &&
((!senderNode->getCanRez() && senderNode->getCanRezTmp()) ||
(!senderNode->getCanRezCertified() && senderNode->getCanRezTmpCertified()))) {
// this node is only allowed to rez temporary entities. if need be, cap the lifetime.
if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME ||
properties.getLifetime() > _maxTmpEntityLifetime) {
properties.setLifetime(_maxTmpEntityLifetime);
if (!isClone) {
if ((isAdd || properties.lifetimeChanged()) &&
((!senderNode->getCanRez() && senderNode->getCanRezTmp()) ||
(!senderNode->getCanRezCertified() && senderNode->getCanRezTmpCertified()))) {
// this node is only allowed to rez temporary entities. if need be, cap the lifetime.
if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME ||
properties.getLifetime() > _maxTmpEntityLifetime) {
properties.setLifetime(_maxTmpEntityLifetime);
bumpTimestamp(properties);
}
}
if (isAdd && properties.getLocked() && !senderNode->isAllowedEditor()) {
// if a node can't change locks, don't allow it to create an already-locked entity -- automatically
// clear the locked property and allow the unlocked entity to be created.
properties.setLocked(false);
bumpTimestamp(properties);
}
}
if (isAdd && properties.getLocked() && !senderNode->isAllowedEditor()) {
// if a node can't change locks, don't allow it to create an already-locked entity -- automatically
// clear the locked property and allow the unlocked entity to be created.
properties.setLocked(false);
bumpTimestamp(properties);
}
// If we got a valid edit packet, then it could be a new entity or it could be an update to
// an existing entity... handle appropriately
if (validEditPacket) {
@ -1566,17 +1611,32 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
} else if (isAdd) {
bool failedAdd = !allowed;
bool isCertified = !properties.getCertificateID().isEmpty();
bool isCloneable = properties.getCloneable();
int cloneLimit = properties.getCloneLimit();
if (!allowed) {
qCDebug(entities) << "Filtered entity add. ID:" << entityItemID;
} else if (!isCertified && !senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
} else if (!isClone && !isCertified && !senderNode->getCanRez() && !senderNode->getCanRezTmp()) {
failedAdd = true;
qCDebug(entities) << "User without 'uncertified rez rights' [" << senderNode->getUUID()
<< "] attempted to add an uncertified entity with ID:" << entityItemID;
} else if (isCertified && !senderNode->getCanRezCertified() && !senderNode->getCanRezTmpCertified()) {
} else if (!isClone && isCertified && !senderNode->getCanRezCertified() && !senderNode->getCanRezTmpCertified()) {
failedAdd = true;
qCDebug(entities) << "User without 'certified rez rights' [" << senderNode->getUUID()
<< "] attempted to add a certified entity with ID:" << entityItemID;
} else if (isClone && isCertified) {
failedAdd = true;
qCDebug(entities) << "User attempted to clone certified entity from entity ID:" << entityIDToClone;
} else if (isClone && !isCloneable) {
failedAdd = true;
qCDebug(entities) << "User attempted to clone non-cloneable entity from entity ID:" << entityIDToClone;
} else if (isClone && entityToClone && entityToClone->getCloneIDs().size() >= cloneLimit && cloneLimit != 0) {
failedAdd = true;
qCDebug(entities) << "User attempted to clone entity ID:" << entityIDToClone << " which reached it's cloneable limit.";
} else {
if (isClone) {
properties.convertToCloneProperties(entityIDToClone);
}
// this is a new entity... assign a new entityID
properties.setCreated(properties.getLastEdited());
properties.setLastEditedBy(senderNode->getUUID());
@ -1597,10 +1657,15 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
}
}
if (newEntity && isClone) {
entityToClone->addCloneID(newEntity->getEntityItemID());
newEntity->setCloneOriginID(entityIDToClone);
}
if (newEntity) {
newEntity->markAsChangedOnServer();
notifyNewlyCreatedEntity(*newEntity, senderNode);
startLogging = usecTimestampNow();
if (wantEditLogging()) {
qCDebug(entities) << "User [" << senderNode->getUUID() << "] added entity. ID:"
@ -1927,6 +1992,7 @@ int EntityTree::processEraseMessage(ReceivedMessage& message, const SharedNodePo
if (shouldEraseEntity(entityID, sourceNode)) {
entityItemIDsToDelete << entityItemID;
cleanupCloneIDs(entityItemID);
}
}
deleteEntities(entityItemIDsToDelete, true, true);
@ -1976,6 +2042,7 @@ int EntityTree::processEraseMessageDetails(const QByteArray& dataByteArray, cons
if (shouldEraseEntity(entityID, sourceNode)) {
entityItemIDsToDelete << entityItemID;
cleanupCloneIDs(entityItemID);
}
}
@ -2322,6 +2389,8 @@ bool EntityTree::readFromMap(QVariantMap& map) {
return false;
}
QMap<QUuid, QVector<QUuid>> cloneIDs;
bool success = true;
foreach (QVariant entityVariant, entitiesQList) {
// QVariantMap --> QScriptValue --> EntityItemProperties --> Entity
@ -2409,11 +2478,43 @@ bool EntityTree::readFromMap(QVariantMap& map) {
}
}
// Convert old cloneable entities so they use cloneableData instead of userData
if (contentVersion < (int)EntityVersion::CloneableData) {
QJsonObject userData = QJsonDocument::fromJson(properties.getUserData().toUtf8()).object();
QJsonObject grabbableKey = userData["grabbableKey"].toObject();
QJsonValue cloneable = grabbableKey["cloneable"];
if (cloneable.isBool() && cloneable.toBool()) {
QJsonValue cloneLifetime = grabbableKey["cloneLifetime"];
QJsonValue cloneLimit = grabbableKey["cloneLimit"];
QJsonValue cloneDynamic = grabbableKey["cloneDynamic"];
QJsonValue cloneAvatarEntity = grabbableKey["cloneAvatarEntity"];
// This is cloneable, we need to convert the properties
properties.setCloneable(true);
properties.setCloneLifetime(cloneLifetime.toInt());
properties.setCloneLimit(cloneLimit.toInt());
properties.setCloneDynamic(cloneDynamic.toBool());
properties.setCloneAvatarEntity(cloneAvatarEntity.toBool());
}
}
EntityItemPointer entity = addEntity(entityItemID, properties);
if (!entity) {
qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType();
success = false;
}
const QUuid& cloneOriginID = entity->getCloneOriginID();
if (!cloneOriginID.isNull()) {
cloneIDs[cloneOriginID].push_back(entity->getEntityItemID());
}
}
for (const auto& entityID : cloneIDs.keys()) {
auto entity = findEntityByID(entityID);
if (entity) {
entity->setCloneIDs(cloneIDs.value(entityID));
}
}
return success;

View file

@ -110,13 +110,14 @@ public:
// The newer API...
void postAddEntity(EntityItemPointer entityItem);
EntityItemPointer addEntity(const EntityItemID& entityID, const EntityItemProperties& properties);
EntityItemPointer addEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool isClone = false);
// use this method if you only know the entityID
bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode = SharedNodePointer(nullptr));
// check if the avatar is a child of this entity, If so set the avatar parentID to null
void unhookChildAvatar(const EntityItemID entityID);
void cleanupCloneIDs(const EntityItemID& entityID);
void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = true);
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false, bool ignoreWarnings = true);

View file

@ -35,7 +35,7 @@ class ReadBitstreamToTreeParams;
* @property {Vec3} direction=0,-1,0 - The direction the light is shining.
* @property {boolean} castShadows=false - If <code>true</code> then shadows are cast. Shadows are cast by avatars, plus
* {@link Entities.EntityType|Model} and {@link Entities.EntityType|Shape} entities that have their
* <code>{@link Entities.EntityProperties|canCastShadows}</code> property set to <code>true</code>.
* <code>{@link Entities.EntityProperties|canCastShadow}</code> property set to <code>true</code>.
*/
class KeyLightPropertyGroup : public PropertyGroup {
public:

View file

@ -258,6 +258,11 @@ public:
QHash<QString, size_t> texcoordSetMap;
};
/**jsdoc
* @typedef {object} FBXAnimationFrame
* @property {Quat[]} rotations
* @property {Vec3[]} translations
*/
/// A single animation frame extracted from an FBX document.
class FBXAnimationFrame {
public:

View file

@ -31,6 +31,10 @@ bool GL41Backend::supportedTextureFormat(const gpu::Element& format) {
case gpu::Semantic::COMPRESSED_EAC_RED_SIGNED:
case gpu::Semantic::COMPRESSED_EAC_XY:
case gpu::Semantic::COMPRESSED_EAC_XY_SIGNED:
// The ARB_texture_compression_bptc extension is not supported on 4.1
// See https://www.g-truc.net/doc/OpenGL%204%20Hardware%20Matrix.pdf
case gpu::Semantic::COMPRESSED_BC6_RGB:
case gpu::Semantic::COMPRESSED_BC7_SRGBA:
return false;
default:
return true;

View file

@ -33,6 +33,8 @@ using namespace gpu::gl45;
bool GL45Backend::supportedTextureFormat(const gpu::Element& format) {
switch (format.getSemantic()) {
// ETC textures are actually required by the OpenGL spec as of 4.3, but aren't always supported by hardware
// They'll be recompressed by OpenGL, which will be slow or have poor quality, so disable them for now
case gpu::Semantic::COMPRESSED_ETC2_RGB:
case gpu::Semantic::COMPRESSED_ETC2_SRGB:
case gpu::Semantic::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA:

View file

@ -279,7 +279,7 @@ controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevic
* moved down. The data value is how far the average position of all touch points moved.</td></tr>
* </tbody>
* </table>
* @typedef Controller.Hardware-Keyboard
* @typedef {object} Controller.Hardware-Keyboard
* @todo <em>Currently, the mouse wheel in an ordinary mouse generates left/right wheel events instead of up/down.</em>
*/
controller::Input::NamedVector KeyboardMouseDevice::InputDevice::getAvailableInputs() const {

View file

@ -179,7 +179,7 @@ public:
* @function ModelCache.prefetch
* @param {string} url - URL of the resource to prefetch.
* @param {object} [extra=null]
* @returns {Resource}
* @returns {ResourceObject}
*/
/**jsdoc
@ -188,7 +188,7 @@ public:
* @param {string} url - URL of the resource to load.
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
* @param {} [extra=null]
* @returns {Resource}
* @returns {object}
*/

View file

@ -195,7 +195,7 @@ public:
* @function TextureCache.prefetch
* @param {string} url - URL of the resource to prefetch.
* @param {object} [extra=null]
* @returns {Resource}
* @returns {ResourceObject}
*/
/**jsdoc
@ -204,7 +204,7 @@ public:
* @param {string} url - URL of the resource to load.
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
* @param {} [extra=null]
* @returns {Resource}
* @returns {object}
*/
@ -261,7 +261,7 @@ protected:
* @param {string} url
* @param {number} type
* @param {number} [maxNumPixels=67108864]
* @returns {Resource}
* @returns {ResourceObject}
*/
// Overload ResourceCache::prefetch to allow specifying texture type for loads
Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS);

View file

@ -138,7 +138,7 @@ public:
* </tr>
* </tbody>
* </table>
* @typedef location.LookupTrigger
* @typedef {number} location.LookupTrigger
*/
enum LookupTrigger {
UserInput,
@ -184,7 +184,7 @@ public slots:
/**jsdoc
* Go to a specified metaverse address.
* @function location.handleLookupString
* @param {string} address - The address to go to: a <code>"hifi:/"<code> address, an IP address (e.g.,
* @param {string} address - The address to go to: a <code>"hifi://"<code> address, an IP address (e.g.,
* <code>"127.0.0.1"</code> or <code>"localhost"</code>), a domain name, a named path on a domain (starts with
* <code>"/"</code>), a position or position and orientation, or a user (starts with <code>"@"</code>).
* @param {boolean} fromSuggestions=false - Set to <code>true</code> if the address is obtained from the "Goto" dialog.

View file

@ -137,7 +137,7 @@ public:
* </tr>
* </tbody>
* </table>
* @typedef Window.ConnectionRefusedReason
* @typedef {number} Window.ConnectionRefusedReason
*/
enum class ConnectionRefusedReason : uint8_t {
Unknown,

View file

@ -87,7 +87,7 @@ private:
class ScriptableResource : public QObject {
/**jsdoc
* @constructor Resource
* @class ResourceObject
*
* @hifi-interface
* @hifi-client-entity
@ -97,11 +97,6 @@ class ScriptableResource : public QObject {
* @property {string} url - URL of this resource.
* @property {Resource.State} state - Current loading state.
*/
/**jsdoc
* @namespace Resource
* @variation 0
* @property {Resource.State} State
*/
Q_OBJECT
Q_PROPERTY(QUrl url READ getURL)
Q_PROPERTY(int state READ getState NOTIFY stateChanged)
@ -109,8 +104,7 @@ class ScriptableResource : public QObject {
public:
/**jsdoc
* @name Resource.State
* @static
* @typedef {object} Resource.State
* @property {number} QUEUED - The resource is queued up, waiting to be loaded.
* @property {number} LOADING - The resource is downloading.
* @property {number} LOADED - The resource has finished downloaded by is not complete.
@ -131,7 +125,7 @@ public:
/**jsdoc
* Release this resource.
* @function Resource#release
* @function ResourceObject#release
*/
Q_INVOKABLE void release();
@ -146,7 +140,7 @@ signals:
/**jsdoc
* Triggered when download progress for this resource has changed.
* @function Resource#progressChanged
* @function ResourceObject#progressChanged
* @param {number} bytesReceived - Byytes downloaded so far.
* @param {number} bytesTotal - Total number of bytes in the resource.
* @returns {Signal}
@ -155,7 +149,7 @@ signals:
/**jsdoc
* Triggered when resource loading state has changed.
* @function Resource#stateChanged
* @function ResourceObject#stateChanged
* @param {Resource.State} state - New state.
* @returns {Signal}
*/
@ -262,7 +256,7 @@ protected slots:
* @function ResourceCache.prefetch
* @param {string} url - URL of the resource to prefetch.
* @param {object} [extra=null]
* @returns {Resource}
* @returns {ResourceObject}
*/
// Prefetches a resource to be held by the QScriptEngine.
// Left as a protected member so subclasses can overload prefetch
@ -275,8 +269,9 @@ protected slots:
* @param {string} url - URL of the resource to load.
* @param {string} [fallback=""] - Fallback URL if load of the desired URL fails.
* @param {} [extra=null]
* @returns {Resource}
* @returns {object}
*/
// FIXME: The return type is not recognized by JavaScript.
/// Loads a resource from the specified URL and returns it.
/// If the caller is on a different thread than the ResourceCache,
/// returns an empty smart pointer and loads its asynchronously.

View file

@ -29,10 +29,11 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::DomainList:
return static_cast<PacketVersion>(DomainListVersion::GetMachineFingerprintFromUUIDSupport);
case PacketType::EntityAdd:
case PacketType::EntityClone:
case PacketType::EntityEdit:
case PacketType::EntityData:
case PacketType::EntityPhysics:
return static_cast<PacketVersion>(EntityVersion::MaterialData);
return static_cast<PacketVersion>(EntityVersion::CollisionMask16Bytes);
case PacketType::EntityQuery:
return static_cast<PacketVersion>(EntityQueryPacketVersion::ConicalFrustums);
case PacketType::AvatarIdentity:

View file

@ -131,6 +131,8 @@ public:
OctreeDataFileReply,
OctreeDataPersist,
EntityClone,
NUM_PACKET_TYPE
};
@ -232,7 +234,9 @@ enum class EntityVersion : PacketVersion {
SoftEntities,
MaterialEntities,
ShadowControl,
MaterialData
MaterialData,
CloneableData,
CollisionMask16Bytes
};
enum class EntityScriptCallMethodVersion : PacketVersion {

View file

@ -109,7 +109,7 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
}
_dynamicsWorld = nullptr;
}
int16_t collisionGroup = computeCollisionGroup();
int32_t collisionGroup = computeCollisionGroup();
if (_rigidBody) {
updateMassProperties();
}
@ -325,7 +325,7 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar
_ghost.setWorldTransform(_rigidBody->getWorldTransform());
}
void CharacterController::jump() {
void CharacterController::jump(const btVector3& dir) {
_pendingFlags |= PENDING_FLAG_JUMP;
}
@ -352,7 +352,7 @@ static const char* stateToStr(CharacterController::State state) {
#endif // #ifdef DEBUG_STATE_CHANGE
void CharacterController::updateCurrentGravity() {
int16_t collisionGroup = computeCollisionGroup();
int32_t collisionGroup = computeCollisionGroup();
if (_state == State::Hover || collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) {
_currentGravity = 0.0f;
} else {
@ -433,7 +433,7 @@ void CharacterController::setCollisionless(bool collisionless) {
}
}
int16_t CharacterController::computeCollisionGroup() const {
int32_t CharacterController::computeCollisionGroup() const {
if (_collisionless) {
return _collisionlessAllowed ? BULLET_COLLISION_GROUP_COLLISIONLESS : BULLET_COLLISION_GROUP_MY_AVATAR;
} else {
@ -446,7 +446,7 @@ void CharacterController::handleChangedCollisionGroup() {
// ATM the easiest way to update collision groups is to remove/re-add the RigidBody
if (_dynamicsWorld) {
_dynamicsWorld->removeRigidBody(_rigidBody);
int16_t collisionGroup = computeCollisionGroup();
int32_t collisionGroup = computeCollisionGroup();
_dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR);
}
_pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_GROUP;
@ -538,7 +538,7 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
btScalar angle = motor.rotation.getAngle();
btVector3 velocity = worldVelocity.rotate(axis, -angle);
int16_t collisionGroup = computeCollisionGroup();
int32_t collisionGroup = computeCollisionGroup();
if (collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS ||
_state == State::Hover || motor.hTimescale == motor.vTimescale) {
// modify velocity
@ -679,7 +679,7 @@ void CharacterController::updateState() {
btVector3 rayStart = _position;
btScalar rayLength = _radius;
int16_t collisionGroup = computeCollisionGroup();
int32_t collisionGroup = computeCollisionGroup();
if (collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) {
rayLength += _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT;
} else {

View file

@ -70,7 +70,7 @@ public:
virtual void preStep(btCollisionWorld *collisionWorld) override;
virtual void playerStep(btCollisionWorld *collisionWorld, btScalar dt) override;
virtual bool canJump() const override { assert(false); return false; } // never call this
virtual void jump() override;
virtual void jump(const btVector3& dir = btVector3(0.0f, 0.0f, 0.0f)) override;
virtual bool onGround() const override;
void clearMotors();
@ -120,7 +120,7 @@ public:
bool isStuck() const { return _isStuck; }
void setCollisionless(bool collisionless);
int16_t computeCollisionGroup() const;
int32_t computeCollisionGroup() const;
void handleChangedCollisionGroup();
bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);

View file

@ -29,13 +29,13 @@ CharacterGhostObject::~CharacterGhostObject() {
}
}
void CharacterGhostObject::setCollisionGroupAndMask(int16_t group, int16_t mask) {
void CharacterGhostObject::setCollisionGroupAndMask(int32_t group, int32_t mask) {
_collisionFilterGroup = group;
_collisionFilterMask = mask;
// TODO: if this probe is in the world reset ghostObject overlap cache
}
void CharacterGhostObject::getCollisionGroupAndMask(int16_t& group, int16_t& mask) const {
void CharacterGhostObject::getCollisionGroupAndMask(int32_t& group, int32_t& mask) const {
group = _collisionFilterGroup;
mask = _collisionFilterMask;
}

View file

@ -28,8 +28,8 @@ public:
CharacterGhostObject() { }
~CharacterGhostObject();
void setCollisionGroupAndMask(int16_t group, int16_t mask);
void getCollisionGroupAndMask(int16_t& group, int16_t& mask) const;
void setCollisionGroupAndMask(int32_t group, int32_t mask);
void getCollisionGroupAndMask(int32_t& group, int32_t& mask) const;
void setRadiusAndHalfHeight(btScalar radius, btScalar halfHeight);
void setUpDirection(const btVector3& up);
@ -54,8 +54,8 @@ protected:
btScalar _radius { 0.0f };
btConvexHullShape* _characterShape { nullptr }; // input, shape of character
CharacterGhostShape* _ghostShape { nullptr }; // internal, shape whose Aabb is used for overlap cache
int16_t _collisionFilterGroup { 0 };
int16_t _collisionFilterMask { 0 };
int32_t _collisionFilterGroup { 0 };
int32_t _collisionFilterMask { 0 };
bool _inWorld { false }; // internal, was added to world
};

View file

@ -779,7 +779,7 @@ QString EntityMotionState::getName() const {
}
// virtual
void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const {
void EntityMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const {
_entity->computeCollisionGroupAndFinalMask(group, mask);
}

View file

@ -84,7 +84,7 @@ public:
virtual QString getName() const override;
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
bool shouldSendBid();
bool isLocallyOwned() const override;

View file

@ -154,7 +154,7 @@ public:
virtual QString getName() const { return ""; }
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const = 0;
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const = 0;
bool isActive() const { return _body ? _body->isActive() : false; }

View file

@ -148,7 +148,7 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
body->setFlags(BT_DISABLE_WORLD_GRAVITY);
motionState->updateBodyMaterialProperties();
int16_t group, mask;
int32_t group, mask;
motionState->computeCollisionGroupAndMask(group, mask);
_dynamicsWorld->addRigidBody(body, group, mask);

View file

@ -181,7 +181,7 @@ class DebugAmbientOcclusionConfig : public render::Job::Config {
Q_PROPERTY(bool showCursorPixel MEMBER showCursorPixel NOTIFY dirty)
Q_PROPERTY(glm::vec2 debugCursorTexcoord MEMBER debugCursorTexcoord NOTIFY dirty)
public:
DebugAmbientOcclusionConfig() : render::Job::Config(true) {}
DebugAmbientOcclusionConfig() : render::Job::Config(false) {}
bool showCursorPixel{ false };
glm::vec2 debugCursorTexcoord{ 0.5f, 0.5f };

View file

@ -122,6 +122,9 @@ static const gpu::Element TEXCOORD4_ELEMENT { gpu::VEC4, gpu::FLOAT, gpu::XYZW }
static gpu::Stream::FormatPointer SOLID_STREAM_FORMAT;
static gpu::Stream::FormatPointer INSTANCED_SOLID_STREAM_FORMAT;
static gpu::Stream::FormatPointer INSTANCED_SOLID_FADE_STREAM_FORMAT;
static gpu::Stream::FormatPointer WIRE_STREAM_FORMAT;
static gpu::Stream::FormatPointer INSTANCED_WIRE_STREAM_FORMAT;
static gpu::Stream::FormatPointer INSTANCED_WIRE_FADE_STREAM_FORMAT;
static const uint SHAPE_VERTEX_STRIDE = sizeof(GeometryCache::ShapeVertex); // position, normal, texcoords, tangent
static const uint SHAPE_NORMALS_OFFSET = offsetof(GeometryCache::ShapeVertex, normal);
@ -690,6 +693,38 @@ gpu::Stream::FormatPointer& getInstancedSolidFadeStreamFormat() {
return INSTANCED_SOLID_FADE_STREAM_FORMAT;
}
gpu::Stream::FormatPointer& getWireStreamFormat() {
if (!WIRE_STREAM_FORMAT) {
WIRE_STREAM_FORMAT = std::make_shared<gpu::Stream::Format>(); // 1 for everyone
WIRE_STREAM_FORMAT->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, POSITION_ELEMENT);
WIRE_STREAM_FORMAT->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, NORMAL_ELEMENT);
}
return WIRE_STREAM_FORMAT;
}
gpu::Stream::FormatPointer& getInstancedWireStreamFormat() {
if (!INSTANCED_WIRE_STREAM_FORMAT) {
INSTANCED_WIRE_STREAM_FORMAT = std::make_shared<gpu::Stream::Format>(); // 1 for everyone
INSTANCED_WIRE_STREAM_FORMAT->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, POSITION_ELEMENT);
INSTANCED_WIRE_STREAM_FORMAT->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, NORMAL_ELEMENT);
INSTANCED_WIRE_STREAM_FORMAT->setAttribute(gpu::Stream::COLOR, gpu::Stream::COLOR, COLOR_ELEMENT, 0, gpu::Stream::PER_INSTANCE);
}
return INSTANCED_WIRE_STREAM_FORMAT;
}
gpu::Stream::FormatPointer& getInstancedWireFadeStreamFormat() {
if (!INSTANCED_WIRE_FADE_STREAM_FORMAT) {
INSTANCED_WIRE_FADE_STREAM_FORMAT = std::make_shared<gpu::Stream::Format>(); // 1 for everyone
INSTANCED_WIRE_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, POSITION_ELEMENT);
INSTANCED_WIRE_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, NORMAL_ELEMENT);
INSTANCED_WIRE_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::COLOR, gpu::Stream::COLOR, COLOR_ELEMENT, 0, gpu::Stream::PER_INSTANCE);
INSTANCED_WIRE_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::TEXCOORD2, gpu::Stream::TEXCOORD2, TEXCOORD4_ELEMENT, 0, gpu::Stream::PER_INSTANCE);
INSTANCED_WIRE_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::TEXCOORD3, gpu::Stream::TEXCOORD3, TEXCOORD4_ELEMENT, 0, gpu::Stream::PER_INSTANCE);
INSTANCED_WIRE_FADE_STREAM_FORMAT->setAttribute(gpu::Stream::TEXCOORD4, gpu::Stream::TEXCOORD4, TEXCOORD4_ELEMENT, 0, gpu::Stream::PER_INSTANCE);
}
return INSTANCED_WIRE_FADE_STREAM_FORMAT;
}
QHash<SimpleProgramKey, gpu::PipelinePointer> GeometryCache::_simplePrograms;
gpu::ShaderPointer GeometryCache::_simpleShader;
@ -827,7 +862,7 @@ void GeometryCache::renderShape(gpu::Batch& batch, Shape shape) {
}
void GeometryCache::renderWireShape(gpu::Batch& batch, Shape shape) {
batch.setInputFormat(getSolidStreamFormat());
batch.setInputFormat(getWireStreamFormat());
_shapes[shape].drawWire(batch);
}
@ -839,7 +874,7 @@ void GeometryCache::renderShape(gpu::Batch& batch, Shape shape, const glm::vec4&
}
void GeometryCache::renderWireShape(gpu::Batch& batch, Shape shape, const glm::vec4& color) {
batch.setInputFormat(getSolidStreamFormat());
batch.setInputFormat(getWireStreamFormat());
// Color must be set after input format
batch._glColor4f(color.r, color.g, color.b, color.a);
_shapes[shape].drawWire(batch);
@ -857,7 +892,7 @@ void GeometryCache::renderShapeInstances(gpu::Batch& batch, Shape shape, size_t
}
void GeometryCache::renderWireShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer) {
batch.setInputFormat(getInstancedSolidStreamFormat());
batch.setInputFormat(getInstancedWireStreamFormat());
setupBatchInstance(batch, colorBuffer);
_shapes[shape].drawWireInstances(batch, count);
}
@ -883,7 +918,7 @@ void GeometryCache::renderFadeShapeInstances(gpu::Batch& batch, Shape shape, siz
void GeometryCache::renderWireFadeShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& colorBuffer,
gpu::BufferPointer& fadeBuffer1, gpu::BufferPointer& fadeBuffer2, gpu::BufferPointer& fadeBuffer3) {
batch.setInputFormat(getInstancedSolidFadeStreamFormat());
batch.setInputFormat(getInstancedWireFadeStreamFormat());
setupBatchFadeInstance(batch, colorBuffer, fadeBuffer1, fadeBuffer2, fadeBuffer3);
_shapes[shape].drawWireInstances(batch, count);
}

View file

@ -195,7 +195,7 @@ class DebugLightClustersConfig : public render::Job::Config {
Q_PROPERTY(bool doDrawClusterFromDepth MEMBER doDrawClusterFromDepth NOTIFY dirty)
Q_PROPERTY(bool doDrawContent MEMBER doDrawContent NOTIFY dirty)
public:
DebugLightClustersConfig() : render::Job::Config(true){}
DebugLightClustersConfig() : render::Job::Config(false){}
bool doDrawGrid{ false };

View file

@ -149,7 +149,7 @@ class DebugSubsurfaceScatteringConfig : public render::Job::Config {
Q_PROPERTY(bool showCursorPixel MEMBER showCursorPixel NOTIFY dirty)
Q_PROPERTY(glm::vec2 debugCursorTexcoord MEMBER debugCursorTexcoord NOTIFY dirty)
public:
DebugSubsurfaceScatteringConfig() : render::Job::Config(true) {}
DebugSubsurfaceScatteringConfig() : render::Job::Config(false) {}
bool showProfile{ false };
bool showLUT{ false };

View file

@ -36,12 +36,11 @@ public:
}
};
Engine::Engine() : Task(EngineTask::JobModel::create("Engine")),
_renderContext(std::make_shared<RenderContext>())
RenderEngine::RenderEngine() : Engine(EngineTask::JobModel::create("Engine"), std::make_shared<RenderContext>())
{
}
void Engine::load() {
void RenderEngine::load() {
auto config = getConfiguration();
const QString configFile= "config/render.json";

View file

@ -25,7 +25,7 @@ namespace render {
class RenderContext : public task::JobContext {
public:
RenderContext() : task::JobContext(trace_render()) {}
RenderContext() : task::JobContext() {}
virtual ~RenderContext() {}
RenderArgs* args;
@ -33,7 +33,9 @@ namespace render {
};
using RenderContextPointer = std::shared_ptr<RenderContext>;
Task_DeclareTypeAliases(RenderContext)
Task_DeclareCategoryTimeProfilerClass(RenderTimeProfiler, trace_render);
Task_DeclareTypeAliases(RenderContext, RenderTimeProfiler)
// Versions of the COnfig integrating a gpu & batch timer
class GPUJobConfig : public JobConfig {
@ -57,10 +59,10 @@ namespace render {
class GPUTaskConfig : public TaskConfig {
Q_OBJECT
Q_PROPERTY(double gpuRunTime READ getGPURunTime)
Q_PROPERTY(double batchRunTime READ getBatchRunTime)
Q_PROPERTY(double gpuRunTime READ getGPURunTime)
Q_PROPERTY(double batchRunTime READ getBatchRunTime)
double _msGPURunTime { 0.0 };
double _msGPURunTime { 0.0 };
double _msBatchRunTime { 0.0 };
public:
@ -80,32 +82,25 @@ namespace render {
// The render engine holds all render tasks, and is itself a render task.
// State flows through tasks to jobs via the render and scene contexts -
// the engine should not be known from its jobs.
class Engine : public Task {
class RenderEngine : public Engine {
public:
Engine();
~Engine() = default;
RenderEngine();
~RenderEngine() = default;
// Load any persisted settings, and set up the presets
// This should be run after adding all jobs, and before building ui
void load();
// Register the scene
void registerScene(const ScenePointer& scene) { _renderContext->_scene = scene; }
void registerScene(const ScenePointer& scene) { _context->_scene = scene; }
// acces the RenderContext
RenderContextPointer getRenderContext() const { return _renderContext; }
// Render a frame
// Must have a scene registered and a context set
void run() { assert(_renderContext); Task::run(_renderContext); }
RenderContextPointer getRenderContext() const { return _context; }
protected:
RenderContextPointer _renderContext;
void run(const RenderContextPointer& context) override { assert(_renderContext); Task::run(_renderContext); }
};
using EnginePointer = std::shared_ptr<Engine>;
using EnginePointer = std::shared_ptr<RenderEngine>;
}

View file

@ -122,6 +122,7 @@ public:
Builder& withDynamic() { _flags.set(DYNAMIC); return (*this); }
Builder& withDeformed() { _flags.set(DEFORMED); return (*this); }
Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); }
Builder& withVisible() { _flags.reset(INVISIBLE); return (*this); }
Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); }
Builder& withLayered() { _flags.set(LAYERED); return (*this); }
Builder& withMetaCullGroup() { _flags.set(META_CULL_GROUP); return (*this); }

View file

@ -21,7 +21,7 @@
namespace render {
class Engine;
class RenderEngine;
class Scene;
// Transaction is the mechanism to make any change to the scene.
@ -236,7 +236,7 @@ protected:
StageMap _stages;
friend class Engine;
friend class RenderEngine;
};
typedef std::shared_ptr<Scene> ScenePointer;

View file

@ -121,7 +121,7 @@ public:
/**jsdoc
* A set of properties that can be passed to {@link Assets.getAsset}.
* @typedef {Object} Assets.GetOptions
* @typedef {object} Assets.GetOptions
* @property {string} [url] an "atp:" style URL, hash, or relative mapped path to fetch
* @property {string} [responseType=text] the desired reponse type (text | arraybuffer | json)
* @property {boolean} [decompress=false] whether to attempt gunzip decompression on the fetched data
@ -137,7 +137,7 @@ public:
/**jsdoc
* Result value returned by {@link Assets.getAsset}.
* @typedef {Object} Assets~getAssetResult
* @typedef {object} Assets~getAssetResult
* @property {string} [url] the resolved "atp:" style URL for the fetched asset
* @property {string} [hash] the resolved hash for the fetched asset
* @property {string|ArrayBuffer|Object} [response] response data (possibly converted per .responseType value)
@ -159,7 +159,7 @@ public:
/**jsdoc
* A set of properties that can be passed to {@link Assets.putAsset}.
* @typedef {Object} Assets.PutOptions
* @typedef {object} Assets.PutOptions
* @property {ArrayBuffer|string} [data] byte buffer or string value representing the new asset's content
* @property {string} [path=null] ATP path mapping to automatically create (upon successful upload to hash)
* @property {boolean} [compress=false] whether to gzip compress data before uploading
@ -174,7 +174,7 @@ public:
/**jsdoc
* Result value returned by {@link Assets.putAsset}.
* @typedef {Object} Assets~putAssetResult
* @typedef {object} Assets~putAssetResult
* @property {string} [url] the resolved "atp:" style URL for the uploaded asset (based on .path if specified, otherwise on the resulting ATP hash)
* @property {string} [path] the uploaded asset's resulting ATP path (or undefined if no path mapping was assigned)
* @property {string} [hash] the uploaded asset's resulting ATP hash

View file

@ -43,7 +43,7 @@
* @hifi-server-entity
* @hifi-assignment-client
*
* @property IDENTITY {Quat} <code>{ x: 0, y: 0, z: 0, w: 1 }</code> : The identity rotation, i.e., no rotation.
* @property {Quat} IDENTITY - <code>{ x: 0, y: 0, z: 0, w: 1 }</code> : The identity rotation, i.e., no rotation.
* <em>Read-only.</em>
* @example <caption>Print the <code>IDENTITY</code> value.</caption>
* print(JSON.stringify(Quat.IDENTITY)); // { x: 0, y: 0, z: 0, w: 1 }

View file

@ -21,7 +21,7 @@
namespace SceneScripting {
/**jsdoc
* @typedef Scene.Stage.Location
* @typedef {object} Scene.Stage.Location
* @property {number} longitude
* @property {number} latitude
* @property {number} altitude
@ -49,7 +49,7 @@ namespace SceneScripting {
using LocationPointer = std::unique_ptr<Location>;
/**jsdoc
* @typedef Scene.Stage.Time
* @typedef {object} Scene.Stage.Time
* @property {number} hour
* @property {number} day
*/
@ -73,7 +73,7 @@ namespace SceneScripting {
using TimePointer = std::unique_ptr<Time>;
/**jsdoc
* @typedef Scene.Stage.KeyLight
* @typedef {object} Scene.Stage.KeyLight
* @property {Vec3} color
* @property {number} intensity
* @property {number} ambientIntensity

View file

@ -558,6 +558,16 @@ static void scriptableResourceFromScriptValue(const QScriptValue& value, Scripta
resource = static_cast<ScriptableResourceRawPtr>(value.toQObject());
}
/**jsdoc
* @namespace Resource
*
* @hifi-interface
* @hifi-client-entity
* @hifi-server-entity
* @hifi-assignment-client
*
* @property {Resource.State} State
*/
static QScriptValue createScriptableResourcePrototype(ScriptEnginePointer engine) {
auto prototype = engine->newObject();

View file

@ -30,7 +30,7 @@
* @hifi-server-entity
* @hifi-assignment-client
*
* @property NULL {Uuid} The null UUID, <code>{00000000-0000-0000-0000-000000000000}</code>.
* @property {Uuid} NULL - The null UUID, <code>{00000000-0000-0000-0000-000000000000}</code>.
*/
/// Scriptable interface for a UUID helper class object. Used exclusively in the JavaScript API

View file

@ -42,8 +42,7 @@
/**jsdoc
* The Vec3 API facilities for generating and manipulating 3-dimensional vectors. High Fidelity uses a right-handed
* Cartesian coordinate system where the y-axis is the "up" and the negative z-axis is the "front" direction.
* <img alt="High Fidelity coordinate system"
* src="https://docs.highfidelity.com/user/pages/06.api-reference/43.vec3/opengl-coord-system.jpg" />
* <img alt="High Fidelity coordinate system" src="https://docs.highfidelity.com/images/opengl-coord-system.jpg" />
*
* @namespace Vec3
* @variation 0

View file

@ -34,13 +34,13 @@ enum CollisionFilterGroups {
*
*/
const int16_t BULLET_COLLISION_GROUP_STATIC = 1 << 0;
const int16_t BULLET_COLLISION_GROUP_DYNAMIC = 1 << 1;
const int16_t BULLET_COLLISION_GROUP_KINEMATIC = 1 << 2;
const int16_t BULLET_COLLISION_GROUP_MY_AVATAR = 1 << 3;
const int16_t BULLET_COLLISION_GROUP_OTHER_AVATAR = 1 << 4;
const int32_t BULLET_COLLISION_GROUP_STATIC = 1 << 0;
const int32_t BULLET_COLLISION_GROUP_DYNAMIC = 1 << 1;
const int32_t BULLET_COLLISION_GROUP_KINEMATIC = 1 << 2;
const int32_t BULLET_COLLISION_GROUP_MY_AVATAR = 1 << 3;
const int32_t BULLET_COLLISION_GROUP_OTHER_AVATAR = 1 << 4;
// ...
const int16_t BULLET_COLLISION_GROUP_COLLISIONLESS = 1 << 14;
const int32_t BULLET_COLLISION_GROUP_COLLISIONLESS = 1 << 31;
/* Note: In order for objectA to collide with objectB at the filter stage
@ -48,21 +48,21 @@ const int16_t BULLET_COLLISION_GROUP_COLLISIONLESS = 1 << 14;
*/
// the default collision mask is: collides with everything except collisionless
const int16_t BULLET_COLLISION_MASK_DEFAULT = ~ BULLET_COLLISION_GROUP_COLLISIONLESS;
const int32_t BULLET_COLLISION_MASK_DEFAULT = ~ BULLET_COLLISION_GROUP_COLLISIONLESS;
// STATIC does not collide with itself (as optimization of physics simulation)
const int16_t BULLET_COLLISION_MASK_STATIC = ~ (BULLET_COLLISION_GROUP_COLLISIONLESS | BULLET_COLLISION_GROUP_KINEMATIC | BULLET_COLLISION_GROUP_STATIC);
const int32_t BULLET_COLLISION_MASK_STATIC = ~ (BULLET_COLLISION_GROUP_COLLISIONLESS | BULLET_COLLISION_GROUP_KINEMATIC | BULLET_COLLISION_GROUP_STATIC);
const int16_t BULLET_COLLISION_MASK_DYNAMIC = BULLET_COLLISION_MASK_DEFAULT;
const int16_t BULLET_COLLISION_MASK_KINEMATIC = BULLET_COLLISION_MASK_STATIC;
const int32_t BULLET_COLLISION_MASK_DYNAMIC = BULLET_COLLISION_MASK_DEFAULT;
const int32_t BULLET_COLLISION_MASK_KINEMATIC = BULLET_COLLISION_MASK_STATIC;
// MY_AVATAR does not collide with itself
const int16_t BULLET_COLLISION_MASK_MY_AVATAR = ~(BULLET_COLLISION_GROUP_COLLISIONLESS | BULLET_COLLISION_GROUP_MY_AVATAR);
const int32_t BULLET_COLLISION_MASK_MY_AVATAR = ~(BULLET_COLLISION_GROUP_COLLISIONLESS | BULLET_COLLISION_GROUP_MY_AVATAR);
const int16_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_MASK_DEFAULT;
const int32_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_MASK_DEFAULT;
// COLLISIONLESS gets an empty mask.
const int16_t BULLET_COLLISION_MASK_COLLISIONLESS = 0;
const int32_t BULLET_COLLISION_MASK_COLLISIONLESS = 0;
/**jsdoc
* <p>An entity may collide with the following types of items:</p>
@ -72,35 +72,35 @@ const int16_t BULLET_COLLISION_MASK_COLLISIONLESS = 0;
* </thead>
* <tbody>
* <tr><td><code>1</code></td><td>Static entities &mdash; non-dynamic entities with no velocity.</td></tr>
* <tr><td><code>2</code></td><td>Dynamic entities &mdash; entities that have their <code>dynamic</code> property set to
* <tr><td><code>2</code></td><td>Dynamic entities &mdash; entities that have their <code>dynamic</code> property set to
* <code>true</code>.</td></tr>
* <tr><td><code>4</code></td><td>Kinematic entities &mdash; non-dynamic entities with velocity.</td></tr>
* <tr><td><code>8</code></td><td>My avatar.</td></tr>
* <tr><td><code>16</code></td><td>Other avatars.</td></tr>
* </tbody>
* </table>
* <p>The values for the collision types that are enabled are added together to give the CollisionMask value. For example, a
* <p>The values for the collision types that are enabled are added together to give the CollisionMask value. For example, a
* value of <code>31</code> means that an entity will collide with all item types.</p>
* @typedef {number} Entities.CollisionMask
*/
// The USER collision groups are exposed to script and can be used to generate per-object collision masks.
// They are not necessarily the same as the BULLET_COLLISION_GROUPS, but we start them off with matching numbers.
const uint8_t USER_COLLISION_GROUP_STATIC = 1 << 0;
const uint8_t USER_COLLISION_GROUP_DYNAMIC = 1 << 1;
const uint8_t USER_COLLISION_GROUP_KINEMATIC = 1 << 2;
const uint8_t USER_COLLISION_GROUP_MY_AVATAR = 1 << 3;
const uint8_t USER_COLLISION_GROUP_OTHER_AVATAR = 1 << 4;
const uint16_t USER_COLLISION_GROUP_STATIC = 1 << 0;
const uint16_t USER_COLLISION_GROUP_DYNAMIC = 1 << 1;
const uint16_t USER_COLLISION_GROUP_KINEMATIC = 1 << 2;
const uint16_t USER_COLLISION_GROUP_MY_AVATAR = 1 << 3;
const uint16_t USER_COLLISION_GROUP_OTHER_AVATAR = 1 << 4;
const uint8_t ENTITY_COLLISION_MASK_DEFAULT =
const uint16_t ENTITY_COLLISION_MASK_DEFAULT =
USER_COLLISION_GROUP_STATIC |
USER_COLLISION_GROUP_DYNAMIC |
USER_COLLISION_GROUP_KINEMATIC |
USER_COLLISION_GROUP_MY_AVATAR |
USER_COLLISION_GROUP_OTHER_AVATAR;
const uint8_t USER_COLLISION_MASK_AVATARS = USER_COLLISION_GROUP_MY_AVATAR | USER_COLLISION_GROUP_OTHER_AVATAR;
const uint16_t USER_COLLISION_MASK_AVATARS = USER_COLLISION_GROUP_MY_AVATAR | USER_COLLISION_GROUP_OTHER_AVATAR;
const int NUM_USER_COLLISION_GROUPS = 5;
const int32_t NUM_USER_COLLISION_GROUPS = 5;
#endif // hifi_PhysicsCollisionGroups_h

View file

@ -61,7 +61,7 @@ glm::quat computeBulletRotationStep(const glm::vec3& angularVelocity, float time
}
/* end Bullet code derivation*/
int16_t Physics::getDefaultCollisionMask(int16_t group) {
int32_t Physics::getDefaultCollisionMask(int32_t group) {
switch(group) {
case BULLET_COLLISION_GROUP_STATIC:
return BULLET_COLLISION_MASK_STATIC;

View file

@ -31,7 +31,7 @@ const float KINEMATIC_ANGULAR_SPEED_THRESHOLD = 0.008f; // ~0.5 deg/sec
glm::quat computeBulletRotationStep(const glm::vec3& angularVelocity, float timeStep);
namespace Physics {
int16_t getDefaultCollisionMask(int16_t group);
int32_t getDefaultCollisionMask(int32_t group);
void setSessionUUID(const QUuid& sessionID);
const QUuid& getSessionUUID();

View file

@ -44,13 +44,14 @@ class Camera : public QObject {
* @hifi-interface
* @hifi-client-entity
*
* @property position {Vec3} The position of the camera. You can set this value only when the camera is in independent mode.
* @property orientation {Quat} The orientation of the camera. You can set this value only when the camera is in independent
* @property {Vec3} position - The position of the camera. You can set this value only when the camera is in independent
* mode.
* @property mode {Camera.Mode} The camera mode.
* @property frustum {ViewFrustum} The camera frustum.
* @property cameraEntity {Uuid} The ID of the entity that is used for the camera position and orientation when the camera
* is in entity mode.
* @property {Quat} orientation - The orientation of the camera. You can set this value only when the camera is in
* independent mode.
* @property {Camera.Mode} mode - The camera mode.
* @property {ViewFrustum} frustum - The camera frustum.
* @property {Uuid} cameraEntity - The ID of the entity that is used for the camera position and orientation when the
* camera is in entity mode.
*/
// FIXME: The cameraEntity property definition is copied from FancyCamera.h.
Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition)

View file

@ -12,6 +12,8 @@
#ifndef hifi_task_Config_h
#define hifi_task_Config_h
#include <chrono>
#include <QtCore/qobject.h>
#include <QtCore/qjsondocument.h>
#include <QtCore/qjsonobject.h>
@ -117,11 +119,19 @@ public:
*/
Q_INVOKABLE void load(const QVariantMap& map) { qObjectFromJsonValue(QJsonObject::fromVariantMap(map), *this); emit loaded(); }
Q_INVOKABLE QObject* getConfig(const QString& name) { return nullptr; }
// Running Time measurement
// The new stats signal is emitted once per run time of a job when stats (cpu runtime) are updated
void setCPURunTime(double mstime) { _msCPURunTime = mstime; emit newStats(); }
void setCPURunTime(const std::chrono::nanoseconds& runtime) { _msCPURunTime = std::chrono::duration<double, std::milli>(runtime).count(); emit newStats(); }
double getCPURunTime() const { return _msCPURunTime; }
// Describe the node graph data connections of the associated Job/Task
Q_INVOKABLE virtual bool isTask() const { return false; }
Q_INVOKABLE virtual QObjectList getSubConfigs() const { return QObjectList(); }
Q_INVOKABLE virtual int getNumSubs() const { return 0; }
Q_INVOKABLE virtual QObject* getSubConfig(int i) const { return nullptr; }
public slots:
/**jsdoc
@ -151,6 +161,8 @@ signals:
void dirtyEnabled();
};
using QConfigPointer = std::shared_ptr<JobConfig>;
class TConfigProxy {
public:
using Config = JobConfig;
@ -173,9 +185,10 @@ public:
using Persistent = PersistentConfig<TaskConfig>;
TaskConfig() = default ;
TaskConfig() = default;
TaskConfig(bool enabled) : JobConfig(enabled) {}
/**jsdoc
* @function Render.getConfig
* @param {string} name
@ -212,6 +225,21 @@ public:
return root->findChild<typename T::Config*>(tokens.front());
}
Q_INVOKABLE bool isTask() const override { return true; }
Q_INVOKABLE QObjectList getSubConfigs() const override {
auto list = findChildren<JobConfig*>(QRegExp(".*"), Qt::FindDirectChildrenOnly);
QObjectList returned;
for (int i = 0; i < list.size(); i++) {
returned.push_back(list[i]);
}
return returned;
}
Q_INVOKABLE int getNumSubs() const override { return getSubConfigs().size(); }
Q_INVOKABLE QObject* getSubConfig(int i) const override {
auto subs = getSubConfigs();
return ((i < 0 || i >= subs.size()) ? nullptr : subs[i]);
}
void connectChildConfig(QConfigPointer childConfig, const std::string& name);
void transferChildrenConfigs(QConfigPointer source);
@ -225,8 +253,6 @@ public slots:
void refresh();
};
using QConfigPointer = std::shared_ptr<QObject>;
}
#endif // hifi_task_Config_h

View file

@ -12,9 +12,7 @@
using namespace task;
JobContext::JobContext(const QLoggingCategory& category) :
profileCategory(category) {
assert(&category);
JobContext::JobContext() {
}
JobContext::~JobContext() {

View file

@ -15,20 +15,15 @@
#include "Config.h"
#include "Varying.h"
#include "SettingHandle.h"
#include <Profile.h>
#include <PerfStat.h>
namespace task {
class JobConcept;
template <class JC> class JobT;
template <class JC> class TaskT;
template <class JC, class TP> class JobT;
template <class JC, class TP> class TaskT;
class JobNoIO {};
// Task Flow control class is a simple per value object used to communicate flow control commands trhough the graph of tasks.
// From within the Job::Run function, you can access it from the JobCOntext and issue commands which will be picked up by the Task calling for the Job run.
// From within the Job::Run function, you can access it from the JobContext and issue commands which will be picked up by the Task calling for the Job run.
// This is first introduced to provide a way to abort all the work from within a task job. see the "abortTask" call
class TaskFlow {
public:
@ -55,11 +50,10 @@ protected:
// The JobContext can be derived to add more global state to it that Jobs can access
class JobContext {
public:
JobContext(const QLoggingCategory& category);
JobContext();
virtual ~JobContext();
std::shared_ptr<JobConfig> jobConfig { nullptr };
const QLoggingCategory& profileCategory;
// Task flow control
TaskFlow taskFlow{};
@ -80,10 +74,11 @@ public:
virtual const Varying getInput() const { return Varying(); }
virtual const Varying getOutput() const { return Varying(); }
virtual Varying& editInput() = 0;
virtual QConfigPointer& getConfiguration() { return _config; }
virtual void applyConfiguration() = 0;
void setCPURunTime(double mstime) { std::static_pointer_cast<Config>(_config)->setCPURunTime(mstime); }
void setCPURunTime(const std::chrono::nanoseconds& runtime) { std::static_pointer_cast<Config>(_config)->setCPURunTime(runtime); }
QConfigPointer _config;
protected:
@ -114,10 +109,11 @@ template <class T, class JC, class I, class O> void jobRun(T& data, const JC& jo
data.run(jobContext, input, output);
}
template <class JC>
template <class JC, class TP>
class Job {
public:
using Context = JC;
using TimeProfiler = TP;
using ContextPointer = std::shared_ptr<Context>;
using Config = JobConfig;
using None = JobNoIO;
@ -143,6 +139,7 @@ public:
const Varying getInput() const override { return _input; }
const Varying getOutput() const override { return _output; }
Varying& editInput() override { return _input; }
template <class... A>
Model(const std::string& name, const Varying& input, QConfigPointer config, A&&... args) :
@ -160,7 +157,7 @@ public:
void applyConfiguration() override {
Duration profileRange(trace_render(), ("configure::" + JobConcept::getName()).c_str());
TimeProfiler probe(("configure::" + JobConcept::getName()));
jobConfigure(_data, *std::static_pointer_cast<C>(Concept::_config));
}
@ -185,6 +182,9 @@ public:
QConfigPointer& getConfiguration() const { return _concept->getConfiguration(); }
void applyConfiguration() { return _concept->applyConfiguration(); }
template <class I> void feedInput(const I& in) { _concept->editInput().template edit<I>() = in; }
template <class I, class S> void feedInput(int index, const S& inS) { (_concept->editInput().template editN<I>(index)).template edit<S>() = inS; }
template <class T> T& edit() {
auto concept = std::static_pointer_cast<typename T::JobModel>(_concept);
assert(concept);
@ -198,14 +198,10 @@ public:
}
virtual void run(const ContextPointer& jobContext) {
PerformanceTimer perfTimer(getName().c_str());
// NOTE: rather than use the PROFILE_RANGE macro, we create a Duration manually
Duration profileRange(jobContext->profileCategory, ("run::" + getName()).c_str());
auto start = usecTimestampNow();
TimeProfiler probe(getName());
auto startTime = std::chrono::high_resolution_clock::now();
_concept->run(jobContext);
_concept->setCPURunTime((double)(usecTimestampNow() - start) / 1000.0);
_concept->setCPURunTime((std::chrono::high_resolution_clock::now() - startTime));
}
protected:
@ -220,13 +216,14 @@ protected:
// The build method is where child Jobs can be added internally to the task
// where the input of the task can be setup to feed the child jobs
// and where the output of the task is defined
template <class JC>
class Task : public Job<JC> {
template <class JC, class TP>
class Task : public Job<JC, TP> {
public:
using Context = JC;
using TimeProfiler = TP;
using ContextPointer = std::shared_ptr<Context>;
using Config = TaskConfig;
using JobType = Job<JC>;
using JobType = Job<JC, TP>;
using None = typename JobType::None;
using Concept = typename JobType::Concept;
using ConceptPointer = typename JobType::ConceptPointer;
@ -242,6 +239,8 @@ public:
const Varying getInput() const override { return _input; }
const Varying getOutput() const override { return _output; }
Varying& editInput() override { return _input; }
typename Jobs::iterator editJob(std::string name) {
typename Jobs::iterator jobIt;
for (jobIt = _jobs.begin(); jobIt != _jobs.end(); ++jobIt) {
@ -295,7 +294,7 @@ public:
auto model = std::make_shared<TaskModel>(name, input, std::make_shared<C>());
{
Duration profileRange(trace_render(), ("build::" + model->getName()).c_str());
TimeProfiler probe("build::" + model->getName());
model->_data.build(*(model), model->_input, model->_output, std::forward<A>(args)...);
}
// Recreate the Config to use the templated type
@ -330,7 +329,7 @@ public:
}
void applyConfiguration() override {
Duration profileRange(trace_render(), ("configure::" + JobConcept::getName()).c_str());
TimeProfiler probe("configure::" + JobConcept::getName());
jobConfigure(_data, *std::static_pointer_cast<C>(Concept::_config));
for (auto& job : TaskConcept::_jobs) {
job.applyConfiguration();
@ -370,15 +369,44 @@ public:
protected:
};
template <class JC, class TP>
class Engine : public Task<JC, TP> {
public:
using Context = JC;
using ContextPointer = std::shared_ptr<Context>;
using Config = TaskConfig;
using TaskType = Task<JC, TP>;
using ConceptPointer = typename TaskType::ConceptPointer;
Engine(const ConceptPointer& concept, const ContextPointer& context) : TaskType(concept), _context(context) {}
~Engine() = default;
void reset(const ContextPointer& context) { _context = context; }
void run() {
if (_context) {
run(_context);
}
}
protected:
void run(const ContextPointer& jobContext) override {
TaskType::run(_context);
}
ContextPointer _context;
};
}
#define Task_DeclareTypeAliases(ContextType) \
#define Task_DeclareTypeAliases(ContextType, TimeProfiler) \
using JobConfig = task::JobConfig; \
using TaskConfig = task::TaskConfig; \
template <class T> using PersistentConfig = task::PersistentConfig<T>; \
using Job = task::Job<ContextType>; \
using Task = task::Task<ContextType>; \
using Job = task::Job<ContextType, TimeProfiler>; \
using Task = task::Task<ContextType, TimeProfiler>; \
using Engine = task::Engine<ContextType, TimeProfiler>; \
using Varying = task::Varying; \
template < typename T0, typename T1 > using VaryingSet2 = task::VaryingSet2<T0, T1>; \
template < typename T0, typename T1, typename T2 > using VaryingSet3 = task::VaryingSet3<T0, T1, T2>; \
@ -389,4 +417,16 @@ protected:
template < typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7 > using VaryingSet8 = task::VaryingSet8<T0, T1, T2, T3, T4, T5, T6, T7>; \
template < class T, int NUM > using VaryingArray = task::VaryingArray<T, NUM>;
#include <Profile.h>
#include <PerfStat.h>
#define Task_DeclareCategoryTimeProfilerClass(className, category) \
class className : public PerformanceTimer { \
public: \
className(const std::string& label) : PerformanceTimer(label.c_str()), profileRange(category(), label.c_str()) {} \
Duration profileRange; \
};
#endif // hifi_task_Task_h

View file

@ -20,7 +20,7 @@ public:
/**jsdoc
* Creates a new button, adds it to this and returns it.
* @function QmlFragmentClass#addButton
* @param properties {Object} button properties
* @param properties {object} button properties
* @returns {TabletButtonProxy}
*/
Q_INVOKABLE QObject* addButton(const QVariant& properties);

View file

@ -493,7 +493,7 @@ protected:
int _stableOrder;
/**jsdoc
* @typedef TabletButtonProxy.ButtonProperties
* @typedef {object} TabletButtonProxy.ButtonProperties
* @property {string} icon - URL to button icon. (50 x 50)
* @property {string} hoverIcon - URL to button icon, displayed during mouse hover. (50 x 50)
* @property {string} activeHoverIcon - URL to button icon used when button is active, and during mouse hover. (50 x 50)

View file

@ -442,7 +442,7 @@ void OculusControllerManager::TouchDevice::stopHapticPulse(bool leftHand) {
* <tr><td><code>RightHand</code></td><td>number</td><td>{@link Pose}</td><td>right hand pose.</td></tr>
* </tbody>
* </table>
* @typedef Controller.Hardware-OculusTouch
* @typedef {object} Controller.Hardware-OculusTouch
*/
controller::Input::NamedVector OculusControllerManager::TouchDevice::getAvailableInputs() const {
using namespace controller;

View file

@ -1297,7 +1297,7 @@ void ViveControllerManager::InputDevice::setConfigFromString(const QString& valu
* <tr><td><code>TrackedObject15</code></td><td>number</td><td>{@link Pose}</td><td>Tracker 15 pose.</td></tr>
* </tbody>
* </table>
* @typedef Controller.Hardware-Vive
* @typedef {object} Controller.Hardware-Vive
*/
controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableInputs() const {
using namespace controller;

View file

@ -0,0 +1,82 @@
//
// Job Engine & Task...
// jet.js
//
// Created by Sam Gateau, 2018/03/28
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
"use strict";
// traverse task tree
function task_traverse(root, functor, depth) {
if (root.isTask()) {
depth++;
for (var i = 0; i <root.getNumSubs(); i++) {
var sub = root.getSubConfig(i);
if (functor(sub, depth, i)) {
task_traverse(sub, functor, depth, 0)
}
}
}
}
function task_traverseTree(root, functor) {
if (functor(root, 0, 0)) {
task_traverse(root, functor, 0)
}
}
// Access job properties
// return all the properties of a job
function job_propKeys(job) {
var keys = Object.keys(job)
var propKeys = [];
for (var k=0; k < keys.length;k++) {
// Filter for relevant property
var key = keys[k]
if ((typeof job[key]) !== "function") {
if ((key !== "objectName") && (key !== "cpuRunTime") && (key !== "enabled")) {
propKeys.push(keys[k]);
}
}
}
return propKeys;
}
// Use this function to create a functor that will fill the specifed array with one entry name per task and job and it s rank
function job_list_functor(jobList, maxDepth) {
if (maxDepth === undefined) maxDepth = 100
return function (job, depth, index) {
jobList.push(job.objectName);
return depth < maxDepth;
}
}
// Use this function to create a functor that will print the content of the Job visited calling the specified 'printout' function
function job_print_functor(printout, showProps, maxDepth) {
if (maxDepth === undefined) maxDepth = 100
return function (job, depth, index) {
var tab = " "
var depthTab = "";
for (var d = 0; d < depth; d++) { depthTab += tab }
printout(depthTab + index + " " + job.objectName + " " + (job.enabled ? "on " : "off ") + job.cpuRunTime + "ms")
if (showProps) {
var keys = job_propKeys(job);
for (var p=0; p < keys.length;p++) {
var prop = job[keys[p]]
printout(depthTab + tab + tab + typeof prop + " " + keys[p] + " " + prop);
}
}
return depth < maxDepth;
}
}
// Expose functions for regular js including this files through the 'Jet' object
/*Jet = {}
Jet.task_traverse = task_traverse
Jet.task_traverseTree = task_traverseTree
Jet.job_propKeys = job_propKeys
Jet.job_print_functor = job_print_functor
*/

View file

@ -0,0 +1,45 @@
//
// jet/TaskList.qml
//
// Created by Sam Gateau, 2018/03/28
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
import QtQuick.Controls 1.4 as Original
import QtQuick.Controls.Styles 1.4
import "qrc:///qml/styles-uit"
import "qrc:///qml/controls-uit" as HifiControls
import "../jet.js" as Jet
Rectangle {
HifiConstants { id: hifi;}
color: hifi.colors.baseGray;
id: root
// width: parent ? parent.width : 200
// height: parent ? parent.height : 400
property var rootConfig : Workload
Original.TextArea {
id: textArea
width: parent.width
height: parent.height
text: ""
}
Component.onCompleted: {
var message = ""
var functor = Jet.job_print_functor(function (line) { message += line + "\n"; }, false);
Jet.task_traverseTree(rootConfig, functor);
textArea.append(message);
}
function clearWindow() {
textArea.remove(0,textArea.length);
}
}

View file

@ -0,0 +1,119 @@
//
// jet/TaskListView.qml
//
// Created by Sam Gateau, 2018/05/09
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
import QtQuick.Controls 1.4 as Original
import QtQuick.Controls.Styles 1.4
import "qrc:///qml/styles-uit"
import "qrc:///qml/controls-uit" as HifiControls
import "../jet.js" as Jet
Rectangle {
HifiConstants { id: hifi;}
color: hifi.colors.baseGray;
id: root;
property var rootConfig : Workload
property var myArray : []
Component.onCompleted: {
var message = ""
var maxDepth = 3;
var jobTreePath = []
var jobsRoot;
var functor = function (job, depth, index) {
var newItem = {"name": job.objectName, "level": depth, "index": index, "subNode": [], "init": depth < maxDepth, "path": ""}
if (depth == 0) {
jobsModel.append(newItem)
jobsRoot = jobsModel.get(0).subNode;
} else {
if (jobTreePath.length < depth) {
var node = jobsRoot;
var path;
for (var n = 0; n < jobTreePath.length; n++) {
newItem.path += (n > 0 ? "." : "") + node.get(jobTreePath[n]).name
node = node.get(jobTreePath[n]).subNode
}
node.append(newItem)
jobTreePath.push(0);
} else if (jobTreePath.length >= depth) {
var node = jobsRoot;
for (var n = 0; n < (depth - 1); n++) {
newItem.path += (n > 0 ? "." : "") + node.get(jobTreePath[n]).name
node = node.get(jobTreePath[n]).subNode
}
node.append(newItem)
jobTreePath[depth-1] = index;
while (jobTreePath.length > depth) {
jobTreePath.pop();
}
}
}
return true;
}
Jet.task_traverseTree(rootConfig, functor);
}
ListModel {
id: jobsModel
}
Component {
id: objRecursiveDelegate
Column {
id: objRecursiveColumn
clip: true
visible: model.init
MouseArea {
width: objRow.implicitWidth
height: objRow.implicitHeight
onDoubleClicked: {
for(var i = 1; i < parent.children.length - 1; ++i) {
parent.children[i].visible = !parent.children[i].visible
}
}
Row {
id: objRow
Item {
height: 1
width: model.level * 15
}
HifiControls.CheckBox {
property var config: root.rootConfig.getConfig(model.path + "." + model.name);
text: (objRecursiveColumn.children.length > 2 ?
objRecursiveColumn.children[1].visible ?
qsTr("- ") : qsTr("+ ") : qsTr(" ")) + model.name + " ms=" + config.cpuRunTime.toFixed(2)
checked: config.enabled
}
}
}
Repeater {
model: subNode
delegate: objRecursiveDelegate
}
}
}
Original.ScrollView {
anchors.fill: parent
ListView {
id: theView
model: jobsModel
delegate: objRecursiveDelegate
}
}
}

View file

@ -0,0 +1,2 @@
TaskList 1.0 TaskList.qml
TaskViewList 1.0 TaskViewList.qml

View file

@ -23,13 +23,22 @@ Item {
anchors.left: parent.left
anchors.right: parent.right
height: 24
property var labelAreaWidthScale: 0.5
property bool integral: false
property var config
property string property
property alias label: labelControl.text
property alias min: sliderControl.minimumValue
property alias max: sliderControl.maximumValue
property alias label: labelControl.text
property bool showLabel: true
property bool showValue: true
signal valueChanged(real value)
Component.onCompleted: {
@ -41,20 +50,12 @@ Item {
HifiControls.Label {
id: labelControl
text: root.label
enabled: true
enabled: root.showLabel
anchors.left: root.left
anchors.right: root.horizontalCenter
width: root.width * root.labelAreaWidthScale
anchors.verticalCenter: root.verticalCenter
}
HifiControls.Label {
id: labelValue
text: sliderControl.value.toFixed(root.integral ? 0 : 2)
anchors.right: root.right
anchors.bottom: root.bottom
anchors.bottomMargin: 0
}
Binding {
id: bindingControl
target: root.config
@ -66,7 +67,7 @@ Item {
HifiControls.Slider {
id: sliderControl
stepSize: root.integral ? 1.0 : 0.0
anchors.left: root.horizontalCenter
anchors.left: labelControl.right
anchors.right: root.right
anchors.rightMargin: 0
anchors.top: root.top
@ -74,4 +75,17 @@ Item {
onValueChanged: { root.valueChanged(value) }
}
HifiControls.Label {
id: labelValue
enabled: root.showValue
text: sliderControl.value.toFixed(root.integral ? 0 : 2)
anchors.right: labelControl.right
anchors.rightMargin: 5
anchors.verticalCenter: root.verticalCenter
}
}

View file

@ -36,9 +36,9 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
spacing: 20
spacing: 5
Column {
spacing: 10
spacing: 5
// padding: 10
Repeater {
model: [
@ -61,7 +61,7 @@ Rectangle {
Column {
spacing: 10
spacing: 5
Repeater {
model: [
"Obscurance:LightingModel:enableObscurance",
@ -81,7 +81,7 @@ Rectangle {
}
Column {
spacing: 10
spacing: 5
Repeater {
model: [
"Ambient:LightingModel:enableAmbientLight",
@ -105,7 +105,7 @@ Rectangle {
Column {
anchors.left: parent.left
anchors.right: parent.right
spacing: 10
spacing: 5
Repeater {
model: [ "Tone Mapping Exposure:ToneMapping:exposure:5.0:-5.0"
]
@ -211,9 +211,9 @@ Rectangle {
Separator {}
Row {
spacing: 10
spacing: 5
Column {
spacing: 10
spacing: 5
HifiControls.CheckBox {
boxSize: 20
@ -254,7 +254,7 @@ Rectangle {
}
Column {
spacing: 10
spacing: 5
HifiControls.CheckBox {
boxSize: 20
text: "Metas"
@ -275,6 +275,13 @@ Rectangle {
}
}
}
Separator {}
HifiControls.Button {
text: "Engine"
// activeFocusOnPress: false
onClicked: {
sendToScript({method: "openEngineView"});
}
}
}
//}
}

View file

@ -0,0 +1,13 @@
function openEngineTaskView() {
// Set up the qml ui
var qml = Script.resolvePath('engineInspector.qml');
var window = new OverlayWindow({
title: 'Render Engine',
source: qml,
width: 300,
height: 400
});
window.setPosition(200, 50);
//window.closed.connect(function() { Script.stop(); });
}
openEngineTaskView();

View file

@ -0,0 +1,30 @@
//
// deferredLighting.qml
//
// Created by Sam Gateau on 6/6/2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
import "qrc:///qml/styles-uit"
import "qrc:///qml/controls-uit" as HifiControls
import "../lib/jet/qml" as Jet
Item {
HifiConstants { id: hifi;}
id: render;
anchors.fill: parent
property var mainViewTask: Render.getConfig("RenderMainView")
Jet.TaskListView {
rootConfig: Render
anchors.fill: render
}
}

View file

@ -64,9 +64,6 @@
button.editProperties({isActive: onLuciScreen});
wireEventBridge(onLuciScreen);
}
function fromQml(message) {
}
button.clicked.connect(onClicked);
tablet.screenChanged.connect(onScreenChanged);
@ -82,14 +79,6 @@
Controller.mouseMoveEvent.connect(function (e) { if (moveDebugCursor) setDebugCursor(e.x, e.y); });
Script.scriptEnding.connect(function () {
if (onLuciScreen) {
tablet.gotoHomeScreen();
}
button.clicked.disconnect(onClicked);
tablet.screenChanged.disconnect(onScreenChanged);
tablet.removeButton(button);
});
function setDebugCursor(x, y) {
nx = (x / Window.innerWidth);
@ -98,4 +87,46 @@
Render.getConfig("RenderMainView").getConfig("Antialiasing").debugCursorTexcoord = { x: nx, y: ny };
}
function fromQml(message) {
switch (message.method) {
case "openEngineView":
openEngineTaskView();
break;
}
}
var engineInspectorView = null
function openEngineTaskView() {
if (engineInspectorView == null) {
var qml = Script.resolvePath('engineInspector.qml');
var window = new OverlayWindow({
title: 'Render Engine',
source: qml,
width: 300,
height: 400
});
window.setPosition(200, 50);
engineInspectorView = window
window.closed.connect(function() { engineInspectorView = null; });
} else {
engineInspectorView.setPosition(200, 50);
}
}
Script.scriptEnding.connect(function () {
if (onLuciScreen) {
tablet.gotoHomeScreen();
}
button.clicked.disconnect(onClicked);
tablet.screenChanged.disconnect(onScreenChanged);
tablet.removeButton(button);
if (engineInspectorView !== null) {
engineInspectorView.close()
}
});
}());

View file

@ -413,8 +413,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
this.cloneHotspot = function(props, controllerData) {
if (entityIsCloneable(props)) {
var worldEntityProps = controllerData.nearbyEntityProperties[this.hand];
var cloneID = cloneEntity(props, worldEntityProps);
var cloneID = cloneEntity(props);
return cloneID;
}

Some files were not shown because too many files have changed in this diff Show more