Fix infinite loop detection when placing players randomly on the newer random map...
[0ad.git] / source / graphics / ObjectEntry.cpp
blob73f39088dc159f1501b9183ff3f01632ddb21116
1 /* Copyright (C) 2016 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "ObjectEntry.h"
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/ObjectBase.h"
29 #include "graphics/ObjectManager.h"
30 #include "graphics/ParticleManager.h"
31 #include "graphics/SkeletonAnim.h"
32 #include "graphics/TextureManager.h"
33 #include "lib/rand.h"
34 #include "ps/CLogger.h"
35 #include "ps/Game.h"
36 #include "ps/World.h"
37 #include "renderer/Renderer.h"
38 #include "simulation2/Simulation2.h"
40 #include <sstream>
42 CObjectEntry::CObjectEntry(CObjectBase* base, CSimulation2& simulation) :
43 m_Base(base), m_Color(1.0f, 1.0f, 1.0f, 1.0f), m_Model(NULL), m_Outdated(false), m_Simulation(simulation)
47 CObjectEntry::~CObjectEntry()
49 for (const std::pair<CStr, CSkeletonAnim*>& anim : m_Animations)
50 delete anim.second;
52 delete m_Model;
56 bool CObjectEntry::BuildVariation(const std::vector<std::set<CStr> >& selections,
57 const std::vector<u8>& variationKey,
58 CObjectManager& objectManager)
60 CObjectBase::Variation variation = m_Base->BuildVariation(variationKey);
62 // Copy the chosen data onto this model:
64 for (std::multimap<CStr, CObjectBase::Samp>::iterator it = variation.samplers.begin(); it != variation.samplers.end(); ++it)
65 m_Samplers.push_back(it->second);
67 m_ModelName = variation.model;
69 if (! variation.color.empty())
71 std::stringstream str;
72 str << variation.color;
73 int r, g, b;
74 if (! (str >> r >> g >> b)) // Any trailing data is ignored
75 LOGERROR("Actor '%s' has invalid RGB color '%s'", utf8_from_wstring(m_Base->m_ShortName), variation.color);
76 else
77 m_Color = CColor(r/255.0f, g/255.0f, b/255.0f, 1.0f);
80 if (variation.decal.m_SizeX && variation.decal.m_SizeZ)
82 CMaterial material = g_Renderer.GetMaterialManager().LoadMaterial(m_Base->m_Material);
84 for (const CObjectBase::Samp& samp : m_Samplers)
86 CTextureProperties textureProps(samp.m_SamplerFile);
87 textureProps.SetWrap(GL_CLAMP_TO_BORDER);
88 CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
89 // TODO: Should check which renderpath is selected and only preload the necessary textures.
90 texture->Prefetch();
91 material.AddSampler(CMaterial::TextureSampler(samp.m_SamplerName, texture));
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 m_Base->m_Properties.m_FloatOnWater);
98 m_Model = new CModelDecal(objectManager.GetTerrain(), decal);
100 return true;
103 if (!variation.particles.empty())
105 m_Model = new CModelParticleEmitter(g_Renderer.GetParticleManager().LoadEmitterType(variation.particles));
106 return true;
109 std::vector<CObjectBase::Prop> props;
111 for (std::multimap<CStr, CObjectBase::Prop>::iterator it = variation.props.begin(); it != variation.props.end(); ++it)
112 props.push_back(it->second);
114 // Build the model:
116 // try and create a model
117 CModelDefPtr modeldef (objectManager.GetMeshManager().GetMesh(m_ModelName));
118 if (!modeldef)
120 LOGERROR("CObjectEntry::BuildVariation(): Model %s failed to load", m_ModelName.string8());
121 return false;
124 // delete old model, create new
125 CModel* model = new CModel(objectManager.GetSkeletonAnimManager(), m_Simulation);
126 delete m_Model;
127 m_Model = model;
128 model->SetMaterial(g_Renderer.GetMaterialManager().LoadMaterial(m_Base->m_Material));
129 model->GetMaterial().AddStaticUniform("objectColor", CVector4D(m_Color.r, m_Color.g, m_Color.b, m_Color.a));
130 model->InitModel(modeldef);
132 if (m_Samplers.empty())
133 LOGERROR("Actor '%s' has no textures.", utf8_from_wstring(m_Base->m_ShortName));
135 for (const CObjectBase::Samp& samp : m_Samplers)
137 CTextureProperties textureProps(samp.m_SamplerFile);
138 textureProps.SetWrap(GL_CLAMP_TO_EDGE);
139 CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
140 // if we've loaded this model we're probably going to render it soon, so prefetch its texture.
141 // All textures are prefetched even in the fixed pipeline, including the normal maps etc.
142 // TODO: Should check which renderpath is selected and only preload the necessary textures.
143 texture->Prefetch();
144 model->GetMaterial().AddSampler(CMaterial::TextureSampler(samp.m_SamplerName, texture));
147 for (const CStrIntern& requSampName : model->GetMaterial().GetRequiredSampler())
149 if (std::find_if(m_Samplers.begin(), m_Samplers.end(),
150 [&](const CObjectBase::Samp& sampler) { return sampler.m_SamplerName == requSampName; }) == m_Samplers.end())
151 LOGERROR("Actor %s: required texture sampler %s not found (material %s)", utf8_from_wstring(m_Base->m_ShortName), requSampName.string().c_str(), m_Base->m_Material.string8().c_str());
154 // calculate initial object space bounds, based on vertex positions
155 model->CalcStaticObjectBounds();
157 // load the animations
158 for (std::multimap<CStr, CObjectBase::Anim>::iterator it = variation.anims.begin(); it != variation.anims.end(); ++it)
160 CStr name = it->first.LowerCase();
162 if (it->second.m_FileName.empty())
163 continue;
164 CSkeletonAnim* anim = model->BuildAnimation(
165 it->second.m_FileName,
166 name,
167 it->second.m_ID,
168 it->second.m_Frequency,
169 it->second.m_Speed,
170 it->second.m_ActionPos,
171 it->second.m_ActionPos2,
172 it->second.m_SoundPos);
173 if (anim)
174 m_Animations.insert(std::make_pair(name, anim));
177 // ensure there's always an idle animation
178 if (m_Animations.find("idle") == m_Animations.end())
180 CSkeletonAnim* anim = new CSkeletonAnim();
181 anim->m_Name = "idle";
182 anim->m_ID = "";
183 anim->m_AnimDef = NULL;
184 anim->m_Frequency = 0;
185 anim->m_Speed = 0.f;
186 anim->m_ActionPos = 0.f;
187 anim->m_ActionPos2 = 0.f;
188 anim->m_SoundPos = 0.f;
189 m_Animations.insert(std::make_pair("idle", anim));
191 // Ignore errors, since they're probably saying this is a non-animated model
192 model->SetAnimation(anim);
194 else
196 // start up idling
197 if (!model->SetAnimation(GetRandomAnimation("idle")))
198 LOGERROR("Failed to set idle animation in model \"%s\"", m_ModelName.string8());
201 // build props - TODO, RC - need to fix up bounds here
202 // TODO: Make sure random variations get handled correctly when a prop fails
203 for (const CObjectBase::Prop& prop : props)
205 // Pluck out the special attachpoint 'projectile'
206 if (prop.m_PropPointName == "projectile")
208 m_ProjectileModelName = prop.m_ModelName;
209 continue;
212 CObjectEntry* oe = objectManager.FindObjectVariation(prop.m_ModelName.c_str(), selections);
213 if (!oe)
215 LOGERROR("Failed to build prop model \"%s\" on actor \"%s\"", utf8_from_wstring(prop.m_ModelName), utf8_from_wstring(m_Base->m_ShortName));
216 continue;
219 // If we don't have a projectile but this prop does (e.g. it's our rider), then
220 // use that as our projectile too
221 if (m_ProjectileModelName.empty() && !oe->m_ProjectileModelName.empty())
222 m_ProjectileModelName = oe->m_ProjectileModelName;
224 CStr ppn = prop.m_PropPointName;
225 bool isAmmo = false;
227 // Handle the special attachpoint 'loaded-<proppoint>'
228 if (ppn.Find("loaded-") == 0)
230 ppn = prop.m_PropPointName.substr(7);
231 isAmmo = true;
234 const SPropPoint* proppoint = modeldef->FindPropPoint(ppn.c_str());
235 if (proppoint)
237 CModelAbstract* propmodel = oe->m_Model->Clone();
238 if (isAmmo)
239 model->AddAmmoProp(proppoint, propmodel, oe);
240 else
241 model->AddProp(proppoint, propmodel, oe, prop.m_minHeight, prop.m_maxHeight, prop.m_selectable);
242 if (propmodel->ToCModel())
243 propmodel->ToCModel()->SetAnimation(oe->GetRandomAnimation("idle"));
245 else
246 LOGERROR("Failed to find matching prop point called \"%s\" in model \"%s\" for actor \"%s\"", ppn, m_ModelName.string8(), utf8_from_wstring(m_Base->m_ShortName));
249 // setup flags
250 if (m_Base->m_Properties.m_CastShadows)
252 model->SetFlags(model->GetFlags()|MODELFLAG_CASTSHADOWS);
255 return true;
258 CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName, const CStr& ID) const
260 std::vector<CSkeletonAnim*> anims = GetAnimations(animationName, ID);
262 int totalFreq = 0;
263 for (CSkeletonAnim* anim : anims)
264 totalFreq += anim->m_Frequency;
266 if (totalFreq == 0)
267 return anims[rand(0, anims.size())];
269 int r = rand(0, totalFreq);
270 for (CSkeletonAnim* anim : anims)
272 r -= anim->m_Frequency;
273 if (r < 0)
274 return anim;
276 return NULL;
279 std::vector<CSkeletonAnim*> CObjectEntry::GetAnimations(const CStr& animationName, const CStr& ID) const
281 std::vector<CSkeletonAnim*> anims;
283 SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName);
284 SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName);
286 for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it)
288 if (ID.empty() || it->second->m_ID == ID)
289 anims.push_back(it->second);
292 if (anims.empty())
294 lower = m_Animations.lower_bound("idle");
295 upper = m_Animations.upper_bound("idle");
296 for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it)
297 anims.push_back(it->second);
300 ENSURE(!anims.empty());
301 return anims;