/// <summary>
		/// Adds the geometry for the polyline only (i.e. it adds no geometry for the start and end caps).
		/// </summary>
		/// <param name="AddPositionAndNormal">The procedure to add position and normal of one triangle vertex.</param>
		/// <param name="AddIndices">The prodedure to add the indices for one triangle.</param>
		/// <param name="vertexIndexOffset">The vertex index offset at the start of this procedure.</param>
		/// <param name="polylinePoints">The polyline points.</param>
		private void AddGeometryForLineOnly(
			Action<PointD3D, VectorD3D> AddPositionAndNormal,
			Action<int, int, int, bool> AddIndices,
			ref int vertexIndexOffset,
			IEnumerable<PolylinePointD3D> polylinePoints
			)
		{
			var crossSectionVertexCount = _crossSection.NumberOfVertices;
			var crossSectionNormalCount = _crossSection.NumberOfNormals;

			PointD3D tp; // transformed position
			VectorD3D tn; // transformed normal

			int currIndex = vertexIndexOffset;

			var polylineEnumerator = polylinePoints.GetEnumerator();
			if (!polylineEnumerator.MoveNext())
				return; // there is nothing to draw here, because no points are in this line

			var veryFirstItem = polylineEnumerator.Current;

			if (!polylineEnumerator.MoveNext())
				return; // there is nothing to draw here, because there is only one point in this line

			var currentItem = polylineEnumerator.Current;
			VectorD3D currSeg = veryFirstItem.ForwardVector;

			// Get the matrix for the start plane
			var matrixCurr = Math3D.Get2DProjectionToPlane(veryFirstItem.WestVector, veryFirstItem.NorthVector, veryFirstItem.Position);

			// ************************** Position and normals for the very first segment ********************************

			// now the positions and normals for the start of the first segment
			for (int i = 0, j = 0; i < crossSectionVertexCount; ++i, ++j)
			{
				_positionsTransformedStartCurrent[i] = matrixCurr.Transform(_crossSection.Vertices(i));
				_normalsTransformedCurrent[j] = matrixCurr.Transform(_crossSection.Normals(j));

				if (_crossSection.IsVertexSharp(i))
				{
					++j;
					_normalsTransformedCurrent[j] = matrixCurr.Transform(_crossSection.Normals(j));
				}
			}

			// ************************** For all polyline segments ****************************************************
			while (polylineEnumerator.MoveNext())
			{
				var nextItem = polylineEnumerator.Current; // this is already the item for the end of the next segment
				VectorD3D nextSeg = nextItem.ForwardVector;

				// ******************** calculate normals for the start of the next segment ********************************
				var matrixNext = Math3D.Get2DProjectionToPlane(nextItem.WestVector, nextItem.NorthVector, currentItem.Position);
				for (int i = 0, j = 0; i < crossSectionVertexCount; ++i, ++j)
				{
					_normalsTransformedNext[j] = matrixNext.Transform(_crossSection.Normals(j));

					if (_crossSection.IsVertexSharp(i))
					{
						++j;
						_normalsTransformedNext[j] = matrixNext.Transform(_crossSection.Normals(j));
					}
				}

				var dot_curr_next = VectorD3D.DotProduct(currSeg, nextSeg);

				// now we have three cases
				// 1st) dot_curr_next is very close to 1, the symmetry plane can be evaluated, but we don't need bevel or miter
				// 2nd) dot_curr_next is very close to -1, the symmetry place can not be evaluated, but the reflection plane can, we can set an end cap here
				// 3rd) normal case both symmetry plane and reflection plane can be evaluated.

				if (!(-Cos01Degree < dot_curr_next)) // next segment is almost 180° to current segment
				{
					// we can not use the symmetry plane here, because it would be close to 90° to the segment directions
					// instead we use the reflection plane
					VectorD3D reflectionPlaneNormal = (currSeg - nextSeg).Normalized;

					PointD3D reflectionPoint = currentItem.Position;

					// now get the matrix for transforming the cross sections positions
					var matrixReflectionPlane = Math3D.Get2DProjectionToPlaneToPlane(currentItem.WestVector, currentItem.NorthVector, currSeg, reflectionPoint, reflectionPlaneNormal);
					// Calculate the positions of the end of the current segment
					for (int i = 0; i < crossSectionVertexCount; ++i)
					{
						_positionsTransformedEndCurrent[i] = _positionsTransformedStartNext[i] = matrixReflectionPlane.Transform(_crossSection.Vertices(i));
					}
					// we put an end cap hereabove
					LineCaps.Flat.AddGeometry(
							AddPositionAndNormal,
							AddIndices,
							ref vertexIndexOffset,
							false,
							reflectionPoint,
							reflectionPlaneNormal,
						_positionsTransformedEndCurrent);
				}
				else // not close to 180°, thus the symmetry plane can be evaluated
				{
					VectorD3D symmetryPlaneNormal = (currSeg + nextSeg).Normalized;
					// now get the matrix for transforming the cross sections positions
					var matrixSymmetryPlane = Math3D.Get2DProjectionToPlaneToPlane(currentItem.WestVector, currentItem.NorthVector, currSeg, currentItem.Position, symmetryPlaneNormal);

					// normal case: consider bevel or miter only if angle > 1 degree, and not close to 180
					// dot_curr_next<=_miterLimitDotThreshold is a criterion that avoids unneccessary computations
					if (dot_curr_next < Cos1Degree && (_lineJoin == PenLineJoin.Bevel || dot_curr_next <= _miterLimitDotThreshold))
					{
						//reflection plane is the plane at which the line seems to be reflected.
						VectorD3D reflectionPlaneNormal = (currSeg - nextSeg).Normalized;

						// For further calculations it is neccessary to rotate the 2D crossection points in such a way
						// that the reflection direction points in the x-direction in the rotated coordinate system.
						// By this it is possible which cross section points are "above" the bevel plane
						// (namely all cross section points with an x-coordinate above a threshold) and which are below.
						// By this it is even possible to determine the exact location where the cross section crosses the
						// bevel plane. This is done by interpolation between two cross section points and determining, at which
						// position the x-coordinate of the line crosses the threshold.

						// To determine the rotation matrix for the cross section points, we need the direction of the reflectionPlaneNormal
						// with respect to the west and north vectors, like so:
						var dot_w = VectorD3D.DotProduct(currentItem.WestVector, reflectionPlaneNormal);
						var dot_n = VectorD3D.DotProduct(currentItem.NorthVector, reflectionPlaneNormal);
						var det = Calc.RMath.Hypot(dot_w, dot_n);
						dot_w /= det;
						dot_n /= det;
						var crossSectionRotationMatrix = new Matrix2x2(dot_w, dot_n, dot_n, -dot_w); // Matrix that will transform our cross section points in a way so that the edge between the 3D lines are in x direction of the transformed points

						// determine maxheight as the maximum of the x-coordinate of the rotated cross section vertices
						double maxheight = 0;
						for (int i = 0; i < crossSectionVertexCount; ++i)
						{
							_crossSectionRotatedVertices[i] = crossSectionRotationMatrix.Transform((VectorD2D)_crossSection.Vertices(i));
							maxheight = Math.Max(maxheight, _crossSectionRotatedVertices[i].X);
						}

						// alphaBy2 is the half angle between the current segment and the next segment
						var alphaBy2 = 0.5 * (Math.PI - Math.Acos(dot_curr_next));
						double heightOfBevelPlane = 0;// height of the bevel plane above the segment middle lines

						switch (_lineJoin)
						{
							case PenLineJoin.Bevel:
								heightOfBevelPlane = maxheight * Math.Sin(alphaBy2); // height of the bevel plane above the segment middle lines
								break;

							case PenLineJoin.Miter:
								heightOfBevelPlane = _miterLimit * maxheight; // height of the bevel plane above the segment middle lines
								break;

							default:
								throw new NotImplementedException();
						}
						// crossSectionDistanceThreshold: if the x-coordinate of the rotated cross section vertices is above this threshold, it needs to be clipped to the bevel plane
						var crossSectionDistanceThreshold = heightOfBevelPlane * Math.Sin(alphaBy2); // height as x-coordinate of the rotated cross section

						bool previousPointIsAboveHeight = _crossSectionRotatedVertices[crossSectionVertexCount - 1].X > crossSectionDistanceThreshold;
						int firstIndexOfBevelVertex = -1;
						for (int i = 0; i < crossSectionVertexCount; ++i)
						{
							bool currentPointIsAboveHeight = _crossSectionRotatedVertices[i].X > crossSectionDistanceThreshold;
							if (currentPointIsAboveHeight && !previousPointIsAboveHeight)
							{
								firstIndexOfBevelVertex = i;
								break;
							}
							previousPointIsAboveHeight = currentPointIsAboveHeight;
						}

						// we need not to take any bevel stuff if firstIndexOfBevelVertex is < 0

						var pointAtBevelPlane = currentItem.Position + heightOfBevelPlane * reflectionPlaneNormal;
						Matrix4x3 bevelMatrix1 = Math3D.GetProjectionToPlane(currSeg, pointAtBevelPlane, reflectionPlaneNormal); // Projects a point from the current segment onto the bevel plane
						Matrix4x3 bevelMatrix2 = Math3D.GetProjectionToPlane(nextSeg, pointAtBevelPlane, reflectionPlaneNormal); // projects a point from the next segment onto the bevel plane

						// Calculate the positions of the end of the current segment
						for (int i = 0; i < crossSectionVertexCount; ++i)
						{
							tp = matrixSymmetryPlane.Transform(_crossSection.Vertices(i));
							// decide whether the transformed point is above or below the bevel plane
							if (_crossSectionRotatedVertices[i].X >= crossSectionDistanceThreshold)
							{
								// then the point is above the bevel plane; we need to project it to the bevel plane
								_positionsTransformedEndCurrent[i] = bevelMatrix1.Transform(tp);
								_positionsTransformedStartNext[i] = bevelMatrix2.Transform(tp);
							}
							else
							{
								_positionsTransformedEndCurrent[i] = _positionsTransformedStartNext[i] = tp;
							}
						}

						#region Bevel plane meshing

						// mesh the bevel plane now
						if (firstIndexOfBevelVertex >= 0)
						{
							// find first index of the polygon on the bevel plane (the first index is not on the plane, then the next indices are on the plane, and the last index is not on the plane)
							int firstPolygonIndex = (firstIndexOfBevelVertex - 1 + crossSectionVertexCount) % crossSectionVertexCount;
							do // for every subsection of the cross section which is on the bevel plane, i.e. for which is the X compoment of the  _crossSectionRotatedVertices >=  crossSectionDistanceThreshold
							{
								// find the last index of the polygon on the bevel plane (the first index is not on the plane, then the next indices are on the plane, and the last index is not on the plane)
								int lastPolygonIndex;
								for (lastPolygonIndex = firstPolygonIndex + 1; lastPolygonIndex < (firstPolygonIndex + crossSectionVertexCount); lastPolygonIndex++)
								{
									if (!(_crossSectionRotatedVertices[lastPolygonIndex % crossSectionVertexCount].X >= crossSectionDistanceThreshold))
										break;
								}

								// mesh the polygon. The trick here is the following: the real polygon on the bevel plane is the original cross section, but cut by a line (which represents in rotated coordinates _crossSectionRotatedVertices.X is equal to  crossSectionDistanceThreshold)
								// thus every polygon on the bevel plane would be unique (depending on the orientation and angle of the line segments)
								// thus in principle every polygon must be meshed separately, but meshing is compute intensive
								// Solution: the meshing triangle indices are the same, whether you would use the exact polygon cut by the line, or if you would use the points of the original cross section next to the cut points.
								// thus we store for every polygon formed as a part of the cross section, defined by the first vertex index, and the last vertex index,
								// the triangle indices that form the polygon mesh
								int[] indexedTriangles;
								if (!_crossSectionPartsTriangleIndices.TryGetValue(new Tuple<int, int>(firstPolygonIndex, lastPolygonIndex), out indexedTriangles))
								{
									var polygon = PolygonClosedD2D.FromPoints(CrossSectionOfLine.GetVerticesFromToIncluding(_crossSection, firstPolygonIndex, lastPolygonIndex)); // polygon formed from a part of the cross section from firstPolygonIndex to (and including) lastPolygonIndex
									indexedTriangles = Triangulate(polygon); // calculate the triangle indices that form the mesh of this polygon
									_crossSectionPartsTriangleIndices.Add(new Tuple<int, int>(firstPolygonIndex, lastPolygonIndex), indexedTriangles); // cache those indices for later use
								}

								// now add and calculate the points on the bevel plane
								// we do this simultaneously for the points of the end of the current segment and the points of the start of the next segment
								for (int i = firstPolygonIndex; i <= lastPolygonIndex; ++i)
								{
									int icurr = i % crossSectionVertexCount;

									PointD2D crossSectionPoint;
									if (i == firstPolygonIndex) // first index, i.e. this index is not on the bevel plane, but next index is on the bevel plane
									{
										int inext = (i + 1) % crossSectionVertexCount;
										double r = (crossSectionDistanceThreshold - _crossSectionRotatedVertices[icurr].X) / (_crossSectionRotatedVertices[inext].X - _crossSectionRotatedVertices[icurr].X);
										if (!(0 <= r && r <= 1))
											throw new InvalidProgramException("r should always be >=0 and <=1, so what's going wrong here?");
										crossSectionPoint = (1 - r) * _crossSection.Vertices(icurr) + r * _crossSection.Vertices(inext); // this is a point on the crossSection that exactly lies on the bevel plane
									}
									else if (i == lastPolygonIndex) // last index, i.e. this index is not on the bevel plane, but the previous index was still on the bevel plane
									{
										int iprev = (i - 1 + crossSectionVertexCount) % crossSectionVertexCount;
										double r = (crossSectionDistanceThreshold - _crossSectionRotatedVertices[iprev].X) / (_crossSectionRotatedVertices[icurr].X - _crossSectionRotatedVertices[iprev].X);
										if (!(0 <= r && r <= 1))
											throw new InvalidProgramException("r should always be >=0 and <=1, so what's going wrong here?");
										crossSectionPoint = (1 - r) * _crossSection.Vertices(iprev) + r * _crossSection.Vertices(icurr); // this is a point on the crossSection that exactly lies on the bevel plane
									}
									else
									{
										crossSectionPoint = _crossSection.Vertices(icurr); // index in the
									}

									tp = matrixSymmetryPlane.Transform(crossSectionPoint);
									// then the point is above the bevel plane; we need to project it to the bevel plane
									var positionsTransformedEndCurrent = bevelMatrix1.Transform(tp);
									var positionsTransformedStartNext = bevelMatrix2.Transform(tp);
									AddPositionAndNormal(positionsTransformedEndCurrent, reflectionPlaneNormal); // points cut from the end of the current line segment lying on the bevel plane
									AddPositionAndNormal(positionsTransformedStartNext, reflectionPlaneNormal); // points cut from the start of the next line segment lying on the bevel plane
								}

								// now add the triangle indices that form the triangle mesh on the bevel plane
								// again, this is done simultaneously for the points of the end of the current segment and the start of the next segment
								for (int i = 0; i < indexedTriangles.Length; i += 3)
								{
									AddIndices(currIndex + 2 * indexedTriangles[i], currIndex + 2 * indexedTriangles[i + 1], currIndex + 2 * indexedTriangles[i + 2], false); // indices for the points cut from the end of the current line segment lying on the bevel plane
									AddIndices(currIndex + 1 + 2 * indexedTriangles[i], currIndex + 1 + 2 * indexedTriangles[i + 1], currIndex + 1 + 2 * indexedTriangles[i + 2], true);  // indices for the points cut from the start of the next line segment lying on the bevel plane
								}

								currIndex += 2 * (lastPolygonIndex + 1 - firstPolygonIndex); // we have added this number of points for the bevel plane

								// now find the next first polygon index that is located on the bevel plane (strictly spoken, we are searching for the point before this point)
								for (firstPolygonIndex = lastPolygonIndex; firstPolygonIndex < crossSectionVertexCount; ++firstPolygonIndex)
								{
									if (_crossSectionRotatedVertices[(firstPolygonIndex + 1) % crossSectionVertexCount].X >= crossSectionDistanceThreshold)
										break;
								}
							} while ((firstPolygonIndex + 1) < crossSectionVertexCount); // for each subsection of the cross section
						} // end of bevel plane meshing

						#endregion Bevel plane meshing

						// now mesh the side faces
						for (int i = 0, j = 0; i < crossSectionVertexCount; ++i, ++j)
						{
							if (_crossSection.IsVertexSharp(i))
							{ ++j; }

							int inext = (i + 1) % crossSectionVertexCount;
							int jnext = (j + 1) % crossSectionNormalCount;

							if ((_crossSectionRotatedVertices[i].X > crossSectionDistanceThreshold && _crossSectionRotatedVertices[inext].X <= crossSectionDistanceThreshold) ||
								(_crossSectionRotatedVertices[i].X <= crossSectionDistanceThreshold && _crossSectionRotatedVertices[inext].X > crossSectionDistanceThreshold))
							{
								double r = (crossSectionDistanceThreshold - _crossSectionRotatedVertices[i].X) / (_crossSectionRotatedVertices[inext].X - _crossSectionRotatedVertices[i].X);
								if (!(0 <= r && r <= 1))
									throw new InvalidProgramException("r should always be >=0 and <=1, so what's going wrong here?");

								var additionalCrossSectionVertex = (1 - r) * _crossSection.Vertices(i) + r * _crossSection.Vertices(inext);
								var additionalCrossSectionNormal = ((1 - r) * _crossSection.Normals(j) + r * _crossSection.Normals(jnext)).Normalized;

								tp = matrixSymmetryPlane.Transform(additionalCrossSectionVertex);
								tn = matrixCurr.Transform(additionalCrossSectionNormal);

								AddPositionAndNormal(tp, tn);
								++currIndex;
								AddPositionAndNormal(_positionsTransformedEndCurrent[i], _normalsTransformedCurrent[j]);
								++currIndex;
								AddPositionAndNormal(_positionsTransformedEndCurrent[inext], _normalsTransformedCurrent[jnext]);
								++currIndex;
								AddIndices(currIndex - 1, currIndex - 2, currIndex - 3, true);

								tn = matrixNext.Transform(additionalCrossSectionNormal);
								AddPositionAndNormal(tp, tn);
								++currIndex;
								AddPositionAndNormal(_positionsTransformedStartNext[i], _normalsTransformedNext[j]);
								++currIndex;
								AddPositionAndNormal(_positionsTransformedStartNext[inext], _normalsTransformedNext[jnext]);
								++currIndex;
								AddIndices(currIndex - 1, currIndex - 2, currIndex - 3, false);
							}
						}
					}
					else // without bevel or miter
					{
						// Calculate the positions of the end of the current segment
						for (int i = 0; i < crossSectionVertexCount; ++i)
						{
							_positionsTransformedEndCurrent[i] = _positionsTransformedStartNext[i] = matrixSymmetryPlane.Transform(_crossSection.Vertices(i));
						}
					}
				}

				// draw the segment from the previous point to the current point
				for (int i = 0, j = 0; i < crossSectionVertexCount; ++i, ++j)
				{
					if (j == 0)
					{
						AddIndices(currIndex, currIndex + 1, currIndex + 2 * crossSectionNormalCount - 2, false);
						AddIndices(currIndex + 2 * crossSectionNormalCount - 2, currIndex + 1, currIndex + 2 * crossSectionNormalCount - 1, false);
					}
					else
					{
						AddIndices(currIndex, currIndex + 1, currIndex - 2, false);
						AddIndices(currIndex - 2, currIndex + 1, currIndex - 1, false);
					}

					AddPositionAndNormal(_positionsTransformedStartCurrent[i], _normalsTransformedCurrent[j]);
					AddPositionAndNormal(_positionsTransformedEndCurrent[i], _normalsTransformedCurrent[j]);
					currIndex += 2;

					if (_crossSection.IsVertexSharp(i))
					{
						++j;
						AddPositionAndNormal(_positionsTransformedStartCurrent[i], _normalsTransformedCurrent[j]);
						AddPositionAndNormal(_positionsTransformedEndCurrent[i], _normalsTransformedCurrent[j]);
						currIndex += 2;
					}
				}
				// previous segment is done now

				// switch lastPositionsTransformed - the positions of the end of the previous segment are then the positions of the start of the new segment
				var h = _positionsTransformedStartCurrent;
				_positionsTransformedStartCurrent = _positionsTransformedStartNext;
				_positionsTransformedStartNext = h;

				// switch normals
				var v = _normalsTransformedCurrent;
				_normalsTransformedCurrent = _normalsTransformedNext;
				_normalsTransformedNext = v;

				currentItem = nextItem;
				currSeg = nextSeg;
				matrixCurr = matrixNext;
			} // for all segments - except the last one

			// *************************** very last segment ***********************************************

			// now add the positions and normals for the end of the last segment and the triangles of the last segment

			matrixCurr = Math3D.Get2DProjectionToPlane(currentItem.WestVector, currentItem.NorthVector, currentItem.Position);
			for (int i = 0, j = 0; i < crossSectionVertexCount; ++i, ++j)
			{
				_positionsTransformedEndCurrent[i] = tp = matrixCurr.Transform(_crossSection.Vertices(i));
			}

			// draw the end line segment now
			for (int i = 0, j = 0; i < crossSectionVertexCount; ++i, ++j)
			{
				if (j == 0)
				{
					AddIndices(currIndex, currIndex + 1, currIndex + 2 * crossSectionNormalCount - 2, false);
					AddIndices(currIndex + 2 * crossSectionNormalCount - 2, currIndex + 1, currIndex + 2 * crossSectionNormalCount - 1, false);
				}
				else
				{
					AddIndices(currIndex, currIndex + 1, currIndex - 2, false);
					AddIndices(currIndex - 2, currIndex + 1, currIndex - 1, false);
				}

				AddPositionAndNormal(_positionsTransformedStartCurrent[i], _normalsTransformedCurrent[j]);
				AddPositionAndNormal(_positionsTransformedEndCurrent[i], _normalsTransformedCurrent[j]);
				currIndex += 2;

				if (_crossSection.IsVertexSharp(i))
				{
					++j;
					AddPositionAndNormal(_positionsTransformedStartCurrent[i], _normalsTransformedCurrent[j]);
					AddPositionAndNormal(_positionsTransformedEndCurrent[i], _normalsTransformedCurrent[j]);
					currIndex += 2;
				}
			}
			// end line segment is done now

			vertexIndexOffset = currIndex;
		}