private static OpeningPoint2D ProjectOpeningPoint(
            Vector2 openingPoint,
            WallData wall,
            UnfoldedCurve unfoldedWall,
            UnfoldedCurve unfoldedWallSide,
            Func <WallPointNormals, Vector2> normalGetter)
        {
/*            Debug.Log (
 *              $"unwrappedWall {string.Join (",", Array.ConvertAll (unwrappedWall.UnwrappedPoints, x => x.ToString ()))}");
 *
 *          Debug.Log (
 *              $"unwrappedWallSide {string.Join (",", Array.ConvertAll (unwrappedWallSide.UnwrappedPoints, x => x.ToString ()))}");*/

            var wallPoints = wall.Points;

            for (int i = 1; i < wallPoints.Length; i++)
            {
                if (openingPoint.x > unfoldedWall.UnfoldedPoints[i].X)
                {
                    continue;
                }

                var normals       = wall.Normals.Value;
                var normal        = normalGetter(normals[i]);
                var prevNormal    = normalGetter(normals[i - 1]);
                var averageNormal = normal + prevNormal;
                var unwrapped     = unfoldedWall
                                    .Unfold(new Vector2(openingPoint.x, 0f))
                                    .ToUnityVector2();

//                var unwrappedDebug = new Vector3 (unwrapped.x, openingPoint.y, unwrapped.y);

                var unwrappedOnSide = unwrapped.TransposePoint(averageNormal, wall.Width / 2f);
                var wrappedOnSide   = unfoldedWallSide.Fold(unwrappedOnSide);
                var result          = new Vector2(wrappedOnSide.x, openingPoint.y);

//                var unwrappedOnSideDebug = new Vector3 (unwrappedOnSide.x, openingPoint.y, unwrappedOnSide.y);

//                Debug.DrawLine(unwrappedDebug, unwrappedOnSideDebug, color, float.MaxValue);
//                Debug.DrawLine(unwrappedDebug, unwrappedOnSideDebug + new Vector3(normal.x, 0f, normal.y), Color.yellow, float.MaxValue);

/*
 *              Debug.Log (
 *                  $"openingPoint {openingPoint} unwrapped {unwrapped} unwrappedOnSide {unwrappedOnSide} wrappedOnSide {wrappedOnSide} result {result}");
 */

                return(new OpeningPoint2D(result, -averageNormal));
            }

            throw new NotImplementedException();
        }
        private static IEnumerable <Mesh> ProcessJambs(
            Hole3DProjection[] hole3DProjections,
            Hole3DProjection[] oppositeHole3DProjections,
            Predicate <OpeningData> isThroughPredicate,
            string name,
            bool flipFaces = false)
        {
            var jambMeshes = new List <Mesh> ();

            for (int i = 0; i < hole3DProjections.Length; i++)
            {
                var main3DHole   = hole3DProjections[i];
                var opening      = main3DHole.Opening;
                var jambVertices = new List <Vector3> ();

                if (isThroughPredicate(opening))
                {
                    var opposite3DHole = oppositeHole3DProjections
                                         .First(x => x.Opening == main3DHole.Opening);
                    jambVertices
                    .AddRange(
                        opposite3DHole
                        .Points
                        .Select(x => x.Position)
                        .ToArray());
                }
                else
                {
                    var jambBackPoints = main3DHole
                                         .Points
                                         .Select(x => x.Position.TransposePoint(x.Normal, opening.Depth))
                                         .ToArray();
                    jambVertices.AddRange(jambBackPoints);

                    var jambCurvePoints = jambBackPoints
                                          .Select(x => new Vector2(x.x, x.z))
                                          .OrderBy(x => x.x)
                                          .ToArray();

                    var jambCurve      = new UnfoldedCurve(jambCurvePoints);
                    var jamb2DVertices = jambBackPoints
                                         .Select(x => new Vector2(jambCurve.Fold(new Vector2(x.x, x.z)).x, x.y))
                                         .ToList();

                    var mesh = PlaneMeshMaker.Triangulate(jamb2DVertices, null, jambCurve, $"{name} back {i}");
                    if (flipFaces)
                    {
                        mesh.FlipFaces();
                    }
                    jambMeshes.Add(mesh);
                }

                jambVertices.AddRange(
                    main3DHole
                    .Points
                    .Select(x => x.Position)
                    .ToArray());

                var jambTriangles      = new List <int> ();
                var verticesHalfLength = main3DHole.Points.Length;
                for (int j = 0; j < verticesHalfLength; j++)
                {
                    var inner     = j;
                    var outer     = verticesHalfLength + inner;
                    var nextInner = (j + 1) % verticesHalfLength;
                    var nextOuter = verticesHalfLength + nextInner;

                    jambTriangles.AddRange(
                        new[]
                    {
                        inner, nextInner, outer, nextInner, nextOuter, outer
                    });
                }

                var jambMesh = MeshGenerator.CreateMesh(
                    jambVertices.ToArray(),
                    jambTriangles.ToArray(),
                    flipFaces,
                    $"{name} {i}");
                jambMeshes.Add(jambMesh);
            }

            return(jambMeshes);
        }