PageRenderTime 70ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/Rendering/Models/Level.cs

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