// // RenderableEntityItem.cpp // interface/src // // Created by Brad Hefta-Gaub on 12/6/13. // Copyright 2013 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 // #include "RenderableEntityItem.h" #include #include "RenderableShapeEntityItem.h" #include "RenderableModelEntityItem.h" #include "RenderableTextEntityItem.h" #include "RenderableImageEntityItem.h" #include "RenderableWebEntityItem.h" #include "RenderableParticleEffectEntityItem.h" #include "RenderableLineEntityItem.h" #include "RenderablePolyLineEntityItem.h" #include "RenderablePolyVoxEntityItem.h" #include "RenderableGridEntityItem.h" #include "RenderableGizmoEntityItem.h" #include "RenderableLightEntityItem.h" #include "RenderableZoneEntityItem.h" #include "RenderableMaterialEntityItem.h" using namespace render; using namespace render::entities; void EntityRenderer::initEntityRenderers() { REGISTER_ENTITY_TYPE_WITH_FACTORY(Model, RenderableModelEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(PolyVox, RenderablePolyVoxEntityItem::factory) } const Transform& EntityRenderer::getModelTransform() const { return _modelTransform; } void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::Status::Getters& statusGetters) { auto nodeList = DependencyManager::get(); 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)render::Item::Status::Icon::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)render::Item::Status::Icon::PACKET_SENT); }); statusGetters.push_back([entity] () -> render::Item::Status::Value { ObjectMotionState* motionState = static_cast(entity->getPhysicsInfo()); if (motionState && motionState->isActive()) { return render::Item::Status::Value(1.0f, render::Item::Status::Value::BLUE, (unsigned char)render::Item::Status::Icon::ACTIVE_IN_BULLET); } return render::Item::Status::Value(0.0f, render::Item::Status::Value::BLUE, (unsigned char)render::Item::Status::Icon::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)render::Item::Status::Icon::SIMULATION_OWNER); } else if (otherOwnSimulation) { return render::Item::Status::Value(1.0f, render::Item::Status::Value::RED, (unsigned char)render::Item::Status::Icon::OTHER_SIMULATION_OWNER); } return render::Item::Status::Value(0.0f, render::Item::Status::Value::BLUE, (unsigned char)render::Item::Status::Icon::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)render::Item::Status::Icon::HAS_ACTIONS); } return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN, (unsigned char)render::Item::Status::Icon::HAS_ACTIONS); }); statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value { if (entity->isAvatarEntity()) { if (entity->getOwningAvatarID() == myNodeID) { return render::Item::Status::Value(1.0f, render::Item::Status::Value::GREEN, (unsigned char)render::Item::Status::Icon::ENTITY_HOST_TYPE); } else { return render::Item::Status::Value(1.0f, render::Item::Status::Value::RED, (unsigned char)render::Item::Status::Icon::ENTITY_HOST_TYPE); } } else if (entity->isLocalEntity()) { return render::Item::Status::Value(1.0f, render::Item::Status::Value::BLUE, (unsigned char)render::Item::Status::Icon::ENTITY_HOST_TYPE); } return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN, (unsigned char)render::Item::Status::Icon::ENTITY_HOST_TYPE); }); } template std::shared_ptr make_renderer(const EntityItemPointer& entity) { // We want to use deleteLater so that renderer destruction gets pushed to the main thread return std::shared_ptr(new T(entity), [](T* ptr) { ptr->deleteLater(); }); } EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _created(entity->getCreated()), _entity(entity) { connect(entity.get(), &EntityItem::requestRenderUpdate, this, [&] { _needsRenderUpdate = true; emit requestRenderUpdate(); }); } EntityRenderer::~EntityRenderer() {} // // Smart payload proxy members, implementing the payload interface // Item::Bound EntityRenderer::getBound() { return _bound; } ShapeKey EntityRenderer::getShapeKey() { if (_primitiveMode == PrimitiveMode::LINES) { return ShapeKey::Builder().withOwnPipeline().withWireframe(); } return ShapeKey::Builder().withOwnPipeline(); } render::hifi::Tag EntityRenderer::getTagMask() const { render::hifi::Tag mask = render::hifi::TAG_NONE; mask = (render::hifi::Tag)(mask | (!_cauterized * render::hifi::TAG_MAIN_VIEW)); mask = (render::hifi::Tag)(mask | (_isVisibleInSecondaryCamera * render::hifi::TAG_SECONDARY_VIEW)); return mask; } render::hifi::Layer EntityRenderer::getHifiRenderLayer() const { switch (_renderLayer) { case RenderLayer::WORLD: return render::hifi::LAYER_3D; case RenderLayer::FRONT: return render::hifi::LAYER_3D_FRONT; case RenderLayer::HUD: return render::hifi::LAYER_3D_HUD; default: return render::hifi::LAYER_3D; } } ItemKey EntityRenderer::getKey() { ItemKey::Builder builder = ItemKey::Builder().withTypeShape().withTypeMeta().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()); if (isTransparent()) { builder.withTransparent(); } else if (_canCastShadow) { builder.withShadowCaster(); } if (!_visible) { builder.withInvisible(); } return builder; } uint32_t EntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) const { if (Item::isValidID(_renderItemID)) { subItems.emplace_back(_renderItemID); return 1; } return 0; } void EntityRenderer::render(RenderArgs* args) { if (!isValidRenderItem()) { return; } if (!_renderUpdateQueued && needsRenderUpdate()) { // FIXME find a way to spread out the calls to needsRenderUpdate so that only a given subset of the // items checks every frame, like 1/N of the tree ever N frames _renderUpdateQueued = true; emit requestRenderUpdate(); } if (_visible && (args->_renderMode != RenderArgs::RenderMode::DEFAULT_RENDER_MODE || !_cauterized)) { doRender(args); } } // // Methods called by the EntityTreeRenderer // EntityRenderer::Pointer EntityRenderer::addToScene(EntityTreeRenderer& renderer, const EntityItemPointer& entity, const ScenePointer& scene, Transaction& transaction) { EntityRenderer::Pointer result; if (!entity) { return result; } using Type = EntityTypes::EntityType_t; auto type = entity->getType(); switch (type) { case Type::Shape: case Type::Box: case Type::Sphere: result = make_renderer(entity); break; case Type::Model: result = make_renderer(entity); break; case Type::Text: result = make_renderer(entity); break; case Type::Image: result = make_renderer(entity); break; case Type::Web: if (!nsightActive()) { result = make_renderer(entity); } break; case Type::ParticleEffect: result = make_renderer(entity); break; case Type::Line: result = make_renderer(entity); break; case Type::PolyLine: result = make_renderer(entity); break; case Type::PolyVox: result = make_renderer(entity); break; case Type::Grid: result = make_renderer(entity); break; case Type::Gizmo: result = make_renderer(entity); break; case Type::Light: result = make_renderer(entity); break; case Type::Zone: result = make_renderer(entity); break; case Type::Material: result = make_renderer(entity); break; default: break; } if (result) { result->addToScene(scene, transaction); } return result; } bool EntityRenderer::addToScene(const ScenePointer& scene, Transaction& transaction) { _renderItemID = scene->allocateID(); // Complicated series of trusses auto renderPayload = std::make_shared(shared_from_this()); Item::Status::Getters statusGetters; makeStatusGetters(_entity, statusGetters); renderPayload->addStatusGetters(statusGetters); transaction.resetItem(_renderItemID, renderPayload); onAddToScene(_entity); updateInScene(scene, transaction); _entity->bumpAncestorChainRenderableVersion(); return true; } void EntityRenderer::removeFromScene(const ScenePointer& scene, Transaction& transaction) { onRemoveFromScene(_entity); transaction.removeItem(_renderItemID); Item::clearID(_renderItemID); _entity->bumpAncestorChainRenderableVersion(); } void EntityRenderer::updateInScene(const ScenePointer& scene, Transaction& transaction) { DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__); if (!isValidRenderItem()) { return; } _updateTime = usecTimestampNow(); // FIXME is this excessive? if (!needsRenderUpdate()) { return; } doRenderUpdateSynchronous(scene, transaction, _entity); transaction.updateItem(_renderItemID, [this](PayloadProxyInterface& self) { if (!isValidRenderItem()) { return; } // Happens on the render thread. Classes should use doRenderUpdateAsynchronous(_entity); _renderUpdateQueued = false; }); } // // Internal methods // // Returns true if the item needs to have updateInscene called because of internal rendering // changes (animation, fading, etc) bool EntityRenderer::needsRenderUpdate() const { if (_needsRenderUpdate) { return true; } if (isFading()) { return true; } if (_prevIsTransparent != isTransparent()) { return true; } return needsRenderUpdateFromEntity(_entity); } // Returns true if the item in question needs to have updateInScene called because of changes in the entity bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity) const { bool success = false; auto bound = _entity->getAABox(success); if (success && _bound != bound) { return true; } auto newModelTransform = _entity->getTransformToCenter(success); // FIXME can we use a stale model transform here? if (success && newModelTransform != _modelTransform) { return true; } if (_moving != entity->isMovingRelativeToParent()) { return true; } return false; } void EntityRenderer::updateModelTransformAndBound() { bool success = false; auto newModelTransform = _entity->getTransformToCenter(success); if (success) { _modelTransform = newModelTransform; } success = false; auto bound = _entity->getAABox(success); if (success) { _bound = bound; } } void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) { DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__); withWriteLock([&] { auto transparent = isTransparent(); auto fading = isFading(); if (fading || _prevIsTransparent != transparent) { emit requestRenderUpdate(); } if (fading) { _isFading = Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f; } _prevIsTransparent = transparent; updateModelTransformAndBound(); _moving = entity->isMovingRelativeToParent(); _visible = entity->getVisible(); setIsVisibleInSecondaryCamera(entity->isVisibleInSecondaryCamera()); setRenderLayer(entity->getRenderLayer()); setPrimitiveMode(entity->getPrimitiveMode()); _canCastShadow = entity->getCanCastShadow(); _cauterized = entity->getCauterized(); _needsRenderUpdate = false; }); } void EntityRenderer::onAddToScene(const EntityItemPointer& entity) { QObject::connect(this, &EntityRenderer::requestRenderUpdate, this, [this] { auto renderer = DependencyManager::get(); if (renderer) { renderer->onEntityChanged(_entity->getID()); } }, Qt::QueuedConnection); _changeHandlerId = entity->registerChangeHandler([](const EntityItemID& changedEntity) { auto renderer = DependencyManager::get(); if (renderer) { renderer->onEntityChanged(changedEntity); } }); } void EntityRenderer::onRemoveFromScene(const EntityItemPointer& entity) { entity->deregisterChangeHandler(_changeHandlerId); QObject::disconnect(this, &EntityRenderer::requestRenderUpdate, this, nullptr); } void EntityRenderer::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].push(material); } void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].remove(material); } glm::vec4 EntityRenderer::calculatePulseColor(const glm::vec4& color, const PulsePropertyGroup& pulseProperties, quint64 start) { if (pulseProperties.getPeriod() == 0.0f || (pulseProperties.getColorMode() == PulseMode::NONE && pulseProperties.getAlphaMode() == PulseMode::NONE)) { return color; } float t = ((float)(usecTimestampNow() - start)) / ((float)USECS_PER_SECOND); float pulse = 0.5f * (cosf(t * (2.0f * (float)M_PI) / pulseProperties.getPeriod()) + 1.0f) * (pulseProperties.getMax() - pulseProperties.getMin()) + pulseProperties.getMin(); float outPulse = (1.0f - pulse); glm::vec4 result = color; if (pulseProperties.getColorMode() == PulseMode::IN_PHASE) { result.r *= pulse; result.g *= pulse; result.b *= pulse; } else if (pulseProperties.getColorMode() == PulseMode::OUT_PHASE) { result.r *= outPulse; result.g *= outPulse; result.b *= outPulse; } if (pulseProperties.getAlphaMode() == PulseMode::IN_PHASE) { result.a *= pulse; } else if (pulseProperties.getAlphaMode() == PulseMode::OUT_PHASE) { result.a *= outPulse; } return result; }