/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
- using System;
- using System.Collections.Generic;
- using Delta.ContentSystem.Rendering;
- using Delta.Rendering.Cameras;
- using Delta.Utilities;
- using Delta.Utilities.Datatypes;
- using NUnit.Framework;
-
- namespace Delta.Rendering.Models
- {
- /// <summary>
- /// Game level.
- /// </summary>
- public class Level
- {
- #region Constants
- /// <summary>
- /// The maximum distance for culling.
- /// </summary>
- private const float MaxCullDistance = 300f;
-
- /// <summary>
- /// Half of maximum distance for culling
- /// </summary>
- private const float HalfMaxCullDistance = MaxCullDistance / 2.0f;
- #endregion
-
- #region IsInsideViewFrustum (Static)
- /// <summary>
- /// Check if the mesh bounding box is inside the frustum.
- /// http://www.cg.tuwien.ac.at/hostings/cescg/CESCG-2002/DSykoraJJelinek/index.html
- /// http://www.lighthouse3d.com/opengl/viewfrustum/index.php?gatest2
- /// </summary>
- /// <param name="mesh">The mesh.</param>
- /// <param name="currentCamera">The current camera.</param>
- /// <param name="meshPosition">The mesh position.</param>
- /// <returns>
- /// <c>true</c> if [is inside view frustum] [the specified mesh]; otherwise, <c>false</c>.
- /// </returns>
- public static bool IsInsideViewFrustum(Mesh mesh,
- BaseCamera currentCamera, Vector meshPosition)
- {
- // fully inside or outside then there is no need to check all
- // the children. (this might not be possible atm since meshes aren't
- // arranged in a hierarchy)
- // this optimization is primarily useful for levels with a lot of objects
- // in different areas of the level.
-
- //Vector negativeVertex;
- Vector positiveVertex = Vector.Zero;
- // Get the mesh boundingBox.
- BoundingBox box = mesh.Geometry.Data.BoundingBox;
- // Initialize an array with the correct world vertex positions
- // for the box.
- // 0 = xMax, 1 = yMax, 2 = zMax
- // 3 = xMin, 4 = yMin, 5 = zMin
- float[] boxValues = {
- box.Max.X + meshPosition.X,
- box.Max.Y + meshPosition.Y,
- box.Max.Z + meshPosition.Z,
- box.Min.X + meshPosition.X,
- box.Min.Y + meshPosition.Y,
- box.Min.Z + meshPosition.Z
- };
-
- // Get the 6 viewFrustum planes.
- Plane[] frustum = currentCamera.ViewFrustum;
-
- // Check vs the plane that the box was outside last. (Plane Coherence)
- byte checkFirstId = mesh.Geometry.Data.CheckFirstPlaneId;
-
- // Find out what vertex in the box lies farthest along the Normal
- // direction of this plane. We do this by checking from the lookupTable
- // that is created in BaseCamera.BuildViewFrustum().
- positiveVertex.X =
- boxValues[currentCamera.VertexLookupTable[checkFirstId, 0]];
- positiveVertex.Y =
- boxValues[currentCamera.VertexLookupTable[checkFirstId, 1]];
- positiveVertex.Z =
- boxValues[currentCamera.VertexLookupTable[checkFirstId, 2]];
-
- // Is the positiveVertex outside?
- if (frustum[checkFirstId].DotCoordinate(positiveVertex) <
- -0.001f)
- {
- // The box is fully outside this plane. Returning false.
- return false;
- }
-
- // Check all the other frustum planes.
- for (byte planeId = 0; planeId < 6; planeId++)
- {
- // We don't have to check this plane again.
- if (planeId == checkFirstId)
- {
- continue;
- }
-
- // Find out what vertex in the box lies farthest along the Normal
- // direction of this plane.
- // We do this by checking from the lookupTable that is created in
- // basecamera::BuildViewFrustum().
- positiveVertex.X =
- boxValues[currentCamera.VertexLookupTable[planeId, 0]];
- positiveVertex.Y =
- boxValues[currentCamera.VertexLookupTable[planeId, 1]];
- positiveVertex.Z =
- boxValues[currentCamera.VertexLookupTable[planeId, 2]];
-
- // is the positiveVertex outside?
- if (frustum[planeId].DotCoordinate(positiveVertex) <
- -0.001f)
- {
- // This plane failed so next time we check versus this plane first.
- mesh.Geometry.Data.CheckFirstPlaneId = planeId;
- // The box is fully outside this plane. Returning false.
- return false;
- }
-
- // intersects with oneanother. 3 states( outisde, intersects, inside)
- // this is only useful if we want to cull some parts of objects.
-
- // Calculate the negativeVertex, it's just the oposite vertex from
- // positiveVertex.
- // You could create a look-up table for this implementation also.
- // The table for the positiveVertex just needs to be inverted.
- //negativeVertex = box.Max + meshPosition;
- //if (frustum[planeId].Normal.X >= 0)
- // negativeVertex.X = box.Min.X + meshPosition.X;
- //if (frustum[planeId].Normal.Y >= 0)
- // negativeVertex.Y = box.Min.Y + meshPosition.Y;
- //if (frustum[planeId].Normal.Z >= 0)
- // negativeVertex.Z = box.Min.Z + meshPosition.Z;
-
- //// is the negativeVertex outside?
- //if (frustum[planeId].DotCoordinate(negativeVertex) < -0.001f)
- //{
- // // The box is intersecting with this plane.
- // return true;
- //}
- }
-
- // The box is inside ALL planes. Returning true.
- return true;
- }
- #endregion
-
-
- #region IsFrustumCullingOn (Public)
- /// <summary>
- /// The flag where the frustum culling can be enabled or disabled.
- /// </summary>
- public bool IsFrustumCullingOn;
- #endregion
-
- #region levelCamera (Public)
- /// <summary>
- /// The camera which moves along the defined camera path
- /// </summary>
- public GameCamera levelCamera;
- #endregion
-
- #region Private
-
- #region levelMeshes (Private)
- /// <summary>
- /// The list instanced meshes with its instancing and render informations.
- /// </summary>
- /// private List<MeshInstance> levelModels;
- private readonly List<Mesh> levelMeshes;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Initializes a new instance of the <see cref="Level"/> class.
- /// </summary>
- /// <param name="setLevelName">Name of the set level.</param>
- /// <param name="setShadowMapName">Name of the set shadow map.</param>
- public Level(string setLevelName, string setShadowMapName)
- : this(LevelData.Get(setLevelName), setShadowMapName)
- {
- }
-
- // Level(setLevelName)
-
- /// <summary>
- /// Initializes a new instance of the <see cref="Level"/> class.
- /// </summary>
- /// <param name="setLevelData">The set level data.</param>
- /// <param name="setShadowMapName">Name of the set shadow map.</param>
- public Level(LevelData setLevelData, string setShadowMapName)
- {
- //levelModels = new List<MeshInstance>();
- //foreach (LevelMeshInfo meshInfo in setLevelData.MeshInfos)
- //{
- // levelModels.Add(new MeshInstance(meshInfo));
- //}
- levelMeshes = new List<Mesh>();
- foreach (MeshData meshInfo in setLevelData.optimizedMeshes)
- {
- // Replace ground, floor and bridge materials with shadow map enabled
- // shader and material data!
- if (String.IsNullOrEmpty(setShadowMapName) == false &&
- (meshInfo.Material.DiffuseMapName ==
- "BridgeFloorHighMediumLowDiffuse" ||
- meshInfo.Material.DiffuseMapName ==
- "InnerGroundBestHighMediumLowDiffuse" ||
- meshInfo.Material.DiffuseMapName ==
- "OuterGroundBestHighMediumLowDiffuse"))
- {
- meshInfo.Material.ShadowMapTexture = setShadowMapName;
- meshInfo.Material.ShaderName += "UseShadow";
- } // if
- levelMeshes.Add(new Mesh(meshInfo));
- } // foreach
-
- // Setup the path camera if it was exported for this level!
- if (setLevelData.cameraData != null)
- {
- levelCamera = new GameCamera(setLevelData.cameraData.Path);
- }
- }
- #endregion
-
- #region Draw (Public)
- /// <summary>
- /// Draw
- /// </summary>
- public void Draw()
- {
- // Optimization to skip Rocks rendering after 30sec ^^
- /*don't like this hack anymore ^^
- long totalTimeMs = Time.Milliseconds;
- * This needs to work fps dependant like the new PathCamera code anyway!
- int frameRate = 30;
- if (this.levelCamera.pathByMatrices != null)
- {
- int numOfAnimations = this.levelCamera.pathByMatrices.Length;
- if (numOfAnimations > 0)
- {
- // Add a pause of 2 seconds at the end!
- int increasedNumOfAnimations = numOfAnimations + 60;
-
- int aniMatrixNum =
- (int)((totalTimeMs * frameRate / 1000) % increasedNumOfAnimations);
-
- // Skip rocks after 30sec each camera loop!
- skipRocks = aniMatrixNum > 30 * 30;
- }
- } // if
- *
-
- // Draw every mesh instance
- int num = 0;
- foreach (Mesh meshInstance in levelMeshes)
- {
- //not helpful always true for the SoulcraftTechDemo, meshes too big:
- //if (IsInsideViewFrustum(meshInstance, BaseCamera.Current, pos))
- /*obs
- // Skip the first mesh (Rocks) after 30s
- if (num == 0 &&
- skipRocks)
- {
- num++;
- continue;
- }
- *
-
- meshInstance.Draw();
- num++;
- } // foreach
- */
- foreach (Mesh meshInstance in levelMeshes)
- {
- meshInstance.Draw();
- } // foreach
- }
- #endregion
-
-
- #region Methods (Private)
-
- #region IsObjectVisible
- /// <summary>
- /// Check if a mesh is visible according to the camera position.
- /// Essentially this class culls objects that are either too far away
- /// or not close enough and located out of the camera FOV.
- /// </summary>
- /// <param name="mesh">Mesh to check against.</param>
- /// <param name="worldPosition">World position to check from.</param>
- /// <returns>True if object is visible otherwise false.</returns>
- private bool IsObjectVisible(Mesh mesh, Vector worldPosition)
- {
- if (IsFrustumCullingOn == false)
- {
- return true;
- }
-
- // Note:
- // As long as we can't make sure that camera is updated before or won't
- // change anymore (in the current frame) after the mesh was added for
- // that frame, we always have eval the ViewFrustum check "on-the-fly"
- BaseCamera sceneCamera = BaseCamera.Current;
-
- // First of all check if the object is too far away. Then we simply skip.
- float distance = Vector.Distance(sceneCamera.Position, worldPosition);
-
- if (distance > MaxCullDistance)
- {
- return false;
- }
-
- // Now we do a more accurate check by comparing mesh boundings with a
- // fictive camera target position which is here the half culling distance
- // exactly in front of the camera that we define and compute here
- Vector normCamDir = Vector.Normalize(sceneCamera.LookDirection);
- Vector cullPosition = sceneCamera.Position +
- (normCamDir * HalfMaxCullDistance);
-
- // Compare now the bounding box of the geometry in that mesh
- // Get the radius of the sphere
- float boundingRadius = mesh.Geometry.Data.BoundingSphere.Radius;
- // and compute the distance from the camera cull position
- // Note: Actually we could do that check directly with the camera
- // position, because it doesn't matter if we do a radial check
- // around the offset-ed camera position (cull position) or the
- // camera position directly
-
- // a position is inside the frustum or not
- float length = Vector.Distance(cullPosition, worldPosition) -
- HalfMaxCullDistance - boundingRadius;
-
- // to know if is the mesh in front of the "culling border"
- // or behind and in that case outside
- return length <= 0f;
- }
- #endregion
-
- #endregion
-
-
- /// <summary>
- /// Tests
- /// </summary>
- internal class LevelTests
- {
- #region IsInsideViewFrustum (LongRunning)
- /// <summary>
- /// Is inside view frustum
- /// </summary>
- [Test, Category("LongRunning")]
- public void IsInsideViewFrustum()
- {
- MaterialData materialData = new MaterialData
- {
- ShaderName = "TexturedShader3D",
- DiffuseMapName = "CollapesdGroundLowMediumHighDiffuse",
- };
- BaseCamera cam = new FreeCamera(new Vector(0.0f, 0.0f, 0.0f));
- cam.Run();
- Mesh boxMesh = Mesh.CreateBox("Box", 2.0f, 2.0f, 2.0f, materialData);
-
- // Infront. Visible
- Vector meshPosition = new Vector(0.0f, 10.0f, 0.0f);
- Assert.True(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
-
- // Behind. Not visible
- meshPosition = new Vector(0.0f, -5.0f, 0.0f);
- Assert.False(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
-
- // To the far left and front. Not visible
- meshPosition = new Vector(-10.0f, 4.0f, 0.0f);
- Assert.False(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
-
- // To the right and front. Visible
- meshPosition = new Vector(-5.0f, 15.0f, 0.0f);
- Assert.True(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
-
- // Far infront still inside farclip. Visible
- meshPosition = new Vector(0.0f, 40.0f, 0.0f);
- Assert.True(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
-
- // Behind and down. Not visible
- meshPosition = new Vector(0.0f, -5.0f, -4.0f);
- Assert.False(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
-
- // Just outside farclip (75.0f). Not visible
- meshPosition = new Vector(0.0f, 80.0f, 0.0f);
- Assert.False(Level.IsInsideViewFrustum(boxMesh, cam, meshPosition));
- }
- #endregion
- }
- }
- }