Esempio n. 1
0
    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);
    }
        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 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();
        }