mirror of
https://github.com/lubosz/overte.git
synced 2025-08-08 03:08:00 +02:00
build CollisionShapes and load RigidBodies even when not yet stepping
This commit is contained in:
parent
6e27b4d1c5
commit
e770bd6142
7 changed files with 71 additions and 58 deletions
|
@ -6401,40 +6401,40 @@ void Application::update(float deltaTime) {
|
||||||
|
|
||||||
getEntities()->preUpdate();
|
getEntities()->preUpdate();
|
||||||
|
|
||||||
if (_physicsEnabled) {
|
auto t0 = std::chrono::high_resolution_clock::now();
|
||||||
auto t0 = std::chrono::high_resolution_clock::now();
|
auto t1 = t0;
|
||||||
auto t1 = t0;
|
{
|
||||||
|
PROFILE_RANGE(simulation_physics, "PrePhysics");
|
||||||
|
PerformanceTimer perfTimer("prePhysics)");
|
||||||
{
|
{
|
||||||
PROFILE_RANGE(simulation_physics, "PrePhysics");
|
PROFILE_RANGE(simulation_physics, "Entities");
|
||||||
PerformanceTimer perfTimer("prePhysics)");
|
PhysicsEngine::Transaction transaction;
|
||||||
{
|
_entitySimulation->buildPhysicsTransaction(transaction);
|
||||||
PROFILE_RANGE(simulation_physics, "Entities");
|
_physicsEngine->processTransaction(transaction);
|
||||||
PhysicsEngine::Transaction transaction;
|
_entitySimulation->handleProcessedPhysicsTransaction(transaction);
|
||||||
_entitySimulation->buildPhysicsTransaction(transaction);
|
}
|
||||||
_physicsEngine->processTransaction(transaction);
|
|
||||||
_entitySimulation->handleProcessedPhysicsTransaction(transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
t1 = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
{
|
||||||
|
PROFILE_RANGE(simulation_physics, "Avatars");
|
||||||
|
PhysicsEngine::Transaction transaction;
|
||||||
|
avatarManager->buildPhysicsTransaction(transaction);
|
||||||
|
_physicsEngine->processTransaction(transaction);
|
||||||
|
avatarManager->handleProcessedPhysicsTransaction(transaction);
|
||||||
|
|
||||||
|
myAvatar->prepareForPhysicsSimulation();
|
||||||
|
_physicsEngine->enableGlobalContactAddedCallback(myAvatar->isFlying());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_physicsEnabled) {
|
||||||
|
{
|
||||||
|
PROFILE_RANGE(simulation_physics, "PrepareActions");
|
||||||
_entitySimulation->applyDynamicChanges();
|
_entitySimulation->applyDynamicChanges();
|
||||||
|
_physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
|
||||||
t1 = std::chrono::high_resolution_clock::now();
|
dynamic->prepareForPhysicsSimulation();
|
||||||
|
});
|
||||||
{
|
|
||||||
PROFILE_RANGE(simulation_physics, "Avatars");
|
|
||||||
PhysicsEngine::Transaction transaction;
|
|
||||||
avatarManager->buildPhysicsTransaction(transaction);
|
|
||||||
_physicsEngine->processTransaction(transaction);
|
|
||||||
avatarManager->handleProcessedPhysicsTransaction(transaction);
|
|
||||||
myAvatar->prepareForPhysicsSimulation();
|
|
||||||
_physicsEngine->enableGlobalContactAddedCallback(myAvatar->isFlying());
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
PROFILE_RANGE(simulation_physics, "PrepareActions");
|
|
||||||
_physicsEngine->forEachDynamic([&](EntityDynamicPointer dynamic) {
|
|
||||||
dynamic->prepareForPhysicsSimulation();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
auto t2 = std::chrono::high_resolution_clock::now();
|
auto t2 = std::chrono::high_resolution_clock::now();
|
||||||
{
|
{
|
||||||
|
|
|
@ -168,7 +168,7 @@ bool isEntityPhysicsReady(const EntityItemPointer& entity) {
|
||||||
bool hasAABox;
|
bool hasAABox;
|
||||||
entity->getAABox(hasAABox);
|
entity->getAABox(hasAABox);
|
||||||
if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) {
|
if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) {
|
||||||
return (!entity->shouldBePhysical() || entity->isReadyToComputeShape() || modelEntity->computeShapeFailedToLoad());
|
return (!entity->shouldBePhysical() || entity->isInPhysicsSimulation() || modelEntity->computeShapeFailedToLoad());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1828,42 +1828,42 @@ void EntityItem::setParentID(const QUuid& value) {
|
||||||
if (!value.isNull() && tree) {
|
if (!value.isNull() && tree) {
|
||||||
EntityItemPointer entity = tree->findEntityByEntityItemID(value);
|
EntityItemPointer entity = tree->findEntityByEntityItemID(value);
|
||||||
if (entity) {
|
if (entity) {
|
||||||
newParentNoBootstrapping = entity->getSpecialFlags() & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
newParentNoBootstrapping = entity->getSpecialFlags() & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!oldParentID.isNull() && tree) {
|
if (!oldParentID.isNull() && tree) {
|
||||||
EntityItemPointer entity = tree->findEntityByEntityItemID(oldParentID);
|
EntityItemPointer entity = tree->findEntityByEntityItemID(oldParentID);
|
||||||
if (entity) {
|
if (entity) {
|
||||||
oldParentNoBootstrapping = entity->getDirtyFlags() & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
oldParentNoBootstrapping = entity->getDirtyFlags() & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!value.isNull() && (value == Physics::getSessionUUID() || value == AVATAR_SELF_ID)) {
|
if (!value.isNull() && (value == Physics::getSessionUUID() || value == AVATAR_SELF_ID)) {
|
||||||
newParentNoBootstrapping |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
newParentNoBootstrapping |= Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!oldParentID.isNull() && (oldParentID == Physics::getSessionUUID() || oldParentID == AVATAR_SELF_ID)) {
|
if (!oldParentID.isNull() && (oldParentID == Physics::getSessionUUID() || oldParentID == AVATAR_SELF_ID)) {
|
||||||
oldParentNoBootstrapping |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
oldParentNoBootstrapping |= Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((bool)(oldParentNoBootstrapping ^ newParentNoBootstrapping)) {
|
if ((bool)(oldParentNoBootstrapping ^ newParentNoBootstrapping)) {
|
||||||
if ((bool)(newParentNoBootstrapping & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) {
|
if ((bool)(newParentNoBootstrapping & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING)) {
|
||||||
markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
|
markSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
|
||||||
forEachDescendant([&](SpatiallyNestablePointer object) {
|
forEachDescendant([&](SpatiallyNestablePointer object) {
|
||||||
if (object->getNestableType() == NestableType::Entity) {
|
if (object->getNestableType() == NestableType::Entity) {
|
||||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
|
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
|
||||||
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
||||||
entity->markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
|
entity->markSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
|
clearSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
|
||||||
forEachDescendant([&](SpatiallyNestablePointer object) {
|
forEachDescendant([&](SpatiallyNestablePointer object) {
|
||||||
if (object->getNestableType() == NestableType::Entity) {
|
if (object->getNestableType() == NestableType::Entity) {
|
||||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
|
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
|
||||||
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
||||||
entity->clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
|
entity->clearSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2102,7 +2102,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int32_t& group, int32_t& mask
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) {
|
if ((bool)(_flags & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING)) {
|
||||||
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
|
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
|
||||||
}
|
}
|
||||||
mask = Physics::getDefaultCollisionMask(group) & (int32_t)(userMask);
|
mask = Physics::getDefaultCollisionMask(group) & (int32_t)(userMask);
|
||||||
|
@ -2173,8 +2173,8 @@ bool EntityItem::addAction(EntitySimulationPointer simulation, EntityDynamicPoin
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityItem::enableNoBootstrap() {
|
void EntityItem::enableNoBootstrap() {
|
||||||
if (!(bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) {
|
if (!(bool)(_flags & Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING)) {
|
||||||
_flags |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
_flags |= Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
|
||||||
_flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
_flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||||
|
|
||||||
// NOTE: unlike disableNoBootstrap() below, we do not call simulation->changeEntity() here
|
// NOTE: unlike disableNoBootstrap() below, we do not call simulation->changeEntity() here
|
||||||
|
@ -2186,7 +2186,7 @@ void EntityItem::enableNoBootstrap() {
|
||||||
if (child->getNestableType() == NestableType::Entity) {
|
if (child->getNestableType() == NestableType::Entity) {
|
||||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
|
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
|
||||||
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
||||||
entity->markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
|
entity->markSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2194,7 +2194,7 @@ void EntityItem::enableNoBootstrap() {
|
||||||
|
|
||||||
void EntityItem::disableNoBootstrap() {
|
void EntityItem::disableNoBootstrap() {
|
||||||
if (!stillHasMyGrabAction()) {
|
if (!stillHasMyGrabAction()) {
|
||||||
_flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
_flags &= ~Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING;
|
||||||
_flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
_flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||||
|
|
||||||
EntityTreePointer entityTree = getTree();
|
EntityTreePointer entityTree = getTree();
|
||||||
|
@ -2207,7 +2207,7 @@ void EntityItem::disableNoBootstrap() {
|
||||||
if (child->getNestableType() == NestableType::Entity) {
|
if (child->getNestableType() == NestableType::Entity) {
|
||||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
|
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
|
||||||
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
||||||
entity->clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING);
|
entity->clearSpecialFlags(Simulation::SPECIAL_FLAG_NO_BOOTSTRAPPING);
|
||||||
simulation->changeEntity(entity);
|
simulation->changeEntity(entity);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2326,7 +2326,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
|
||||||
if (removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) {
|
if (removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) {
|
||||||
disableNoBootstrap();
|
disableNoBootstrap();
|
||||||
} else {
|
} else {
|
||||||
// NO-OP: we assume SPECIAL_FLAGS_NO_BOOTSTRAPPING bits and collision group are correct
|
// NO-OP: we assume SPECIAL_FLAG_NO_BOOTSTRAPPING bits and collision group are correct
|
||||||
// because they should have been set correctly when the action was added
|
// because they should have been set correctly when the action was added
|
||||||
// and/or when children were linked
|
// and/or when children were linked
|
||||||
}
|
}
|
||||||
|
@ -3154,21 +3154,21 @@ DEFINE_PROPERTY_ACCESSOR(quint32, StaticCertificateVersion, staticCertificateVer
|
||||||
uint32_t EntityItem::getDirtyFlags() const {
|
uint32_t EntityItem::getDirtyFlags() const {
|
||||||
uint32_t result;
|
uint32_t result;
|
||||||
withReadLock([&] {
|
withReadLock([&] {
|
||||||
result = _flags & Simulation::DIRTY_FLAGS;
|
result = _flags & Simulation::DIRTY_FLAGS_MASK;
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityItem::markDirtyFlags(uint32_t mask) {
|
void EntityItem::markDirtyFlags(uint32_t mask) {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
mask &= Simulation::DIRTY_FLAGS;
|
mask &= Simulation::DIRTY_FLAGS_MASK;
|
||||||
_flags |= mask;
|
_flags |= mask;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityItem::clearDirtyFlags(uint32_t mask) {
|
void EntityItem::clearDirtyFlags(uint32_t mask) {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
mask &= Simulation::DIRTY_FLAGS;
|
mask &= Simulation::DIRTY_FLAGS_MASK;
|
||||||
_flags &= ~mask;
|
_flags &= ~mask;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3176,21 +3176,21 @@ void EntityItem::clearDirtyFlags(uint32_t mask) {
|
||||||
uint32_t EntityItem::getSpecialFlags() const {
|
uint32_t EntityItem::getSpecialFlags() const {
|
||||||
uint32_t result;
|
uint32_t result;
|
||||||
withReadLock([&] {
|
withReadLock([&] {
|
||||||
result = _flags & Simulation::SPECIAL_FLAGS;
|
result = _flags & Simulation::SPECIAL_FLAGS_MASK;
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityItem::markSpecialFlags(uint32_t mask) {
|
void EntityItem::markSpecialFlags(uint32_t mask) {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
mask &= Simulation::SPECIAL_FLAGS;
|
mask &= Simulation::SPECIAL_FLAGS_MASK;
|
||||||
_flags |= mask;
|
_flags |= mask;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityItem::clearSpecialFlags(uint32_t mask) {
|
void EntityItem::clearSpecialFlags(uint32_t mask) {
|
||||||
withWriteLock([&] {
|
withWriteLock([&] {
|
||||||
mask &= Simulation::SPECIAL_FLAGS;
|
mask &= Simulation::SPECIAL_FLAGS_MASK;
|
||||||
_flags &= ~mask;
|
_flags &= ~mask;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -424,8 +424,9 @@ public:
|
||||||
|
|
||||||
bool isSimulated() const { return _simulated; }
|
bool isSimulated() const { return _simulated; }
|
||||||
|
|
||||||
void* getPhysicsInfo() const { return _physicsInfo; }
|
bool isInPhysicsSimulation() const { return (bool)(_flags & Simulation::SPECIAL_FLAG_IN_PHYSICS_SIMULATION); }
|
||||||
|
|
||||||
|
void* getPhysicsInfo() const { return _physicsInfo; }
|
||||||
void setPhysicsInfo(void* data) { _physicsInfo = data; }
|
void setPhysicsInfo(void* data) { _physicsInfo = data; }
|
||||||
|
|
||||||
EntityTreeElementPointer getElement() const { return _element; }
|
EntityTreeElementPointer getElement() const { return _element; }
|
||||||
|
|
|
@ -743,6 +743,15 @@ bool EntityMotionState::shouldSendBid() const {
|
||||||
&& !_entity->getLocked();
|
&& !_entity->getLocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityMotionState::setRigidBody(btRigidBody* body) {
|
||||||
|
ObjectMotionState::setRigidBody(body);
|
||||||
|
if (_body) {
|
||||||
|
_entity->markSpecialFlags(Simulation::SPECIAL_FLAG_IN_PHYSICS_SIMULATION);
|
||||||
|
} else {
|
||||||
|
_entity->clearSpecialFlags(Simulation::SPECIAL_FLAG_IN_PHYSICS_SIMULATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t EntityMotionState::computeFinalBidPriority() const {
|
uint8_t EntityMotionState::computeFinalBidPriority() const {
|
||||||
return (_region == workload::Region::R1) ?
|
return (_region == workload::Region::R1) ?
|
||||||
glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) : 0;
|
glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) : 0;
|
||||||
|
|
|
@ -100,6 +100,8 @@ public:
|
||||||
void saveKinematicState(btScalar timeStep) override;
|
void saveKinematicState(btScalar timeStep) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void setRigidBody(btRigidBody* body) override;
|
||||||
|
|
||||||
uint8_t computeFinalBidPriority() const;
|
uint8_t computeFinalBidPriority() const;
|
||||||
void updateSendVelocities();
|
void updateSendVelocities();
|
||||||
uint64_t getNextBidExpiry() const { return _nextBidExpiry; }
|
uint64_t getNextBidExpiry() const { return _nextBidExpiry; }
|
||||||
|
|
|
@ -40,13 +40,14 @@ namespace Simulation {
|
||||||
const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = 0x2000; // our own bid priority has changed
|
const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = 0x2000; // our own bid priority has changed
|
||||||
|
|
||||||
// bits 17-32 are reservied for special flags
|
// bits 17-32 are reservied for special flags
|
||||||
const uint32_t SPECIAL_FLAGS_NO_BOOTSTRAPPING = 0x10000;
|
const uint32_t SPECIAL_FLAG_NO_BOOTSTRAPPING = 0x10000;
|
||||||
|
const uint32_t SPECIAL_FLAG_IN_PHYSICS_SIMULATION = 0x20000;
|
||||||
|
|
||||||
const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION;
|
const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION;
|
||||||
const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY;
|
const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY;
|
||||||
const uint32_t SPECIAL_FLAGS = SPECIAL_FLAGS_NO_BOOTSTRAPPING;
|
const uint32_t SPECIAL_FLAGS_MASK = SPECIAL_FLAG_NO_BOOTSTRAPPING | SPECIAL_FLAG_IN_PHYSICS_SIMULATION;
|
||||||
|
|
||||||
const uint32_t DIRTY_FLAGS = DIRTY_POSITION |
|
const uint32_t DIRTY_FLAGS_MASK = DIRTY_POSITION |
|
||||||
DIRTY_ROTATION |
|
DIRTY_ROTATION |
|
||||||
DIRTY_LINEAR_VELOCITY |
|
DIRTY_LINEAR_VELOCITY |
|
||||||
DIRTY_ANGULAR_VELOCITY |
|
DIRTY_ANGULAR_VELOCITY |
|
||||||
|
|
Loading…
Reference in a new issue