//----------------------------------------------------------------------------- // Track.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Text; using RacingGame.Graphics; using RacingGame.Helpers; using RacingGame.Landscapes; using RacingGame.Shaders; using Model = RacingGame.Graphics.Model; using RacingGame.GameLogic; namespace RacingGame.Tracks { /// /// Track for our road in the game. Generated with help of the TrackLine /// class, which is loaded by a couple of spline points we get exported /// from 3ds max. /// public class Track : TrackLine, IDisposable { /// /// Factor for streching the width of the road back texture, smaller /// values will strech the texture more. 1.0f means we use the same as /// the road, which is defined in TrackVertex.RoadWidthScale. /// const float RoadBackHullTextureWidthFactor = 1.0f; /// /// Factor for streching the width of the road tunnel texture, smaller /// values will strech the texture more. 1.0f means we use the same as /// the road, which is defined in TrackVertex.RoadWidthScale. /// const float RoadTunnelTextureWidthFactor = 0.25f; /// /// Factor for texels we use from the roadBack texture for the sides. /// Most of the texture is used for the back side, but the sides /// are also at the top and bottom of the texture, use this factor /// to find out how much it is in the texture. /// const float RoadBackSideTextureHeight = 0.135f; /// /// Same as RoadBackSideTextureHeight, but we have more space for /// the sides in the tunnel texture! /// const float RoadTunnelSideTextureHeight = 0.235f; /// /// Palm and latern gap for the autogeneration. /// const float PalmAndLaternGap = 20.0f; /// /// Put a checkpoint every 500m /// const float CheckpointGap = 500.0f; /// /// Gap for signs, don't put them closer than this together. /// const float SignGap = 24; /// /// Road material for the top of the road. /// Material roadMaterial = new Material( "Road", "RoadNormal"); /// /// Road back material, for the other side of the road and the sides. /// Material roadBackMaterial = new Material( "RoadBack", "RoadBackNormal"); /// /// Road tunnel material, used whereever we got tunnels. /// Material roadTunnelMaterial = new Material( // Use mainly ambient color (tunnel uses lightmaps) new Color(182, 182, 182), new Color(80, 80, 80), new Color(64, 64, 64), "RoadTunnel", "RoadTunnelNormal", "", ""); /// /// Road cement material, used for the columns the road is staying on. /// Material roadCementMaterial = new Material( "RoadCement", "RoadCementNormal"); /// /// Guard rail material, used for the left and right guard rails. /// It is also used for the guard rail /// Material guardRailMaterial = new Material( new Color(72, 72, 72), new Color(182, 182, 182), new Color(225, 225, 225), "Leitplanke", "LeitplankeNormal", "", ""); /// /// Vertices for the road itself. /// TangentVertex[] roadVertices = null; /// /// Vertex buffer for the road. /// VertexBuffer roadVb = null; /// /// Index buffer for the road. /// IndexBuffer roadIb = null; /// /// Vertices for the road back (hull, bottom side, sides). /// TangentVertex[] roadBackVertices = null; /// /// Vertex buffer for the road back (has different texture coordinates) /// VertexBuffer roadBackVb = null; /// /// Index buffer for the road back. /// IndexBuffer roadBackIb = null; /// /// Vertices for the road tunnels (sides and top). /// TangentVertex[] roadTunnelVertices = null; /// /// Remember road tunnel indices because determinating the count is /// not so easy (we can have multiple tunnels in here). /// int[] roadTunnelIndices = null; /// /// Vertex buffer for the road back (has different texture coordinates) /// VertexBuffer roadTunnelVb = null; /// /// Index buffer for the road back. /// IndexBuffer roadTunnelIb = null; /// /// Left and right guard rails. /// Renders the autogenerated rails and the helper piles to backup the /// gruard rail (leitplanke_pfahl.x model file, which is generated /// seperately). /// GuardRail leftRail = null, rightRail = null; /// /// Track columns, used for the columns the road is staying on. /// Renders autogenerated column objects and a helper segment for the /// ground (TrackColumnSegment.x model file, which is generated /// seperately). /// TrackColumns columns = null; /// /// Remember checkpoint segment positions for easier checkpoint checking. /// List checkpointSegmentPositions = new List(); /// /// Start position /// /// Vector 3 public Vector3 StartPosition { get { return points[0].pos; } } /// /// Start direction /// /// Vector 3 public Vector3 StartDirection { get { return points[0].dir; } } /// /// Start up vector /// /// Vector 3 public Vector3 StartUpVector { get { return points[0].up; } } /// /// Length /// /// Float public float Length { get { return points.Count * 100.0f / (float)NumberOfIterationsPer100Meters; } } /// /// Number of segments /// /// int public int NumberOfSegments { get { return points.Count; } } /// /// Remember checkpoint segment positions for easier checkpoint checking. /// public List CheckpointSegmentPositions { get { return checkpointSegmentPositions; } } /// /// Create track /// /// Track name to load /// Landscape to check if we are above it public Track(string setTrackName, Landscape landscape) : base(TrackData.Load(setTrackName), landscape) { GenerateVerticesAndObjects(landscape); } /// /// Reload /// /// Track name /// Landscape public void Reload(string setTrackName, Landscape landscape) { // Do we need to load the base track again? // Always reload! Else we might mess up the checkpoints, etc. base.Load(TrackData.Load(setTrackName), landscape); GenerateVerticesAndObjects(landscape); } /// /// Generate vertices and objects /// private void GenerateVerticesAndObjects(Landscape landscape) { // Each road segment gets 5 points: // left, left middle, middle, right middle, right. // The reason for this is that we would bad triangle errors if the // road gets wider and wider. This happens because we need to render // quad, but we can only render triangles, which often have different // orientations, which makes the road very bumpy. This still happens // with 8 polygons instead of 2, but it is much better this way. // Another trick is not to do so much iterations in TrackLine, which // causes this problem. Better to have a not so round track, but at // least the road up/down itself is smooth. // The last point is duplicated (see TrackLine) because we have 2 sets // of texture coordinates for it (begin block, end block). // So for the index buffer we only use points.Count-1 blocks. roadVertices = new TangentVertex[points.Count * 5]; // Current texture coordinate for the roadway (in direction of movement) for (int num = 0; num < points.Count; num++) { // Get vertices with help of the properties in the TrackVertex class. // For the road itself we only need vertices for the left and right // side, which are vertex number 0 and 1. roadVertices[num * 5 + 0] = points[num].RightTangentVertex; roadVertices[num * 5 + 1] = points[num].MiddleRightTangentVertex; roadVertices[num * 5 + 2] = points[num].MiddleTangentVertex; roadVertices[num * 5 + 3] = points[num].MiddleLeftTangentVertex; roadVertices[num * 5 + 4] = points[num].LeftTangentVertex; } // fix //roadVb = new VertexBuffer( // BaseGame.Device, // typeof(TangentVertex), // roadVertices.Length, // ResourceUsage.WriteOnly, // ResourceManagementMode.Automatic); roadVb = new VertexBuffer( BaseGame.Device, typeof(TangentVertex), roadVertices.Length, BufferUsage.WriteOnly); roadVb.SetData(roadVertices); // Also calculate all indices, we have 8 polygons for each segment with // 3 vertices each. We got 1 segment less than points because the // last point is duplicated (different tex coords). int[] indices = new int[(points.Count - 1) * 8 * 3]; int vertexIndex = 0; for (int num = 0; num < points.Count - 1; num++) { // We only use 3 vertices (and the next 3 vertices), // but we have to construct all 24 indices for our 4 polygons. for (int sideNum = 0; sideNum < 4; sideNum++) { // Each side needs 2 polygons. // 1. Polygon indices[num * 24 + 6 * sideNum + 0] = vertexIndex + sideNum; indices[num * 24 + 6 * sideNum + 1] = vertexIndex + 5 + 1 + sideNum; indices[num * 24 + 6 * sideNum + 2] = vertexIndex + 5 + sideNum; // 2. Polygon indices[num * 24 + 6 * sideNum + 3] = vertexIndex + 5 + 1 + sideNum; indices[num * 24 + 6 * sideNum + 4] = vertexIndex + sideNum; indices[num * 24 + 6 * sideNum + 5] = vertexIndex + 1 + sideNum; } // Go to the next 5 vertices vertexIndex += 5; } // Set road back index buffer // fix //roadIb = new IndexBuffer( // BaseGame.Device, // typeof(int), // indices.Length, // ResourceUsage.WriteOnly, // ResourceManagementMode.Automatic); roadIb = new IndexBuffer( BaseGame.Device, typeof(int), indices.Length, BufferUsage.WriteOnly); roadIb.SetData(indices); // We need 4 vertices per cross-section edge of the road back hull roadBackVertices = new TangentVertex[points.Count * 4]; for (int num = 0; num < points.Count; num++) { // Left side of the road roadBackVertices[num * 4 + 0] = points[num].LeftTangentVertex; roadBackVertices[num * 4 + 0].uv = new Vector2( roadBackVertices[num * 4 + 0].U * RoadBackHullTextureWidthFactor, 0.0f); // Left lower side of the road roadBackVertices[num * 4 + 1] = points[num].BottomLeftSideTangentVertex; roadBackVertices[num * 4 + 1].uv = new Vector2( roadBackVertices[num * 4 + 0].U * RoadBackHullTextureWidthFactor, RoadBackSideTextureHeight); // Right lower side of the road roadBackVertices[num * 4 + 2] = points[num].BottomRightSideTangentVertex; roadBackVertices[num * 4 + 2].uv = new Vector2( roadBackVertices[num * 4 + 0].U * RoadBackHullTextureWidthFactor, 1.0f - RoadBackSideTextureHeight); // Right side of the road roadBackVertices[num * 4 + 3] = points[num].RightTangentVertex; roadBackVertices[num * 4 + 3].uv = new Vector2( roadBackVertices[num * 4 + 3].U * RoadBackHullTextureWidthFactor, 1.0f); } // Set road back vertex buffer // fix //roadBackVb = new VertexBuffer( // BaseGame.Device, // typeof(TangentVertex), // roadBackVertices.Length, // ResourceUsage.WriteOnly, // ResourceManagementMode.Automatic); roadBackVb = new VertexBuffer( BaseGame.Device, typeof(TangentVertex), roadBackVertices.Length, BufferUsage.WriteOnly); roadBackVb.SetData(roadBackVertices); // Also calculate all indices, we have 6 polygons for each segment with // 3 vertices each. We got 1 segment less than points because the // last point is duplicated (different tex coords). int[] backIndices = new int[(points.Count - 1) * 6 * 3]; vertexIndex = 0; for (int num = 0; num < points.Count - 1; num++) { // We only use 4 vertices (and the next 4 vertices), // but we have to construct all 18 indices for our 6 polygons. for (int sideNum = 0; sideNum < 3; sideNum++) { // Each side needs 2 polygons. // 1. Polygon backIndices[num * 18 + 6 * sideNum + 0] = vertexIndex + sideNum; backIndices[num * 18 + 6 * sideNum + 1] = vertexIndex + 5 + sideNum; backIndices[num * 18 + 6 * sideNum + 2] = vertexIndex + 4 + sideNum; // 2. Polygon backIndices[num * 18 + 6 * sideNum + 3] = vertexIndex + 5 + sideNum; backIndices[num * 18 + 6 * sideNum + 4] = vertexIndex + sideNum; backIndices[num * 18 + 6 * sideNum + 5] = vertexIndex + 1 + sideNum; } // Go to the next 4 vertices vertexIndex += 4; } // Set road back index buffer // fix //roadBackIb = new IndexBuffer( // BaseGame.Device, // typeof(int), // backIndices.Length, // ResourceUsage.WriteOnly, // ResourceManagementMode.Automatic); roadBackIb = new IndexBuffer( BaseGame.Device, typeof(int), backIndices.Length, BufferUsage.WriteOnly); roadBackIb.SetData(backIndices); // Only generate tunnels for the parts were we want to have tunnels for. int totalTunnelLength = 0; foreach (RoadHelperPosition tunnelPos in helperPositions) if (tunnelPos.type == TrackData.RoadHelper.HelperType.Tunnel) totalTunnelLength += 1 + (tunnelPos.endNum - tunnelPos.startNum); // Lets use 4 vertices per segment, we could improve that later // by adding more vertices for a round tunnel. roadTunnelVertices = new TangentVertex[totalTunnelLength * 4]; vertexIndex = 0; foreach (RoadHelperPosition tunnelPos in helperPositions) if (tunnelPos.type == TrackData.RoadHelper.HelperType.Tunnel) for (int num = tunnelPos.startNum; num <= tunnelPos.endNum; num++) { // Left side of the road roadTunnelVertices[vertexIndex + 0] = points[num].LeftTangentVertex; roadTunnelVertices[vertexIndex + 0].uv = new Vector2( roadTunnelVertices[vertexIndex + 0].U * RoadTunnelTextureWidthFactor, 0.0f); // Left top side of the road roadTunnelVertices[vertexIndex + 1] = points[num].TunnelTopLeftSideTangentVertex; roadTunnelVertices[vertexIndex + 1].uv = new Vector2( roadTunnelVertices[vertexIndex + 1].U * RoadTunnelTextureWidthFactor, RoadTunnelSideTextureHeight); // Right top side of the road roadTunnelVertices[vertexIndex + 2] = points[num].TunnelTopRightSideTangentVertex; roadTunnelVertices[vertexIndex + 2].uv = new Vector2( roadTunnelVertices[vertexIndex + 2].U * RoadTunnelTextureWidthFactor, 1.0f - RoadTunnelSideTextureHeight); // Right side of the road roadTunnelVertices[vertexIndex + 3] = points[num].RightTangentVertex; roadTunnelVertices[vertexIndex + 3].uv = new Vector2( roadTunnelVertices[vertexIndex + 3].U * RoadTunnelTextureWidthFactor, 1.0f); // Adjust normals for the 2 lower points roadTunnelVertices[vertexIndex + 0].normal *= -1; roadTunnelVertices[vertexIndex + 3].normal *= -1; roadTunnelVertices[vertexIndex + 0].tangent *= -1; roadTunnelVertices[vertexIndex + 3].tangent *= -1; vertexIndex += 4; } // Set road back vertex buffer if (roadTunnelVertices.Length > 0) { // fix //roadTunnelVb = new VertexBuffer( // BaseGame.Device, // typeof(TangentVertex), // roadTunnelVertices.Length, // ResourceUsage.WriteOnly, // ResourceManagementMode.Automatic); roadTunnelVb = new VertexBuffer( BaseGame.Device, typeof(TangentVertex), roadTunnelVertices.Length, BufferUsage.WriteOnly); roadTunnelVb.SetData(roadTunnelVertices); // Also calculate all indices, we have 6 polygons for each segment with // 3 vertices each. We got 1 segment less than points because the // last point is duplicated (different tex coords). int totalIndices = 0; foreach (RoadHelperPosition tunnelPos in helperPositions) if (tunnelPos.type == TrackData.RoadHelper.HelperType.Tunnel) totalIndices += (tunnelPos.endNum - tunnelPos.startNum); roadTunnelIndices = new int[totalIndices * 6 * 3]; vertexIndex = 0; int tunnelIndex = 0; foreach (RoadHelperPosition tunnelPos in helperPositions) if (tunnelPos.type == TrackData.RoadHelper.HelperType.Tunnel) { for (int num = tunnelPos.startNum; num < tunnelPos.endNum; num++) { // We only use 4 vertices (and the next 4 vertices), // but we have to construct all 18 indices for our 6 polygons. for (int sideNum = 0; sideNum < 3; sideNum++) { // Each side needs 2 polygons. // Note: This polygons are rendered with culling off because // we want to see the inside and outside of the tunnel. // 1. Polygon roadTunnelIndices[tunnelIndex + 0] = vertexIndex + sideNum; roadTunnelIndices[tunnelIndex + 2] = vertexIndex + 4 + sideNum; roadTunnelIndices[tunnelIndex + 1] = vertexIndex + 5 + sideNum; // 2. Polygon roadTunnelIndices[tunnelIndex + 3] = vertexIndex + 5 + sideNum; roadTunnelIndices[tunnelIndex + 5] = vertexIndex + 1 + sideNum; roadTunnelIndices[tunnelIndex + 4] = vertexIndex + sideNum; tunnelIndex += 6; } // Go to the next 4 vertices vertexIndex += 4; } // Skip 4 vertices till the next tunnel vertexIndex += 4; } // Set road back index buffer // fix //roadTunnelIb = new IndexBuffer( // BaseGame.Device, // typeof(int), // roadTunnelIndices.Length, // ResourceUsage.WriteOnly, // ResourceManagementMode.Automatic); roadTunnelIb = new IndexBuffer( BaseGame.Device, typeof(int), roadTunnelIndices.Length, BufferUsage.WriteOnly); roadTunnelIb.SetData(roadTunnelIndices); } leftRail = new GuardRail(points, GuardRail.Modes.Left, landscape); rightRail = new GuardRail(points, GuardRail.Modes.Right, landscape); columns = new TrackColumns(points, landscape); GenerateObjectsForTrack(landscape); } private void GenerateObjectsForTrack(Landscape landscape) { // Auto generate palms and laterns at the side of our road. float lastGap = 0;//PalmAndLaternGap; int generatedNum = 0; for (int num = 0; num < points.Count; num++) { bool palms = false; bool laterns = false; // Check if there are any palms or laterns here foreach (RoadHelperPosition helper in helperPositions) if (num >= helper.startNum && num <= helper.endNum) { if (helper.type == TrackData.RoadHelper.HelperType.Palms) palms = true; else if (helper.type == TrackData.RoadHelper.HelperType.Laterns) laterns = true; } // No palms or laterns here? if (palms == false && laterns == false) // Then skip continue; // Distance of the current position to the next position float distance = Vector3.Distance( points[(num + 1) % points.Count].pos, points[num].pos); // Have we reach or go over the holder gap ? if (lastGap - distance <= 0) { // The unit vectors for our local point space Vector3 right = points[num].right; Vector3 dir = points[num].dir; Vector3 up = points[num].up; // Find out if this is a looping bool upsideDown = up.Z < +0.05f; bool movingUp = dir.Z > 0.65f; bool movingDown = dir.Z < -0.65f; if (upsideDown || movingUp || movingDown) // Skip generation here! continue; // Create the coordinate system for the current point by the 3 unit // vectors. Matrix pointSpace = Matrix.Identity; pointSpace.Right = right; pointSpace.Up = dir; pointSpace.Forward = -up; // Catmull interpolation, instead the linear interpolation, for a // better position calculation, especially in curves. Vector3 p1 = points[num - 1 < 0 ? points.Count - 1 : num - 1].pos; Vector3 p2 = points[num].pos; Vector3 p3 = points[(num + 1) % points.Count].pos; Vector3 p4 = points[(num + 2) % points.Count].pos; Vector3 objPoint = Vector3.CatmullRom(p1, p2, p3, p4, lastGap / distance); generatedNum++; // Store the calculated render matrix for this holder pile object if (landscape != null) { if (palms) { // Check height, // skip palm generation if height is more than 11m if (objPoint.Z - landscape.GetMapHeight(objPoint.X, objPoint.Y) < 11) { int randomNum = RandomHelper.GetRandomInt(4); // Less propability for small palm if (randomNum == 3) randomNum = RandomHelper.GetRandomInt(4); landscape.AddObjectToRender( // Random palms randomNum == 0 ? "AlphaPalm" : randomNum == 1 ? "AlphaPalm2" : randomNum == 2 ? "AlphaPalm3" : "AlphaPalmSmall", // Scale them up a little Matrix.CreateScale(1.25f) * // Randomly rotate palms Matrix.CreateRotationZ( RandomHelper.GetRandomFloat(0, MathHelper.Pi * 2)) * // Put left/right Matrix.CreateTranslation(right * (generatedNum % 2 == 0 ? 0.6f : -0.6f) * points[num].roadWidth * TrackVertex.RoadWidthScale) // Put below the landscape to make it stick // to the ground * Matrix.CreateTranslation(new Vector3(0, 0, -50)) * // And finally we calculate to correct position where // the palm reaches exactly the holder gap Matrix.CreateTranslation(objPoint), // Enable this for shadow map generation true); } } else { landscape.AddObjectToRender( // Random palms or laterns? "Laterne", // Rotate laterns fixed left/right Matrix.CreateRotationZ( generatedNum % 2 == 0 ? MathHelper.Pi : 0.0f) * // Put left/right Matrix.CreateTranslation(new Vector3( (generatedNum % 2 == 0 ? 0.5f : -0.5f) * points[num].roadWidth * TrackVertex.RoadWidthScale - 0.35f, 0, -0.2f)) * // the ordinary transformation to the // current point space pointSpace * // At last we calculate to correct position where the // latern reaches exactly the holder gap Matrix.CreateTranslation(objPoint), // Enable this for shadow map generation true); } } // We have just set a pile, the next pile will be set after // reaching the next holder gap. lastGap += PalmAndLaternGap; } // The distance we have to cover until the next position. // We subtract our current distance from the remaining gap distance, // which will then be checked in the next loop. lastGap -= distance; } // Add the goal and start light models always! if (landscape != null) { Vector3 startRight = points[0].right; Vector3 startDir = points[0].dir; Vector3 startUp = points[0].up; Matrix startPointSpace = Matrix.Identity; startPointSpace.Right = startRight; startPointSpace.Up = startDir; startPointSpace.Forward = -startUp; landscape.AddObjectToRender( // Use RacingGame banners "Banner6", // Scale them up to fit on the road Matrix.CreateScale(points[0].roadWidth) * // Scale up 1.1, but compensate 1.2f done in landscape.AddObject Matrix.CreateScale(1.051f) * Matrix.CreateTranslation(new Vector3(0, -5.1f, 0)) * // the ordinary transformation to the current point space startPointSpace * // Add the correct position where the goal is Matrix.CreateTranslation(points[0].pos), // Enable this for shadow map generation true); landscape.AddObjectToRender( // All 3 modes are handled and updated in BasePlayer class "StartLight3", Matrix.CreateScale(1.1f) * // Put startlight 6 meters away, and on the right road side! Matrix.CreateTranslation(new Vector3( points[0].roadWidth * TrackVertex.RoadWidthScale * 0.50f - 0.3f, 6, -0.2f)) * // the ordinary transformation to the current point space startPointSpace * // Add the correct position where the goal is Matrix.CreateTranslation(points[0].pos), // Enable this for shadow map generation true); } // Make sure we don't reuse any of the old checkpoint positions. checkpointSegmentPositions.Clear(); // Auto generate checkpoints every 500 meters. lastGap = CheckpointGap; float signGap = SignGap; // Don't add another one near the end! for (int num = 0; num < points.Count - 24; num++) { // Distance of the current position to the next position float distance = Vector3.Distance( points[(num + 1) % points.Count].pos, points[num].pos); // The unit vectors for our local point space Vector3 right = points[num].right; Vector3 dir = points[num].dir; Vector3 up = points[num].up; // Find out if this is a looping bool upsideDown = up.Z < +0.05f; bool movingUp = dir.Z > 0.65f; bool movingDown = dir.Z < -0.65f; if (upsideDown || movingUp || movingDown) // Skip generation here! continue; // Create the coordinate system for the current point by the 3 unit // vectors. Matrix pointSpace = Matrix.Identity; pointSpace.Right = right; pointSpace.Up = dir; pointSpace.Forward = -up; // Catmull interpolation, instead the linear interpolation, for a // better position calculation, especially in curves. Vector3 p1 = points[num - 1 < 0 ? points.Count - 1 : num - 1].pos; Vector3 p2 = points[num].pos; Vector3 p3 = points[(num + 1) % points.Count].pos; Vector3 p4 = points[(num + 2) % points.Count].pos; // Have we reach or go over the holder gap ? if (lastGap - distance <= 0 && landscape != null) { Vector3 objPoint = Vector3.CatmullRom(p1, p2, p3, p4, lastGap / distance); // Store the calculated render matrix for this holder pile object int randomNum = RandomHelper.GetRandomInt(6); landscape.AddObjectToRender( // Random banners randomNum == 0 ? "Banner" : randomNum == 1 ? "Banner2" : randomNum == 2 ? "Banner3" : randomNum == 3 ? "Banner4" : randomNum == 4 ? "Banner5" : "Banner6", // Scale them up to fit on the road Matrix.CreateScale(points[num].roadWidth) * Matrix.CreateTranslation(new Vector3(0, 0, -0.1f)) * // the ordinary transformation to the current point space pointSpace * // And finally we calculate to correct position where the palm // reaches exactly the holder gap Matrix.CreateTranslation(objPoint), // Enable this for shadow map generation true); // Remember this segment for easier checking later. checkpointSegmentPositions.Add(num); // We have just set a pile, the next pile will be set after // reaching the next holder gap. lastGap += CheckpointGap; } else if (signGap - distance <= 0 && num >= 25 && landscape != null) { Vector3 objPoint = Vector3.CatmullRom(p1, p2, p3, p4, signGap / distance); // Find out how curvy this point is by going back 25 points. Vector3 backPos = points[(num - 25) % points.Count].pos; // Calculate virtualBackPos as if the road were straight bool loopingAhead = points[(num + 60) % points.Count].up.Z < 0.15f; // Calc angle Vector3 angleVec = Vector3.Normalize(backPos - points[num].pos); float roadAngle = Vector3Helper.GetAngleBetweenVectors( angleVec, Vector3.Normalize(-points[num].dir)); // If road goes to the left, use negative angle value! if (Vector3.Distance(points[num].right, angleVec) < Vector3.Distance(-points[num].right, angleVec)) roadAngle = -roadAngle; // Now compare, if the backPos is more than 12 meters down, // add a warning sign. if (loopingAhead)//backPos.Z > virtualBackPos.Z + 20) { landscape.AddObjectToRender( "SignWarning", //Matrix.CreateRotationZ(-MathHelper.Pi/2.0f) * // Put it on the right side Matrix.CreateTranslation(new Vector3( points[num].roadWidth * TrackVertex.RoadWidthScale * 0.5f - 0.1f, 0, -0.25f)) * // the ordinary transformation to the current point space pointSpace * // And finally we calculate to correct position where the obj // reaches exactly the holder gap Matrix.CreateTranslation(objPoint), // Enable this for shadow map generation true); } // Else check if the angle less than -24 degrees (pi/7.5) else if (roadAngle < -MathHelper.Pi / 7.5f) { // Show right road sign landscape.AddObjectToRender( "SignCurveRight", Matrix.CreateRotationZ(MathHelper.Pi / 2.0f) * // Put it on the left side Matrix.CreateTranslation(new Vector3( -points[num].roadWidth * TrackVertex.RoadWidthScale * 0.5f - 0.15f, 0, -0.25f)) * // the ordinary transformation to the current point space pointSpace * // And finally we calculate to correct position where the obj // reaches exactly the holder gap Matrix.CreateTranslation(objPoint), // Enable this for shadow map generation true); } // Same for other side else if (roadAngle > MathHelper.Pi / 7.5f) { // Show right road sign landscape.AddObjectToRender( "SignCurveLeft", Matrix.CreateRotationZ(-MathHelper.Pi / 2.0f) * // Put it on the right side Matrix.CreateTranslation(new Vector3( points[num].roadWidth * TrackVertex.RoadWidthScale * 0.5f - 0.15f, 0, -0.25f)) * // the ordinary transformation to the current point space pointSpace * // And finally we calculate to correct position where the obj // reaches exactly the holder gap Matrix.CreateTranslation(objPoint), // Enable this for shadow map generation true); } // Also generate banner signs if roadAngle is at least 18 degrees else if (roadAngle < -MathHelper.Pi / 10.0f || roadAngle > MathHelper.Pi / 10.0f || // Randomly generate sign RandomHelper.GetRandomInt(9) == 4) { // Also mix in random curve signs, this is still a curve int rndValue = RandomHelper.GetRandomInt(3); // Randomize again if not that curvy here if (rndValue == 0 && Math.Abs(roadAngle) < MathHelper.Pi / 24) rndValue = RandomHelper.GetRandomInt(3); else if (Math.Abs(roadAngle) < MathHelper.Pi / 20 && RandomHelper.GetRandomInt(2) == 1) roadAngle *= -1; // Show right road sign landscape.AddObjectToRender( rndValue == 0 ? (roadAngle > 0 ? "SignCurveLeft" : "SignCurveRight") : (rndValue == 1 ? "Sign" : "Sign2"), Matrix.CreateRotationZ( (roadAngle > 0 ? -1 : 1) * MathHelper.Pi / 2.0f) * // Put it on the left side Matrix.CreateTranslation(new Vector3( (roadAngle > 0 ? 1 : -1) * points[num].roadWidth * TrackVertex.RoadWidthScale * 0.5f - (rndValue == 0 ? 0.15f : 0.005f), 0, -0.25f)) * // the ordinary transformation to the current point space pointSpace * // And finally we calculate to correct position where the obj // reaches exactly the holder gap Matrix.CreateTranslation(objPoint), // Enable this for shadow map generation true); } // We have just set a sign (or not), check for next sign after gap. signGap += SignGap; } // The distance we have to cover until the next position. // We subtract our current distance from the remaining gap distance, // which will then be checked in the next loop. lastGap -= distance; signGap -= distance; } // Randomly generate, but don't collide with existing objects // or the track! for (int num = 0; num < points.Count; num += 2) { if (landscape != null) { // Get landscape height here float landscapeHeight = landscape.GetMapHeight(points[num].pos.X, points[num].pos.Y); // Skip object generation at great heights! if (points[num].pos.Z - landscapeHeight > 60.0f) continue; } // The unit vectors for our local point space Vector3 right = points[num].right; Vector3 dir = points[num].dir; Vector3 up = points[num].up; // Find out if this is a looping bool upsideDown = up.Z < +0.05f; bool movingUp = dir.Z > 0.65f; bool movingDown = dir.Z < -0.65f; if (upsideDown || movingUp || movingDown) // Skip generation here! continue; // Show twice as many objects in high details mode int randomMaxPropability; if (BaseGame.HighDetail) randomMaxPropability = 5; else randomMaxPropability = 10; // Generate stuff in 20% of the cases if (RandomHelper.GetRandomInt(randomMaxPropability) == 0 && landscape != null) { // Get random name int randomObjNum = RandomHelper.GetRandomInt( landscape.autoGenerationNames.Length); // If above 6, generate again if (randomObjNum >= 6) randomObjNum = RandomHelper.GetRandomInt( landscape.autoGenerationNames.Length); // Don't generate so many casinos if (randomObjNum == landscape.autoGenerationNames.Length - 1 && RandomHelper.GetRandomInt(3) < 2) randomObjNum = RandomHelper.GetRandomInt( landscape.autoGenerationNames.Length); // Ok, generate float distance = RandomHelper.GetRandomFloat(26, 88); // For casinos make sure the object is far enough away. if (randomObjNum == landscape.autoGenerationNames.Length - 1) distance += 20; bool side = RandomHelper.GetRandomInt(2) == 0; float rotation = RandomHelper.GetRandomFloat(0, MathHelper.Pi * 2); landscape.AddObjectToRender( landscape.autoGenerationNames[randomObjNum], rotation, points[num].pos, points[num].right, distance * (side ? 1 : -1)); } } } /// /// Dispose /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Dispose /// /// Disposing protected virtual void Dispose(bool disposing) { if (disposing) { roadMaterial.Dispose(); roadBackMaterial.Dispose(); roadTunnelMaterial.Dispose(); roadCementMaterial.Dispose(); guardRailMaterial.Dispose(); roadVb.Dispose(); roadIb.Dispose(); roadBackVb.Dispose(); roadBackIb.Dispose(); roadTunnelVb.Dispose(); roadTunnelIb.Dispose(); leftRail.Dispose(); rightRail.Dispose(); columns.Dispose(); } } /// /// Render /// public void Render() { // We use tangent vertices for everything here // Restore the world matrix BaseGame.WorldMatrix = Matrix.Identity; // Make sure Anisotropic filtering is enabled for the road BaseGame.Device.SamplerStates[0] = SamplerState.AnisotropicWrap; //BaseGame.Device.SamplerStates[0].MinFilter = TextureFilter.Anisotropic; //BaseGame.Device.SamplerStates[0].MagFilter = TextureFilter.Anisotropic; //BaseGame.Device.SamplerStates[0].MipFilter = TextureFilter.Linear; //BaseGame.Device.SamplerStates[0].MaxAnisotropy = 8; // 8 is enough, isn't it? // Render the road itself ShaderEffect.normalMapping.Render( roadMaterial, // Use antrisopic filtering only if we have a fast GPU BaseGame.HighDetail ? "SpecularRoad20" : "Specular20", new BaseGame.RenderHandler(RenderRoadVertices)); // Render the road back hull ShaderEffect.normalMapping.Render( roadBackMaterial, // Use antrisopic filtering only if we have a fast GPU BaseGame.HighDetail ? "SpecularRoad20" : "Specular20", new BaseGame.RenderHandler(RenderRoadBackVertices)); // Render all tunnels (culling off) if (roadTunnelVb != null) { ShaderEffect.normalMapping.Render( roadTunnelMaterial, "Diffuse20", new BaseGame.RenderHandler(RenderRoadTunnelVertices)); } // Render all guard rails leftRail.Render(guardRailMaterial); rightRail.Render(guardRailMaterial); // Render all columns columns.Render(roadCementMaterial); } /// /// Render road vertices /// private void RenderRoadVertices() { BaseGame.Device.SetVertexBuffer(roadVb); BaseGame.Device.Indices = roadIb; BaseGame.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, (points.Count - 1) * 8); } /// /// Render road back vertices /// private void RenderRoadBackVertices() { BaseGame.Device.SetVertexBuffer(roadBackVb); BaseGame.Device.Indices = roadBackIb; BaseGame.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, (points.Count - 1) * 6); } /// /// Render road tunnel vertices /// private void RenderRoadTunnelVertices() { if (roadTunnelVb == null) return; // Disable culling (render tunnel from both sides) BaseGame.Device.RasterizerState = RasterizerState.CullNone; // Render vertices BaseGame.Device.SetVertexBuffer(roadTunnelVb); BaseGame.Device.Indices = roadTunnelIb; BaseGame.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, roadTunnelIndices.Length / 3); // Restore culling (default is always counter clockwise) BaseGame.Device.RasterizerState = RasterizerState.CullCounterClockwise; } /// /// Generate shadow /// public void GenerateShadow() { // Generate shadows for the road and the tunnel, ignore the road back ShaderEffect.shadowMapping.UpdateGenerateShadowWorldMatrix( Matrix.Identity); // We use tangent vertices for everything here // Disable culling (render road and tunnel from both sides, // this gives correct shadows to loopings, tunnels and overlappings) BaseGame.Device.RasterizerState = RasterizerState.CullNone; // Render road and tunnels RenderRoadVertices(); RenderRoadTunnelVertices(); // Generate shadows for both rails leftRail.GenerateShadow(); rightRail.GenerateShadow(); // And for all columns //not required, we don't see near columns anyway: //columns.GenerateShadow(); } /// /// Use shadow /// public void UseShadow() { // Receive shadow on the landscape, just render it out. ShaderEffect.shadowMapping.UpdateCalcShadowWorldMatrix( Matrix.Identity); RenderRoadVertices(); // Tunnel shadows are kinda important ^^ RenderRoadTunnelVertices(); // Guard rails do not need to receive shadow often, but it will look // very wrong if the car does not throw shadows at the guard rails. // For that reason lets include them. leftRail.UseShadow(); rightRail.UseShadow(); // And for all columns //not required, we don't see near columns anyway: //columns.UseShadow(); } /// /// Get track position matrix, put in a value between 0 and 1 and /// you get a position on the track (0=start, 1=end). /// /// Track position percent /// Road width /// Next road width /// Matrix public Matrix GetTrackPositionMatrix(float trackPositionPercent, out float roadWidth, out float nextRoadWidth) { // Make sure we are between 0 and 1 while (trackPositionPercent < 0) trackPositionPercent += 1; while (trackPositionPercent > 1) trackPositionPercent -= 1; int num = ((int)(trackPositionPercent * points.Count)) % points.Count; // Get position with catmul rom spline TrackVertex p1 = points[num - 1 < 0 ? points.Count - 1 : num - 1]; TrackVertex p2 = points[num]; TrackVertex p3 = points[(num + 1) % points.Count]; TrackVertex p4 = points[(num + 2) % points.Count]; float eachPointPercent = 1.0f / (float)points.Count; float pointPercent = (trackPositionPercent - num * eachPointPercent) / eachPointPercent; //dunno why this is bumpy //Log.Write("pointPercent="+pointPercent); Vector3 interpolatedPos = Vector3.CatmullRom( p1.pos, p2.pos, p3.pos, p4.pos, pointPercent); Vector3 interpolatedDir = Vector3.CatmullRom( p1.dir, p2.dir, p3.dir, p4.dir, pointPercent); Vector3 interpolatedRight = Vector3.CatmullRom( p1.right, p2.right, p3.right, p4.right, pointPercent); Vector3 interpolatedUp = Vector3.CatmullRom( p1.up, p2.up, p3.up, p4.up, pointPercent); // Build matrix with interpolated values and return it Matrix mat = Matrix.Identity; mat.Right = interpolatedRight; mat.Up = interpolatedUp; mat.Forward = interpolatedDir; mat.Translation = interpolatedPos; roadWidth = MathHelper.Lerp(p2.roadWidth, p3.roadWidth, pointPercent) * TrackVertex.RoadWidthScale; nextRoadWidth = p4.roadWidth * TrackVertex.RoadWidthScale; return mat;// * //Matrix.CreateTranslation(interpolatedPos); } /// /// Get track position matrix /// /// Track segment number /// Track segment percent /// Road width /// Next road width /// Matrix public Matrix GetTrackPositionMatrix( int trackSegmentNum, float trackSegmentPercent, out float roadWidth, out float nextRoadWidth) { // Make sure we are between 0 and 1 if (trackSegmentPercent < 0) trackSegmentPercent = 0; if (trackSegmentPercent > 1) trackSegmentPercent = 1; float pointPercent = trackSegmentPercent; int num = trackSegmentNum % points.Count; // Get position with catmul rom spline TrackVertex p1 = points[num - 1 < 0 ? points.Count - 1 : num - 1]; TrackVertex p2 = points[num]; TrackVertex p3 = points[(num + 1) % points.Count]; TrackVertex p4 = points[(num + 2) % points.Count]; Vector3 interpolatedPos = Vector3.CatmullRom( p1.pos, p2.pos, p3.pos, p4.pos, pointPercent); Vector3 interpolatedDir = Vector3.CatmullRom( p1.dir, p2.dir, p3.dir, p4.dir, pointPercent); Vector3 interpolatedRight = Vector3.CatmullRom( p1.right, p2.right, p3.right, p4.right, pointPercent); Vector3 interpolatedUp = Vector3.CatmullRom( p1.up, p2.up, p3.up, p4.up, pointPercent); // Build matrix with interpolated values and return it Matrix mat = Matrix.Identity; mat.Right = interpolatedRight; mat.Up = interpolatedUp; mat.Forward = interpolatedDir; mat.Translation = interpolatedPos; roadWidth = MathHelper.Lerp(p2.roadWidth, p3.roadWidth, pointPercent) * TrackVertex.RoadWidthScale; nextRoadWidth = //p4.roadWidth * TrackVertex.RoadWidthScale; MathHelper.Lerp(p3.roadWidth, p4.roadWidth, pointPercent) * TrackVertex.RoadWidthScale; return mat; } /// /// Update car track position /// /// Car position /// Track segment number /// Track position percent public void UpdateCarTrackPosition( Vector3 carPos, ref int trackSegmentNumber, ref float trackSegmentPercent) { // Make sure trackSegmentNumber is valid, its also easier working with // num instead of the long name trackSegmentNumber. int num = trackSegmentNumber; // Check until car is between trackSegmentNumber and the next segemnt bool gotCarInThisSegment = false; float thisPointDist = 0; float nextPointDist = 1; int maxNumberOfIterations = 100; do { TrackVertex thisPoint = points[num]; TrackVertex nextPoint = points[(num + 1) % points.Count]; // First check if car is behind trackSegmentNumber thisPointDist = Vector3Helper.SignedDistanceToPlane(carPos, thisPoint.pos, -thisPoint.dir); nextPointDist = Vector3Helper.SignedDistanceToPlane(carPos, nextPoint.pos, nextPoint.dir); if (thisPointDist < 0) num--; // Then check if we still are inside this segment else if (nextPointDist < 0) num++; else // Ok, we got it. gotCarInThisSegment = true; if (num < 0) num = points.Count - 1; if (num >= points.Count) num = 0; // Get outa here if we are above the max. iterations! if (maxNumberOfIterations-- < 0) return; } while (gotCarInThisSegment == false); trackSegmentNumber = num; // Btw: Is this a tunnel? Then disable lens flares! // Check every 10 frames to save a little performance. if (BaseGame.TotalFrames % 10 == 0) disableLensFlareInTunnel = IsTunnel(num); // Also calculate our track segment position float segmentLength = thisPointDist + nextPointDist; if (segmentLength == 0) trackSegmentPercent = 0; else trackSegmentPercent = thisPointDist / segmentLength; } /// /// Disable lens flare if we are inside a tunnel, looks wrong /// otherwise because we can't do occlusion querying in XNA yet. /// internal static bool disableLensFlareInTunnel = false; /// /// Is tunnel /// /// Track segment /// Bool public bool IsTunnel(int trackSegment) { // Check all tunnels for (int num = 0; num < helperPositions.Count; num++) { RoadHelperPosition tunnelPos = helperPositions[num]; if (tunnelPos.type == TrackData.RoadHelper.HelperType.Tunnel && trackSegment >= tunnelPos.startNum && trackSegment <= tunnelPos.endNum) return true; } // No tunnel found here return false; } } }