1 /* Copyright (C) 2018 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 "ActorViewer.h"
21
22 #include "View.h"
23
24 #include "graphics/ColladaManager.h"
25 #include "graphics/LOSTexture.h"
26 #include "graphics/Unit.h"
27 #include "graphics/Model.h"
28 #include "graphics/ModelDef.h"
29 #include "graphics/ObjectManager.h"
30 #include "graphics/ParticleManager.h"
31 #include "graphics/Patch.h"
32 #include "graphics/SkeletonAnimManager.h"
33 #include "graphics/Terrain.h"
34 #include "graphics/TerrainTextureEntry.h"
35 #include "graphics/TerrainTextureManager.h"
36 #include "graphics/TerritoryTexture.h"
37 #include "graphics/UnitManager.h"
38 #include "graphics/Overlay.h"
39 #include "maths/MathUtil.h"
40 #include "ps/Filesystem.h"
41 #include "ps/CLogger.h"
42 #include "ps/GameSetup/Config.h"
43 #include "ps/ProfileViewer.h"
44 #include "renderer/Renderer.h"
45 #include "renderer/Scene.h"
46 #include "renderer/SkyManager.h"
47 #include "renderer/WaterManager.h"
48 #include "scriptinterface/ScriptInterface.h"
49 #include "simulation2/Simulation2.h"
50 #include "simulation2/components/ICmpOwnership.h"
51 #include "simulation2/components/ICmpPosition.h"
52 #include "simulation2/components/ICmpRangeManager.h"
53 #include "simulation2/components/ICmpTerrain.h"
54 #include "simulation2/components/ICmpUnitMotion.h"
55 #include "simulation2/components/ICmpVisual.h"
56 #include "simulation2/components/ICmpWaterManager.h"
57 #include "simulation2/helpers/Render.h"
58
59 struct ActorViewerImpl : public Scene
60 {
61 NONCOPYABLE(ActorViewerImpl);
62 public:
ActorViewerImplActorViewerImpl63 ActorViewerImpl() :
64 Entity(INVALID_ENTITY),
65 Terrain(),
66 ColladaManager(g_VFS),
67 MeshManager(ColladaManager),
68 SkeletonAnimManager(ColladaManager),
69 UnitManager(),
70 Simulation2(&UnitManager, g_ScriptRuntime, &Terrain),
71 ObjectManager(MeshManager, SkeletonAnimManager, Simulation2),
72 LOSTexture(Simulation2),
73 TerritoryTexture(Simulation2)
74 {
75 UnitManager.SetObjectManager(ObjectManager);
76 }
77
78 entity_id_t Entity;
79 CStrW CurrentUnitID;
80 CStrW CurrentUnitAnim;
81 float CurrentSpeed;
82 bool WalkEnabled;
83 bool GroundEnabled;
84 bool WaterEnabled;
85 bool ShadowsEnabled;
86
87 // Whether shadows, sky and water are enabled outside of the actor viewer.
88 bool OldShadows;
89 bool OldSky;
90 bool OldWater;
91
92 bool SelectionBoxEnabled;
93 bool AxesMarkerEnabled;
94 int PropPointsMode; // 0 disabled, 1 for point markers, 2 for point markers + axes
95
96 SColor4ub Background;
97
98 CTerrain Terrain;
99
100 CColladaManager ColladaManager;
101 CMeshManager MeshManager;
102 CSkeletonAnimManager SkeletonAnimManager;
103 CObjectManager ObjectManager;
104 CUnitManager UnitManager;
105 CSimulation2 Simulation2;
106 CLOSTexture LOSTexture;
107 CTerritoryTexture TerritoryTexture;
108
109 SOverlayLine SelectionBoxOverlay;
110 SOverlayLine AxesMarkerOverlays[3];
111 std::vector<CModel::Prop> Props;
112 std::vector<SOverlayLine> PropPointOverlays;
113
114 // Simplistic implementation of the Scene interface
EnumerateObjectsActorViewerImpl115 virtual void EnumerateObjects(const CFrustum& frustum, SceneCollector* c)
116 {
117 if (GroundEnabled)
118 {
119 for (ssize_t pj = 0; pj < Terrain.GetPatchesPerSide(); ++pj)
120 for (ssize_t pi = 0; pi < Terrain.GetPatchesPerSide(); ++pi)
121 c->Submit(Terrain.GetPatch(pi, pj));
122 }
123
124 CmpPtr<ICmpVisual> cmpVisual(Simulation2, Entity);
125 if (cmpVisual)
126 {
127 // add selection box outlines manually
128 if (SelectionBoxEnabled)
129 {
130 SelectionBoxOverlay.m_Color = CColor(35/255.f, 86/255.f, 188/255.f, .75f); // pretty blue
131 SelectionBoxOverlay.m_Thickness = 2;
132
133 SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), SelectionBoxOverlay);
134 c->Submit(&SelectionBoxOverlay);
135 }
136
137 // add origin axis thingy
138 if (AxesMarkerEnabled)
139 {
140 CMatrix3D worldSpaceAxes;
141 // offset from the ground a little bit to prevent fighting with the floor texture (also note: SetTranslation
142 // sets the identity 3x3 transformation matrix, which are the world axes)
143 worldSpaceAxes.SetTranslation(cmpVisual->GetPosition() + CVector3D(0, 0.02f, 0));
144 SimRender::ConstructAxesMarker(worldSpaceAxes, AxesMarkerOverlays[0], AxesMarkerOverlays[1], AxesMarkerOverlays[2]);
145
146 c->Submit(&AxesMarkerOverlays[0]);
147 c->Submit(&AxesMarkerOverlays[1]);
148 c->Submit(&AxesMarkerOverlays[2]);
149 }
150
151 // add prop point overlays
152 if (PropPointsMode > 0 && Props.size() > 0)
153 {
154 PropPointOverlays.clear(); // doesn't clear capacity, but should be ok since the number of prop points is usually pretty limited
155 for (size_t i = 0; i < Props.size(); ++i)
156 {
157 CModel::Prop& prop = Props[i];
158 if (prop.m_Model) // should always be the case
159 {
160 // prop point positions are automatically updated during animations etc. by CModel::ValidatePosition
161 const CMatrix3D& propCoordSystem = prop.m_Model->GetTransform();
162
163 SOverlayLine pointGimbal;
164 pointGimbal.m_Color = CColor(1.f, 0.f, 1.f, 1.f);
165 SimRender::ConstructGimbal(propCoordSystem.GetTranslation(), 0.05f, pointGimbal);
166 PropPointOverlays.push_back(pointGimbal);
167
168 if (PropPointsMode > 1)
169 {
170 // scale the prop axes coord system down a bit to distinguish them from the main world-space axes markers
171 CMatrix3D displayCoordSystem = propCoordSystem;
172 displayCoordSystem.Scale(0.5f, 0.5f, 0.5f);
173 // revert translation scaling
174 displayCoordSystem._14 = propCoordSystem._14;
175 displayCoordSystem._24 = propCoordSystem._24;
176 displayCoordSystem._34 = propCoordSystem._34;
177
178 // construct an XYZ axes marker for the prop's coordinate system
179 SOverlayLine xAxis, yAxis, zAxis;
180 SimRender::ConstructAxesMarker(displayCoordSystem, xAxis, yAxis, zAxis);
181 PropPointOverlays.push_back(xAxis);
182 PropPointOverlays.push_back(yAxis);
183 PropPointOverlays.push_back(zAxis);
184 }
185 }
186 }
187
188 for (size_t i = 0; i < PropPointOverlays.size(); ++i)
189 {
190 c->Submit(&PropPointOverlays[i]);
191 }
192 }
193 }
194
195 // send a RenderSubmit message so the components can submit their visuals to the renderer
196 Simulation2.RenderSubmit(*c, frustum, false);
197 }
198
GetLOSTextureActorViewerImpl199 virtual CLOSTexture& GetLOSTexture()
200 {
201 return LOSTexture;
202 }
203
GetTerritoryTextureActorViewerImpl204 virtual CTerritoryTexture& GetTerritoryTexture()
205 {
206 return TerritoryTexture;
207 }
208
209 /**
210 * Recursively fetches the props of the currently displayed entity model and its submodels, and stores them for rendering.
211 */
212 void UpdatePropList();
213 void UpdatePropListRecursive(CModelAbstract* model);
214
215 };
216
UpdatePropList()217 void ActorViewerImpl::UpdatePropList()
218 {
219 Props.clear();
220
221 CmpPtr<ICmpVisual> cmpVisual(Simulation2, Entity);
222 if (cmpVisual)
223 {
224 CUnit* unit = cmpVisual->GetUnit();
225 if (unit)
226 {
227 CModelAbstract& modelAbstract = unit->GetModel();
228 UpdatePropListRecursive(&modelAbstract);
229 }
230 }
231 }
232
UpdatePropListRecursive(CModelAbstract * modelAbstract)233 void ActorViewerImpl::UpdatePropListRecursive(CModelAbstract* modelAbstract)
234 {
235 ENSURE(modelAbstract);
236
237 CModel* model = modelAbstract->ToCModel();
238 if (model)
239 {
240 std::vector<CModel::Prop>& modelProps = model->GetProps();
241 for (CModel::Prop& modelProp : modelProps)
242 {
243 Props.push_back(modelProp);
244 if (modelProp.m_Model)
245 UpdatePropListRecursive(modelProp.m_Model);
246 }
247 }
248 }
249
ActorViewer()250 ActorViewer::ActorViewer()
251 : m(*new ActorViewerImpl())
252 {
253 m.WalkEnabled = false;
254 m.GroundEnabled = true;
255 m.WaterEnabled = false;
256 m.ShadowsEnabled = g_Renderer.GetOptionBool(CRenderer::OPT_SHADOWS);
257 m.SelectionBoxEnabled = false;
258 m.AxesMarkerEnabled = false;
259 m.PropPointsMode = 0;
260 m.Background = SColor4ub(0, 0, 0, 255);
261
262 // Create a tiny empty piece of terrain, just so we can put shadows
263 // on it without having to think too hard
264 m.Terrain.Initialize(2, NULL);
265 CTerrainTextureEntry* tex = g_TexMan.FindTexture("whiteness");
266 if (tex)
267 {
268 for (ssize_t pi = 0; pi < m.Terrain.GetPatchesPerSide(); ++pi)
269 {
270 for (ssize_t pj = 0; pj < m.Terrain.GetPatchesPerSide(); ++pj)
271 {
272 CPatch* patch = m.Terrain.GetPatch(pi, pj);
273 for (ssize_t i = 0; i < PATCH_SIZE; ++i)
274 {
275 for (ssize_t j = 0; j < PATCH_SIZE; ++j)
276 {
277 CMiniPatch& mp = patch->m_MiniPatches[i][j];
278 mp.Tex = tex;
279 mp.Priority = 0;
280 }
281 }
282 }
283 }
284 }
285 else
286 {
287 debug_warn(L"Failed to load whiteness texture");
288 }
289
290 // Prepare the simulation
291 m.Simulation2.LoadDefaultScripts();
292 m.Simulation2.ResetState();
293
294 // Set player data
295 m.Simulation2.SetMapSettings(m.Simulation2.GetPlayerDefaults());
296 m.Simulation2.LoadPlayerSettings(true);
297
298 // Tell the simulation we've already loaded the terrain
299 CmpPtr<ICmpTerrain> cmpTerrain(m.Simulation2, SYSTEM_ENTITY);
300 if (cmpTerrain)
301 cmpTerrain->ReloadTerrain(false);
302
303 // Remove FOW since we're in Atlas
304 CmpPtr<ICmpRangeManager> cmpRangeManager(m.Simulation2, SYSTEM_ENTITY);
305 if (cmpRangeManager)
306 cmpRangeManager->SetLosRevealAll(-1, true);
307
308 m.Simulation2.InitGame();
309 }
310
~ActorViewer()311 ActorViewer::~ActorViewer()
312 {
313 delete &m;
314 }
315
GetSimulation2()316 CSimulation2* ActorViewer::GetSimulation2()
317 {
318 return &m.Simulation2;
319 }
320
GetEntity()321 entity_id_t ActorViewer::GetEntity()
322 {
323 return m.Entity;
324 }
325
UnloadObjects()326 void ActorViewer::UnloadObjects()
327 {
328 m.ObjectManager.UnloadObjects();
329 }
330
SetActor(const CStrW & name,const CStrW & animation,player_id_t playerID)331 void ActorViewer::SetActor(const CStrW& name, const CStrW& animation, player_id_t playerID)
332 {
333 bool needsAnimReload = false;
334
335 CStrW id = name;
336
337 // Recreate the entity, if we don't have one or if the new one is different
338 if (m.Entity == INVALID_ENTITY || id != m.CurrentUnitID)
339 {
340 // Delete the old entity (if any)
341 if (m.Entity != INVALID_ENTITY)
342 {
343 m.Simulation2.DestroyEntity(m.Entity);
344 m.Simulation2.FlushDestroyedEntities();
345 m.Entity = INVALID_ENTITY;
346 }
347
348 // Clear particles associated with deleted entity
349 g_Renderer.GetParticleManager().ClearUnattachedEmitters();
350
351 // If there's no actor to display, return with nothing loaded
352 if (id.empty())
353 return;
354
355 m.Entity = m.Simulation2.AddEntity(L"preview|" + id);
356 if (m.Entity == INVALID_ENTITY)
357 return;
358
359 CmpPtr<ICmpPosition> cmpPosition(m.Simulation2, m.Entity);
360 if (cmpPosition)
361 {
362 ssize_t c = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2;
363 cmpPosition->JumpTo(entity_pos_t::FromInt(c), entity_pos_t::FromInt(c));
364 cmpPosition->SetYRotation(entity_angle_t::Pi());
365 }
366
367 CmpPtr<ICmpOwnership> cmpOwnership(m.Simulation2, m.Entity);
368 if (cmpOwnership)
369 cmpOwnership->SetOwner(playerID);
370
371 needsAnimReload = true;
372 }
373
374 if (animation != m.CurrentUnitAnim)
375 needsAnimReload = true;
376
377 if (needsAnimReload)
378 {
379 CStr anim = animation.ToUTF8().LowerCase();
380
381 // Emulate the typical simulation animation behaviour
382 float speed;
383 float repeattime = 0.f;
384 if (anim == "walk")
385 {
386 CmpPtr<ICmpUnitMotion> cmpUnitMotion(m.Simulation2, m.Entity);
387 if (cmpUnitMotion)
388 speed = cmpUnitMotion->GetWalkSpeed().ToFloat();
389 else
390 speed = 7.f; // typical unit speed
391
392 m.CurrentSpeed = speed;
393 }
394 else if (anim == "run")
395 {
396 CmpPtr<ICmpUnitMotion> cmpUnitMotion(m.Simulation2, m.Entity);
397 if (cmpUnitMotion)
398 speed = cmpUnitMotion->GetRunSpeed().ToFloat();
399 else
400 speed = 12.f; // typical unit speed
401
402 m.CurrentSpeed = speed;
403 }
404 else if (anim == "melee")
405 {
406 speed = 1.f; // speed will be ignored if we have a repeattime
407 m.CurrentSpeed = 0.f;
408
409 CStr code = "var cmp = Engine.QueryInterface("+CStr::FromUInt(m.Entity)+", IID_Attack); " +
410 "if (cmp) cmp.GetTimers(cmp.GetBestAttack()).repeat; else 0;";
411 m.Simulation2.GetScriptInterface().Eval(code.c_str(), repeattime);
412 }
413 else
414 {
415 // Play the animation at normal speed, but movement speed is zero
416 speed = 1.f;
417 m.CurrentSpeed = 0.f;
418 }
419
420 CStr sound;
421 if (anim == "melee")
422 sound = "attack";
423 else if (anim == "build")
424 sound = "build";
425 else if (anim.Find("gather_") == 0)
426 sound = anim;
427
428 CmpPtr<ICmpVisual> cmpVisual(m.Simulation2, m.Entity);
429 if (cmpVisual)
430 {
431 // TODO: SetEntitySelection(anim)
432 cmpVisual->SelectAnimation(anim, false, fixed::FromFloat(speed));
433 if (repeattime)
434 cmpVisual->SetAnimationSyncRepeat(fixed::FromFloat(repeattime));
435 }
436
437 // update prop list for new entity/animation (relies on needsAnimReload also getting called for entire entity changes)
438 m.UpdatePropList();
439 }
440
441 m.CurrentUnitID = id;
442 m.CurrentUnitAnim = animation;
443 }
444
SetEnabled(bool enabled)445 void ActorViewer::SetEnabled(bool enabled)
446 {
447 if (enabled)
448 {
449 // Set shadows, sky and water.
450 m.OldShadows = g_Renderer.GetOptionBool(CRenderer::OPT_SHADOWS);
451 g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS, m.ShadowsEnabled);
452
453 m.OldSky = g_Renderer.GetSkyManager()->m_RenderSky;
454 g_Renderer.GetSkyManager()->m_RenderSky = false;
455
456 m.OldWater = g_Renderer.GetWaterManager()->m_RenderWater;
457 g_Renderer.GetWaterManager()->m_RenderWater = m.WaterEnabled;
458 }
459 else
460 {
461 // Restore the old renderer state
462 g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS, m.OldShadows);
463 g_Renderer.GetSkyManager()->m_RenderSky = m.OldSky;
464 g_Renderer.GetWaterManager()->m_RenderWater = m.OldWater;
465 }
466 }
467
SetBackgroundColor(const SColor4ub & color)468 void ActorViewer::SetBackgroundColor(const SColor4ub& color)
469 {
470 m.Background = color;
471 m.Terrain.SetBaseColor(color);
472 }
473
SetWalkEnabled(bool enabled)474 void ActorViewer::SetWalkEnabled(bool enabled) { m.WalkEnabled = enabled; }
SetGroundEnabled(bool enabled)475 void ActorViewer::SetGroundEnabled(bool enabled) { m.GroundEnabled = enabled; }
SetWaterEnabled(bool enabled)476 void ActorViewer::SetWaterEnabled(bool enabled)
477 {
478 m.WaterEnabled = enabled;
479 // Adjust water level
480 entity_pos_t waterLevel = entity_pos_t::FromFloat(enabled ? 10.f : 0.f);
481 CmpPtr<ICmpWaterManager> cmpWaterManager(m.Simulation2, SYSTEM_ENTITY);
482 if (cmpWaterManager)
483 cmpWaterManager->SetWaterLevel(waterLevel);
484 }
SetShadowsEnabled(bool enabled)485 void ActorViewer::SetShadowsEnabled(bool enabled) { m.ShadowsEnabled = enabled; }
SetBoundingBoxesEnabled(bool enabled)486 void ActorViewer::SetBoundingBoxesEnabled(bool enabled) { m.SelectionBoxEnabled = enabled; }
SetAxesMarkerEnabled(bool enabled)487 void ActorViewer::SetAxesMarkerEnabled(bool enabled) { m.AxesMarkerEnabled = enabled; }
SetPropPointsMode(int mode)488 void ActorViewer::SetPropPointsMode(int mode) { m.PropPointsMode = mode; }
489
SetStatsEnabled(bool enabled)490 void ActorViewer::SetStatsEnabled(bool enabled)
491 {
492 if (enabled)
493 g_ProfileViewer.ShowTable("renderer");
494 else
495 g_ProfileViewer.ShowTable("");
496 }
497
Render()498 void ActorViewer::Render()
499 {
500 m.Terrain.MakeDirty(RENDERDATA_UPDATE_COLOR);
501
502 g_Renderer.SetClearColor(m.Background);
503
504 // Set simulation context for rendering purposes
505 g_Renderer.SetSimulation(&m.Simulation2);
506
507 g_Renderer.BeginFrame();
508
509 // Find the centre of the interesting region, in the middle of the patch
510 // and half way up the model (assuming there is one)
511 CVector3D centre;
512 CmpPtr<ICmpVisual> cmpVisual(m.Simulation2, m.Entity);
513 if (cmpVisual)
514 cmpVisual->GetBounds().GetCentre(centre);
515 else
516 centre.Y = 0.f;
517 centre.X = centre.Z = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2;
518
519 CCamera camera = AtlasView::GetView_Actor()->GetCamera();
520 camera.m_Orientation.Translate(centre.X, centre.Y, centre.Z);
521 camera.UpdateFrustum();
522
523 g_Renderer.SetSceneCamera(camera, camera);
524
525 g_Renderer.RenderScene(m);
526
527 glDisable(GL_DEPTH_TEST);
528 g_Logger->Render();
529 g_ProfileViewer.RenderProfile();
530 glEnable(GL_DEPTH_TEST);
531
532 g_Renderer.EndFrame();
533
534 ogl_WarnIfError();
535 }
536
Update(float simFrameLength,float realFrameLength)537 void ActorViewer::Update(float simFrameLength, float realFrameLength)
538 {
539 m.Simulation2.Update((int)(simFrameLength*1000));
540 m.Simulation2.Interpolate(simFrameLength, 0, realFrameLength);
541
542 if (m.WalkEnabled && m.CurrentSpeed)
543 {
544 CmpPtr<ICmpPosition> cmpPosition(m.Simulation2, m.Entity);
545 if (cmpPosition)
546 {
547 // Move the model by speed*simFrameLength forwards
548 float z = cmpPosition->GetPosition().Z.ToFloat();
549 z -= m.CurrentSpeed*simFrameLength;
550 // Wrap at the edges, so it doesn't run off into the horizon
551 ssize_t c = TERRAIN_TILE_SIZE * m.Terrain.GetPatchesPerSide()*PATCH_SIZE/2;
552 if (z < c - TERRAIN_TILE_SIZE*PATCH_SIZE * 0.1f)
553 z = c + TERRAIN_TILE_SIZE*PATCH_SIZE * 0.1f;
554 cmpPosition->JumpTo(cmpPosition->GetPosition().X, entity_pos_t::FromFloat(z));
555 }
556 }
557 }
558