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