/// <summary> /// Clamps a road path to the terrain height. /// </summary> /// <param name="road">The path that represents the road.</param> /// <param name="terrain">The terrain represented by a <see cref="HeightField"/>.</param> /// <remarks> /// The y position of each path key is set to the terrain height at the xz position. /// </remarks> /// <exception cref="ArgumentNullException"> /// <paramref name="terrain"/> is <see langword="null"/>. /// </exception> public static void ClampRoadToTerrain(Path3F road, HeightField terrain) { if (road == null) return; if (terrain == null) throw new ArgumentNullException("terrain"); foreach (var key in road) { Vector3F position = key.Point; float height = terrain.GetHeight(position.X, position.Z); if (!Numeric.IsNaN(height)) { position.Y = height; key.Point = position; } } }
public void Sort() { Path3F empty = new Path3F(); empty.Sort(); Path3F path = CreatePath(); // Un-sort the keys. path[7].Parameter = 13; path.Sort(); Assert.AreEqual(8, path.Count); Assert.AreEqual(10, path[0].Parameter); Assert.AreEqual(13, path[1].Parameter); Assert.AreEqual(15, path[2].Parameter); // ... Assert.AreEqual(35, path[7].Parameter); }
public void GetLength() { Path3F empty = new Path3F(); empty.Sort(); Assert.AreEqual(0, empty.GetLength(0, 1, 100, 0.0001f)); Path3F path = CreatePath(); path.PreLoop = CurveLoopType.Constant; path.PostLoop = CurveLoopType.Oscillate; Assert.IsTrue(Numeric.AreEqual((new Vector3F(0, 0, 1) - new Vector3F(1, 2, 3)).Length, path.GetLength(-1, 12, 100, 0.0001f), 0.001f)); Assert.IsTrue(Numeric.AreEqual((new Vector3F(0, 0, 1) - new Vector3F(1, 2, 3)).Length, path.GetLength(-1, 20, 100, 0.0001f), 0.001f)); CatmullRomSegment3F catmullOscillate = new CatmullRomSegment3F() { Point1 = new Vector3F(10, 12, 14), Point2 = new Vector3F(10, 14, 8), Point3 = new Vector3F(20, 14, 8), Point4 = new Vector3F(30, 14, 8), }; float desiredLength = catmullOscillate.GetLength(0, 1, 20, 0.0001f); float actualLength = path.GetLength(40, 50, 20, 0.0001f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.001f)); desiredLength = catmullOscillate.GetLength(1, 0.8f, 20, 0.0001f); actualLength = path.GetLength(52, 50, 20, 0.0001f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.001f)); desiredLength = catmullOscillate.GetLength(1, 0.8f, 20, 0.0001f) * 2; actualLength = path.GetLength(52, 48, 20, 0.0001f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.001f)); path.PreLoop = CurveLoopType.Linear; path.PostLoop = CurveLoopType.Cycle; path.PreLoop = CurveLoopType.Cycle; path.PostLoop = CurveLoopType.CycleOffset; path.PreLoop = CurveLoopType.CycleOffset; path.PostLoop = CurveLoopType.Linear; path.PreLoop = CurveLoopType.Oscillate; path.PostLoop = CurveLoopType.Constant; }
/// <summary> /// Clamps a road path to the terrain height. /// </summary> /// <param name="road">The path that represents the road.</param> /// <param name="terrain">The terrain represented by a <see cref="HeightField"/>.</param> /// <remarks> /// The y position of each path key is set to the terrain height at the xz position. /// </remarks> /// <exception cref="ArgumentNullException"> /// <paramref name="terrain"/> is <see langword="null"/>. /// </exception> public static void ClampRoadToTerrain(Path3F road, HeightField terrain) { if (road == null) { return; } if (terrain == null) { throw new ArgumentNullException("terrain"); } foreach (var key in road) { Vector3F position = key.Point; float height = terrain.GetHeight(position.X, position.Z); if (!Numeric.IsNaN(height)) { position.Y = height; key.Point = position; } } }
// Add a random path. private void CreateRandomPath() { var path = new Path3F(); var point = new Vector3F(0, 0, 0); path.Add(new PathKey3F { Interpolation = SplineInterpolation.CatmullRom, Parameter = 0, Point = point }); for (int i = 1; i < 10; i++) { point += RandomHelper.Random.NextQuaternionF().Rotate(new Vector3F(0, 0.5f, 0)); path.Add(new PathKey3F { Interpolation = SplineInterpolation.CatmullRom, Parameter = i, Point = point }); } var pathFigure = new PathFigure3F(); pathFigure.Segments.Add(path); var pathLineNode = new FigureNode(pathFigure) { Name = "RandomPath", PoseLocal = new Pose(new Vector3F(4, 1, 2)), StrokeThickness = 3, StrokeColor = new Vector3F(0.5f, 0.3f, 1), StrokeAlpha = 1f, DashInWorldSpace = true, StrokeDashPattern = new Vector4F(10, 1, 1, 1) / 100, }; _scene.Children.Add(pathLineNode); }
public void GetKey() { Path3F empty = new Path3F(); empty.Sort(); Assert.AreEqual(-1, empty.GetKeyIndex(20)); Path3F path = CreatePath(); path.PreLoop = CurveLoopType.Constant; path.PostLoop = CurveLoopType.Oscillate; Assert.AreEqual(0, path.GetKeyIndex(-28)); Assert.AreEqual(0, path.GetKeyIndex(3)); Assert.AreEqual(0, path.GetKeyIndex(10)); Assert.AreEqual(3, path.GetKeyIndex(20)); Assert.AreEqual(4, path.GetKeyIndex(28)); Assert.AreEqual(7, path.GetKeyIndex(40)); Assert.AreEqual(6, path.GetKeyIndex(42)); Assert.AreEqual(2, path.GetKeyIndex(78)); path.PreLoop = CurveLoopType.Linear; path.PostLoop = CurveLoopType.Cycle; Assert.AreEqual(-1, path.GetKeyIndex(-28)); Assert.AreEqual(-1, path.GetKeyIndex(3)); Assert.AreEqual(0, path.GetKeyIndex(10)); Assert.AreEqual(3, path.GetKeyIndex(20)); Assert.AreEqual(4, path.GetKeyIndex(28)); Assert.AreEqual(7, path.GetKeyIndex(40)); Assert.AreEqual(0, path.GetKeyIndex(42)); Assert.AreEqual(2, path.GetKeyIndex(78)); path.PreLoop = CurveLoopType.Cycle; path.PostLoop = CurveLoopType.CycleOffset; Assert.AreEqual(5, path.GetKeyIndex(-28)); Assert.AreEqual(5, path.GetKeyIndex(3)); Assert.AreEqual(0, path.GetKeyIndex(10)); Assert.AreEqual(3, path.GetKeyIndex(20)); Assert.AreEqual(4, path.GetKeyIndex(28)); Assert.AreEqual(7, path.GetKeyIndex(40)); Assert.AreEqual(0, path.GetKeyIndex(42)); Assert.AreEqual(2, path.GetKeyIndex(78)); path.PreLoop = CurveLoopType.CycleOffset; path.PostLoop = CurveLoopType.Linear; Assert.AreEqual(5, path.GetKeyIndex(-28)); Assert.AreEqual(5, path.GetKeyIndex(3)); Assert.AreEqual(0, path.GetKeyIndex(10)); Assert.AreEqual(3, path.GetKeyIndex(20)); Assert.AreEqual(4, path.GetKeyIndex(28)); Assert.AreEqual(7, path.GetKeyIndex(40)); Assert.AreEqual(7, path.GetKeyIndex(42)); Assert.AreEqual(7, path.GetKeyIndex(78)); path.PreLoop = CurveLoopType.Oscillate; path.PostLoop = CurveLoopType.Constant; Assert.AreEqual(5, path.GetKeyIndex(-28)); Assert.AreEqual(1, path.GetKeyIndex(3)); Assert.AreEqual(0, path.GetKeyIndex(10)); Assert.AreEqual(3, path.GetKeyIndex(20)); Assert.AreEqual(4, path.GetKeyIndex(28)); Assert.AreEqual(7, path.GetKeyIndex(40)); Assert.AreEqual(7, path.GetKeyIndex(42)); Assert.AreEqual(7, path.GetKeyIndex(78)); }
private void CreatePath() { // Create a cyclic path. (More information on paths can be found in the DigitalRune // Mathematics documentation and related samples.) _path = new Path3F { SmoothEnds = true, PreLoop = CurveLoopType.Cycle, PostLoop = CurveLoopType.Cycle }; // The curvature of the path is defined by a number of path keys. _path.Add(new PathKey3F { Parameter = 0, // The path parameter defines position of the path key on the curve. Point = new Vector3(-4, 0.5f, -3), // The world space position of the path key. Interpolation = SplineInterpolation.CatmullRom, // The type of interpolation that is used between this path key and the next. }); _path.Add(new PathKey3F { Parameter = 1, Point = new Vector3(-1, 0.5f, -5), Interpolation = SplineInterpolation.CatmullRom, }); _path.Add(new PathKey3F { Parameter = 2, Point = new Vector3(3, 0.5f, -4), Interpolation = SplineInterpolation.CatmullRom, }); _path.Add(new PathKey3F { Parameter = 3, Point = new Vector3(0, 0.5f, 0), Interpolation = SplineInterpolation.CatmullRom, }); _path.Add(new PathKey3F { Parameter = 4, Point = new Vector3(-3, 0.5f, 3), Interpolation = SplineInterpolation.CatmullRom, }); _path.Add(new PathKey3F { Parameter = 5, Point = new Vector3(-1, 0.5f, 5), Interpolation = SplineInterpolation.CatmullRom, }); _path.Add(new PathKey3F { Parameter = 6, Point = new Vector3(0, 0.5f, 0), Interpolation = SplineInterpolation.CatmullRom, }); // The last key uses the same position as the first key to create a closed path. PathKey3F lastKey = new PathKey3F { Parameter = _path.Count, Point = _path[0].Point, Interpolation = SplineInterpolation.CatmullRom, }; _path.Add(lastKey); // The current path parameter goes from 0 to 7. This path parameter is not linearly // proportional to the path length. This is not suitable for animations. // To move an object with constant speed along a path, the path parameter should // be linearly proportional to the length of the path. // ParameterizeByLength() changes the path parameter so that the path parameter // at the each key is equal to the length of path (measured from the first key position // to the current key position). // ParameterizeByLength() uses an iterative process, we end the process after 10 // iterations or when the error is less than 0.01f. _path.ParameterizeByLength(10, 0.01f); // Sample the path for rendering. int numberOfSamples = _pointList.Length - 1; float pathLength = _path.Last().Parameter; for (int i = 0; i <= numberOfSamples; i++) { Vector3 pointOnPath = _path.GetPoint(pathLength / numberOfSamples * i); _pointList[i] = pointOnPath; } }
public void SerializationXml() { PathKey3F pathKey1 = new PathKey3F { Interpolation = SplineInterpolation.Bezier, Parameter = 56.7f, Point = new Vector3F(1.2f, 3.4f, 5.6f), TangentIn = new Vector3F(0.7f, 2.6f, 5.1f), TangentOut = new Vector3F(1.9f, 3.3f, 5.9f) }; PathKey3F pathKey2 = new PathKey3F { Interpolation = SplineInterpolation.Hermite, Parameter = 66.7f, Point = new Vector3F(2.2f, 1.4f, 6.6f), TangentIn = new Vector3F(1.7f, 3.6f, 4.1f), TangentOut = new Vector3F(2.9f, 2.3f, 6.9f) }; Path3F path = new Path3F { pathKey1, pathKey2 }; path.PreLoop = CurveLoopType.Cycle; path.PostLoop = CurveLoopType.CycleOffset; path.SmoothEnds = true; const string fileName = "SerializationPath3F.xml"; if (File.Exists(fileName)) File.Delete(fileName); XmlSerializer serializer = new XmlSerializer(typeof(Path3F)); StreamWriter writer = new StreamWriter(fileName); serializer.Serialize(writer, path); writer.Close(); serializer = new XmlSerializer(typeof(Path3F)); FileStream fileStream = new FileStream(fileName, FileMode.Open); path = (Path3F)serializer.Deserialize(fileStream); Assert.AreEqual(2, path.Count); MathAssert.AreEqual(pathKey1, path[0]); MathAssert.AreEqual(pathKey2, path[1]); Assert.AreEqual(CurveLoopType.Cycle, path.PreLoop); Assert.AreEqual(CurveLoopType.CycleOffset, path.PostLoop); Assert.AreEqual(true, path.SmoothEnds); }
private void CreateRoad() { //RandomHelper.Random = new Random(1234567); // Set isClosed to true join the start and the end of the road. bool isClosed = false; // Create a new TerrainRoadLayer which paints a road onto the terrain. // The road itself is defined by a mesh which is set later. _roadLayer = new TerrainRoadLayer(GraphicsService) { DiffuseColor = new Vector3F(0.5f), SpecularColor = new Vector3F(1), DiffuseTexture = ContentManager.Load<Texture2D>("Terrain/Road-Asphalt-Diffuse"), NormalTexture = ContentManager.Load<Texture2D>("Terrain/Road-Asphalt-Normal"), SpecularTexture = ContentManager.Load<Texture2D>("Terrain/Road-Asphalt-Specular"), HeightTexture = ContentManager.Load<Texture2D>("Terrain/Road-Asphalt-Height"), // The size of the tileable detail textures in world space units. TileSize = 5, // The border blend range controls how the border of the road fades out. // We fade out 5% of the texture on each side of the road. BorderBlendRange = new Vector4F(0.05f, 0.05f, 0.05f, 0.05f), }; // Create 3D spline path with some semi-random control points. _roadPath = new Path3F { PreLoop = isClosed ? CurveLoopType.Cycle : CurveLoopType.Linear, PostLoop = isClosed ? CurveLoopType.Cycle : CurveLoopType.Linear, SmoothEnds = isClosed, }; // The position of the next path key. Vector3F position = new Vector3F( RandomHelper.Random.NextFloat(-20, 20), 0, RandomHelper.Random.NextFloat(-20, 20)); // The direction to the next path key. Vector3F direction = QuaternionF.CreateRotationY(RandomHelper.Random.NextFloat(0, 10)).Rotate(Vector3F.Forward); // Add path keys. for (int j = 0; j < 10; j++) { // Instead of a normal PathKey3F, we use a TerrainRoadPathKey which allows to control // the road with and the side falloff. var key = new TerrainRoadPathKey { Interpolation = SplineInterpolation.CatmullRom, Parameter = j, Point = position, // The width of the road at the path key. Width = RandomHelper.Random.NextFloat(6, 10), // The side falloff (which is used in ClampTerrainToRoad to blend the height values with // the road). SideFalloff = RandomHelper.Random.NextFloat(20, 40), }; _roadPath.Add(key); // Get next random position and direction. position += direction * RandomHelper.Random.NextFloat(20, 40); position.Y += RandomHelper.Random.NextFloat(-2, 2); direction = QuaternionF.CreateRotationY(RandomHelper.Random.NextFloat(-1, 1)) .Rotate(direction); } if (isClosed) { // To create a closed path the first and the last key should be identical. _roadPath[_roadPath.Count - 1].Point = _roadPath[0].Point; ((TerrainRoadPathKey)_roadPath[_roadPath.Count - 1]).Width = ((TerrainRoadPathKey)_roadPath[0]).Width; ((TerrainRoadPathKey)_roadPath[_roadPath.Count - 1]).SideFalloff = ((TerrainRoadPathKey)_roadPath[0]).SideFalloff; // Since the path is closed we do not have to fade out the start and the end of the road. _roadLayer.BorderBlendRange *= new Vector4F(1, 0, 1, 0); } // Convert the path to a mesh. Submesh roadSubmesh; Aabb roadAabb; float roadLength; TerrainRoadLayer.CreateMesh( GraphicsService.GraphicsDevice, _roadPath, 4, 10, 0.1f, out roadSubmesh, out roadAabb, out roadLength); // Set the mesh in the road layer. _roadLayer.SetMesh(roadSubmesh, roadAabb, roadLength, true); if (isClosed) { // When the path is closed, the end texture and the start texture coordinates should // match. This is the case if (roadLength / tileSize) is an integer. var numberOfTiles = (int)(roadLength / _roadLayer.TileSize); _roadLayer.TileSize = roadLength / numberOfTiles; } // The road layer is added to the layers of a tile. We have to add the road to each terrain // tile which it overlaps. foreach (var tile in _terrainObject.TerrainNode.Terrain.Tiles) if (GeometryHelper.HaveContact(tile.Aabb, _roadLayer.Aabb.Value)) tile.Layers.Add(_roadLayer); }
// Creates a random 3D path. private void CreatePath() { // Create a cyclic path. _path = new Path3F { PreLoop = CurveLoopType.Cycle, PostLoop = CurveLoopType.Cycle, SmoothEnds = true }; // Add random path key points. for (int i = 0; i < 5; i++) { float x = RandomHelper.Random.NextFloat(-3, 3); float y = RandomHelper.Random.NextFloat(1, 3); float z = RandomHelper.Random.NextFloat(-3, 0); var key = new PathKey3F { Parameter = i, Point = new Vector3F(x, y, z), Interpolation = SplineInterpolation.CatmullRom }; _path.Add(key); } // The last key uses the same position as the first key to create a closed path. var lastKey = new PathKey3F { Parameter = _path.Count, Point = _path[0].Point, Interpolation = SplineInterpolation.CatmullRom, }; _path.Add(lastKey); // The current path parameter goes from 0 to 5. This path parameter is not linearly // proportional to the path length. This is not suitable for animations. // To move an object with constant speed along the path, the path parameter should // be linearly proportional to the length of the path. // ParameterizeByLength() changes the path parameter so that the path parameter // at the each key is equal to the length of path (measured from the first key position // to the current key position). // ParameterizeByLength() uses and iterative process, we end the process after 10 // iterations or when the error is less than 0.001f. _path.ParameterizeByLength(10, 0.001f); // Now, the parameter of the first key (_path[0]) is unchanged. // The parameter of the second key (_path[1]) is equal to the length of the path // from the first key to the second key. // The parameter of the third key (_path[2]) is equal to the length of the path // from the first key to the third key. // And so on. // The parameter of the last key is equal to the length of the whole path: // float pathLength = _path[_path.Count - 1].Parameter; // Important: The path parameter is now equal to the path length at the path keys. // But in general between path keys the path parameter is not linearly proportional // to the path length. This is due to the nature of splines. // // Example: // Lets assume the second path key is at path length 100 and the third key is // at path length 200. // If we call _path.GetPoint(100), we get the position of the second key. // If we call _path.GetPoint(200), we get the position ot the third key. // We can call _path.GetPoint(130) to get a position on the path between the second and // third key. But it is not guaranteed that the path is exactly 130 long at this position. // We only know that the point is somewhere between 100 and 200 path length. // // To get the path point at exactly the distance 130 from the path start, we have to call // float parameter = _path.GetParameterFromLength(130, 10, 0.01f); // This uses an iterative root finding process to find the path parameter where the // path length is 130. // Then we can get the path position with // Vector3F pathPointAt130Length = _path.GetPoint(parameter). }
private void CreatePath() { // Create a cyclic path. (More information on paths can be found in the DigitalRune // Mathematics documentation and related samples.) _path = new Path3F { SmoothEnds = true, PreLoop = CurveLoopType.Cycle, PostLoop = CurveLoopType.Cycle }; // The curvature of the path is defined by a number of path keys. _path.Add(new PathKey3F { Parameter = 0, // The path parameter defines position of the path key on the curve. Point = new Vector3F(-4, 0.5f, -3), // The world space position of the path key. Interpolation = SplineInterpolation.CatmullRom, // The type of interpolation that is used between this path key and the next. }); _path.Add(new PathKey3F { Parameter = 1, Point = new Vector3F(-1, 0.5f, -5), Interpolation = SplineInterpolation.CatmullRom, }); _path.Add(new PathKey3F { Parameter = 2, Point = new Vector3F(3, 0.5f, -4), Interpolation = SplineInterpolation.CatmullRom, }); _path.Add(new PathKey3F { Parameter = 3, Point = new Vector3F(0, 0.5f, 0), Interpolation = SplineInterpolation.CatmullRom, }); _path.Add(new PathKey3F { Parameter = 4, Point = new Vector3F(-3, 0.5f, 3), Interpolation = SplineInterpolation.CatmullRom, }); _path.Add(new PathKey3F { Parameter = 5, Point = new Vector3F(-1, 0.5f, 5), Interpolation = SplineInterpolation.CatmullRom, }); _path.Add(new PathKey3F { Parameter = 6, Point = new Vector3F(0, 0.5f, 0), Interpolation = SplineInterpolation.CatmullRom, }); // The last key uses the same position as the first key to create a closed path. PathKey3F lastKey = new PathKey3F { Parameter = _path.Count, Point = _path[0].Point, Interpolation = SplineInterpolation.CatmullRom, }; _path.Add(lastKey); // The current path parameter goes from 0 to 7. This path parameter is not linearly // proportional to the path length. This is not suitable for animations. // To move an object with constant speed along a path, the path parameter should // be linearly proportional to the length of the path. // ParameterizeByLength() changes the path parameter so that the path parameter // at the each key is equal to the length of path (measured from the first key position // to the current key position). // ParameterizeByLength() uses an iterative process, we end the process after 10 // iterations or when the error is less than 0.01f. _path.ParameterizeByLength(10, 0.01f); // Sample the path for rendering. int numberOfSamples = _pointList.Length - 1; float pathLength = _path.Last().Parameter; for (int i = 0; i <= numberOfSamples; i++) { Vector3F pointOnPath = _path.GetPoint(pathLength / numberOfSamples * i); _pointList[i] = pointOnPath; } }
public static void ClampTerrainToRoad(HeightField terrain, Path3F road, float defaultWidth, float defaultSideFalloff, int maxNumberOfIterations, float tolerance) { if (terrain == null) throw new ArgumentNullException("terrain"); if (road == null) throw new ArgumentNullException("road"); // Compute list of line segments. (2 points per line segment!) var flattenedPoints = new List<Vector3F>(); road.Flatten(flattenedPoints, maxNumberOfIterations, tolerance); // Abort if path is empty. int numberOfLineSegments = flattenedPoints.Count / 2; if (numberOfLineSegments <= 0) return; // Compute accumulated lengths. (One entry for each entry in flattenedPoints.) float[] accumulatedLengths = new float[flattenedPoints.Count]; accumulatedLengths[0] = 0; for (int i = 1; i < flattenedPoints.Count; i += 2) { Vector3F previous = flattenedPoints[i - 1]; Vector3F current = flattenedPoints[i]; float length = (current - previous).Length; accumulatedLengths[i] = accumulatedLengths[i - 1] + length; if (i + 1 < flattenedPoints.Count) accumulatedLengths[i + 1] = accumulatedLengths[i]; } // Create a mapping between accumulatedLength and the path keys. // (accumulatedLength --> key) var pathLengthsAndKeys = new List<Pair<float, TerrainRoadPathKey>>(); { int index = 0; foreach (var key in road) { Vector3F position = key.Point; var roadKey = key as TerrainRoadPathKey; if (roadKey == null) { roadKey = new TerrainRoadPathKey { Point = key.Point, Width = defaultWidth, SideFalloff = defaultSideFalloff, }; } for (; index < flattenedPoints.Count; index++) { if (Vector3F.AreNumericallyEqual(position, flattenedPoints[index])) { pathLengthsAndKeys.Add(new Pair<float, TerrainRoadPathKey>(accumulatedLengths[index], roadKey)); break; } bool isLastLineSegment = (index + 2 == flattenedPoints.Count); if (!isLastLineSegment) index++; } index++; } } // Create a list of interpolated road widths and side falloffs. (One entry for each entry in flattenedPoints.) var halfWidths = new float[flattenedPoints.Count]; var sideFalloffs = new float[flattenedPoints.Count]; int previousKeyIndex = 0; var previousKey = pathLengthsAndKeys[0]; var nextKey = pathLengthsAndKeys[1]; halfWidths[0] = 0.5f * pathLengthsAndKeys[0].Second.Width; sideFalloffs[0] = pathLengthsAndKeys[0].Second.SideFalloff; for (int i = 1; i < flattenedPoints.Count; i += 2) { if (accumulatedLengths[i] > nextKey.First) { previousKey = nextKey; previousKeyIndex++; nextKey = pathLengthsAndKeys[previousKeyIndex + 1]; } float p = (accumulatedLengths[i] - previousKey.First) / (nextKey.First - previousKey.First); halfWidths[i] = 0.5f * InterpolationHelper.Lerp(previousKey.Second.Width, nextKey.Second.Width, p); sideFalloffs[i] = InterpolationHelper.Lerp(previousKey.Second.SideFalloff, nextKey.Second.SideFalloff, p); if (i + 1 < flattenedPoints.Count) { halfWidths[i + 1] = halfWidths[i]; sideFalloffs[i + 1] = sideFalloffs[i]; } } // Get AABB of road with the side falloff. Aabb aabbWithSideFalloffs; { Vector3F p = flattenedPoints[0]; float r = halfWidths[0] + sideFalloffs[0]; aabbWithSideFalloffs = new Aabb(new Vector3F(p.X - r, 0, p.Z - r), new Vector3F(p.X + r, 0, p.Z + r)); for (int i = 1; i < flattenedPoints.Count; i += 2) { p = flattenedPoints[i]; r = halfWidths[i] + sideFalloffs[i]; aabbWithSideFalloffs.Grow(new Vector3F(p.X - r, 0, p.Z - r)); aabbWithSideFalloffs.Grow(new Vector3F(p.X + r, 0, p.Z + r)); } } // Terrain properties. int numberOfSamplesX = terrain.NumberOfSamplesX; int numberOfSamplesZ = terrain.NumberOfSamplesZ; int numberOfCellsX = numberOfSamplesX - 1; int numberOfCellsZ = numberOfSamplesZ - 1; float widthX = terrain.WidthX; float cellSizeX = widthX / numberOfCellsX; float widthZ = terrain.WidthZ; float cellSizeZ = widthZ / numberOfCellsZ; float cellSizeDiagonal = (float)Math.Sqrt(cellSizeX * cellSizeX + cellSizeZ * cellSizeZ); bool isClosed = Vector3F.AreNumericallyEqual(flattenedPoints[0], flattenedPoints[flattenedPoints.Count - 1]); { // Get the line segments which of the road border. List<Vector4F> segments = new List<Vector4F>(); // 2 points per segment. Vector3F lastOrthonormal = Vector3F.Right; Vector4F previousV1 = Vector4F.Zero; Vector4F previousV2 = Vector4F.Zero; for (int i = 0; i < flattenedPoints.Count; i++) { Vector3F start = flattenedPoints[i]; Vector3F previous; bool isFirstPoint = (i == 0); if (!isFirstPoint) previous = flattenedPoints[i - 1]; else if (isClosed && road.SmoothEnds) previous = flattenedPoints[flattenedPoints.Count - 2]; else previous = start; Vector3F next; bool isLastPoint = (i + 1 == flattenedPoints.Count); if (!isLastPoint) next = flattenedPoints[i + 1]; else if (isClosed && road.SmoothEnds) next = flattenedPoints[1]; else next = start; Vector3F tangent = next - previous; Vector3F orthonormal = new Vector3F(tangent.Z, 0, -tangent.X); if (!orthonormal.TryNormalize()) orthonormal = lastOrthonormal; // Add 2 vertices two segments for the road side border. // // pV1 pV2 (previous vertices) // x x // | | // x x // v1 v2 (current vertices) // // We store the side falloff with the vertex: // Vectors are 4D. Height is y. Side falloff is w. Vector4F v1 = new Vector4F(start - orthonormal * (halfWidths[i] + 0), sideFalloffs[i]); Vector4F v2 = new Vector4F(start + orthonormal * (halfWidths[i] + 0), sideFalloffs[i]); if (i > 0) { segments.Add(previousV1); segments.Add(v1); segments.Add(previousV2); segments.Add(v2); if (isLastPoint && !isClosed) { // A segment for the end of the road. segments.Add(v1); segments.Add(v2); } } else { if (!isClosed) { // A segment for the start of the road. segments.Add(v1); segments.Add(v2); } } previousV1 = v1; previousV2 = v2; lastOrthonormal = orthonormal; // The flattened points list contains 2 points per line segment, which means that there // are duplicate intermediate points, which we skip. bool isLastLineSegment = (i + 2 == flattenedPoints.Count); if (!isLastLineSegment) i++; } // Apply the side falloff to the terrain heights. // We use a padding where the road influence is 100% because we want road width to be flat // but that means that we also have to set some triangle vertices outside the road width to // full 100% road height. float padding = cellSizeDiagonal; ClampHeightsToLineSegments(terrain, aabbWithSideFalloffs, segments, padding); } // Clamp the terrain heights to the inner part of the road. // We create quads for the road mesh and clamp the heights to the quad triangles. { Vector3F previousV1 = Vector3F.Zero; Vector3F previousV2 = Vector3F.Zero; Vector3F lastOrthonormal = Vector3F.Right; for (int i = 0; i < flattenedPoints.Count; i++) { Vector3F start = flattenedPoints[i]; Vector3F previous; bool isFirstPoint = (i == 0); if (!isFirstPoint) previous = flattenedPoints[i - 1]; else if (isClosed && road.SmoothEnds) previous = flattenedPoints[flattenedPoints.Count - 2]; else previous = start; Vector3F next; bool isLastPoint = (i + 1 == flattenedPoints.Count); if (!isLastPoint) next = flattenedPoints[i + 1]; else if (isClosed && road.SmoothEnds) next = flattenedPoints[1]; else next = start; Vector3F tangent = next - previous; Vector3F orthonormal = new Vector3F(tangent.Z, 0, -tangent.X); if (!orthonormal.TryNormalize()) orthonormal = lastOrthonormal; // Add 2 vertices to create a mesh like this: // // pV1 pV2 (previous vertices) // x---------------x // | | // x---------------x // v1 v2 (current vertices) // // Then we check all height samples against these triangles. // Vectors are 4D. Height is y. Influence is w. Vector3F v1 = start - orthonormal * halfWidths[i]; Vector3F v2 = start + orthonormal * halfWidths[i]; if (i > 0) ClampHeightsToQuad(terrain, previousV1, previousV2, v1, v2); previousV1 = v1; previousV2 = v2; lastOrthonormal = orthonormal; // The flattened points list contains 2 points per line segment, which means that there // are duplicate intermediate points, which we skip. bool isLastLineSegment = (i + 2 == flattenedPoints.Count); if (!isLastLineSegment) i++; } } terrain.Invalidate(); }
public static void CreateMesh(GraphicsDevice graphicsDevice, Path3F path, float defaultWidth, int maxNumberOfIterations, float tolerance, out Submesh submesh, out Aabb aabb, out float roadLength) { if (graphicsDevice == null) throw new ArgumentNullException("graphicsDevice"); if (path == null) throw new ArgumentNullException("path"); // Compute list of line segments. (2 points per line segment!) var flattenedPoints = new List<Vector3F>(); path.Flatten(flattenedPoints, maxNumberOfIterations, tolerance); // Abort if path is empty. int numberOfLineSegments = flattenedPoints.Count / 2; if (numberOfLineSegments <= 0) { submesh = null; aabb = new Aabb(); roadLength = 0; return; } // Compute accumulated lengths. (One entry for each entry in flattenedPoints.) float[] accumulatedLengths = new float[flattenedPoints.Count]; accumulatedLengths[0] = 0; for (int i = 1; i < flattenedPoints.Count; i += 2) { Vector3F previous = flattenedPoints[i - 1]; Vector3F current = flattenedPoints[i]; float length = (current - previous).Length; accumulatedLengths[i] = accumulatedLengths[i - 1] + length; if (i + 1 < flattenedPoints.Count) accumulatedLengths[i + 1] = accumulatedLengths[i]; } // Total road length. roadLength = accumulatedLengths[accumulatedLengths.Length - 1]; // Create a mapping between accumulatedLength and the path key widths. // (accumulatedLength --> TerrainRoadPathKey.Width) var widthKeys = new List<Pair<float, float>>(); { int index = 0; foreach (var key in path) { Vector3F position = key.Point; var roadKey = key as TerrainRoadPathKey; float width = (roadKey != null) ? roadKey.Width : defaultWidth; for (; index < flattenedPoints.Count; index++) { if (Vector3F.AreNumericallyEqual(position, flattenedPoints[index])) { widthKeys.Add(new Pair<float, float>(accumulatedLengths[index], width)); break; } bool isLastLineSegment = (index + 2 == flattenedPoints.Count); if (!isLastLineSegment) index++; } index++; } } // Create a list of interpolated road widths. (One entry for each entry in flattenedPoints.) var widths = new float[flattenedPoints.Count]; int previousKeyIndex = 0; var previousKey = widthKeys[0]; var nextKey = widthKeys[1]; widths[0] = widthKeys[0].Second; for (int i = 1; i < flattenedPoints.Count; i += 2) { if (accumulatedLengths[i] > nextKey.First) { previousKey = nextKey; previousKeyIndex++; nextKey = widthKeys[previousKeyIndex + 1]; } float p = (accumulatedLengths[i] - previousKey.First) / (nextKey.First - previousKey.First); widths[i] = InterpolationHelper.Lerp(previousKey.Second, nextKey.Second, p); if (i + 1 < flattenedPoints.Count) widths[i + 1] = widths[i]; } // Compute vertices and indices. var vertices = new List<TerrainLayerVertex>(numberOfLineSegments * 2 + 2); var indices = new List<int>(numberOfLineSegments * 6); // Two triangles per line segment. Vector3F lastOrthonormal = Vector3F.UnitX; aabb = new Aabb(flattenedPoints[0], flattenedPoints[0]); bool isClosed = Vector3F.AreNumericallyEqual(flattenedPoints[0], flattenedPoints[flattenedPoints.Count - 1]); for (int i = 0; i < flattenedPoints.Count; i++) { Vector3F start = flattenedPoints[i]; Vector3F previous; bool isFirstPoint = (i == 0); if (!isFirstPoint) previous = flattenedPoints[i - 1]; else if (isClosed && path.SmoothEnds) previous = flattenedPoints[flattenedPoints.Count - 2]; else previous = start; Vector3F next; bool isLastPoint = (i + 1 == flattenedPoints.Count); if (!isLastPoint) next = flattenedPoints[i + 1]; else if (isClosed && path.SmoothEnds) next = flattenedPoints[1]; else next = start; Vector3F tangent = next - previous; Vector3F orthonormal = new Vector3F(tangent.Z, 0, -tangent.X); if (!orthonormal.TryNormalize()) orthonormal = lastOrthonormal; // Add indices to add two triangles between the current and the next vertices. if (!isLastPoint) { int baseIndex = vertices.Count; // 2 3 // x----x // |\ | ^ // | \ | | road // | \ | | direction // | \| | // x----x // 0 1 indices.Add(baseIndex); indices.Add(baseIndex + 1); indices.Add(baseIndex + 2); indices.Add(baseIndex + 1); indices.Add(baseIndex + 3); indices.Add(baseIndex + 2); } // Add two vertices. Vector3F leftVertex = start - orthonormal * (widths[i] / 2); Vector3F rightVertex = start + orthonormal * (widths[i] / 2); vertices.Add(new TerrainLayerVertex(new Vector2(leftVertex.X, leftVertex.Z), new Vector2(0, accumulatedLengths[i]))); vertices.Add(new TerrainLayerVertex(new Vector2(rightVertex.X, rightVertex.Z), new Vector2(1, accumulatedLengths[i]))); // Grow AABB aabb.Grow(leftVertex); aabb.Grow(rightVertex); lastOrthonormal = orthonormal; // The flattened points list contains 2 points per line segment, which means that there // are duplicate intermediate points, which we skip. bool isLastLineSegment = (i + 2 == flattenedPoints.Count); if (!isLastLineSegment) i++; } Debug.Assert(vertices.Count == (numberOfLineSegments * 2 + 2)); Debug.Assert(indices.Count == (numberOfLineSegments * 6)); // The road is projected onto the terrain, therefore the computed y limits are not correct. // (unless the terrain was clamped to the road). aabb.Minimum.Y = 0; aabb.Maximum.Y = 0; // Convert to submesh. submesh = new Submesh { PrimitiveCount = indices.Count / 3, PrimitiveType = PrimitiveType.TriangleList, VertexCount = vertices.Count, VertexBuffer = new VertexBuffer(graphicsDevice, TerrainLayerVertex.VertexDeclaration, vertices.Count, BufferUsage.WriteOnly), IndexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.ThirtyTwoBits, indices.Count, BufferUsage.WriteOnly) }; submesh.VertexBuffer.SetData(vertices.ToArray()); submesh.IndexBuffer.SetData(indices.ToArray()); }
public void OneKeyCurvesTest() { // Test linear curves with 1 point Path3F curve = new Path3F(); curve.Add(new PathKey3F() { Parameter = 1, Point = new Vector3F(1, 2, 0), Interpolation = SplineInterpolation.Linear, }); curve.PreLoop = CurveLoopType.Constant; curve.PostLoop = CurveLoopType.Constant; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Linear; curve.PostLoop = CurveLoopType.Linear; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Cycle; curve.PostLoop = CurveLoopType.Cycle; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.CycleOffset; curve.PostLoop = CurveLoopType.CycleOffset; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Oscillate; curve.PostLoop = CurveLoopType.Oscillate; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); // Test step curves with 1 point curve = new Path3F(); curve.Add(new PathKey3F() { Parameter = 1, Point = new Vector3F(1, 2, 0), Interpolation = SplineInterpolation.StepLeft, }); curve.PreLoop = CurveLoopType.Constant; curve.PostLoop = CurveLoopType.Constant; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Linear; curve.PostLoop = CurveLoopType.Linear; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Cycle; curve.PostLoop = CurveLoopType.Cycle; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.CycleOffset; curve.PostLoop = CurveLoopType.CycleOffset; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Oscillate; curve.PostLoop = CurveLoopType.Oscillate; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); // Test B-spline curves with 1 point curve = new Path3F(); curve.Add(new PathKey3F() { Parameter = 1, Point = new Vector3F(1, 2, 0), Interpolation = SplineInterpolation.BSpline, }); curve.PreLoop = CurveLoopType.Constant; curve.PostLoop = CurveLoopType.Constant; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Linear; curve.PostLoop = CurveLoopType.Linear; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Cycle; curve.PostLoop = CurveLoopType.Cycle; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.CycleOffset; curve.PostLoop = CurveLoopType.CycleOffset; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Oscillate; curve.PostLoop = CurveLoopType.Oscillate; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); // Test Catmull-Rom curves with 1 point curve = new Path3F(); curve.Add(new PathKey3F() { Parameter = 1, Point = new Vector3F(1, 2, 0), Interpolation = SplineInterpolation.CatmullRom, }); curve.PreLoop = CurveLoopType.Constant; curve.PostLoop = CurveLoopType.Constant; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Linear; curve.PostLoop = CurveLoopType.Linear; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Cycle; curve.PostLoop = CurveLoopType.Cycle; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.CycleOffset; curve.PostLoop = CurveLoopType.CycleOffset; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Oscillate; curve.PostLoop = CurveLoopType.Oscillate; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); // Test Hermite curves with 1 point curve = new Path3F(); curve.Add(new PathKey3F() { Parameter = 1, Point = new Vector3F(1, 2, 0), Interpolation = SplineInterpolation.Hermite, TangentIn = new Vector3F(2, -2, 0), TangentOut = new Vector3F(2, 2, 0), }); curve.PreLoop = CurveLoopType.Constant; curve.PostLoop = CurveLoopType.Constant; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 20, 0.01f)); curve.PreLoop = CurveLoopType.Constant; curve.PostLoop = CurveLoopType.Linear; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(3, 4, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(2, 2, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(2, 2, 0), curve.GetTangent(2)); Assert.IsTrue(Numeric.AreEqual(new Vector3F(2, 2, 0).Length, curve.GetLength(0, 2, 10, 0.01f), 0.1f)); curve.PreLoop = CurveLoopType.Linear; curve.PostLoop = CurveLoopType.Constant; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(-1, 4, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(2, -2, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(2, -2, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.IsTrue(Numeric.AreEqual(new Vector3F(2, 2, 0).Length, curve.GetLength(0, 2, 10, 0.01f), 0.1f)); curve.PreLoop = CurveLoopType.Linear; curve.PostLoop = CurveLoopType.Linear; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(-1, 4, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(3, 4, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(2, -2, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(2, 2, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(2, 2, 0), curve.GetTangent(2)); Assert.IsTrue(Numeric.AreEqual(new Vector3F(4, 4, 0).Length, curve.GetLength(0, 2, 10, 0.01f), 0.1f)); curve.PreLoop = CurveLoopType.Cycle; curve.PostLoop = CurveLoopType.Cycle; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.CycleOffset; curve.PostLoop = CurveLoopType.CycleOffset; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Oscillate; curve.PostLoop = CurveLoopType.Oscillate; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); // Test Bezier curves with 1 point curve = new Path3F(); curve.Add(new PathKey3F() { Parameter = 1, Point = new Vector3F(1, 2, 0), Interpolation = SplineInterpolation.Bezier, TangentIn = new Vector3F(1, 2, 0) - new Vector3F(2, -2, 0) / 3, TangentOut = new Vector3F(1, 2, 0) + new Vector3F(2, 2, 0) / 3, }); curve.PreLoop = CurveLoopType.Constant; curve.PostLoop = CurveLoopType.Constant; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Constant; curve.PostLoop = CurveLoopType.Linear; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(3, 4, 0), curve.GetPoint(2))); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(2, 2, 0), curve.GetTangent(1))); Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(2, 2, 0), curve.GetTangent(2))); Assert.IsTrue(Numeric.AreEqual(new Vector3F(2, 2, 0).Length, curve.GetLength(0, 2, 10, 0.01f), 0.1f)); curve.PreLoop = CurveLoopType.Linear; curve.PostLoop = CurveLoopType.Constant; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(-1, 4, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(2, -2, 0), curve.GetTangent(0))); Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(2, -2, 0), curve.GetTangent(1))); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.IsTrue(Numeric.AreEqual(new Vector3F(2, 2, 0).Length, curve.GetLength(0, 2, 10, 0.01f), 0.1f)); curve.PreLoop = CurveLoopType.Linear; curve.PostLoop = CurveLoopType.Linear; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(-1, 4, 0), curve.GetPoint(0)); Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(3, 4, 0), curve.GetPoint(2))); Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(2, -2, 0), curve.GetTangent(0))); Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(2, 2, 0), curve.GetTangent(1))); Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(2, 2, 0), curve.GetTangent(2))); Assert.IsTrue(Numeric.AreEqual(new Vector3F(4, 4, 0).Length, curve.GetLength(0, 2, 10, 0.01f), 0.1f)); curve.PreLoop = CurveLoopType.Cycle; curve.PostLoop = CurveLoopType.Cycle; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.CycleOffset; curve.PostLoop = CurveLoopType.CycleOffset; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); curve.PreLoop = CurveLoopType.Oscillate; curve.PostLoop = CurveLoopType.Oscillate; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(2)); Assert.AreEqual(0, curve.GetLength(0, 2, 10, 0.01f)); }
public void LoopParameter() { Path3F empty = new Path3F(); empty.Sort(); Assert.AreEqual(3, empty.LoopParameter(3)); Assert.AreEqual(false, empty.IsInMirroredOscillation(3)); Path3F path = CreatePath(); path.PreLoop = CurveLoopType.Constant; path.PostLoop = CurveLoopType.Oscillate; Assert.AreEqual(10, path.LoopParameter(3)); Assert.AreEqual(false, path.IsInMirroredOscillation(3)); Assert.AreEqual(10, path.LoopParameter(10)); Assert.AreEqual(false, path.IsInMirroredOscillation(10)); Assert.AreEqual(13, path.LoopParameter(13)); Assert.AreEqual(false, path.IsInMirroredOscillation(13)); Assert.AreEqual(40, path.LoopParameter(40)); Assert.AreEqual(false, path.IsInMirroredOscillation(40)); Assert.AreEqual(35, path.LoopParameter(45)); Assert.AreEqual(true, path.IsInMirroredOscillation(45)); Assert.AreEqual(14, path.LoopParameter(74)); Assert.AreEqual(false, path.IsInMirroredOscillation(74)); path.PreLoop = CurveLoopType.Linear; path.PostLoop = CurveLoopType.Cycle; Assert.AreEqual(3, path.LoopParameter(3)); Assert.AreEqual(false, path.IsInMirroredOscillation(3)); Assert.AreEqual(10, path.LoopParameter(10)); Assert.AreEqual(false, path.IsInMirroredOscillation(10)); Assert.AreEqual(13, path.LoopParameter(13)); Assert.AreEqual(false, path.IsInMirroredOscillation(13)); Assert.AreEqual(40, path.LoopParameter(40)); Assert.AreEqual(false, path.IsInMirroredOscillation(40)); Assert.AreEqual(15, path.LoopParameter(45)); Assert.AreEqual(false, path.IsInMirroredOscillation(45)); Assert.AreEqual(14, path.LoopParameter(74)); Assert.AreEqual(false, path.IsInMirroredOscillation(74)); path.PreLoop = CurveLoopType.Cycle; path.PostLoop = CurveLoopType.CycleOffset; Assert.AreEqual(20, path.LoopParameter(-40)); Assert.AreEqual(false, path.IsInMirroredOscillation(-40)); Assert.AreEqual(33, path.LoopParameter(3)); Assert.AreEqual(false, path.IsInMirroredOscillation(3)); Assert.AreEqual(10, path.LoopParameter(10)); Assert.AreEqual(false, path.IsInMirroredOscillation(10)); Assert.AreEqual(13, path.LoopParameter(13)); Assert.AreEqual(false, path.IsInMirroredOscillation(13)); Assert.AreEqual(40, path.LoopParameter(40)); Assert.AreEqual(false, path.IsInMirroredOscillation(40)); Assert.AreEqual(15, path.LoopParameter(45)); Assert.AreEqual(false, path.IsInMirroredOscillation(45)); Assert.AreEqual(14, path.LoopParameter(74)); Assert.AreEqual(false, path.IsInMirroredOscillation(74)); Assert.AreEqual(20, path.LoopParameter(200)); Assert.AreEqual(false, path.IsInMirroredOscillation(200)); path.PreLoop = CurveLoopType.CycleOffset; path.PostLoop = CurveLoopType.Linear; Assert.AreEqual(20, path.LoopParameter(-40)); Assert.AreEqual(false, path.IsInMirroredOscillation(-40)); Assert.AreEqual(33, path.LoopParameter(3)); Assert.AreEqual(false, path.IsInMirroredOscillation(3)); Assert.AreEqual(10, path.LoopParameter(10)); Assert.AreEqual(false, path.IsInMirroredOscillation(10)); Assert.AreEqual(13, path.LoopParameter(13)); Assert.AreEqual(false, path.IsInMirroredOscillation(13)); Assert.AreEqual(40, path.LoopParameter(40)); Assert.AreEqual(false, path.IsInMirroredOscillation(40)); Assert.AreEqual(45, path.LoopParameter(45)); Assert.AreEqual(false, path.IsInMirroredOscillation(45)); Assert.AreEqual(74, path.LoopParameter(74)); Assert.AreEqual(false, path.IsInMirroredOscillation(74)); Assert.AreEqual(200, path.LoopParameter(200)); Assert.AreEqual(false, path.IsInMirroredOscillation(200)); path.PreLoop = CurveLoopType.Oscillate; path.PostLoop = CurveLoopType.Constant; Assert.AreEqual(20, path.LoopParameter(-40)); Assert.AreEqual(false, path.IsInMirroredOscillation(-40)); Assert.AreEqual(17, path.LoopParameter(3)); Assert.AreEqual(true, path.IsInMirroredOscillation(3)); Assert.AreEqual(10, path.LoopParameter(10)); Assert.AreEqual(false, path.IsInMirroredOscillation(10)); Assert.AreEqual(13, path.LoopParameter(13)); Assert.AreEqual(false, path.IsInMirroredOscillation(13)); Assert.AreEqual(40, path.LoopParameter(40)); Assert.AreEqual(false, path.IsInMirroredOscillation(40)); Assert.AreEqual(40, path.LoopParameter(45)); Assert.AreEqual(false, path.IsInMirroredOscillation(45)); Assert.AreEqual(40, path.LoopParameter(74)); Assert.AreEqual(false, path.IsInMirroredOscillation(74)); Assert.AreEqual(40, path.LoopParameter(200)); Assert.AreEqual(false, path.IsInMirroredOscillation(200)); }
public void ParameterizeByLength() { Path3F empty = new Path3F(); empty.Sort(); empty.ParameterizeByLength(20, 0.001f); // No exception, do nothing. Path3F path = CreatePath(); Path3F lengthPath = CreatePath(); lengthPath.ParameterizeByLength(20, 0.001f); Assert.AreEqual(0, lengthPath[0].Parameter); Assert.AreEqual(3, lengthPath[1].Parameter); Assert.AreEqual(3, lengthPath[2].Parameter); Assert.AreEqual(3, lengthPath[3].Parameter); float step = 0.001f; float length = 3; int i = 4; float u = 20; Vector3F oldPoint = path.GetPoint(u); for (; u < 51 && i<10; u += step) { if (Numeric.AreEqual(u, path[i].Parameter)) { Assert.IsTrue(Numeric.AreEqual(length, lengthPath[i].Parameter, 0.01f)); // Set explicit values against numerical problems. length = lengthPath[i].Parameter; u = path[i].Parameter; oldPoint = path.GetPoint(u); i++; } Vector3F newPoint = path.GetPoint(u + step); length += (newPoint - oldPoint).Length; oldPoint = newPoint; } Assert.AreEqual(10, i); // Have we checked all keys? path.PreLoop = CurveLoopType.Constant; path.PostLoop = CurveLoopType.Oscillate; path.PreLoop = CurveLoopType.Linear; path.PostLoop = CurveLoopType.Cycle; path.PreLoop = CurveLoopType.Cycle; path.PostLoop = CurveLoopType.CycleOffset; path.PreLoop = CurveLoopType.CycleOffset; path.PostLoop = CurveLoopType.Linear; path.PreLoop = CurveLoopType.Oscillate; path.PostLoop = CurveLoopType.Constant; }
public void TwoKeyCurvesTest() { Path3F curve = new Path3F(); curve.Add(new PathKey3F() { Parameter = 1, Point = new Vector3F(1, 2, 0), Interpolation = SplineInterpolation.CatmullRom, }); curve.Add(new PathKey3F() { Parameter = 3, Point = new Vector3F(3, 4, 0), Interpolation = SplineInterpolation.CatmullRom, }); curve.PreLoop = CurveLoopType.Constant; curve.PostLoop = CurveLoopType.Constant; Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(0)); Assert.AreEqual(new Vector3F(1, 2, 0), curve.GetPoint(1)); Assert.AreEqual(new Vector3F(2, 3, 0), curve.GetPoint(2)); Assert.AreEqual(new Vector3F(3, 4, 0), curve.GetPoint(3)); Assert.AreEqual(new Vector3F(3, 4, 0), curve.GetPoint(4)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(0)); Assert.AreEqual(new Vector3F(1, 1, 0), curve.GetTangent(1)); Assert.AreEqual(new Vector3F(1, 1, 0), curve.GetTangent(2)); Assert.AreEqual(new Vector3F(1, 1, 0), curve.GetTangent(3)); Assert.AreEqual(new Vector3F(0, 0, 0), curve.GetTangent(4)); Assert.IsTrue(Numeric.AreEqual(new Vector3F(2, 2, 0).Length, curve.GetLength(0, 4, 10, 0.01f), 0.01f)); curve.PreLoop = CurveLoopType.Linear; curve.PostLoop = CurveLoopType.Linear; curve.PreLoop = CurveLoopType.Cycle; curve.PostLoop = CurveLoopType.Cycle; curve.PreLoop = CurveLoopType.CycleOffset; curve.PostLoop = CurveLoopType.CycleOffset; curve.PreLoop = CurveLoopType.Oscillate; curve.PostLoop = CurveLoopType.Oscillate; }
private void CreateRoad() { //RandomHelper.Random = new Random(1234567); // Set isClosed to true join the start and the end of the road. bool isClosed = false; // Create a new TerrainRoadLayer which paints a road onto the terrain. // The road itself is defined by a mesh which is set later. _roadLayer = new TerrainRoadLayer(GraphicsService) { DiffuseColor = new Vector3(0.5f), SpecularColor = new Vector3(1), DiffuseTexture = ContentManager.Load <Texture2D>("Terrain/Road-Asphalt-Diffuse"), NormalTexture = ContentManager.Load <Texture2D>("Terrain/Road-Asphalt-Normal"), SpecularTexture = ContentManager.Load <Texture2D>("Terrain/Road-Asphalt-Specular"), HeightTexture = ContentManager.Load <Texture2D>("Terrain/Road-Asphalt-Height"), // The size of the tileable detail textures in world space units. TileSize = 5, // The border blend range controls how the border of the road fades out. // We fade out 5% of the texture on each side of the road. BorderBlendRange = new Vector4(0.05f, 0.05f, 0.05f, 0.05f), }; // Create 3D spline path with some semi-random control points. _roadPath = new Path3F { PreLoop = isClosed ? CurveLoopType.Cycle : CurveLoopType.Linear, PostLoop = isClosed ? CurveLoopType.Cycle : CurveLoopType.Linear, SmoothEnds = isClosed, }; // The position of the next path key. Vector3 position = new Vector3( RandomHelper.Random.NextFloat(-20, 20), 0, RandomHelper.Random.NextFloat(-20, 20)); // The direction to the next path key. Vector3 direction = Quaternion.CreateRotationY(RandomHelper.Random.NextFloat(0, 10)).Rotate(Vector3.Forward); // Add path keys. for (int j = 0; j < 10; j++) { // Instead of a normal PathKey3F, we use a TerrainRoadPathKey which allows to control // the road with and the side falloff. var key = new TerrainRoadPathKey { Interpolation = SplineInterpolation.CatmullRom, Parameter = j, Point = position, // The width of the road at the path key. Width = RandomHelper.Random.NextFloat(6, 10), // The side falloff (which is used in ClampTerrainToRoad to blend the height values with // the road). SideFalloff = RandomHelper.Random.NextFloat(20, 40), }; _roadPath.Add(key); // Get next random position and direction. position += direction * RandomHelper.Random.NextFloat(20, 40); position.Y += RandomHelper.Random.NextFloat(-2, 2); direction = Quaternion.CreateRotationY(RandomHelper.Random.NextFloat(-1, 1)) .Rotate(direction); } if (isClosed) { // To create a closed path the first and the last key should be identical. _roadPath[_roadPath.Count - 1].Point = _roadPath[0].Point; ((TerrainRoadPathKey)_roadPath[_roadPath.Count - 1]).Width = ((TerrainRoadPathKey)_roadPath[0]).Width; ((TerrainRoadPathKey)_roadPath[_roadPath.Count - 1]).SideFalloff = ((TerrainRoadPathKey)_roadPath[0]).SideFalloff; // Since the path is closed we do not have to fade out the start and the end of the road. _roadLayer.BorderBlendRange *= new Vector4(1, 0, 1, 0); } // Convert the path to a mesh. Submesh roadSubmesh; Aabb roadAabb; float roadLength; TerrainRoadLayer.CreateMesh( GraphicsService.GraphicsDevice, _roadPath, 4, 10, 0.1f, out roadSubmesh, out roadAabb, out roadLength); // Set the mesh in the road layer. _roadLayer.SetMesh(roadSubmesh, roadAabb, roadLength, true); if (isClosed) { // When the path is closed, the end texture and the start texture coordinates should // match. This is the case if (roadLength / tileSize) is an integer. var numberOfTiles = (int)(roadLength / _roadLayer.TileSize); _roadLayer.TileSize = roadLength / numberOfTiles; } // The road layer is added to the layers of a tile. We have to add the road to each terrain // tile which it overlaps. foreach (var tile in _terrainObject.TerrainNode.Terrain.Tiles) { if (GeometryHelper.HaveContact(tile.Aabb, _roadLayer.Aabb.Value)) { tile.Layers.Add(_roadLayer); } } }
public void GetParameterByLength() { Path3F empty = new Path3F(); empty.ParameterizeByLength(20, 0.001f); // No exception, do nothing. Assert.IsTrue(float.IsNaN(empty.GetParameterFromLength(10, 20, 0.1f))); Path3F path = CreatePath(); path.ParameterizeByLength(20, 0.001f); Assert.AreEqual(0, path.GetParameterFromLength(0, 20, 0.001f)); Assert.AreEqual(3, path.GetParameterFromLength(3, 20, 0.001f)); Assert.AreEqual(path[4].Parameter, path.GetParameterFromLength(path[4].Parameter, 20, 0.01f)); Assert.AreEqual(path[5].Parameter, path.GetParameterFromLength(path[5].Parameter, 20, 0.01f)); Assert.AreEqual(path[6].Parameter, path.GetParameterFromLength(path[6].Parameter, 20, 0.01f)); Assert.AreEqual(path[7].Parameter, path.GetParameterFromLength(path[7].Parameter, 20, 0.01f)); Assert.AreEqual(path[8].Parameter, path.GetParameterFromLength(path[8].Parameter, 20, 0.01f)); Assert.AreEqual(path[9].Parameter, path.GetParameterFromLength(path[9].Parameter, 20, 0.01f)); float pathLength = path[9].Parameter; float desiredLength = 11; float actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.01f)); desiredLength = 26; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.01f)); desiredLength = 33.5f; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.001f), 20, 0.001f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.01f)); path.PreLoop = CurveLoopType.Linear; path.PostLoop = CurveLoopType.Linear; desiredLength = 60; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.001f), 20, 0.001f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = -10f; actualLength = -path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.001f), 20, 0.001f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.01f)); path.PreLoop = CurveLoopType.CycleOffset; path.PostLoop = CurveLoopType.CycleOffset; path.ParameterizeByLength(20, 0.001f); desiredLength = -90; actualLength = -path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = -50; actualLength = -path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = -30; actualLength = -path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = 50; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = 100; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = 130; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = 200; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); path.PreLoop = CurveLoopType.Oscillate; path.PostLoop = CurveLoopType.Cycle; path.ParameterizeByLength(20, 0.001f); desiredLength = -110; actualLength = -path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(-3 * pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(-2 * pathLength > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = -50; actualLength = -path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(-2 * pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(-1 * pathLength > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = -30; actualLength = -path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(-1 * pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(0 > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = 50; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(1 * pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(2 * pathLength > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = 110; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(2 * pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(3 * pathLength > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = 130; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(3 * pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(4 * pathLength > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = 190; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(4 * pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(5 * pathLength > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.2f)); path.PreLoop = CurveLoopType.Cycle; path.PostLoop = CurveLoopType.Oscillate; path.ParameterizeByLength(20, 0.001f); desiredLength = -90; actualLength = -path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(-3 * pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(-2 * pathLength > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = -50; actualLength = -path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(-2 * pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(-1 * pathLength > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = -30; actualLength = -path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(-1 * pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(0 > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = 50; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(2 * pathLength > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = 110; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(2 * pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(3 * pathLength > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = 130; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(3 * pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(4 * pathLength > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); desiredLength = 210; actualLength = path.GetLength(0, path.GetParameterFromLength(desiredLength, 20, 0.01f), 20, 0.01f); Assert.IsTrue(5 * pathLength < path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(6 * pathLength > path.GetParameterFromLength(desiredLength, 20, 0.01f)); Assert.IsTrue(Numeric.AreEqual(desiredLength, actualLength, 0.1f)); // Test path with zero length. path = new Path3F(); path.Add(new PathKey3F() { Parameter = 10, Point = new Vector3F(0, 0, 1), Interpolation = SplineInterpolation.Linear, TangentIn = new Vector3F(1, 0, 0), TangentOut = new Vector3F(1, 0, 0), }); path.ParameterizeByLength(20, 0.001f); Assert.AreEqual(0, path.GetParameterFromLength(0, 20, 0.1f)); path.Add(new PathKey3F() { Parameter = 20, Point = new Vector3F(0, 0, 1), Interpolation = SplineInterpolation.Linear, TangentIn = new Vector3F(1, 0, 0), TangentOut = new Vector3F(1, 0, 0), }); path.ParameterizeByLength(20, 0.001f); Assert.AreEqual(0, path.GetParameterFromLength(0, 20, 0.1f)); }
private Path3F CreatePath() { Path3F path = new Path3F(); path.Add(new PathKey3F() { Parameter = 10, Point = new Vector3(1, 2, 3), Interpolation = SplineInterpolation.StepLeft, TangentIn = new Vector3(1, 0, 0), TangentOut = new Vector3(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 15, Point = new Vector3(4, 5, 7), Interpolation = SplineInterpolation.StepCentered, TangentIn = new Vector3(1, 0, 0), TangentOut = new Vector3(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 18, Point = new Vector3(5, 7, 10), Interpolation = SplineInterpolation.StepRight, TangentIn = new Vector3(1, 0, 0), TangentOut = new Vector3(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 20, Point = new Vector3(5, 7, 13), Interpolation = SplineInterpolation.Linear, TangentIn = new Vector3(1, 0, 0), TangentOut = new Vector3(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 25, Point = new Vector3(6, 7, 14), Interpolation = SplineInterpolation.Bezier, TangentIn = new Vector3(5, 6, 13), TangentOut = new Vector3(7, 8, 15), }); path.Add(new PathKey3F() { Parameter = 31, Point = new Vector3(8, 10, 16), Interpolation = SplineInterpolation.BSpline, TangentIn = new Vector3(1, 0, 0), TangentOut = new Vector3(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 35, Point = new Vector3(10, 12, 14), Interpolation = SplineInterpolation.Hermite, TangentIn = new Vector3(1, 0, 0), TangentOut = new Vector3(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 40, Point = new Vector3(10, 14, 8), Interpolation = SplineInterpolation.CatmullRom, TangentIn = new Vector3(1, 0, 0), TangentOut = new Vector3(1, 0, 0), }); return(path); }
private Path3F CreatePath() { Path3F path = new Path3F(); path.Add(new PathKey3F() { Parameter = 10, Point = new Vector3F(0, 0, 1), Interpolation = SplineInterpolation.Linear, TangentIn = new Vector3F(1, 0, 0), TangentOut = new Vector3F(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 12, Point = new Vector3F(1, 2, 3), Interpolation = SplineInterpolation.StepLeft, TangentIn = new Vector3F(1, 0, 0), TangentOut = new Vector3F(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 15, Point = new Vector3F(4, 5, 7), Interpolation = SplineInterpolation.StepCentered, TangentIn = new Vector3F(1, 0, 0), TangentOut = new Vector3F(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 18, Point = new Vector3F(5, 7, 10), Interpolation = SplineInterpolation.StepRight, TangentIn = new Vector3F(1, 0, 0), TangentOut = new Vector3F(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 20, Point = new Vector3F(5, 7, 13), Interpolation = SplineInterpolation.Linear, TangentIn = new Vector3F(1, 0, 0), TangentOut = new Vector3F(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 31, Point = new Vector3F(8, 10, 16), Interpolation = SplineInterpolation.BSpline, TangentIn = new Vector3F(1, 0, 0), TangentOut = new Vector3F(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 35, Point = new Vector3F(10, 12, 14), Interpolation = SplineInterpolation.Hermite, TangentIn = new Vector3F(1, 0, 0), TangentOut = new Vector3F(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 25, Point = new Vector3F(6, 7, 14), Interpolation = SplineInterpolation.Bezier, TangentIn = new Vector3F(5, 6, 13), TangentOut = new Vector3F(7, 8, 15), }); path.Add(new PathKey3F() { Parameter = 40, Point = new Vector3F(10, 14, 8), Interpolation = SplineInterpolation.CatmullRom, TangentIn = new Vector3F(1, 0, 0), TangentOut = new Vector3F(1, 0, 0), }); path.Add(new PathKey3F() { Parameter = 50, Point = new Vector3F(20, 14, 8), Interpolation = SplineInterpolation.CatmullRom, TangentIn = new Vector3F(1, 0, 0), TangentOut = new Vector3F(1, 0, 0), }); path.Sort(); return path; }
public void GetPointShouldReturnNanIfPathIsEmpty() { Path3F empty = new Path3F(); empty.Sort(); Vector3F p = empty.GetPoint(-0.5f); Assert.IsNaN(p.X); Assert.IsNaN(p.Y); Assert.IsNaN(p.Z); p = empty.GetPoint(0); Assert.IsNaN(p.X); Assert.IsNaN(p.Y); Assert.IsNaN(p.Z); p = empty.GetPoint(0.5f); Assert.IsNaN(p.X); Assert.IsNaN(p.Y); Assert.IsNaN(p.Z); }
public static void CreateMesh(GraphicsDevice graphicsDevice, Path3F path, float defaultWidth, int maxNumberOfIterations, float tolerance, out Submesh submesh, out Aabb aabb, out float roadLength) { if (graphicsDevice == null) { throw new ArgumentNullException("graphicsDevice"); } if (path == null) { throw new ArgumentNullException("path"); } // Compute list of line segments. (2 points per line segment!) var flattenedPoints = new List <Vector3F>(); path.Flatten(flattenedPoints, maxNumberOfIterations, tolerance); // Abort if path is empty. int numberOfLineSegments = flattenedPoints.Count / 2; if (numberOfLineSegments <= 0) { submesh = null; aabb = new Aabb(); roadLength = 0; return; } // Compute accumulated lengths. (One entry for each entry in flattenedPoints.) float[] accumulatedLengths = new float[flattenedPoints.Count]; accumulatedLengths[0] = 0; for (int i = 1; i < flattenedPoints.Count; i += 2) { Vector3F previous = flattenedPoints[i - 1]; Vector3F current = flattenedPoints[i]; float length = (current - previous).Length; accumulatedLengths[i] = accumulatedLengths[i - 1] + length; if (i + 1 < flattenedPoints.Count) { accumulatedLengths[i + 1] = accumulatedLengths[i]; } } // Total road length. roadLength = accumulatedLengths[accumulatedLengths.Length - 1]; // Create a mapping between accumulatedLength and the path key widths. // (accumulatedLength --> TerrainRoadPathKey.Width) var widthKeys = new List <Pair <float, float> >(); { int index = 0; foreach (var key in path) { Vector3F position = key.Point; var roadKey = key as TerrainRoadPathKey; float width = (roadKey != null) ? roadKey.Width : defaultWidth; for (; index < flattenedPoints.Count; index++) { if (Vector3F.AreNumericallyEqual(position, flattenedPoints[index])) { widthKeys.Add(new Pair <float, float>(accumulatedLengths[index], width)); break; } bool isLastLineSegment = (index + 2 == flattenedPoints.Count); if (!isLastLineSegment) { index++; } } index++; } } // Create a list of interpolated road widths. (One entry for each entry in flattenedPoints.) var widths = new float[flattenedPoints.Count]; int previousKeyIndex = 0; var previousKey = widthKeys[0]; var nextKey = widthKeys[1]; widths[0] = widthKeys[0].Second; for (int i = 1; i < flattenedPoints.Count; i += 2) { if (accumulatedLengths[i] > nextKey.First) { previousKey = nextKey; previousKeyIndex++; nextKey = widthKeys[previousKeyIndex + 1]; } float p = (accumulatedLengths[i] - previousKey.First) / (nextKey.First - previousKey.First); widths[i] = InterpolationHelper.Lerp(previousKey.Second, nextKey.Second, p); if (i + 1 < flattenedPoints.Count) { widths[i + 1] = widths[i]; } } // Compute vertices and indices. var vertices = new List <TerrainLayerVertex>(numberOfLineSegments * 2 + 2); var indices = new List <int>(numberOfLineSegments * 6); // Two triangles per line segment. Vector3F lastOrthonormal = Vector3F.UnitX; aabb = new Aabb(flattenedPoints[0], flattenedPoints[0]); bool isClosed = Vector3F.AreNumericallyEqual(flattenedPoints[0], flattenedPoints[flattenedPoints.Count - 1]); for (int i = 0; i < flattenedPoints.Count; i++) { Vector3F start = flattenedPoints[i]; Vector3F previous; bool isFirstPoint = (i == 0); if (!isFirstPoint) { previous = flattenedPoints[i - 1]; } else if (isClosed && path.SmoothEnds) { previous = flattenedPoints[flattenedPoints.Count - 2]; } else { previous = start; } Vector3F next; bool isLastPoint = (i + 1 == flattenedPoints.Count); if (!isLastPoint) { next = flattenedPoints[i + 1]; } else if (isClosed && path.SmoothEnds) { next = flattenedPoints[1]; } else { next = start; } Vector3F tangent = next - previous; Vector3F orthonormal = new Vector3F(tangent.Z, 0, -tangent.X); if (!orthonormal.TryNormalize()) { orthonormal = lastOrthonormal; } // Add indices to add two triangles between the current and the next vertices. if (!isLastPoint) { int baseIndex = vertices.Count; // 2 3 // x----x // |\ | ^ // | \ | | road // | \ | | direction // | \| | // x----x // 0 1 indices.Add(baseIndex); indices.Add(baseIndex + 1); indices.Add(baseIndex + 2); indices.Add(baseIndex + 1); indices.Add(baseIndex + 3); indices.Add(baseIndex + 2); } // Add two vertices. Vector3F leftVertex = start - orthonormal * (widths[i] / 2); Vector3F rightVertex = start + orthonormal * (widths[i] / 2); vertices.Add(new TerrainLayerVertex(new Vector2(leftVertex.X, leftVertex.Z), new Vector2(0, accumulatedLengths[i]))); vertices.Add(new TerrainLayerVertex(new Vector2(rightVertex.X, rightVertex.Z), new Vector2(1, accumulatedLengths[i]))); // Grow AABB aabb.Grow(leftVertex); aabb.Grow(rightVertex); lastOrthonormal = orthonormal; // The flattened points list contains 2 points per line segment, which means that there // are duplicate intermediate points, which we skip. bool isLastLineSegment = (i + 2 == flattenedPoints.Count); if (!isLastLineSegment) { i++; } } Debug.Assert(vertices.Count == (numberOfLineSegments * 2 + 2)); Debug.Assert(indices.Count == (numberOfLineSegments * 6)); // The road is projected onto the terrain, therefore the computed y limits are not correct. // (unless the terrain was clamped to the road). aabb.Minimum.Y = 0; aabb.Maximum.Y = 0; // Convert to submesh. submesh = new Submesh { PrimitiveCount = indices.Count / 3, PrimitiveType = PrimitiveType.TriangleList, VertexCount = vertices.Count, VertexBuffer = new VertexBuffer(graphicsDevice, TerrainLayerVertex.VertexDeclaration, vertices.Count, BufferUsage.WriteOnly), IndexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.ThirtyTwoBits, indices.Count, BufferUsage.WriteOnly) }; submesh.VertexBuffer.SetData(vertices.ToArray()); submesh.IndexBuffer.SetData(indices.ToArray()); }
public void GetTangent() { Path3F path = CreatePath(); path.PreLoop = CurveLoopType.Constant; path.PostLoop = CurveLoopType.Oscillate; Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(0, 0, 0), path.GetTangent(-10))); Assert.IsTrue(Vector3F.AreNumericallyEqual((new Vector3F(1, 2, 3) - new Vector3F(0, 0, 1)) / 2, path.GetTangent(10))); Assert.IsTrue(Vector3F.AreNumericallyEqual((new Vector3F(1, 2, 3) - new Vector3F(0, 0, 1)) / 2, path.GetTangent(11.5f))); Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(0, 0, 0), path.GetTangent(16))); Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(0, 0, 0), path.GetTangent(85))); CatmullRomSegment3F catmullOscillate = new CatmullRomSegment3F() { Point1 = new Vector3F(10, 12, 14), Point2 = new Vector3F(10, 14, 8), Point3 = new Vector3F(20, 14, 8), Point4 = new Vector3F(30, 14, 8), }; Assert.IsTrue(Vector3F.AreNumericallyEqual(catmullOscillate.GetTangent(0.3f) / 10.0f, path.GetTangent(43))); Assert.IsTrue(Vector3F.AreNumericallyEqual(-catmullOscillate.GetTangent(0.7f) / 10.0f, path.GetTangent(53))); path.PreLoop = CurveLoopType.Linear; path.PostLoop = CurveLoopType.Cycle; Assert.IsTrue(Vector3F.AreNumericallyEqual((new Vector3F(1, 2, 3) - new Vector3F(0, 0, 1)) / 2, path.GetTangent(0))); path.PreLoop = CurveLoopType.Cycle; path.PostLoop = CurveLoopType.CycleOffset; Assert.IsTrue(Vector3F.AreNumericallyEqual(catmullOscillate.GetTangent(0.4f) / 10.0f, path.GetTangent(-36))); Assert.IsTrue(Vector3F.AreNumericallyEqual(catmullOscillate.GetTangent(0.4f) / 10.0f, path.GetTangent(4))); Assert.IsTrue(Vector3F.AreNumericallyEqual(catmullOscillate.GetTangent(0.3f) / 10.0f, path.GetTangent(83))); path.PreLoop = CurveLoopType.CycleOffset; path.PostLoop = CurveLoopType.Linear; Assert.IsTrue(Vector3F.AreNumericallyEqual(catmullOscillate.GetTangent(1f) / 10.0f, path.GetTangent(434))); path.PreLoop = CurveLoopType.Oscillate; path.PostLoop = CurveLoopType.Constant; path = new Path3F(); path.Add(new PathKey3F() { Parameter = 25, Point = new Vector3F(6, 7, 14), Interpolation = SplineInterpolation.Bezier, TangentIn = new Vector3F(5, 6, 13), TangentOut = new Vector3F(7, 8, 15), }); path.Add(new PathKey3F() { Parameter = 35, Point = new Vector3F(10, 12, 14), Interpolation = SplineInterpolation.Hermite, TangentIn = new Vector3F(1, 0, 0), TangentOut = new Vector3F(1, 0, 0), }); path.PreLoop = CurveLoopType.Linear; path.PostLoop = CurveLoopType.Linear; float Δu = path[1].Parameter - path[0].Parameter; Assert.IsTrue(Vector3F.AreNumericallyEqual((new Vector3F(6, 7, 14) - new Vector3F(5, 6, 13)) * 3 / Δu, path.GetTangent(0))); Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(1, 0, 0) / Δu, path.GetTangent(100))); path[1].Parameter = 25; path[0].Parameter = 35; path.Sort(); Δu = path[1].Parameter - path[0].Parameter; Assert.IsTrue(Vector3F.AreNumericallyEqual(new Vector3F(1, 0, 0) / Δu, path.GetTangent(0))); Assert.IsTrue(Vector3F.AreNumericallyEqual((new Vector3F(7, 8, 15) - new Vector3F(6, 7, 14)) * 3 / Δu, path.GetTangent(100))); path.Add(new PathKey3F() { Parameter = 15, Point = new Vector3F(0, 0, 0), Interpolation = SplineInterpolation.BSpline, }); path.Sort(); }
public static void ClampTerrainToRoad(HeightField terrain, Path3F road, float defaultWidth, float defaultSideFalloff, int maxNumberOfIterations, float tolerance) { if (terrain == null) { throw new ArgumentNullException("terrain"); } if (road == null) { throw new ArgumentNullException("road"); } // Compute list of line segments. (2 points per line segment!) var flattenedPoints = new List <Vector3F>(); road.Flatten(flattenedPoints, maxNumberOfIterations, tolerance); // Abort if path is empty. int numberOfLineSegments = flattenedPoints.Count / 2; if (numberOfLineSegments <= 0) { return; } // Compute accumulated lengths. (One entry for each entry in flattenedPoints.) float[] accumulatedLengths = new float[flattenedPoints.Count]; accumulatedLengths[0] = 0; for (int i = 1; i < flattenedPoints.Count; i += 2) { Vector3F previous = flattenedPoints[i - 1]; Vector3F current = flattenedPoints[i]; float length = (current - previous).Length; accumulatedLengths[i] = accumulatedLengths[i - 1] + length; if (i + 1 < flattenedPoints.Count) { accumulatedLengths[i + 1] = accumulatedLengths[i]; } } // Create a mapping between accumulatedLength and the path keys. // (accumulatedLength --> key) var pathLengthsAndKeys = new List <Pair <float, TerrainRoadPathKey> >(); { int index = 0; foreach (var key in road) { Vector3F position = key.Point; var roadKey = key as TerrainRoadPathKey; if (roadKey == null) { roadKey = new TerrainRoadPathKey { Point = key.Point, Width = defaultWidth, SideFalloff = defaultSideFalloff, }; } for (; index < flattenedPoints.Count; index++) { if (Vector3F.AreNumericallyEqual(position, flattenedPoints[index])) { pathLengthsAndKeys.Add(new Pair <float, TerrainRoadPathKey>(accumulatedLengths[index], roadKey)); break; } bool isLastLineSegment = (index + 2 == flattenedPoints.Count); if (!isLastLineSegment) { index++; } } index++; } } // Create a list of interpolated road widths and side falloffs. (One entry for each entry in flattenedPoints.) var halfWidths = new float[flattenedPoints.Count]; var sideFalloffs = new float[flattenedPoints.Count]; int previousKeyIndex = 0; var previousKey = pathLengthsAndKeys[0]; var nextKey = pathLengthsAndKeys[1]; halfWidths[0] = 0.5f * pathLengthsAndKeys[0].Second.Width; sideFalloffs[0] = pathLengthsAndKeys[0].Second.SideFalloff; for (int i = 1; i < flattenedPoints.Count; i += 2) { if (accumulatedLengths[i] > nextKey.First) { previousKey = nextKey; previousKeyIndex++; nextKey = pathLengthsAndKeys[previousKeyIndex + 1]; } float p = (accumulatedLengths[i] - previousKey.First) / (nextKey.First - previousKey.First); halfWidths[i] = 0.5f * InterpolationHelper.Lerp(previousKey.Second.Width, nextKey.Second.Width, p); sideFalloffs[i] = InterpolationHelper.Lerp(previousKey.Second.SideFalloff, nextKey.Second.SideFalloff, p); if (i + 1 < flattenedPoints.Count) { halfWidths[i + 1] = halfWidths[i]; sideFalloffs[i + 1] = sideFalloffs[i]; } } // Get AABB of road with the side falloff. Aabb aabbWithSideFalloffs; { Vector3F p = flattenedPoints[0]; float r = halfWidths[0] + sideFalloffs[0]; aabbWithSideFalloffs = new Aabb(new Vector3F(p.X - r, 0, p.Z - r), new Vector3F(p.X + r, 0, p.Z + r)); for (int i = 1; i < flattenedPoints.Count; i += 2) { p = flattenedPoints[i]; r = halfWidths[i] + sideFalloffs[i]; aabbWithSideFalloffs.Grow(new Vector3F(p.X - r, 0, p.Z - r)); aabbWithSideFalloffs.Grow(new Vector3F(p.X + r, 0, p.Z + r)); } } // Terrain properties. int numberOfSamplesX = terrain.NumberOfSamplesX; int numberOfSamplesZ = terrain.NumberOfSamplesZ; int numberOfCellsX = numberOfSamplesX - 1; int numberOfCellsZ = numberOfSamplesZ - 1; float widthX = terrain.WidthX; float cellSizeX = widthX / numberOfCellsX; float widthZ = terrain.WidthZ; float cellSizeZ = widthZ / numberOfCellsZ; float cellSizeDiagonal = (float)Math.Sqrt(cellSizeX * cellSizeX + cellSizeZ * cellSizeZ); bool isClosed = Vector3F.AreNumericallyEqual(flattenedPoints[0], flattenedPoints[flattenedPoints.Count - 1]); { // Get the line segments which of the road border. List <Vector4F> segments = new List <Vector4F>(); // 2 points per segment. Vector3F lastOrthonormal = Vector3F.Right; Vector4F previousV1 = Vector4F.Zero; Vector4F previousV2 = Vector4F.Zero; for (int i = 0; i < flattenedPoints.Count; i++) { Vector3F start = flattenedPoints[i]; Vector3F previous; bool isFirstPoint = (i == 0); if (!isFirstPoint) { previous = flattenedPoints[i - 1]; } else if (isClosed && road.SmoothEnds) { previous = flattenedPoints[flattenedPoints.Count - 2]; } else { previous = start; } Vector3F next; bool isLastPoint = (i + 1 == flattenedPoints.Count); if (!isLastPoint) { next = flattenedPoints[i + 1]; } else if (isClosed && road.SmoothEnds) { next = flattenedPoints[1]; } else { next = start; } Vector3F tangent = next - previous; Vector3F orthonormal = new Vector3F(tangent.Z, 0, -tangent.X); if (!orthonormal.TryNormalize()) { orthonormal = lastOrthonormal; } // Add 2 vertices two segments for the road side border. // // pV1 pV2 (previous vertices) // x x // | | // x x // v1 v2 (current vertices) // // We store the side falloff with the vertex: // Vectors are 4D. Height is y. Side falloff is w. Vector4F v1 = new Vector4F(start - orthonormal * (halfWidths[i] + 0), sideFalloffs[i]); Vector4F v2 = new Vector4F(start + orthonormal * (halfWidths[i] + 0), sideFalloffs[i]); if (i > 0) { segments.Add(previousV1); segments.Add(v1); segments.Add(previousV2); segments.Add(v2); if (isLastPoint && !isClosed) { // A segment for the end of the road. segments.Add(v1); segments.Add(v2); } } else { if (!isClosed) { // A segment for the start of the road. segments.Add(v1); segments.Add(v2); } } previousV1 = v1; previousV2 = v2; lastOrthonormal = orthonormal; // The flattened points list contains 2 points per line segment, which means that there // are duplicate intermediate points, which we skip. bool isLastLineSegment = (i + 2 == flattenedPoints.Count); if (!isLastLineSegment) { i++; } } // Apply the side falloff to the terrain heights. // We use a padding where the road influence is 100% because we want road width to be flat // but that means that we also have to set some triangle vertices outside the road width to // full 100% road height. float padding = cellSizeDiagonal; ClampHeightsToLineSegments(terrain, aabbWithSideFalloffs, segments, padding); } // Clamp the terrain heights to the inner part of the road. // We create quads for the road mesh and clamp the heights to the quad triangles. { Vector3F previousV1 = Vector3F.Zero; Vector3F previousV2 = Vector3F.Zero; Vector3F lastOrthonormal = Vector3F.Right; for (int i = 0; i < flattenedPoints.Count; i++) { Vector3F start = flattenedPoints[i]; Vector3F previous; bool isFirstPoint = (i == 0); if (!isFirstPoint) { previous = flattenedPoints[i - 1]; } else if (isClosed && road.SmoothEnds) { previous = flattenedPoints[flattenedPoints.Count - 2]; } else { previous = start; } Vector3F next; bool isLastPoint = (i + 1 == flattenedPoints.Count); if (!isLastPoint) { next = flattenedPoints[i + 1]; } else if (isClosed && road.SmoothEnds) { next = flattenedPoints[1]; } else { next = start; } Vector3F tangent = next - previous; Vector3F orthonormal = new Vector3F(tangent.Z, 0, -tangent.X); if (!orthonormal.TryNormalize()) { orthonormal = lastOrthonormal; } // Add 2 vertices to create a mesh like this: // // pV1 pV2 (previous vertices) // x---------------x // | | // x---------------x // v1 v2 (current vertices) // // Then we check all height samples against these triangles. // Vectors are 4D. Height is y. Influence is w. Vector3F v1 = start - orthonormal * halfWidths[i]; Vector3F v2 = start + orthonormal * halfWidths[i]; if (i > 0) { ClampHeightsToQuad(terrain, previousV1, previousV2, v1, v2); } previousV1 = v1; previousV2 = v2; lastOrthonormal = orthonormal; // The flattened points list contains 2 points per line segment, which means that there // are duplicate intermediate points, which we skip. bool isLastLineSegment = (i + 2 == flattenedPoints.Count); if (!isLastLineSegment) { i++; } } } terrain.Invalidate(); }
public void GetTangentShouldReturnZeroIfPathIsEmpty() { Path3F empty = new Path3F(); empty.Sort(); Assert.AreEqual(Vector3F.Zero, empty.GetTangent(-0.5f)); Assert.AreEqual(Vector3F.Zero, empty.GetTangent(0)); Assert.AreEqual(Vector3F.Zero, empty.GetTangent(0.5f)); }