/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}