void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB) { BT_PROFILE("ownershipInfection"); const btCollisionObject* characterObject = _characterController ? _characterController->getCollisionObject() : nullptr; ObjectMotionState* motionStateA = static_cast<ObjectMotionState*>(objectA->getUserPointer()); ObjectMotionState* motionStateB = static_cast<ObjectMotionState*>(objectB->getUserPointer()); if (motionStateB && ((motionStateA && motionStateA->getSimulatorID() == _sessionID && !objectA->isStaticObject()) || (objectA == characterObject))) { // NOTE: we might own the simulation of a kinematic object (A) // but we don't claim ownership of kinematic objects (B) based on collisions here. if (!objectB->isStaticOrKinematicObject() && motionStateB->getSimulatorID() != _sessionID) { quint8 priority = motionStateA ? motionStateA->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY; motionStateB->bump(priority); } } else if (motionStateA && ((motionStateB && motionStateB->getSimulatorID() == _sessionID && !objectB->isStaticObject()) || (objectB == characterObject))) { // SIMILARLY: we might own the simulation of a kinematic object (B) // but we don't claim ownership of kinematic objects (A) based on collisions here. if (!objectA->isStaticOrKinematicObject() && motionStateA->getSimulatorID() != _sessionID) { quint8 priority = motionStateB ? motionStateB->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY; motionStateA->bump(priority); } } }
// Note: The "type" property is set in EntityItem::getActionArguments(). QVariantMap ObjectDynamic::getArguments() { QVariantMap arguments; withReadLock([&]{ if (_expires == 0) { arguments["ttl"] = 0.0f; } else { quint64 now = usecTimestampNow(); arguments["ttl"] = (float)(_expires - now) / (float)USECS_PER_SECOND; } arguments["tag"] = _tag; EntityItemPointer entity = _ownerEntity.lock(); if (entity) { ObjectMotionState* motionState = static_cast<ObjectMotionState*>(entity->getPhysicsInfo()); if (motionState) { arguments["::active"] = motionState->isActive(); arguments["::motion-type"] = motionTypeToString(motionState->getMotionType()); } else { arguments["::no-motion-state"] = true; } } arguments["isMine"] = isMine(); }); return arguments; }
void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args) { if (args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; batch.setModelTransform(entity->getTransformToCenter()); // we want to include the scale as well auto nodeList = DependencyManager::get<NodeList>(); const QUuid& myNodeID = nodeList->getSessionUUID(); bool highlightSimulationOwnership = (entity->getSimulatorID() == myNodeID); if (highlightSimulationOwnership) { glm::vec4 greenColor(0.0f, 1.0f, 0.2f, 1.0f); renderBoundingBox(entity, args, 0.08f, greenColor); } quint64 now = usecTimestampNow(); if (now - entity->getLastEditedFromRemote() < 0.1f * USECS_PER_SECOND) { glm::vec4 redColor(1.0f, 0.0f, 0.0f, 1.0f); renderBoundingBox(entity, args, 0.16f, redColor); } if (now - entity->getLastBroadcast() < 0.2f * USECS_PER_SECOND) { glm::vec4 yellowColor(1.0f, 1.0f, 0.2f, 1.0f); renderBoundingBox(entity, args, 0.24f, yellowColor); } ObjectMotionState* motionState = static_cast<ObjectMotionState*>(entity->getPhysicsInfo()); if (motionState && motionState->isActive()) { glm::vec4 blueColor(0.0f, 0.0f, 1.0f, 1.0f); renderBoundingBox(entity, args, 0.32f, blueColor); } } }
void PhysicsEngine::bump(ObjectMotionState* motionState) { // Find all objects that touch the object corresponding to motionState and flag the other objects // for simulation ownership by the local simulation. assert(motionState); btCollisionObject* object = motionState->getRigidBody(); int numManifolds = _collisionDispatcher->getNumManifolds(); for (int i = 0; i < numManifolds; ++i) { btPersistentManifold* contactManifold = _collisionDispatcher->getManifoldByIndexInternal(i); if (contactManifold->getNumContacts() > 0) { const btCollisionObject* objectA = static_cast<const btCollisionObject*>(contactManifold->getBody0()); const btCollisionObject* objectB = static_cast<const btCollisionObject*>(contactManifold->getBody1()); if (objectB == object) { if (!objectA->isStaticOrKinematicObject()) { ObjectMotionState* motionStateA = static_cast<ObjectMotionState*>(objectA->getUserPointer()); if (motionStateA) { motionStateA->bump(VOLUNTEER_SIMULATION_PRIORITY); objectA->setActivationState(ACTIVE_TAG); } } } else if (objectA == object) { if (!objectB->isStaticOrKinematicObject()) { ObjectMotionState* motionStateB = static_cast<ObjectMotionState*>(objectB->getUserPointer()); if (motionStateB) { motionStateB->bump(VOLUNTEER_SIMULATION_PRIORITY); objectB->setActivationState(ACTIVE_TAG); } } } } } }
void ObjectDynamic::forceBodyNonStatic() { auto ownerEntity = _ownerEntity.lock(); if (!ownerEntity) { return; } void* physicsInfo = ownerEntity->getPhysicsInfo(); ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo); if (motionState && motionState->getMotionType() == MOTION_TYPE_STATIC) { ownerEntity->flagForMotionStateChange(); } }
btRigidBody* ObjectAction::getRigidBody() { auto ownerEntity = _ownerEntity.lock(); if (!ownerEntity) { return nullptr; } void* physicsInfo = ownerEntity->getPhysicsInfo(); if (!physicsInfo) { return nullptr; } ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo); return motionState->getRigidBody(); }
CollisionEvents& PhysicsEngine::getCollisionEvents() { const uint32_t CONTINUE_EVENT_FILTER_FREQUENCY = 10; _collisionEvents.clear(); // scan known contacts and trigger events ContactMap::iterator contactItr = _contactMap.begin(); while (contactItr != _contactMap.end()) { ContactInfo& contact = contactItr->second; ContactEventType type = contact.computeType(_numContactFrames); if(type != CONTACT_EVENT_TYPE_CONTINUE || _numSubsteps % CONTINUE_EVENT_FILTER_FREQUENCY == 0) { ObjectMotionState* motionStateA = static_cast<ObjectMotionState*>(contactItr->first._a); ObjectMotionState* motionStateB = static_cast<ObjectMotionState*>(contactItr->first._b); glm::vec3 velocityChange = (motionStateA ? motionStateA->getObjectLinearVelocityChange() : glm::vec3(0.0f)) + (motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f)); if (motionStateA && motionStateA->getType() == MOTIONSTATE_TYPE_ENTITY) { QUuid idA = motionStateA->getObjectID(); QUuid idB; if (motionStateB && motionStateB->getType() == MOTIONSTATE_TYPE_ENTITY) { idB = motionStateB->getObjectID(); } glm::vec3 position = bulletToGLM(contact.getPositionWorldOnB()) + _originOffset; glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB); _collisionEvents.push_back(Collision(type, idA, idB, position, penetration, velocityChange)); } else if (motionStateB && motionStateB->getType() == MOTIONSTATE_TYPE_ENTITY) { QUuid idB = motionStateB->getObjectID(); glm::vec3 position = bulletToGLM(contact.getPositionWorldOnA()) + _originOffset; // NOTE: we're flipping the order of A and B (so that the first objectID is never NULL) // hence we must negate the penetration. glm::vec3 penetration = - bulletToGLM(contact.distance * contact.normalWorldOnB); _collisionEvents.push_back(Collision(type, idB, QUuid(), position, penetration, velocityChange)); } } if (type == CONTACT_EVENT_TYPE_END) { ContactMap::iterator iterToDelete = contactItr; ++contactItr; _contactMap.erase(iterToDelete); } else { ++contactItr; } } return _collisionEvents; }
void ObjectActionOffset::updateActionWorker(btScalar deltaTimeStep) { withTryReadLock([&] { auto ownerEntity = _ownerEntity.lock(); if (!ownerEntity) { return; } void* physicsInfo = ownerEntity->getPhysicsInfo(); if (!physicsInfo) { return; } ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo); btRigidBody* rigidBody = motionState->getRigidBody(); if (!rigidBody) { qDebug() << "ObjectActionOffset::updateActionWorker no rigidBody"; return; } const float MAX_LINEAR_TIMESCALE = 600.0f; // 10 minutes is a long time if (_positionalTargetSet && _linearTimeScale < MAX_LINEAR_TIMESCALE) { glm::vec3 objectPosition = bulletToGLM(rigidBody->getCenterOfMassPosition()); glm::vec3 springAxis = objectPosition - _pointToOffsetFrom; // from anchor to object float distance = glm::length(springAxis); if (distance > FLT_EPSILON) { springAxis /= distance; // normalize springAxis // compute (critically damped) target velocity of spring relaxation glm::vec3 offset = (distance - _linearDistance) * springAxis; glm::vec3 targetVelocity = (-1.0f / _linearTimeScale) * offset; // compute current velocity and its parallel component glm::vec3 currentVelocity = bulletToGLM(rigidBody->getLinearVelocity()); glm::vec3 parallelVelocity = glm::dot(currentVelocity, springAxis) * springAxis; // we blend the parallel component with the spring's target velocity to get the new velocity float blend = deltaTimeStep / _linearTimeScale; if (blend > 1.0f) { blend = 1.0f; } rigidBody->setLinearVelocity(glmToBullet(currentVelocity + blend * (targetVelocity - parallelVelocity))); } } }); }
btRigidBody* ObjectDynamic::getOtherRigidBody(EntityItemID otherEntityID) { EntityItemPointer otherEntity = getEntityByID(otherEntityID); if (!otherEntity) { return nullptr; } void* otherPhysicsInfo = otherEntity->getPhysicsInfo(); if (!otherPhysicsInfo) { return nullptr; } ObjectMotionState* otherMotionState = static_cast<ObjectMotionState*>(otherPhysicsInfo); if (!otherMotionState) { return nullptr; } return otherMotionState->getRigidBody(); }
btRigidBody* ObjectDynamic::getRigidBody() { ObjectMotionState* motionState = nullptr; withReadLock([&]{ auto ownerEntity = _ownerEntity.lock(); if (!ownerEntity) { return; } void* physicsInfo = ownerEntity->getPhysicsInfo(); if (!physicsInfo) { return; } motionState = static_cast<ObjectMotionState*>(physicsInfo); }); if (motionState) { return motionState->getRigidBody(); } return nullptr; }
void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& motionStates, const QUuid& sessionID) { QMutexLocker lock(&_mutex); // walk the motionStates looking for those that correspond to entities for (auto stateItr : motionStates) { ObjectMotionState* state = &(*stateItr); if (state && state->getType() == MOTIONSTATE_TYPE_ENTITY) { EntityMotionState* entityState = static_cast<EntityMotionState*>(state); EntityItemPointer entity = entityState->getEntity(); assert(entity.get()); if (entityState->isCandidateForOwnership(sessionID)) { _outgoingChanges.insert(entityState); } _entitiesToSort.insert(entity); } } uint32_t numSubsteps = _physicsEngine->getNumSubsteps(); if (_lastStepSendPackets != numSubsteps) { _lastStepSendPackets = numSubsteps; if (sessionID.isNull()) { // usually don't get here, but if so --> nothing to do _outgoingChanges.clear(); return; } // look for entities to prune or update QSet<EntityMotionState*>::iterator stateItr = _outgoingChanges.begin(); while (stateItr != _outgoingChanges.end()) { EntityMotionState* state = *stateItr; if (!state->isCandidateForOwnership(sessionID)) { // prune stateItr = _outgoingChanges.erase(stateItr); } else if (state->shouldSendUpdate(numSubsteps, sessionID)) { // update state->sendUpdate(_entityPacketSender, sessionID, numSubsteps); ++stateItr; } else { ++stateItr; } } } }
void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) { auto ownerEntity = _ownerEntity.lock(); if (!ownerEntity) { qDebug() << "AvatarActionHold::doKinematicUpdate -- no owning entity"; return; } void* physicsInfo = ownerEntity->getPhysicsInfo(); if (!physicsInfo) { qDebug() << "AvatarActionHold::doKinematicUpdate -- no owning physics info"; return; } ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo); btRigidBody* rigidBody = motionState ? motionState->getRigidBody() : nullptr; if (!rigidBody) { qDebug() << "AvatarActionHold::doKinematicUpdate -- no rigidBody"; return; } withWriteLock([&] { if (_kinematicSetVelocity) { if (_previousSet) { glm::vec3 positionalVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep; rigidBody->setLinearVelocity(glmToBullet(positionalVelocity)); } } btTransform worldTrans = rigidBody->getWorldTransform(); worldTrans.setOrigin(glmToBullet(_positionalTarget)); worldTrans.setRotation(glmToBullet(_rotationalTarget)); rigidBody->setWorldTransform(worldTrans); motionState->dirtyInternalKinematicChanges(); _previousPositionalTarget = _positionalTarget; _previousRotationalTarget = _rotationalTarget; _previousSet = true; }); activateBody(); }
void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB) { BT_PROFILE("ownershipInfection"); if (_sessionID.isNull()) { return; } const btCollisionObject* characterObject = _characterController ? _characterController->getCollisionObject() : nullptr; ObjectMotionState* a = static_cast<ObjectMotionState*>(objectA->getUserPointer()); ObjectMotionState* b = static_cast<ObjectMotionState*>(objectB->getUserPointer()); if (b && ((a && a->getSimulatorID() == _sessionID && !objectA->isStaticObject()) || (objectA == characterObject))) { // NOTE: we might own the simulation of a kinematic object (A) // but we don't claim ownership of kinematic objects (B) based on collisions here. if (!objectB->isStaticOrKinematicObject()) { b->bump(); } } else if (a && ((b && b->getSimulatorID() == _sessionID && !objectB->isStaticObject()) || (objectB == characterObject))) { // SIMILARLY: we might own the simulation of a kinematic object (B) // but we don't claim ownership of kinematic objects (A) based on collisions here. if (!objectA->isStaticOrKinematicObject()) { a->bump(); } } }
void ObjectActionTravelOriented::updateActionWorker(btScalar deltaTimeStep) { withReadLock([&] { auto ownerEntity = _ownerEntity.lock(); if (!ownerEntity) { return; } void* physicsInfo = ownerEntity->getPhysicsInfo(); if (!physicsInfo) { return; } ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo); btRigidBody* rigidBody = motionState->getRigidBody(); if (!rigidBody) { qCDebug(physics) << "ObjectActionTravelOriented::updateActionWorker no rigidBody"; return; } const float MAX_TIMESCALE = 600.0f; // 10 min is a long time if (_angularTimeScale > MAX_TIMESCALE) { return; } // find normalized velocity glm::vec3 velocity = bulletToGLM(rigidBody->getLinearVelocity()); float speed = glm::length(velocity); const float TRAVEL_ORIENTED_TOO_SLOW = 0.001f; // meters / second if (speed < TRAVEL_ORIENTED_TOO_SLOW) { return; } glm::vec3 direction = glm::normalize(velocity); // find current angle of "forward" btQuaternion bodyRotation = rigidBody->getOrientation(); glm::quat orientation = bulletToGLM(bodyRotation); glm::vec3 forwardInWorldFrame = glm::normalize(orientation * _forward); // find the rotation that would line up direction and forward glm::quat neededRotation = glm::rotation(forwardInWorldFrame, direction); glm::quat rotationalTarget = neededRotation * orientation; btVector3 targetAngularVelocity(0.0f, 0.0f, 0.0f); auto alignmentDot = bodyRotation.dot(glmToBullet(rotationalTarget)); const float ALMOST_ONE = 0.99999f; if (glm::abs(alignmentDot) < ALMOST_ONE) { btQuaternion target = glmToBullet(rotationalTarget); if (alignmentDot < 0.0f) { target = -target; } // if dQ is the incremental rotation that gets an object from Q0 to Q1 then: // // Q1 = dQ * Q0 // // solving for dQ gives: // // dQ = Q1 * Q0^ btQuaternion deltaQ = target * bodyRotation.inverse(); float speed = deltaQ.getAngle() / _angularTimeScale; targetAngularVelocity = speed * deltaQ.getAxis(); if (speed > rigidBody->getAngularSleepingThreshold()) { rigidBody->activate(); } } // this action is aggresively critically damped and defeats the current velocity rigidBody->setAngularVelocity(targetAngularVelocity); }); }
void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { if (!tryLockForRead()) { // don't risk hanging the thread running the physics simulation qDebug() << "ObjectActionSpring::updateActionWorker lock failed"; return; } auto ownerEntity = _ownerEntity.lock(); if (!ownerEntity) { return; } void* physicsInfo = ownerEntity->getPhysicsInfo(); if (!physicsInfo) { unlock(); return; } ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo); btRigidBody* rigidBody = motionState->getRigidBody(); if (!rigidBody) { unlock(); qDebug() << "ObjectActionSpring::updateActionWorker no rigidBody"; return; } const float MAX_TIMESCALE = 600.0f; // 10 min is a long time if (_linearTimeScale < MAX_TIMESCALE) { btVector3 offset = rigidBody->getCenterOfMassPosition() - glmToBullet(_positionalTarget); float offsetLength = offset.length(); float speed = (offsetLength > FLT_EPSILON) ? glm::min(offsetLength / _linearTimeScale, SPRING_MAX_SPEED) : 0.0f; // this action is aggresively critically damped and defeats the current velocity rigidBody->setLinearVelocity((- speed / offsetLength) * offset); } if (_angularTimeScale < MAX_TIMESCALE) { btVector3 targetVelocity(0.0f, 0.0f, 0.0f); btQuaternion bodyRotation = rigidBody->getOrientation(); auto alignmentDot = bodyRotation.dot(glmToBullet(_rotationalTarget)); const float ALMOST_ONE = 0.99999f; if (glm::abs(alignmentDot) < ALMOST_ONE) { btQuaternion target = glmToBullet(_rotationalTarget); if (alignmentDot < 0.0f) { target = -target; } // if dQ is the incremental rotation that gets an object from Q0 to Q1 then: // // Q1 = dQ * Q0 // // solving for dQ gives: // // dQ = Q1 * Q0^ btQuaternion deltaQ = target * bodyRotation.inverse(); float angle = deltaQ.getAngle(); const float MIN_ANGLE = 1.0e-4f; if (angle > MIN_ANGLE) { targetVelocity = (angle / _angularTimeScale) * deltaQ.getAxis(); } } // this action is aggresively critically damped and defeats the current velocity rigidBody->setAngularVelocity(targetVelocity); } unlock(); }
void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) { auto ownerEntity = _ownerEntity.lock(); if (!ownerEntity) { qDebug() << "AvatarActionHold::doKinematicUpdate -- no owning entity"; return; } if (ownerEntity->getParentID() != QUuid()) { // if the held entity has been given a parent, stop acting on it. return; } void* physicsInfo = ownerEntity->getPhysicsInfo(); if (!physicsInfo) { qDebug() << "AvatarActionHold::doKinematicUpdate -- no owning physics info"; return; } ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo); btRigidBody* rigidBody = motionState ? motionState->getRigidBody() : nullptr; if (!rigidBody) { qDebug() << "AvatarActionHold::doKinematicUpdate -- no rigidBody"; return; } withWriteLock([&]{ if (_previousSet && _positionalTarget != _previousPositionalTarget) { // don't average in a zero velocity if we get the same data glm::vec3 oneFrameVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep; _measuredLinearVelocities[_measuredLinearVelocitiesIndex++] = oneFrameVelocity; if (_measuredLinearVelocitiesIndex >= AvatarActionHold::velocitySmoothFrames) { _measuredLinearVelocitiesIndex = 0; } } glm::vec3 measuredLinearVelocity; for (int i = 0; i < AvatarActionHold::velocitySmoothFrames; i++) { // there is a bit of lag between when someone releases the trigger and when the software reacts to // the release. we calculate the velocity from previous frames but we don't include several // of the most recent. // // if _measuredLinearVelocitiesIndex is // 0 -- ignore i of 3 4 5 // 1 -- ignore i of 4 5 0 // 2 -- ignore i of 5 0 1 // 3 -- ignore i of 0 1 2 // 4 -- ignore i of 1 2 3 // 5 -- ignore i of 2 3 4 // This code is now disabled, but I'm leaving it commented-out because I suspect it will come back. // if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || // (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex || // (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) { // continue; // } measuredLinearVelocity += _measuredLinearVelocities[i]; } measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames // - 3 // 3 because of the 3 we skipped, above ); if (_kinematicSetVelocity) { rigidBody->setLinearVelocity(glmToBullet(measuredLinearVelocity)); rigidBody->setAngularVelocity(glmToBullet(_angularVelocityTarget)); } btTransform worldTrans = rigidBody->getWorldTransform(); worldTrans.setOrigin(glmToBullet(_positionalTarget)); worldTrans.setRotation(glmToBullet(_rotationalTarget)); rigidBody->setWorldTransform(worldTrans); motionState->dirtyInternalKinematicChanges(); _previousPositionalTarget = _positionalTarget; _previousRotationalTarget = _rotationalTarget; _previousDeltaTimeStep = deltaTimeStep; _previousSet = true; }); forceBodyNonStatic(); activateBody(true); }
void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::Status::Getters& statusGetters) { auto nodeList = DependencyManager::get<NodeList>(); const QUuid& myNodeID = nodeList->getSessionUUID(); statusGetters.push_back([entity]() -> render::Item::Status::Value { uint64_t delta = usecTimestampNow() - entity->getLastEditedFromRemote(); const float WAIT_THRESHOLD_INV = 1.0f / (0.2f * USECS_PER_SECOND); float normalizedDelta = delta * WAIT_THRESHOLD_INV; // Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD // Color is red if last update is after WAIT_THRESHOLD, green otherwise (120 deg is green) return render::Item::Status::Value(1.0f - normalizedDelta, (normalizedDelta > 1.0f ? render::Item::Status::Value::GREEN : render::Item::Status::Value::RED), (unsigned char)RenderItemStatusIcon::PACKET_RECEIVED); }); statusGetters.push_back([entity] () -> render::Item::Status::Value { uint64_t delta = usecTimestampNow() - entity->getLastBroadcast(); const float WAIT_THRESHOLD_INV = 1.0f / (0.4f * USECS_PER_SECOND); float normalizedDelta = delta * WAIT_THRESHOLD_INV; // Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD // Color is Magenta if last update is after WAIT_THRESHOLD, cyan otherwise (180 deg is green) return render::Item::Status::Value(1.0f - normalizedDelta, (normalizedDelta > 1.0f ? render::Item::Status::Value::MAGENTA : render::Item::Status::Value::CYAN), (unsigned char)RenderItemStatusIcon::PACKET_SENT); }); statusGetters.push_back([entity] () -> render::Item::Status::Value { ObjectMotionState* motionState = static_cast<ObjectMotionState*>(entity->getPhysicsInfo()); if (motionState && motionState->isActive()) { return render::Item::Status::Value(1.0f, render::Item::Status::Value::BLUE, (unsigned char)RenderItemStatusIcon::ACTIVE_IN_BULLET); } return render::Item::Status::Value(0.0f, render::Item::Status::Value::BLUE, (unsigned char)RenderItemStatusIcon::ACTIVE_IN_BULLET); }); statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value { bool weOwnSimulation = entity->getSimulationOwner().matchesValidID(myNodeID); bool otherOwnSimulation = !weOwnSimulation && !entity->getSimulationOwner().isNull(); if (weOwnSimulation) { return render::Item::Status::Value(1.0f, render::Item::Status::Value::BLUE, (unsigned char)RenderItemStatusIcon::SIMULATION_OWNER); } else if (otherOwnSimulation) { return render::Item::Status::Value(1.0f, render::Item::Status::Value::RED, (unsigned char)RenderItemStatusIcon::OTHER_SIMULATION_OWNER); } return render::Item::Status::Value(0.0f, render::Item::Status::Value::BLUE, (unsigned char)RenderItemStatusIcon::SIMULATION_OWNER); }); statusGetters.push_back([entity] () -> render::Item::Status::Value { if (entity->hasActions()) { return render::Item::Status::Value(1.0f, render::Item::Status::Value::GREEN, (unsigned char)RenderItemStatusIcon::HAS_ACTIONS); } return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN, (unsigned char)RenderItemStatusIcon::HAS_ACTIONS); }); statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value { if (entity->getClientOnly()) { if (entity->getOwningAvatarID() == myNodeID) { return render::Item::Status::Value(1.0f, render::Item::Status::Value::GREEN, (unsigned char)RenderItemStatusIcon::CLIENT_ONLY); } else { return render::Item::Status::Value(1.0f, render::Item::Status::Value::RED, (unsigned char)RenderItemStatusIcon::CLIENT_ONLY); } } return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN, (unsigned char)RenderItemStatusIcon::CLIENT_ONLY); }); }