internal RoadCollection(RoadTopology topology, AssetLoadContext loadContext, HeightMap heightMap) : this() { // The map stores road segments with no connectivity: // - a segment is from point A to point B // - with a road type name // - and start and end curve types (angled, tight curve, broad curve). // The goal is to create road networks of connected road segments, // where a network has only a single road type. // A road network is composed of 2 or more nodes. // A network is a (potentially) cyclic graph. // A road node has > 1 and <= 4 edges connected to it. // A node can be part of multiple networks. // An edge can only exist in one network. // TODO: If a node stored in the map has > 4 edges, the extra edges // are put into a separate network. var networks = topology.BuildNetworks(); foreach (var network in networks) { foreach (var edge in network.Edges) { var startPosition = edge.Start.TopologyNode.Position; var endPosition = edge.End.TopologyNode.Position; startPosition.Z += heightMap.GetHeight(startPosition.X, startPosition.Y); endPosition.Z += heightMap.GetHeight(endPosition.X, endPosition.Y); _roads.Add(AddDisposable(new Road( loadContext, heightMap, edge.TopologyEdge.Template, startPosition, endPosition))); } } }
internal Road( ContentManager contentManager, HeightMap heightMap, RoadTemplate template, Vector3 startPosition, Vector3 endPosition) { const float heightBias = 1f; const float createNewVerticesHeightDeltaThreshold = 0.002f; var distance = Vector3.Distance(startPosition, endPosition); var direction = Vector3.Normalize(endPosition - startPosition); var centerToEdgeDirection = Vector3.Cross(Vector3.UnitZ, direction); var up = Vector3.Cross(direction, centerToEdgeDirection); var halfWidth = template.RoadWidth / 2; var textureAtlasSplit = 1 / 3f; var vertices = new List <RoadVertex>(); // Step along road segment in units of 10. If the delta between // (a) the straight line from previous point to finish and // (b) the actual height of the terrain at this point // is > a threshold, create extra vertices. // TODO: I don't know if this is the right algorithm. void AddVertexPair(in Vector3 position, float distanceAlongRoad) { var u = distanceAlongRoad / 50; var p0 = position - centerToEdgeDirection * halfWidth; p0.Z += heightBias; vertices.Add(new RoadVertex { Position = p0, Normal = up, UV = new Vector2(u, 0) }); var p1 = position + centerToEdgeDirection * halfWidth; p1.Z += heightBias; vertices.Add(new RoadVertex { Position = p1, Normal = up, UV = new Vector2(u, textureAtlasSplit) }); } AddVertexPair(startPosition, 0); var previousPoint = startPosition; var previousPointDistance = 0; for (var currentDistance = 10; currentDistance < distance; currentDistance += 10) { var position = startPosition + direction * currentDistance; var actualHeight = heightMap.GetHeight(position.X, position.Y); var interpolatedHeight = MathUtility.Lerp(previousPoint.Z, endPosition.Z, (currentDistance - previousPointDistance) / distance); if (Math.Abs(actualHeight - interpolatedHeight) > createNewVerticesHeightDeltaThreshold) { AddVertexPair(position, currentDistance); previousPoint = position; previousPointDistance = currentDistance; } } // Add last chunk. AddVertexPair(endPosition, distance); _boundingBox = BoundingBox.CreateFromPoints(vertices.Select(x => x.Position)); _vertexBuffer = AddDisposable(contentManager.GraphicsDevice.CreateStaticBuffer( vertices.ToArray(), BufferUsage.VertexBuffer)); var indices = new List <ushort>(); for (var i = 0; i < vertices.Count - 2; i += 2) { indices.Add((ushort)(i + 0)); indices.Add((ushort)(i + 1)); indices.Add((ushort)(i + 2)); indices.Add((ushort)(i + 1)); indices.Add((ushort)(i + 2)); indices.Add((ushort)(i + 3)); } _numIndices = (uint)indices.Count; _indexBuffer = AddDisposable(contentManager.GraphicsDevice.CreateStaticBuffer( indices.ToArray(), BufferUsage.IndexBuffer)); _material = AddDisposable(new RoadMaterial( contentManager, contentManager.EffectLibrary.Road)); var texture = contentManager.Load <Texture>(Path.Combine("Art", "Textures", template.Texture)); _material.SetTexture(texture); }