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

This commit is contained in:
NissimHadar 2018-05-24 13:30:57 -07:00
commit 3dbebe2347
60 changed files with 1182 additions and 367 deletions

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

@ -649,7 +649,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

@ -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

@ -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

@ -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();
@ -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

@ -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

@ -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

@ -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

@ -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

@ -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;
}

View file

@ -235,8 +235,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
// switch to grabbing
var targetCloneable = entityIsCloneable(targetProps);
if (targetCloneable) {
var worldEntityProps = controllerData.nearbyEntityProperties[this.hand];
var cloneID = cloneEntity(targetProps, worldEntityProps);
var cloneID = cloneEntity(targetProps);
var cloneProps = Entities.getEntityProperties(cloneID);
this.targetEntityID = cloneID;
this.startNearGrabAction(controllerData, cloneProps);

View file

@ -35,6 +35,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
this.lastUnexpectedChildrenCheckTime = 0;
this.robbed = false;
this.highlightedEntity = null;
this.cloneAllowed = true;
this.parameters = makeDispatcherModuleParameters(
500,
@ -272,6 +273,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) {
this.checkForUnexpectedChildren(controllerData);
this.robbed = false;
this.cloneAllowed = true;
return makeRunningValues(false, [], []);
}
@ -335,13 +337,16 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
var targetCloneable = entityIsCloneable(targetProps);
if (targetCloneable) {
var worldEntityProps = controllerData.nearbyEntityProperties[this.hand];
var cloneID = cloneEntity(targetProps, worldEntityProps);
var cloneProps = Entities.getEntityProperties(cloneID);
this.grabbing = true;
this.targetEntityID = cloneID;
this.startNearParentingGrabEntity(controllerData, cloneProps);
if (this.cloneAllowed) {
var cloneID = cloneEntity(targetProps);
if (cloneID !== null) {
var cloneProps = Entities.getEntityProperties(cloneID);
this.grabbing = true;
this.targetEntityID = cloneID;
this.startNearParentingGrabEntity(controllerData, cloneProps);
this.cloneAllowed = false; // prevent another clone call until inputs released
}
}
} else if (targetProps) {
this.grabbing = true;
this.startNearParentingGrabEntity(controllerData, targetProps);

View file

@ -1040,12 +1040,13 @@ function loaded() {
elWantsTrigger.checked = false;
elIgnoreIK.checked = true;
elCloneable.checked = false;
elCloneableDynamic.checked = false;
elCloneable.checked = properties.cloneable;
elCloneableDynamic.checked = properties.cloneDynamic;
elCloneableAvatarEntity.checked = properties.cloneAvatarEntity;
elCloneableGroup.style.display = elCloneable.checked ? "block": "none";
elCloneableLimit.value = 0;
elCloneableLifetime.value = 300;
elCloneableLimit.value = properties.cloneLimit;
elCloneableLifetime.value = properties.cloneLifetime;
var grabbablesSet = false;
var parsedUserData = {};
try {
@ -1069,27 +1070,6 @@ function loaded() {
} else {
elIgnoreIK.checked = true;
}
if ("cloneable" in grabbableData) {
elCloneable.checked = grabbableData.cloneable;
elCloneableGroup.style.display = elCloneable.checked ? "block" : "none";
elCloneableDynamic.checked =
grabbableData.cloneDynamic ? grabbableData.cloneDynamic : properties.dynamic;
if (elCloneable.checked) {
if ("cloneLifetime" in grabbableData) {
elCloneableLifetime.value =
grabbableData.cloneLifetime ? grabbableData.cloneLifetime : 300;
}
if ("cloneLimit" in grabbableData) {
elCloneableLimit.value = grabbableData.cloneLimit ? grabbableData.cloneLimit : 0;
}
if ("cloneAvatarEntity" in grabbableData) {
elCloneableAvatarEntity.checked =
grabbableData.cloneAvatarEntity ? grabbableData.cloneAvatarEntity : false;
}
}
} else {
elCloneable.checked = false;
}
}
} catch (e) {
// TODO: What should go here?
@ -1460,45 +1440,12 @@ function loaded() {
}
userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true);
});
elCloneableDynamic.addEventListener('change', function(event) {
userDataChanger("grabbableKey", "cloneDynamic", event.target, elUserData, -1);
});
elCloneableAvatarEntity.addEventListener('change', function(event) {
userDataChanger("grabbableKey", "cloneAvatarEntity", event.target, elUserData, -1);
});
elCloneable.addEventListener('change', function (event) {
var checked = event.target.checked;
if (checked) {
multiDataUpdater("grabbableKey", {
cloneLifetime: elCloneableLifetime,
cloneLimit: elCloneableLimit,
cloneDynamic: elCloneableDynamic,
cloneAvatarEntity: elCloneableAvatarEntity,
cloneable: event.target,
grabbable: null
}, elUserData, {});
elCloneableGroup.style.display = "block";
updateProperty('dynamic', false);
} else {
multiDataUpdater("grabbableKey", {
cloneLifetime: null,
cloneLimit: null,
cloneDynamic: null,
cloneAvatarEntity: null,
cloneable: false
}, elUserData, {});
elCloneableGroup.style.display = "none";
}
});
var numberListener = function (event) {
userDataChanger("grabbableKey",
event.target.getAttribute("data-user-data-type"), parseInt(event.target.value), elUserData, false);
};
elCloneableLifetime.addEventListener('change', numberListener);
elCloneableLimit.addEventListener('change', numberListener);
elCloneable.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneable'));
elCloneableDynamic.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneDynamic'));
elCloneableAvatarEntity.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneAvatarEntity'));
elCloneableLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneLifetime'));
elCloneableLimit.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneLimit'));
elWantsTrigger.addEventListener('change', function() {
userDataChanger("grabbableKey", "wantsTrigger", elWantsTrigger, elUserData, false);

View file

@ -33,8 +33,7 @@ if (typeof Object.assign !== 'function') {
entityIsCloneable = function(props) {
if (props) {
var grabbableData = getGrabbableData(props);
return grabbableData.cloneable;
return props.cloneable;
}
return false;
};
@ -42,56 +41,19 @@ entityIsCloneable = function(props) {
propsAreCloneDynamic = function(props) {
var cloneable = entityIsCloneable(props);
if (cloneable) {
var grabInfo = getGrabbableData(props);
if (grabInfo.cloneDynamic) {
return true;
}
return props.cloneDynamic;
}
return false;
};
cloneEntity = function(props, worldEntityProps) {
// we need all the properties, for this
var cloneableProps = Entities.getEntityProperties(props.id);
var count = 0;
worldEntityProps.forEach(function(itemWE) {
if (itemWE.name.indexOf('-clone-' + cloneableProps.id) !== -1) {
count++;
}
});
var grabInfo = getGrabbableData(cloneableProps);
var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 0;
if (count >= limit && limit !== 0) {
return null;
cloneEntity = function(props) {
var entityToClone = props.id;
var certificateID = Entities.getEntityProperties(entityToClone, ['certificateID']).certificateID;
// ensure entity is cloneable and does not have a certificate ID, whereas cloneable limits
// will now be handled by the server where the entity add will fail if limit reached
if (entityIsCloneable(props) && (certificateID === undefined || certificateID.length === 0)) {
var cloneID = Entities.cloneEntity(entityToClone);
return cloneID;
}
cloneableProps.name = cloneableProps.name + '-clone-' + cloneableProps.id;
var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300;
var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false;
var triggerable = grabInfo.triggerable ? grabInfo.triggerable : false;
var avatarEntity = grabInfo.cloneAvatarEntity ? grabInfo.cloneAvatarEntity : false;
var cUserData = Object.assign({}, JSON.parse(cloneableProps.userData));
var cProperties = Object.assign({}, cloneableProps);
delete cUserData.grabbableKey.cloneLifetime;
delete cUserData.grabbableKey.cloneable;
delete cUserData.grabbableKey.cloneDynamic;
delete cUserData.grabbableKey.cloneLimit;
delete cUserData.grabbableKey.cloneAvatarEntity;
delete cProperties.id;
cProperties.dynamic = dynamic;
cProperties.locked = false;
cUserData.grabbableKey.triggerable = triggerable;
cUserData.grabbableKey.grabbable = true;
cProperties.lifetime = lifetime;
cProperties.userData = JSON.stringify(cUserData);
var cloneID = Entities.addEntity(cProperties, avatarEntity);
return cloneID;
return null;
};

View file

@ -128,7 +128,8 @@ DISPATCHER_PROPERTIES = [
"dimensions",
"userData",
"type",
"href"
"href",
"cloneable"
];
// priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step

View file

@ -1107,7 +1107,7 @@ private:
}
} };
render::EnginePointer _renderEngine { new render::Engine() };
render::EnginePointer _renderEngine { new render::RenderEngine() };
render::ScenePointer _main3DScene { new render::Scene(glm::vec3(-0.5f * (float)TREE_SCALE), (float)TREE_SCALE) };
QSize _size;
QSettings _settings;