LCOV - code coverage report
Current view: top level - source/graphics - ObjectEntry.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 1 147 0.7 %
Date: 2023-01-19 00:18:29 Functions: 2 8 25.0 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "ObjectEntry.h"
      21             : 
      22             : #include "graphics/Decal.h"
      23             : #include "graphics/Material.h"
      24             : #include "graphics/MaterialManager.h"
      25             : #include "graphics/MeshManager.h"
      26             : #include "graphics/Model.h"
      27             : #include "graphics/ModelDef.h"
      28             : #include "graphics/ModelDummy.h"
      29             : #include "graphics/ObjectBase.h"
      30             : #include "graphics/ObjectManager.h"
      31             : #include "graphics/ParticleManager.h"
      32             : #include "graphics/SkeletonAnim.h"
      33             : #include "graphics/SkeletonAnimManager.h"
      34             : #include "graphics/TextureManager.h"
      35             : #include "lib/rand.h"
      36             : #include "ps/CLogger.h"
      37             : #include "ps/Game.h"
      38             : #include "ps/World.h"
      39             : #include "renderer/Renderer.h"
      40             : #include "renderer/SceneRenderer.h"
      41             : #include "simulation2/Simulation2.h"
      42             : 
      43             : #include <sstream>
      44             : 
      45           0 : CObjectEntry::CObjectEntry(const std::shared_ptr<CObjectBase>& base, CSimulation2& simulation) :
      46           0 :     m_Base(base), m_Color(1.0f, 1.0f, 1.0f, 1.0f), m_Model(NULL), m_Simulation(simulation)
      47             : {
      48           0 : }
      49             : 
      50           0 : CObjectEntry::~CObjectEntry()
      51             : {
      52           0 :     delete m_Model;
      53           0 : }
      54             : 
      55             : 
      56           0 : bool CObjectEntry::BuildVariation(const std::vector<const std::set<CStr>*>& completeSelections,
      57             :                                   const std::vector<u8>& variationKey,
      58             :                                   CObjectManager& objectManager)
      59             : {
      60           0 :     CObjectBase::Variation variation = m_Base->BuildVariation(variationKey);
      61             : 
      62             :     // Copy the chosen data onto this model:
      63             : 
      64           0 :     for (std::multimap<CStr, CObjectBase::Samp>::iterator it = variation.samplers.begin(); it != variation.samplers.end(); ++it)
      65           0 :         m_Samplers.push_back(it->second);
      66             : 
      67           0 :     m_ModelName = variation.model;
      68             : 
      69           0 :     if (! variation.color.empty())
      70             :     {
      71           0 :         std::stringstream str;
      72           0 :         str << variation.color;
      73             :         int r, g, b;
      74           0 :         if (! (str >> r >> g >> b)) // Any trailing data is ignored
      75           0 :             LOGERROR("Actor '%s' has invalid RGB color '%s'", m_Base->GetIdentifier(), variation.color);
      76             :         else
      77           0 :             m_Color = CColor(r/255.0f, g/255.0f, b/255.0f, 1.0f);
      78             :     }
      79             : 
      80           0 :     if (variation.decal.m_SizeX && variation.decal.m_SizeZ)
      81             :     {
      82           0 :         CMaterial material = g_Renderer.GetSceneRenderer().GetMaterialManager().LoadMaterial(m_Base->m_Material);
      83             : 
      84           0 :         for (const CObjectBase::Samp& samp : m_Samplers)
      85             :         {
      86           0 :             CTextureProperties textureProps(samp.m_SamplerFile);
      87           0 :             textureProps.SetAddressMode(Renderer::Backend::Sampler::AddressMode::CLAMP_TO_BORDER);
      88           0 :             CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
      89             :             // TODO: Should check which renderpath is selected and only preload the necessary textures.
      90           0 :             texture->Prefetch();
      91           0 :             material.AddSampler(CMaterial::TextureSampler(samp.m_SamplerName, texture));
      92             :         }
      93             : 
      94             :         SDecal decal(material,
      95             :             variation.decal.m_SizeX, variation.decal.m_SizeZ,
      96             :             variation.decal.m_Angle, variation.decal.m_OffsetX, variation.decal.m_OffsetZ,
      97           0 :             m_Base->m_Properties.m_FloatOnWater);
      98           0 :         m_Model = new CModelDecal(objectManager.GetTerrain(), decal);
      99             : 
     100           0 :         return true;
     101             :     }
     102             : 
     103           0 :     if (!variation.particles.empty())
     104             :     {
     105           0 :         m_Model = new CModelParticleEmitter(g_Renderer.GetSceneRenderer().GetParticleManager().LoadEmitterType(variation.particles));
     106           0 :         return true;
     107             :     }
     108             : 
     109           0 :     if (variation.model.empty())
     110             :     {
     111           0 :         m_Model = new CModelDummy();
     112           0 :         return true;
     113             :     }
     114             : 
     115           0 :     std::vector<CObjectBase::Prop> props;
     116             : 
     117           0 :     for (std::multimap<CStr, CObjectBase::Prop>::iterator it = variation.props.begin(); it != variation.props.end(); ++it)
     118           0 :         props.push_back(it->second);
     119             : 
     120             :     // Build the model:
     121             : 
     122             :     // try and create a model
     123           0 :     CModelDefPtr modeldef (objectManager.GetMeshManager().GetMesh(m_ModelName));
     124           0 :     if (!modeldef)
     125             :     {
     126           0 :         LOGERROR("CObjectEntry::BuildVariation(): Model %s failed to load", m_ModelName.string8());
     127           0 :         return false;
     128             :     }
     129             : 
     130             :     // delete old model, create new
     131           0 :     CModel* model = new CModel(m_Simulation);
     132           0 :     delete m_Model;
     133           0 :     m_Model = model;
     134           0 :     model->SetMaterial(g_Renderer.GetSceneRenderer().GetMaterialManager().LoadMaterial(m_Base->m_Material));
     135           0 :     model->GetMaterial().AddStaticUniform("objectColor", CVector4D(m_Color.r, m_Color.g, m_Color.b, m_Color.a));
     136           0 :     model->InitModel(modeldef);
     137             : 
     138           0 :     if (m_Samplers.empty())
     139           0 :         LOGERROR("Actor '%s' has no textures.", m_Base->GetIdentifier());
     140             : 
     141           0 :     for (const CObjectBase::Samp& samp : m_Samplers)
     142             :     {
     143           0 :         CTextureProperties textureProps(samp.m_SamplerFile);
     144           0 :         textureProps.SetAddressMode(Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
     145           0 :         CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
     146             :         // if we've loaded this model we're probably going to render it soon, so prefetch its texture.
     147             :         // All textures are prefetched even in the fixed pipeline, including the normal maps etc.
     148             :         // TODO: Should check which renderpath is selected and only preload the necessary textures.
     149           0 :         texture->Prefetch();
     150           0 :         model->GetMaterial().AddSampler(CMaterial::TextureSampler(samp.m_SamplerName, texture));
     151             :     }
     152             : 
     153           0 :     for (const CStrIntern& requSampName : model->GetMaterial().GetRequiredSampler())
     154             :     {
     155           0 :         if (std::find_if(m_Samplers.begin(), m_Samplers.end(),
     156           0 :                          [&](const CObjectBase::Samp& sampler) { return sampler.m_SamplerName == requSampName; }) == m_Samplers.end())
     157           0 :             LOGERROR("Actor %s: required texture sampler %s not found (material %s)", m_Base->GetIdentifier(), requSampName.string().c_str(), m_Base->m_Material.string8().c_str());
     158             :     }
     159             : 
     160             :     // calculate initial object space bounds, based on vertex positions
     161           0 :     model->CalcStaticObjectBounds();
     162             : 
     163             :     // load the animations
     164           0 :     for (std::multimap<CStr, CObjectBase::Anim>::iterator it = variation.anims.begin(); it != variation.anims.end(); ++it)
     165             :     {
     166           0 :         CStr name = it->first.LowerCase();
     167             : 
     168           0 :         if (it->second.m_FileName.empty())
     169           0 :             continue;
     170           0 :         std::unique_ptr<CSkeletonAnim> anim = objectManager.GetSkeletonAnimManager().BuildAnimation(
     171           0 :             it->second.m_FileName,
     172             :             name,
     173           0 :             it->second.m_ID,
     174           0 :             it->second.m_Frequency,
     175           0 :             it->second.m_Speed,
     176           0 :             it->second.m_ActionPos,
     177           0 :             it->second.m_ActionPos2,
     178           0 :             it->second.m_SoundPos);
     179           0 :         if (anim)
     180           0 :             m_Animations.emplace(name, std::move(anim));
     181             :     }
     182             : 
     183             :     // ensure there's always an idle animation
     184           0 :     if (m_Animations.find("idle") == m_Animations.end())
     185             :     {
     186           0 :         std::unique_ptr<CSkeletonAnim> anim = std::make_unique<CSkeletonAnim>();
     187           0 :         anim->m_Name = "idle";
     188           0 :         anim->m_ID = "";
     189           0 :         anim->m_AnimDef = NULL;
     190           0 :         anim->m_Frequency = 0;
     191           0 :         anim->m_Speed = 0.f;
     192           0 :         anim->m_ActionPos = 0.f;
     193           0 :         anim->m_ActionPos2 = 0.f;
     194           0 :         anim->m_SoundPos = 0.f;
     195           0 :         SkeletonAnimMap::const_iterator it = m_Animations.emplace("idle", std::move(anim));
     196             : 
     197             :         // Ignore errors, since they're probably saying this is a non-animated model
     198           0 :         model->SetAnimation(it->second.get());
     199             :     }
     200             :     else
     201             :     {
     202             :         // start up idling
     203           0 :         if (!model->SetAnimation(GetRandomAnimation("idle")))
     204           0 :             LOGERROR("Failed to set idle animation in model \"%s\"", m_ModelName.string8());
     205             :     }
     206             : 
     207             :     // build props - TODO, RC - need to fix up bounds here
     208             :     // TODO: Make sure random variations get handled correctly when a prop fails
     209           0 :     for (const CObjectBase::Prop& prop : props)
     210             :     {
     211             :         // Pluck out the special attachpoint 'projectile'
     212           0 :         if (prop.m_PropPointName == "projectile")
     213             :         {
     214           0 :             m_ProjectileModelName = prop.m_ModelName;
     215           0 :             continue;
     216             :         }
     217             : 
     218           0 :         CObjectEntry* oe = nullptr;
     219           0 :         if (auto [success, actorDef] = objectManager.FindActorDef(prop.m_ModelName.c_str()); success)
     220           0 :             oe = objectManager.FindObjectVariation(actorDef.GetBase(m_Base->m_QualityLevel), completeSelections);
     221             : 
     222           0 :         if (!oe)
     223             :         {
     224           0 :             LOGERROR("Failed to build prop model \"%s\" on actor \"%s\"", utf8_from_wstring(prop.m_ModelName), m_Base->GetIdentifier());
     225           0 :             continue;
     226             :         }
     227             : 
     228             :         // If we don't have a projectile but this prop does (e.g. it's our rider), then
     229             :         // use that as our projectile too
     230           0 :         if (m_ProjectileModelName.empty() && !oe->m_ProjectileModelName.empty())
     231           0 :             m_ProjectileModelName = oe->m_ProjectileModelName;
     232             : 
     233           0 :         CStr ppn = prop.m_PropPointName;
     234           0 :         bool isAmmo = false;
     235             : 
     236             :         // Handle the special attachpoint 'loaded-<proppoint>'
     237           0 :         if (ppn.Find("loaded-") == 0)
     238             :         {
     239           0 :             ppn = prop.m_PropPointName.substr(7);
     240           0 :             isAmmo = true;
     241             :         }
     242             : 
     243           0 :         const SPropPoint* proppoint = modeldef->FindPropPoint(ppn.c_str());
     244           0 :         if (proppoint)
     245             :         {
     246           0 :             CModelAbstract* propmodel = oe->m_Model->Clone();
     247           0 :             if (isAmmo)
     248           0 :                 model->AddAmmoProp(proppoint, propmodel, oe);
     249             :             else
     250           0 :                 model->AddProp(proppoint, propmodel, oe, prop.m_minHeight, prop.m_maxHeight, prop.m_selectable);
     251           0 :             if (propmodel->ToCModel())
     252           0 :                 propmodel->ToCModel()->SetAnimation(oe->GetRandomAnimation("idle"));
     253             :         }
     254             :         else
     255           0 :             LOGERROR("Failed to find matching prop point called \"%s\" in model \"%s\" for actor \"%s\"", ppn, m_ModelName.string8(), m_Base->GetIdentifier());
     256             :     }
     257             : 
     258             :     // Setup flags.
     259           0 :     if (m_Base->m_Properties.m_CastShadows)
     260             :     {
     261           0 :         model->SetFlags(model->GetFlags() | MODELFLAG_CASTSHADOWS);
     262             :     }
     263             : 
     264           0 :     if (m_Base->m_Properties.m_FloatOnWater)
     265             :     {
     266           0 :         model->SetFlags(model->GetFlags() | MODELFLAG_FLOATONWATER);
     267             :     }
     268             : 
     269           0 :     return true;
     270             : }
     271             : 
     272           0 : CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName, const CStr& ID) const
     273             : {
     274           0 :     std::vector<CSkeletonAnim*> anims = GetAnimations(animationName, ID);
     275             : 
     276           0 :     int totalFreq = 0;
     277           0 :     for (CSkeletonAnim* anim : anims)
     278           0 :         totalFreq += anim->m_Frequency;
     279             : 
     280           0 :     if (totalFreq == 0)
     281           0 :         return anims[rand(0, anims.size())];
     282             : 
     283           0 :     int r = rand(0, totalFreq);
     284           0 :     for (CSkeletonAnim* anim : anims)
     285             :     {
     286           0 :         r -= anim->m_Frequency;
     287           0 :         if (r < 0)
     288           0 :             return anim;
     289             :     }
     290           0 :     return NULL;
     291             : }
     292             : 
     293           0 : std::vector<CSkeletonAnim*> CObjectEntry::GetAnimations(const CStr& animationName, const CStr& ID) const
     294             : {
     295           0 :     std::vector<CSkeletonAnim*> anims;
     296             : 
     297           0 :     SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName);
     298           0 :     SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName);
     299             : 
     300           0 :     for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it)
     301             :     {
     302           0 :         if (ID.empty() || it->second->m_ID == ID)
     303           0 :             anims.push_back(it->second.get());
     304             :     }
     305             : 
     306           0 :     if (anims.empty())
     307             :     {
     308           0 :         lower = m_Animations.lower_bound("idle");
     309           0 :         upper = m_Animations.upper_bound("idle");
     310           0 :         for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it)
     311           0 :             anims.push_back(it->second.get());
     312             :     }
     313             : 
     314           0 :     ENSURE(!anims.empty());
     315           0 :     return anims;
     316           3 : }

Generated by: LCOV version 1.13