From 3bd99f9f1689f9bd18fc614ab184b48d37ca6592 Mon Sep 17 00:00:00 2001 From: Matthew Mott Date: Thu, 4 Feb 2021 20:08:36 +0000 Subject: [PATCH] Initial list of attached entities in EntityNode EntityNode maintains a list of entity node pointers representing the attached entities, which are submitted at render time. The basic rendering is working and covered by a unit test, but the attached entity appears at the origin since there is not yet any handling of the localToWorld matrix. --- radiantcore/entity/EntityNode.cpp | 49 ++++++++++++++++--- radiantcore/entity/EntityNode.h | 47 +++++++++++++++++- .../rendersystem/backend/OpenGLShaderPass.cpp | 14 +++--- test/Entity.cpp | 49 ++++++++++++++----- test/resources/tdm/def/lights.def | 22 +++++++++ 5 files changed, 156 insertions(+), 25 deletions(-) diff --git a/radiantcore/entity/EntityNode.cpp b/radiantcore/entity/EntityNode.cpp index ee3a871cd6..e17155fc4d 100644 --- a/radiantcore/entity/EntityNode.cpp +++ b/radiantcore/entity/EntityNode.cpp @@ -22,7 +22,9 @@ EntityNode::EntityNode(const IEntityClassPtr& eclass) : _keyObservers(_spawnArgs), _shaderParms(_keyObservers, _colourKey), _direction(1,0,0) -{} +{ + createAttachedEntities(); +} EntityNode::EntityNode(const EntityNode& other) : IEntityNode(other), @@ -41,7 +43,9 @@ EntityNode::EntityNode(const EntityNode& other) : _keyObservers(_spawnArgs), _shaderParms(_keyObservers, _colourKey), _direction(1,0,0) -{} +{ + createAttachedEntities(); +} EntityNode::~EntityNode() { @@ -112,6 +116,25 @@ void EntityNode::destruct() TargetableNode::destruct(); } +void EntityNode::createAttachedEntities() +{ + _spawnArgs.forEachAttachment( + [this](const Entity::Attachment& a) + { + auto cls = GlobalEntityClassManager().findClass(a.eclass); + if (!cls) + { + rWarning() << "EntityNode [" << _eclass->getName() + << "]: cannot attach non-existent entity class '" + << a.eclass << "'\n"; + return; + } + + _attachedEnts.push_back(GlobalEntityModule().createEntity(cls)); + } + ); +} + void EntityNode::onEntityClassChanged() { // By default, we notify the KeyObservers attached to this entity @@ -256,9 +279,13 @@ scene::INode::Type EntityNode::getNodeType() const return Type::Entity; } -void EntityNode::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void EntityNode::renderSolid(RenderableCollector& collector, + const VolumeTest& volume) const { - // Nothing here + // Render any attached entities + renderAttachments( + [&](const scene::INodePtr& n) { n->renderSolid(collector, volume); } + ); } void EntityNode::renderWireframe(RenderableCollector& collector, @@ -267,8 +294,14 @@ void EntityNode::renderWireframe(RenderableCollector& collector, // Submit renderable text name if required if (EntitySettings::InstancePtr()->getRenderEntityNames()) { - collector.addRenderable(*getWireShader(), _renderableName, localToWorld()); - } + collector.addRenderable(*getWireShader(), _renderableName, + localToWorld()); + } + + // Render any attached entities + renderAttachments( + [&](const scene::INodePtr& n) { n->renderWireframe(collector, volume); } + ); } void EntityNode::acquireShaders() @@ -298,6 +331,10 @@ void EntityNode::setRenderSystem(const RenderSystemPtr& renderSystem) // The colour key is maintaining a shader object as well _colourKey.setRenderSystem(renderSystem); + + // Make sure any attached entities have a render system too + for (IEntityNodePtr node: _attachedEnts) + node->setRenderSystem(renderSystem); } std::size_t EntityNode::getHighlightFlags() diff --git a/radiantcore/entity/EntityNode.h b/radiantcore/entity/EntityNode.h index 3701ec9665..eda54d010b 100644 --- a/radiantcore/entity/EntityNode.h +++ b/radiantcore/entity/EntityNode.h @@ -75,7 +75,20 @@ class EntityNode : sigc::connection _eclassChangedConn; -protected: + // List of attached sub-entities that we will submit for rendering (but are + // otherwise non-interactable). + // + // Although scene::Node already has the ability to store children, this is a + // separate list of entity nodes for two reasons: (1) there is a lot of + // other code which walks the scene graph for various reasons (e.g. map + // saving), and I don't want to have to audit the entire codebase to make + // sure that everything will play nicely with entities as children of other + // entities, and (2) storing entity node pointers instead of generic node + // pointers avoids some extra dynamic_casting. + using AttachedEntities = std::list; + AttachedEntities _attachedEnts; + + protected: // The Constructor needs the eclass EntityNode(const IEntityClassPtr& eclass); @@ -166,6 +179,36 @@ class EntityNode : void acquireShaders(); void acquireShaders(const RenderSystemPtr& renderSystem); -}; + + // Create entity nodes for all attached entities + void createAttachedEntities(); + + // Render all attached entities + template void renderAttachments(RenderFunc func) const + { + for (const IEntityNodePtr& ent: _attachedEnts) + { + // Attached entities might themselves have child nodes (e.g. func_static + // which has its model as a child node), so we must traverse() the + // attached entities, not just render them alone + struct ChildRenderer : public scene::NodeVisitor + { + RenderFunc _func; + ChildRenderer(RenderFunc f): _func(f) + {} + + bool pre(const scene::INodePtr& node) + { + _func(node); + return true; + } + }; + + ChildRenderer cr(func); + ent->traverse(cr); + } + } + + }; } // namespace entity diff --git a/radiantcore/rendersystem/backend/OpenGLShaderPass.cpp b/radiantcore/rendersystem/backend/OpenGLShaderPass.cpp index ce85733bd4..f7d5d92632 100644 --- a/radiantcore/rendersystem/backend/OpenGLShaderPass.cpp +++ b/radiantcore/rendersystem/backend/OpenGLShaderPass.cpp @@ -472,7 +472,7 @@ void OpenGLShaderPass::applyState(OpenGLState& current, } // end of changingBitsMask-dependent changes // Set depth function - if (requiredState & RENDER_DEPTHTEST + if (requiredState & RENDER_DEPTHTEST && _glState.getDepthFunc() != current.getDepthFunc()) { glDepthFunc(_glState.getDepthFunc()); @@ -649,11 +649,13 @@ void OpenGLShaderPass::setUpLightingCalculation(OpenGLState& current, const Matrix4& objTransform, std::size_t time) { + // Get the light shader and examine its first (and only valid) layer assert(light); + ShaderPtr shader = light->getShader(); + assert(shader); - // Get the light shader and examine its first (and only valid) layer - const MaterialPtr& lightShader = light->getShader()->getMaterial(); - ShaderLayer* layer = lightShader ? lightShader->firstLayer() : nullptr; + const MaterialPtr& lightMat = shader->getMaterial(); + ShaderLayer* layer = lightMat ? lightMat->firstLayer() : nullptr; if (!layer) return; // Calculate viewer location in object space @@ -665,7 +667,7 @@ void OpenGLShaderPass::setUpLightingCalculation(OpenGLState& current, // Get the XY and Z falloff texture numbers. GLuint attenuation_xy = layer->getTexture()->getGLTexNum(); - GLuint attenuation_z = lightShader->lightFalloffImage()->getGLTexNum(); + GLuint attenuation_z = lightMat->lightFalloffImage()->getGLTexNum(); // Bind the falloff textures assert(current.testRenderFlag(RENDER_TEXTURE_2D)); @@ -689,7 +691,7 @@ void OpenGLShaderPass::setUpLightingCalculation(OpenGLState& current, GLProgram::Params parms( light->getLightOrigin(), layer->getColour(), world2light ); - parms.ambientFactor = lightShader->isAmbientLight() ? 1.0f : 0.0f; + parms.ambientFactor = lightMat->isAmbientLight() ? 1.0f : 0.0f; parms.invertVertexColour = _glState.isColourInverted(); assert(current.glProgram); diff --git a/test/Entity.cpp b/test/Entity.cpp index 988691899c..b83f3c1fe5 100644 --- a/test/Entity.cpp +++ b/test/Entity.cpp @@ -319,14 +319,29 @@ namespace render::NopVolumeTest volumeTest; TestRenderableCollector collector; + // Whether to render solid or wireframe + const bool renderSolid; + // Keep track of nodes visited int nodesVisited = 0; + // Construct + RenderFixture(bool solid = false): renderSolid(solid) + {} + // NodeVisitor implementation bool pre(const scene::INodePtr& node) override { + // Count the node itself ++nodesVisited; - node->renderWireframe(collector, volumeTest); + + // Render the node in appropriate mode + if (renderSolid) + node->renderSolid(collector, volumeTest); + else + node->renderWireframe(collector, volumeTest); + + // Continue traversing return true; } }; @@ -450,20 +465,32 @@ TEST_F(EntityTest, RenderAttachedLightEntity) auto& spawnArgs = torch->getEntity(); EXPECT_EQ(spawnArgs.getKeyValue("model"), "models/torch.lwo"); - RenderFixture rf; + // We must render in solid mode to get the light source + RenderFixture rf(true); torch->setRenderSystem(rf.backend); // The entity node itself does not render the model; it is a parent node // with the model as a child (e.g. as a StaticModelNode). Therefore we must - // traverse our "mini-scenegraph" to render the model. - torch->traverseChildren(rf); - - // The node visitor should have visited one child node (a static model) and - // collected 3 renderables in total (because the torch model has several - // surfaces). - EXPECT_EQ(rf.nodesVisited, 1); - EXPECT_EQ(rf.collector.renderables, 3); - EXPECT_EQ(rf.collector.lights, 0); + // traverse our "mini-scenegraph" to render the model as well as the + // attached entities. + torch->traverse(rf); + + // The node visitor should have visited the entity itself and one child node (a + // static model) + EXPECT_EQ(rf.nodesVisited, 2); + + // There should be 3 renderables from the torch (because the entity has a + // shadowmesh and a collision mesh as well as the main model) and one from + // the light (the origin diamond). + EXPECT_EQ(rf.collector.renderables, 4); + + // The attached light should have been submitted as a light source + EXPECT_EQ(rf.collector.lights, 1); + + // The submitted light should be fully realised with a light shader + const RendererLight* rLight = rf.collector.lightPtrs.front(); + ASSERT_TRUE(rLight); + EXPECT_TRUE(rLight->getShader()); } } \ No newline at end of file diff --git a/test/resources/tdm/def/lights.def b/test/resources/tdm/def/lights.def index 6728d409f4..48cf88dfea 100644 --- a/test/resources/tdm/def/lights.def +++ b/test/resources/tdm/def/lights.def @@ -111,3 +111,25 @@ entitydef light_torchflame_small "light_radius" "240 240 240" //larger than other similar flames to be consistent with older maps } +entitydef light_cageflame_small // casts shadows with bars +{ + "inherit" "light_extinguishable" + + "mins" "-6 -6 -6" + "maxs" "6 6 24" + + "editor_displayFolder" "Lights/Light Sources/Torch Flames" + "editor_usage" "Torch-sized flame for cage-lights, casts faint bar shadows. Light pulses but is static. For moving light, add 'inherit' 'light_extinguishable_moving' to entity. " + + "model_lit" "torchflame_new01_small.prt" + "model_extinguished" "tdm_smoke_torchout.prt" + + "snd_lit" "fire_torch_small" + "snd_extinguished" "machine_steam01" + + "falloff" "0" + "texture" "lights/8pt_cageflicker" + + "_color" "0.9 0.6 0.40" + "light_radius" "230 230 250" +} \ No newline at end of file