diff --git a/examples/entityScripts/changeColorOnEnterLeave.js b/examples/entityScripts/changeColorOnEnterLeave.js new file mode 100644 index 0000000000..909fa6e814 --- /dev/null +++ b/examples/entityScripts/changeColorOnEnterLeave.js @@ -0,0 +1,26 @@ +// +// changeColorOnEnterLeave.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 3/31/16. +// Copyright 2016 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 +// + +(function(){ + function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + } + + this.enterEntity = function(myID) { + print("enterEntity() myID:" + myID); + Entities.editEntity(myID, { color: { red: getRandomInt(128,255), green: getRandomInt(128,255), blue: getRandomInt(128,255)} }); + }; + + this.leaveEntity = function(myID) { + print("leaveEntity() myID:" + myID); + Entities.editEntity(myID, { color: { red: getRandomInt(128,255), green: getRandomInt(128,255), blue: getRandomInt(128,255)} }); + }; +}) \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1c6fec1c67..ffc9b0333c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3357,9 +3357,10 @@ void Application::update(float deltaTime) { } { PROFILE_RANGE_EX("HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount()); - PerformanceTimer perfTimer("havestChanges"); + PerformanceTimer perfTimer("harvestChanges"); if (_physicsEngine->hasOutgoingChanges()) { getEntities()->getTree()->withWriteLock([&] { + PerformanceTimer perfTimer("handleOutgoingChanges"); const VectorOfMotionStates& outgoingChanges = _physicsEngine->getOutgoingChanges(); _entitySimulation.handleOutgoingChanges(outgoingChanges, Physics::getSessionUUID()); avatarManager->handleOutgoingChanges(outgoingChanges); @@ -3375,6 +3376,7 @@ void Application::update(float deltaTime) { // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk // deadlock.) _entitySimulation.handleCollisionEvents(collisionEvents); + // NOTE: the getEntities()->update() call below will wait for lock // and will simulate entity motion (the EntityTree has been given an EntitySimulation). getEntities()->update(); // update the models... diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 65ac5197c8..edc7f7d754 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -130,6 +130,7 @@ void EntityTreeRenderer::setTree(OctreePointer newTree) { } void EntityTreeRenderer::update() { + PerformanceTimer perfTimer("ETRupdate"); if (_tree && !_shuttingDown) { EntityTreePointer tree = std::static_pointer_cast(_tree); tree->update(); @@ -159,12 +160,14 @@ void EntityTreeRenderer::update() { bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3& avatarPosition, QVector* entitiesContainingAvatar) { bool didUpdate = false; - float radius = 1.0f; // for now, assume 1 meter radius + float radius = 0.01f; // for now, assume 0.01 meter radius, because we actually check the point inside later QVector foundEntities; // find the entities near us // don't let someone else change our tree while we search _tree->withReadLock([&] { + + // FIXME - if EntityTree had a findEntitiesContainingPoint() this could theoretically be a little faster std::static_pointer_cast(_tree)->findEntities(avatarPosition, radius, foundEntities); // Whenever you're in an intersection between zones, we will always choose the smallest zone. @@ -173,36 +176,37 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3& _bestZoneVolume = std::numeric_limits::max(); // create a list of entities that actually contain the avatar's position - foreach(EntityItemPointer entity, foundEntities) { - if (entity->contains(avatarPosition)) { - if (entitiesContainingAvatar) { - *entitiesContainingAvatar << entity->getEntityItemID(); - } + for (auto& entity : foundEntities) { + auto isZone = entity->getType() == EntityTypes::Zone; + auto hasScript = !entity->getScript().isEmpty(); - // if this entity is a zone, use this time to determine the bestZone - if (entity->getType() == EntityTypes::Zone) { - if (!entity->getVisible()) { - #ifdef WANT_DEBUG - qCDebug(entitiesrenderer) << "not visible"; - #endif - } else { + // only consider entities that are zones or have scripts, all other entities can + // be ignored because they can have events fired on them. + // FIXME - this could be optimized further by determining if the script is loaded + // and if it has either an enterEntity or leaveEntity method + if (isZone || hasScript) { + // now check to see if the point contains our entity, this can be expensive if + // the entity has a collision hull + if (entity->contains(avatarPosition)) { + if (entitiesContainingAvatar) { + *entitiesContainingAvatar << entity->getEntityItemID(); + } + + // if this entity is a zone and visible, determine if it is the bestZone + if (isZone && entity->getVisible()) { float entityVolumeEstimate = entity->getVolumeEstimate(); if (entityVolumeEstimate < _bestZoneVolume) { _bestZoneVolume = entityVolumeEstimate; _bestZone = std::dynamic_pointer_cast(entity); - } - else if (entityVolumeEstimate == _bestZoneVolume) { + } else if (entityVolumeEstimate == _bestZoneVolume) { + // in the case of the volume being equal, we will use the + // EntityItemID to deterministically pick one entity over the other if (!_bestZone) { _bestZoneVolume = entityVolumeEstimate; _bestZone = std::dynamic_pointer_cast(entity); - } - else { - // in the case of the volume being equal, we will use the - // EntityItemID to deterministically pick one entity over the other - if (entity->getEntityItemID() < _bestZone->getEntityItemID()) { - _bestZoneVolume = entityVolumeEstimate; - _bestZone = std::dynamic_pointer_cast(entity); - } + } else if (entity->getEntityItemID() < _bestZone->getEntityItemID()) { + _bestZoneVolume = entityVolumeEstimate; + _bestZone = std::dynamic_pointer_cast(entity); } } } @@ -217,13 +221,24 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(const glm::vec3& }); return didUpdate; } + bool EntityTreeRenderer::checkEnterLeaveEntities() { + PerformanceTimer perfTimer("checkEnterLeaveEntities"); + auto now = usecTimestampNow(); bool didUpdate = false; if (_tree && !_shuttingDown) { glm::vec3 avatarPosition = _viewState->getAvatarPosition(); - if (avatarPosition != _lastAvatarPosition) { + // we want to check our enter/leave state if we've moved a significant amount, or + // if some amount of time has elapsed since we last checked. We check the time + // elapsed because zones or entities might have been created "around us" while we've + // been stationary + auto movedEnough = glm::distance(avatarPosition, _lastAvatarPosition) > ZONE_CHECK_DISTANCE; + auto enoughTimeElapsed = (now - _lastZoneCheck) > ZONE_CHECK_INTERVAL; + + if (movedEnough || enoughTimeElapsed) { + _lastZoneCheck = now; QVector entitiesContainingAvatar; didUpdate = findBestZoneAndMaybeContainingEntities(avatarPosition, &entitiesContainingAvatar); @@ -248,8 +263,6 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { } _currentEntitiesInside = entitiesContainingAvatar; _lastAvatarPosition = avatarPosition; - } else { - didUpdate = findBestZoneAndMaybeContainingEntities(avatarPosition, nullptr); } } return didUpdate; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 095723dc9a..0fd5adc3f0 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -178,6 +178,10 @@ private: std::shared_ptr _bestZone; float _bestZoneVolume; + quint64 _lastZoneCheck { 0 }; + const quint64 ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz + const float ZONE_CHECK_DISTANCE = 0.001f; + glm::vec3 _previousKeyLightColor; float _previousKeyLightIntensity; float _previousKeyLightAmbientIntensity;