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 : }
|