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 "Model.h"
21 :
22 : #include "graphics/Decal.h"
23 : #include "graphics/MeshManager.h"
24 : #include "graphics/ModelDef.h"
25 : #include "graphics/ObjectEntry.h"
26 : #include "graphics/SkeletonAnim.h"
27 : #include "graphics/SkeletonAnimDef.h"
28 : #include "maths/BoundingBoxAligned.h"
29 : #include "maths/Quaternion.h"
30 : #include "lib/sysdep/rtl.h"
31 : #include "ps/CLogger.h"
32 : #include "ps/CStrInternStatic.h"
33 : #include "ps/Profile.h"
34 : #include "renderer/RenderingOptions.h"
35 : #include "simulation2/components/ICmpTerrain.h"
36 : #include "simulation2/components/ICmpWaterManager.h"
37 : #include "simulation2/Simulation2.h"
38 :
39 :
40 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
41 : // Constructor
42 0 : CModel::CModel(CSimulation2& simulation)
43 : : m_Flags(0), m_Anim(NULL), m_AnimTime(0), m_Simulation(simulation),
44 0 : m_BoneMatrices(NULL), m_AmmoPropPoint(NULL), m_AmmoLoadedProp(0)
45 : {
46 0 : }
47 :
48 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
49 : // Destructor
50 0 : CModel::~CModel()
51 : {
52 0 : ReleaseData();
53 0 : }
54 :
55 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
56 : // ReleaseData: delete anything allocated by the model
57 0 : void CModel::ReleaseData()
58 : {
59 0 : rtl_FreeAligned(m_BoneMatrices);
60 :
61 0 : for (size_t i = 0; i < m_Props.size(); ++i)
62 0 : delete m_Props[i].m_Model;
63 0 : m_Props.clear();
64 :
65 0 : m_pModelDef = CModelDefPtr();
66 0 : }
67 :
68 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
69 : // InitModel: setup model from given geometry
70 0 : bool CModel::InitModel(const CModelDefPtr& modeldef)
71 : {
72 : // clean up any existing data first
73 0 : ReleaseData();
74 :
75 0 : m_pModelDef = modeldef;
76 :
77 0 : size_t numBones = modeldef->GetNumBones();
78 0 : if (numBones != 0)
79 : {
80 0 : size_t numBlends = modeldef->GetNumBlends();
81 :
82 : // allocate matrices for bone transformations
83 : // (one extra matrix is used for the special case of bind-shape relative weighting)
84 0 : m_BoneMatrices = (CMatrix3D*)rtl_AllocateAligned(sizeof(CMatrix3D) * (numBones + 1 + numBlends), 16);
85 0 : for (size_t i = 0; i < numBones + 1 + numBlends; ++i)
86 : {
87 0 : m_BoneMatrices[i].SetIdentity();
88 : }
89 : }
90 :
91 0 : m_PositionValid = true;
92 :
93 0 : return true;
94 : }
95 :
96 :
97 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
98 : // CalcBound: calculate the world space bounds of this model
99 0 : void CModel::CalcBounds()
100 : {
101 : // Need to calculate the object bounds first, if that hasn't already been done
102 0 : if (! (m_Anim && m_Anim->m_AnimDef))
103 : {
104 0 : if (m_ObjectBounds.IsEmpty())
105 0 : CalcStaticObjectBounds();
106 : }
107 : else
108 : {
109 0 : if (m_Anim->m_ObjectBounds.IsEmpty())
110 0 : CalcAnimatedObjectBounds(m_Anim->m_AnimDef, m_Anim->m_ObjectBounds);
111 0 : ENSURE(! m_Anim->m_ObjectBounds.IsEmpty()); // (if this happens, it'll be recalculating the bounds every time)
112 0 : m_ObjectBounds = m_Anim->m_ObjectBounds;
113 : }
114 :
115 : // Ensure the transform is set correctly before we use it
116 0 : ValidatePosition();
117 :
118 : // Now transform the object-space bounds to world-space bounds
119 0 : m_ObjectBounds.Transform(GetTransform(), m_WorldBounds);
120 0 : }
121 :
122 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
123 : // CalcObjectBounds: calculate object space bounds of this model, based solely on vertex positions
124 0 : void CModel::CalcStaticObjectBounds()
125 : {
126 0 : PROFILE2("CalcStaticObjectBounds");
127 0 : m_pModelDef->GetMaxBounds(nullptr, !(m_Flags & MODELFLAG_NOLOOPANIMATION), m_ObjectBounds);
128 0 : }
129 :
130 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
131 : // CalcAnimatedObjectBound: calculate bounds encompassing all vertex positions for given animation
132 0 : void CModel::CalcAnimatedObjectBounds(CSkeletonAnimDef* anim, CBoundingBoxAligned& result)
133 : {
134 0 : PROFILE2("CalcAnimatedObjectBounds");
135 0 : m_pModelDef->GetMaxBounds(anim, !(m_Flags & MODELFLAG_NOLOOPANIMATION), result);
136 0 : }
137 :
138 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
139 0 : const CBoundingBoxAligned CModel::GetWorldBoundsRec()
140 : {
141 0 : CBoundingBoxAligned bounds = GetWorldBounds();
142 0 : for (size_t i = 0; i < m_Props.size(); ++i)
143 0 : bounds += m_Props[i].m_Model->GetWorldBoundsRec();
144 0 : return bounds;
145 : }
146 :
147 0 : const CBoundingBoxAligned CModel::GetObjectSelectionBoundsRec()
148 : {
149 0 : CBoundingBoxAligned objBounds = GetObjectBounds(); // updates the (children-not-included) object-space bounds if necessary
150 :
151 : // now extend these bounds to include the props' selection bounds (if any)
152 0 : for (size_t i = 0; i < m_Props.size(); ++i)
153 : {
154 0 : const Prop& prop = m_Props[i];
155 0 : if (prop.m_Hidden || !prop.m_Selectable)
156 0 : continue; // prop is hidden from rendering, so it also shouldn't be used for selection
157 :
158 0 : CBoundingBoxAligned propSelectionBounds = prop.m_Model->GetObjectSelectionBoundsRec();
159 0 : if (propSelectionBounds.IsEmpty())
160 0 : continue; // submodel does not wish to participate in selection box, exclude it
161 :
162 : // We have the prop's bounds in its own object-space; now we need to transform them so they can be properly added
163 : // to the bounds in our object-space. For that, we need the transform of the prop attachment point.
164 : //
165 : // We have the prop point information; however, it's not trivial to compute its exact location in our object-space
166 : // since it may or may not be attached to a bone (see SPropPoint), which in turn may or may not be in the middle of
167 : // an animation. The bone matrices might be of interest, but they're really only meant to be used for the animation
168 : // system and are quite opaque to use from the outside (see @ref ValidatePosition).
169 : //
170 : // However, a nice side effect of ValidatePosition is that it also computes the absolute world-space transform of
171 : // our props and sets it on their respective models. In particular, @ref ValidatePosition will compute the prop's
172 : // world-space transform as either
173 : //
174 : // T' = T x B x O
175 : // or
176 : // T' = T x O
177 : //
178 : // where T' is the prop's world-space transform, T is our world-space transform, O is the prop's local
179 : // offset/rotation matrix, and B is an optional transformation matrix of the bone the prop is attached to
180 : // (taking into account animation and everything).
181 : //
182 : // From this, it is clear that either O or B x O is the object-space transformation matrix of the prop. So,
183 : // all we need to do is apply our own inverse world-transform T^(-1) to T' to get our desired result. Luckily,
184 : // this is precomputed upon setting the transform matrix (see @ref SetTransform), so it is free to fetch.
185 :
186 0 : CMatrix3D propObjectTransform = prop.m_Model->GetTransform(); // T'
187 0 : propObjectTransform.Concatenate(GetInvTransform()); // T^(-1) x T'
188 :
189 : // Transform the prop's bounds into our object coordinate space
190 0 : CBoundingBoxAligned transformedPropSelectionBounds;
191 0 : propSelectionBounds.Transform(propObjectTransform, transformedPropSelectionBounds);
192 :
193 0 : objBounds += transformedPropSelectionBounds;
194 : }
195 :
196 0 : return objBounds;
197 : }
198 :
199 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
200 : // Update: update this model to the given time, in msec
201 0 : void CModel::UpdateTo(float time)
202 : {
203 : // update animation time, but don't calculate bone matrices - do that (lazily) when
204 : // something requests them; that saves some calculation work for offscreen models,
205 : // and also assures the world space, inverted bone matrices (required for normal
206 : // skinning) are up to date with respect to m_Transform
207 0 : m_AnimTime = time;
208 :
209 : // mark vertices as dirty
210 0 : SetDirty(RENDERDATA_UPDATE_VERTICES);
211 :
212 : // mark matrices as dirty
213 0 : InvalidatePosition();
214 0 : }
215 :
216 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
217 : // InvalidatePosition
218 0 : void CModel::InvalidatePosition()
219 : {
220 0 : m_PositionValid = false;
221 :
222 0 : for (size_t i = 0; i < m_Props.size(); ++i)
223 0 : m_Props[i].m_Model->InvalidatePosition();
224 0 : }
225 :
226 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
227 : // ValidatePosition: ensure that current transform and bone matrices are both uptodate
228 0 : void CModel::ValidatePosition()
229 : {
230 0 : if (m_PositionValid)
231 : {
232 0 : ENSURE(!m_Parent || m_Parent->m_PositionValid);
233 0 : return;
234 : }
235 :
236 0 : if (m_Parent && !m_Parent->m_PositionValid)
237 : {
238 : // Make sure we don't base our calculations on
239 : // a parent animation state that is out of date.
240 0 : m_Parent->ValidatePosition();
241 :
242 : // Parent will recursively call our validation.
243 0 : ENSURE(m_PositionValid);
244 0 : return;
245 : }
246 :
247 0 : if (m_Anim && m_BoneMatrices)
248 : {
249 : // PROFILE( "generating bone matrices" );
250 :
251 0 : ENSURE(m_pModelDef->GetNumBones() == m_Anim->m_AnimDef->GetNumKeys());
252 :
253 0 : m_Anim->m_AnimDef->BuildBoneMatrices(m_AnimTime, m_BoneMatrices, !(m_Flags & MODELFLAG_NOLOOPANIMATION));
254 : }
255 0 : else if (m_BoneMatrices)
256 : {
257 : // Bones but no animation - probably a buggy actor forgot to set up the animation,
258 : // so just render it in its bind pose
259 :
260 0 : for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
261 : {
262 0 : m_BoneMatrices[i].SetIdentity();
263 0 : m_BoneMatrices[i].Rotate(m_pModelDef->GetBones()[i].m_Rotation);
264 0 : m_BoneMatrices[i].Translate(m_pModelDef->GetBones()[i].m_Translation);
265 : }
266 : }
267 :
268 : // For CPU skinning, we precompute as much as possible so that the only
269 : // per-vertex work is a single matrix*vec multiplication.
270 : // For GPU skinning, we try to minimise CPU work by doing most computation
271 : // in the vertex shader instead.
272 : // Using g_RenderingOptions to detect CPU vs GPU is a bit hacky,
273 : // and this doesn't allow the setting to change at runtime, but there isn't
274 : // an obvious cleaner way to determine what data needs to be computed,
275 : // and GPU skinning is a rarely-used experimental feature anyway.
276 0 : bool worldSpaceBoneMatrices = !g_RenderingOptions.GetGPUSkinning();
277 0 : bool computeBlendMatrices = !g_RenderingOptions.GetGPUSkinning();
278 :
279 0 : if (m_BoneMatrices && worldSpaceBoneMatrices)
280 : {
281 : // add world-space transformation to m_BoneMatrices
282 0 : const CMatrix3D transform = GetTransform();
283 0 : for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
284 0 : m_BoneMatrices[i].Concatenate(transform);
285 : }
286 :
287 : // our own position is now valid; now we can safely update our props' positions without fearing
288 : // that doing so will cause a revalidation of this model (see recursion above).
289 0 : m_PositionValid = true;
290 :
291 0 : CMatrix3D translate;
292 0 : CVector3D objTranslation = m_Transform.GetTranslation();
293 0 : float objectHeight = 0.0f;
294 :
295 0 : CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY);
296 0 : if (cmpTerrain)
297 0 : objectHeight = cmpTerrain->GetExactGroundLevel(objTranslation.X, objTranslation.Z);
298 :
299 : // Object height is incorrect for floating objects. We use water height instead.
300 0 : CmpPtr<ICmpWaterManager> cmpWaterManager(m_Simulation, SYSTEM_ENTITY);
301 0 : if (cmpWaterManager)
302 : {
303 0 : float waterHeight = cmpWaterManager->GetExactWaterLevel(objTranslation.X, objTranslation.Z);
304 0 : if (waterHeight >= objectHeight && m_Flags & MODELFLAG_FLOATONWATER)
305 0 : objectHeight = waterHeight;
306 : }
307 :
308 : // re-position and validate all props
309 0 : for (const Prop& prop : m_Props)
310 : {
311 0 : CMatrix3D proptransform = prop.m_Point->m_Transform;
312 0 : if (prop.m_Point->m_BoneIndex != 0xff)
313 : {
314 0 : CMatrix3D boneMatrix = m_BoneMatrices[prop.m_Point->m_BoneIndex];
315 0 : if (!worldSpaceBoneMatrices)
316 0 : boneMatrix.Concatenate(GetTransform());
317 0 : proptransform.Concatenate(boneMatrix);
318 : }
319 : else
320 : {
321 : // not relative to any bone; just apply world-space transformation (i.e. relative to object-space origin)
322 0 : proptransform.Concatenate(m_Transform);
323 : }
324 :
325 : // Adjust prop height to terrain level when needed
326 0 : if (cmpTerrain && (prop.m_MaxHeight != 0.f || prop.m_MinHeight != 0.f))
327 : {
328 0 : const CVector3D& propTranslation = proptransform.GetTranslation();
329 0 : const float propTerrain = cmpTerrain->GetExactGroundLevel(propTranslation.X, propTranslation.Z);
330 0 : const float translateHeight = std::min(prop.m_MaxHeight, std::max(prop.m_MinHeight, propTerrain - objectHeight));
331 0 : translate.SetTranslation(0.f, translateHeight, 0.f);
332 0 : proptransform.Concatenate(translate);
333 : }
334 :
335 0 : prop.m_Model->SetTransform(proptransform);
336 0 : prop.m_Model->ValidatePosition();
337 : }
338 :
339 0 : if (m_BoneMatrices)
340 : {
341 0 : for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
342 : {
343 0 : m_BoneMatrices[i] = m_BoneMatrices[i] * m_pModelDef->GetInverseBindBoneMatrices()[i];
344 : }
345 :
346 : // Note: there is a special case of joint influence, in which the vertex
347 : // is influenced by the bind-shape transform instead of a particular bone,
348 : // which we indicate with the blending bone ID set to the total number
349 : // of bones. But since we're skinning in world space, we use the model's
350 : // world space transform and store that matrix in this special index.
351 : // (see http://trac.wildfiregames.com/ticket/1012)
352 0 : m_BoneMatrices[m_pModelDef->GetNumBones()] = m_Transform;
353 :
354 0 : if (computeBlendMatrices)
355 0 : m_pModelDef->BlendBoneMatrices(m_BoneMatrices);
356 : }
357 : }
358 :
359 :
360 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
361 : // SetAnimation: set the given animation as the current animation on this model;
362 : // return false on error, else true
363 0 : bool CModel::SetAnimation(CSkeletonAnim* anim, bool once)
364 : {
365 0 : m_Anim = nullptr; // in case something fails
366 :
367 0 : if (anim)
368 : {
369 0 : m_Flags &= ~MODELFLAG_NOLOOPANIMATION;
370 :
371 0 : if (once)
372 0 : m_Flags |= MODELFLAG_NOLOOPANIMATION;
373 :
374 : // Not rigged or animation is not valid.
375 0 : if (!m_BoneMatrices || !anim->m_AnimDef)
376 0 : return false;
377 :
378 0 : if (anim->m_AnimDef->GetNumKeys() != m_pModelDef->GetNumBones())
379 : {
380 0 : LOGERROR("Mismatch between model's skeleton and animation's skeleton (%s.dae has %lu model bones while the animation %s has %lu animation keys.)",
381 : m_pModelDef->GetName().string8().c_str() ,
382 : static_cast<unsigned long>(m_pModelDef->GetNumBones()),
383 : anim->m_Name.c_str(),
384 : static_cast<unsigned long>(anim->m_AnimDef->GetNumKeys()));
385 0 : return false;
386 : }
387 :
388 : // Reset the cached bounds when the animation is changed.
389 0 : m_ObjectBounds.SetEmpty();
390 0 : InvalidateBounds();
391 :
392 : // Start anim from beginning.
393 0 : m_AnimTime = 0;
394 : }
395 :
396 0 : m_Anim = anim;
397 :
398 0 : return true;
399 : }
400 :
401 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
402 : // CopyAnimation
403 0 : void CModel::CopyAnimationFrom(CModel* source)
404 : {
405 0 : m_Anim = source->m_Anim;
406 0 : m_AnimTime = source->m_AnimTime;
407 :
408 0 : m_ObjectBounds.SetEmpty();
409 0 : InvalidateBounds();
410 0 : }
411 :
412 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
413 : // AddProp: add a prop to the model on the given point
414 0 : void CModel::AddProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry, float minHeight, float maxHeight, bool selectable)
415 : {
416 : // position model according to prop point position
417 :
418 : // this next call will invalidate the bounds of "model", which will in turn also invalidate the selection box
419 0 : model->SetTransform(point->m_Transform);
420 0 : model->m_Parent = this;
421 :
422 0 : Prop prop;
423 0 : prop.m_Point = point;
424 0 : prop.m_Model = model;
425 0 : prop.m_ObjectEntry = objectentry;
426 0 : prop.m_MinHeight = minHeight;
427 0 : prop.m_MaxHeight = maxHeight;
428 0 : prop.m_Selectable = selectable;
429 0 : m_Props.push_back(prop);
430 0 : }
431 :
432 0 : void CModel::AddAmmoProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry)
433 : {
434 0 : AddProp(point, model, objectentry);
435 0 : m_AmmoPropPoint = point;
436 0 : m_AmmoLoadedProp = m_Props.size() - 1;
437 0 : m_Props[m_AmmoLoadedProp].m_Hidden = true;
438 :
439 : // we only need to invalidate the selection box here if it is based on props and their visibilities
440 0 : if (!m_CustomSelectionShape)
441 0 : m_SelectionBoxValid = false;
442 0 : }
443 :
444 0 : void CModel::ShowAmmoProp()
445 : {
446 0 : if (m_AmmoPropPoint == NULL)
447 0 : return;
448 :
449 : // Show the ammo prop, hide all others on the same prop point
450 0 : for (size_t i = 0; i < m_Props.size(); ++i)
451 0 : if (m_Props[i].m_Point == m_AmmoPropPoint)
452 0 : m_Props[i].m_Hidden = (i != m_AmmoLoadedProp);
453 :
454 : // we only need to invalidate the selection box here if it is based on props and their visibilities
455 0 : if (!m_CustomSelectionShape)
456 0 : m_SelectionBoxValid = false;
457 : }
458 :
459 0 : void CModel::HideAmmoProp()
460 : {
461 0 : if (m_AmmoPropPoint == NULL)
462 0 : return;
463 :
464 : // Hide the ammo prop, show all others on the same prop point
465 0 : for (size_t i = 0; i < m_Props.size(); ++i)
466 0 : if (m_Props[i].m_Point == m_AmmoPropPoint)
467 0 : m_Props[i].m_Hidden = (i == m_AmmoLoadedProp);
468 :
469 : // we only need to invalidate here if the selection box is based on props and their visibilities
470 0 : if (!m_CustomSelectionShape)
471 0 : m_SelectionBoxValid = false;
472 : }
473 :
474 0 : CModelAbstract* CModel::FindFirstAmmoProp()
475 : {
476 0 : if (m_AmmoPropPoint)
477 0 : return m_Props[m_AmmoLoadedProp].m_Model;
478 :
479 0 : for (size_t i = 0; i < m_Props.size(); ++i)
480 : {
481 0 : CModel* propModel = m_Props[i].m_Model->ToCModel();
482 0 : if (propModel)
483 : {
484 0 : CModelAbstract* model = propModel->FindFirstAmmoProp();
485 0 : if (model)
486 0 : return model;
487 : }
488 : }
489 :
490 0 : return NULL;
491 : }
492 :
493 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
494 : // Clone: return a clone of this model
495 0 : CModelAbstract* CModel::Clone() const
496 : {
497 0 : CModel* clone = new CModel(m_Simulation);
498 0 : clone->m_ObjectBounds = m_ObjectBounds;
499 0 : clone->InitModel(m_pModelDef);
500 0 : clone->SetMaterial(m_Material);
501 0 : clone->SetAnimation(m_Anim);
502 0 : clone->SetFlags(m_Flags);
503 :
504 0 : for (size_t i = 0; i < m_Props.size(); i++)
505 : {
506 : // eek! TODO, RC - need to investigate shallow clone here
507 0 : if (m_AmmoPropPoint && i == m_AmmoLoadedProp)
508 0 : clone->AddAmmoProp(m_Props[i].m_Point, m_Props[i].m_Model->Clone(), m_Props[i].m_ObjectEntry);
509 : else
510 0 : clone->AddProp(m_Props[i].m_Point, m_Props[i].m_Model->Clone(), m_Props[i].m_ObjectEntry, m_Props[i].m_MinHeight, m_Props[i].m_MaxHeight, m_Props[i].m_Selectable);
511 : }
512 :
513 0 : return clone;
514 : }
515 :
516 :
517 : /////////////////////////////////////////////////////////////////////////////////////////////////////////////
518 : // SetTransform: set the transform on this object, and reorientate props accordingly
519 0 : void CModel::SetTransform(const CMatrix3D& transform)
520 : {
521 : // call base class to set transform on this object
522 0 : CRenderableObject::SetTransform(transform);
523 0 : InvalidatePosition();
524 0 : }
525 :
526 : //////////////////////////////////////////////////////////////////////////
527 :
528 0 : void CModel::AddFlagsRec(int flags)
529 : {
530 0 : m_Flags |= flags;
531 :
532 0 : if (flags & MODELFLAG_IGNORE_LOS)
533 0 : m_Material.AddShaderDefine(str_IGNORE_LOS, str_1);
534 :
535 0 : for (size_t i = 0; i < m_Props.size(); ++i)
536 0 : if (m_Props[i].m_Model->ToCModel())
537 0 : m_Props[i].m_Model->ToCModel()->AddFlagsRec(flags);
538 0 : }
539 :
540 0 : void CModel::RemoveShadowsRec()
541 : {
542 0 : m_Flags &= ~MODELFLAG_CASTSHADOWS;
543 :
544 0 : m_Material.AddShaderDefine(str_DISABLE_RECEIVE_SHADOWS, str_1);
545 :
546 0 : for (size_t i = 0; i < m_Props.size(); ++i)
547 : {
548 0 : if (m_Props[i].m_Model->ToCModel())
549 0 : m_Props[i].m_Model->ToCModel()->RemoveShadowsRec();
550 0 : else if (m_Props[i].m_Model->ToCModelDecal())
551 0 : m_Props[i].m_Model->ToCModelDecal()->RemoveShadows();
552 : }
553 0 : }
554 :
555 0 : void CModel::SetMaterial(const CMaterial &material)
556 : {
557 0 : m_Material = material;
558 0 : }
559 :
560 0 : void CModel::SetPlayerID(player_id_t id)
561 : {
562 0 : CModelAbstract::SetPlayerID(id);
563 :
564 0 : for (std::vector<Prop>::iterator it = m_Props.begin(); it != m_Props.end(); ++it)
565 0 : it->m_Model->SetPlayerID(id);
566 0 : }
567 :
568 0 : void CModel::SetShadingColor(const CColor& color)
569 : {
570 0 : CModelAbstract::SetShadingColor(color);
571 :
572 0 : for (std::vector<Prop>::iterator it = m_Props.begin(); it != m_Props.end(); ++it)
573 0 : it->m_Model->SetShadingColor(color);
574 3 : }
|