PageRenderTime 89ms CodeModel.GetById 68ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 0ms

/Rendering/Models/Level.cs

#
C# | 396 lines | 170 code | 46 blank | 180 comment | 12 complexity | e1a2aae3aa3b199b44f64fcb0daebf1c MD5 | raw file
  1using System;
  2using System.Collections.Generic;
  3using Delta.ContentSystem.Rendering;
  4using Delta.Rendering.Cameras;
  5using Delta.Utilities;
  6using Delta.Utilities.Datatypes;
  7using NUnit.Framework;
  8
  9namespace Delta.Rendering.Models
 10{
 11	/// <summary>
 12	/// Game level.
 13	/// </summary>
 14	public class Level
 15	{
 16		#region Constants
 17		/// <summary>
 18		/// The maximum distance for culling.
 19		/// </summary>
 20		private const float MaxCullDistance = 300f;
 21
 22		/// <summary>
 23		/// Half of maximum distance for culling
 24		/// </summary>
 25		private const float HalfMaxCullDistance = MaxCullDistance / 2.0f;
 26		#endregion
 27
 28		#region IsInsideViewFrustum (Static)
 29		/// <summary>
 30		/// Check if the mesh bounding box is inside the frustum.
 31		/// http://www.cg.tuwien.ac.at/hostings/cescg/CESCG-2002/DSykoraJJelinek/index.html
 32		/// http://www.lighthouse3d.com/opengl/viewfrustum/index.php?gatest2
 33		/// </summary>
 34		/// <param name="mesh">The mesh.</param>
 35		/// <param name="currentCamera">The current camera.</param>
 36		/// <param name="meshPosition">The mesh position.</param>
 37		/// <returns>
 38		///   <c>true</c> if [is inside view frustum] [the specified mesh]; otherwise, <c>false</c>.
 39		/// </returns>
 40		public static bool IsInsideViewFrustum(Mesh mesh,
 41			BaseCamera currentCamera, Vector meshPosition)
 42		{
 43			// fully inside or outside then there is no need to check all
 44			// the children. (this might not be possible atm since meshes aren't
 45			// arranged in a hierarchy)
 46			// this optimization is primarily useful for levels with a lot of objects
 47			// in different areas of the level.
 48
 49			//Vector negativeVertex;
 50			Vector positiveVertex = Vector.Zero;
 51			// Get the mesh boundingBox.
 52			BoundingBox box = mesh.Geometry.Data.BoundingBox;
 53			// Initialize an array with the correct world vertex positions 
 54			// for the box.
 55			// 0 = xMax, 1 = yMax, 2 = zMax
 56			// 3 = xMin, 4 = yMin, 5 = zMin
 57			float[] boxValues = {
 58				box.Max.X + meshPosition.X,
 59				box.Max.Y + meshPosition.Y,
 60				box.Max.Z + meshPosition.Z,
 61				box.Min.X + meshPosition.X,
 62				box.Min.Y + meshPosition.Y,
 63				box.Min.Z + meshPosition.Z
 64			};
 65
 66			// Get the 6 viewFrustum planes.
 67			Plane[] frustum = currentCamera.ViewFrustum;
 68
 69			// Check vs the plane that the box was outside last. (Plane Coherence)
 70			byte checkFirstId = mesh.Geometry.Data.CheckFirstPlaneId;
 71
 72			// Find out what vertex in the box lies farthest along the Normal
 73			// direction of this plane. We do this by checking from the lookupTable
 74			// that is created in BaseCamera.BuildViewFrustum().
 75			positiveVertex.X =
 76				boxValues[currentCamera.VertexLookupTable[checkFirstId, 0]];
 77			positiveVertex.Y =
 78				boxValues[currentCamera.VertexLookupTable[checkFirstId, 1]];
 79			positiveVertex.Z =
 80				boxValues[currentCamera.VertexLookupTable[checkFirstId, 2]];
 81
 82			// Is the positiveVertex outside?
 83			if (frustum[checkFirstId].DotCoordinate(positiveVertex) <
 84			    -0.001f)
 85			{
 86				// The box is fully outside this plane. Returning false.
 87				return false;
 88			}
 89
 90			// Check all the other frustum planes.
 91			for (byte planeId = 0; planeId < 6; planeId++)
 92			{
 93				// We don't have to check this plane again.
 94				if (planeId == checkFirstId)
 95				{
 96					continue;
 97				}
 98
 99				// Find out what vertex in the box lies farthest along the Normal
100				// direction of this plane.
101				// We do this by checking from the lookupTable that is created in
102				// basecamera::BuildViewFrustum().
103				positiveVertex.X =
104					boxValues[currentCamera.VertexLookupTable[planeId, 0]];
105				positiveVertex.Y =
106					boxValues[currentCamera.VertexLookupTable[planeId, 1]];
107				positiveVertex.Z =
108					boxValues[currentCamera.VertexLookupTable[planeId, 2]];
109
110				// is the positiveVertex outside?
111				if (frustum[planeId].DotCoordinate(positiveVertex) <
112				    -0.001f)
113				{
114					// This plane failed so next time we check versus this plane first.
115					mesh.Geometry.Data.CheckFirstPlaneId = planeId;
116					// The box is fully outside this plane. Returning false.
117					return false;
118				}
119
120				// intersects with oneanother. 3 states( outisde, intersects, inside)
121				// this is only useful if we want to cull some parts of objects.
122
123				// Calculate the negativeVertex, it's just the oposite vertex from
124				// positiveVertex.
125				// You could create a look-up table for this implementation also.
126				// The table for the positiveVertex just needs to be inverted.
127				//negativeVertex = box.Max + meshPosition;
128				//if (frustum[planeId].Normal.X >= 0)
129				//  negativeVertex.X = box.Min.X + meshPosition.X;
130				//if (frustum[planeId].Normal.Y >= 0)
131				//  negativeVertex.Y = box.Min.Y + meshPosition.Y;
132				//if (frustum[planeId].Normal.Z >= 0)
133				//  negativeVertex.Z = box.Min.Z + meshPosition.Z;
134
135				//// is the negativeVertex outside?
136				//if (frustum[planeId].DotCoordinate(negativeVertex) < -0.001f)
137				//{
138				//  // The box is intersecting with this plane.
139				//  return true;
140				//}
141			}
142
143			// The box is inside ALL planes. Returning true.
144			return true;
145		}
146		#endregion
147
148
149		#region IsFrustumCullingOn (Public)
150		/// <summary>
151		/// The flag where the frustum culling can be enabled or disabled.
152		/// </summary>
153		public bool IsFrustumCullingOn;
154		#endregion
155
156		#region levelCamera (Public)
157		/// <summary>
158		/// The camera which moves along the defined camera path
159		/// </summary>
160		public GameCamera levelCamera;
161		#endregion
162
163		#region Private
164
165		#region levelMeshes (Private)
166		/// <summary>
167		/// The list instanced meshes with its instancing and render informations.
168		/// </summary>
169		/// private List<MeshInstance> levelModels;
170		private readonly List<Mesh> levelMeshes;
171		#endregion
172
173		#endregion
174
175		#region Constructors
176		/// <summary>
177		/// Initializes a new instance of the <see cref="Level"/> class.
178		/// </summary>
179		/// <param name="setLevelName">Name of the set level.</param>
180		/// <param name="setShadowMapName">Name of the set shadow map.</param>
181		public Level(string setLevelName, string setShadowMapName)
182			: this(LevelData.Get(setLevelName), setShadowMapName)
183		{
184		}
185
186		// Level(setLevelName)
187
188		/// <summary>
189		/// Initializes a new instance of the <see cref="Level"/> class.
190		/// </summary>
191		/// <param name="setLevelData">The set level data.</param>
192		/// <param name="setShadowMapName">Name of the set shadow map.</param>
193		public Level(LevelData setLevelData, string setShadowMapName)
194		{
195			//levelModels = new List<MeshInstance>();
196			//foreach (LevelMeshInfo meshInfo in setLevelData.MeshInfos)
197			//{
198			//  levelModels.Add(new MeshInstance(meshInfo));
199			//}
200			levelMeshes = new List<Mesh>();
201			foreach (MeshData meshInfo in setLevelData.optimizedMeshes)
202			{
203				// Replace ground, floor and bridge materials with shadow map enabled
204				// shader and material data!
205				if (String.IsNullOrEmpty(setShadowMapName) == false &&
206				    (meshInfo.Material.DiffuseMapName ==
207				     "BridgeFloorHighMediumLowDiffuse" ||
208				     meshInfo.Material.DiffuseMapName ==
209				     "InnerGroundBestHighMediumLowDiffuse" ||
210				     meshInfo.Material.DiffuseMapName ==
211				     "OuterGroundBestHighMediumLowDiffuse"))
212				{
213					meshInfo.Material.ShadowMapTexture = setShadowMapName;
214					meshInfo.Material.ShaderName += "UseShadow";
215				} // if
216				levelMeshes.Add(new Mesh(meshInfo));
217			} // foreach
218
219			// Setup the path camera if it was exported for this level!
220			if (setLevelData.cameraData != null)
221			{
222				levelCamera = new GameCamera(setLevelData.cameraData.Path);
223			}
224		}
225		#endregion
226
227		#region Draw (Public)
228		/// <summary>
229		/// Draw
230		/// </summary>
231		public void Draw()
232		{
233			// Optimization to skip Rocks rendering after 30sec ^^
234			/*don't like this hack anymore ^^
235			long totalTimeMs = Time.Milliseconds;
236			 * This needs to work fps dependant like the new PathCamera code anyway!
237			int frameRate = 30;
238			if (this.levelCamera.pathByMatrices != null)
239			{
240				int numOfAnimations = this.levelCamera.pathByMatrices.Length;
241				if (numOfAnimations > 0)
242				{
243					// Add a pause of 2 seconds at the end!
244					int increasedNumOfAnimations = numOfAnimations + 60;
245
246					int aniMatrixNum =
247						(int)((totalTimeMs * frameRate / 1000) % increasedNumOfAnimations);
248
249					// Skip rocks after 30sec each camera loop!
250					skipRocks = aniMatrixNum > 30 * 30;
251				}
252			} // if
253			 *
254
255			// Draw every mesh instance
256			int num = 0;
257			foreach (Mesh meshInstance in levelMeshes)
258			{
259				//not helpful always true for the SoulcraftTechDemo, meshes too big:
260				//if (IsInsideViewFrustum(meshInstance, BaseCamera.Current, pos))
261				/*obs
262				// Skip the first mesh (Rocks) after 30s
263				if (num == 0 &&
264					skipRocks)
265				{
266					num++;
267					continue;
268				}
269				 *
270
271				meshInstance.Draw();
272				num++;
273			} // foreach
274			 */
275			foreach (Mesh meshInstance in levelMeshes)
276			{
277				meshInstance.Draw();
278			} // foreach
279		}
280		#endregion
281
282
283		#region Methods (Private)
284
285		#region IsObjectVisible
286		/// <summary>
287		/// Check if a mesh is visible according to the camera position. 
288		/// Essentially this class culls objects that are either too far away
289		/// or not close enough and located out of the camera FOV.
290		/// </summary>
291		/// <param name="mesh">Mesh to check against.</param>
292		/// <param name="worldPosition">World position to check from.</param>
293		/// <returns>True if object is visible otherwise false.</returns>
294		private bool IsObjectVisible(Mesh mesh, Vector worldPosition)
295		{
296			if (IsFrustumCullingOn == false)
297			{
298				return true;
299			}
300
301			// Note:
302			// As long as we can't make sure that camera is updated before or won't
303			// change anymore (in the current frame) after the mesh was added for
304			// that frame, we always have eval the ViewFrustum check "on-the-fly"
305			BaseCamera sceneCamera = BaseCamera.Current;
306
307			// First of all check if the object is too far away. Then we simply skip.
308			float distance = Vector.Distance(sceneCamera.Position, worldPosition);
309
310			if (distance > MaxCullDistance)
311			{
312				return false;
313			}
314
315			// Now we do a more accurate check by comparing mesh boundings with a
316			// fictive camera target position which is here the half culling distance
317			// exactly in front of the camera that we define and compute here
318			Vector normCamDir = Vector.Normalize(sceneCamera.LookDirection);
319			Vector cullPosition = sceneCamera.Position +
320			                      (normCamDir * HalfMaxCullDistance);
321
322			// Compare now the bounding box of the geometry in that mesh
323			// Get the radius of the sphere
324			float boundingRadius = mesh.Geometry.Data.BoundingSphere.Radius;
325			// and compute the distance from the camera cull position
326			// Note: Actually we could do that check directly with the camera
327			// position, because it doesn't matter if we do a radial check
328			// around the offset-ed camera position (cull position) or the
329			// camera position directly
330
331			//					a position is inside the frustum or not
332			float length = Vector.Distance(cullPosition, worldPosition) -
333			               HalfMaxCullDistance - boundingRadius;
334
335			// to know if is the mesh in front of the "culling border"
336			// or behind and in that case outside
337			return length <= 0f;
338		}
339		#endregion
340
341		#endregion
342
343
344		/// <summary>
345		/// Tests
346		/// </summary>
347		internal class LevelTests
348		{
349			#region IsInsideViewFrustum (LongRunning)
350			/// <summary>
351			/// Is inside view frustum
352			/// </summary>
353			[Test, Category("LongRunning")]
354			public void IsInsideViewFrustum()
355			{
356				MaterialData materialData = new MaterialData
357				{
358					ShaderName = "TexturedShader3D",
359					DiffuseMapName = "CollapesdGroundLowMediumHighDiffuse",
360				};
361				BaseCamera cam = new FreeCamera(new Vector(0.0f, 0.0f, 0.0f));
362				cam.Run();
363				Mesh boxMesh = Mesh.CreateBox("Box", 2.0f, 2.0f, 2.0f, materialData);
364
365				// Infront. Visible
366				Vector meshPosition = new Vector(0.0f, 10.0f, 0.0f);
367				Assert.True(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
368
369				// Behind. Not visible
370				meshPosition = new Vector(0.0f, -5.0f, 0.0f);
371				Assert.False(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
372
373				// To the far left and front. Not visible
374				meshPosition = new Vector(-10.0f, 4.0f, 0.0f);
375				Assert.False(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
376
377				// To the right and front. Visible
378				meshPosition = new Vector(-5.0f, 15.0f, 0.0f);
379				Assert.True(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
380
381				// Far infront still inside farclip. Visible
382				meshPosition = new Vector(0.0f, 40.0f, 0.0f);
383				Assert.True(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
384
385				// Behind and down. Not visible
386				meshPosition = new Vector(0.0f, -5.0f, -4.0f);
387				Assert.False(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
388
389				// Just outside farclip (75.0f). Not visible
390				meshPosition = new Vector(0.0f, 80.0f, 0.0f);
391				Assert.False(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
392			}
393			#endregion
394		}
395	}
396}