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"
34 #include "ps/CLogger.h"
37 #include "renderer/Renderer.h"
38 #include "simulation2/Simulation2.h"
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
)
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
;
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
);
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.
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
);
103 if (!variation
.particles
.empty())
105 m_Model
= new CModelParticleEmitter(g_Renderer
.GetParticleManager().LoadEmitterType(variation
.particles
));
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
);
116 // try and create a model
117 CModelDefPtr
modeldef (objectManager
.GetMeshManager().GetMesh(m_ModelName
));
120 LOGERROR("CObjectEntry::BuildVariation(): Model %s failed to load", m_ModelName
.string8());
124 // delete old model, create new
125 CModel
* model
= new CModel(objectManager
.GetSkeletonAnimManager(), m_Simulation
);
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.
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())
164 CSkeletonAnim
* anim
= model
->BuildAnimation(
165 it
->second
.m_FileName
,
168 it
->second
.m_Frequency
,
170 it
->second
.m_ActionPos
,
171 it
->second
.m_ActionPos2
,
172 it
->second
.m_SoundPos
);
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";
183 anim
->m_AnimDef
= NULL
;
184 anim
->m_Frequency
= 0;
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
);
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
;
212 CObjectEntry
* oe
= objectManager
.FindObjectVariation(prop
.m_ModelName
.c_str(), selections
);
215 LOGERROR("Failed to build prop model \"%s\" on actor \"%s\"", utf8_from_wstring(prop
.m_ModelName
), utf8_from_wstring(m_Base
->m_ShortName
));
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
;
227 // Handle the special attachpoint 'loaded-<proppoint>'
228 if (ppn
.Find("loaded-") == 0)
230 ppn
= prop
.m_PropPointName
.substr(7);
234 const SPropPoint
* proppoint
= modeldef
->FindPropPoint(ppn
.c_str());
237 CModelAbstract
* propmodel
= oe
->m_Model
->Clone();
239 model
->AddAmmoProp(proppoint
, propmodel
, oe
);
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"));
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
));
250 if (m_Base
->m_Properties
.m_CastShadows
)
252 model
->SetFlags(model
->GetFlags()|MODELFLAG_CASTSHADOWS
);
258 CSkeletonAnim
* CObjectEntry::GetRandomAnimation(const CStr
& animationName
, const CStr
& ID
) const
260 std::vector
<CSkeletonAnim
*> anims
= GetAnimations(animationName
, ID
);
263 for (CSkeletonAnim
* anim
: anims
)
264 totalFreq
+= anim
->m_Frequency
;
267 return anims
[rand(0, anims
.size())];
269 int r
= rand(0, totalFreq
);
270 for (CSkeletonAnim
* anim
: anims
)
272 r
-= anim
->m_Frequency
;
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
);
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());