public void Test1()
        {
            To   = TerrainPatch.FromId(new Point(103, 138));
            From = TerrainPatch.FromId(new Point(103, 139));

            To.FillPointsAndMatrices(Terrain);
            From.FillPoints(Terrain);
            To.UpdateHorizonBugHunt(From);
            Console.WriteLine(@"Test1 finished.");
        }
        public void LoadRam()
        {
            Patch.FillPoints(tiles.TileTreeNode.Terrain);
            var width    = Patch.Width;
            var height   = Patch.Height;
            var vertices = new Vertex[VertexCount];

            if (vertices.Length > 65536)
            {
                throw new Exception("Mesh too large");
            }
            float xf = 1f / (width - 1);
            float yf = 1f / (height - 1);

            for (var line = 0; line < height; line++)
            {
                var points_row = Patch.Points[line];
                for (var sample = 0; sample < width; sample++)
                {
                    var idx = line * height + sample;
                    vertices[idx].TexCoord.X = sample * xf;
                    vertices[idx].TexCoord.Y = line * yf;

                    var pt = points_row[sample];

                    vertices[idx].Normal.X = 0f;
                    vertices[idx].Normal.Y = 0f;
                    vertices[idx].Normal.Z = 1f;

                    vertices[idx].Position.X = (float)(pt.X * Kilometers);
                    vertices[idx].Position.Y = (float)(pt.Y * Kilometers);
                    vertices[idx].Position.Z = (float)(pt.Z * Kilometers);

                    //Console.WriteLine($"[{vertices[idx].Position.X},{vertices[idx].Position.Y},{vertices[idx].Position.Z}]");
                }
            }

            // Define a mesh
            var elements = new ushort[(width - 1) * (height - 1) * 6];

            {
                var ptr  = 0;
                var xMax = width - 1;
                var yMax = height - 1;
                for (var x = 0; x < xMax; x++)
                {
                    for (var y = 0; y < yMax; y++)
                    {
                        var v = x * height + y;

                        //Console.WriteLine(@"v={0}", v);

                        elements[ptr++] = (ushort)(v + height);    // a
                        elements[ptr++] = (ushort)(v + 1);         // b
                        elements[ptr++] = (ushort)v;               // c

                        //Console.WriteLine(@"tri [{0}, {1}, {2}]", x + YSize, v + 1, v);

                        elements[ptr++] = (ushort)(v + height);     // a
                        elements[ptr++] = (ushort)(v + height + 1); // b
                        elements[ptr++] = (ushort)(v + 1);          // c

                        //Console.WriteLine(@"tri [{0}, {1}, {2}]", v + YSize, v + YSize + 1, v + 1);
                    }
                }
            }

            if (true)
            {
                // Average the normals
                var buf = DEM.GetNormalAverager(VertexCount);
                for (var i = 0; i < VertexCount; i++)
                {
                    buf[i].Reset();
                }

                var triCount = elements.Length / 3;
                var ptr      = 0;
                for (var tri = 0; tri < triCount; tri++)
                {
                    var p1 = elements[ptr++];
                    var p2 = elements[ptr++];
                    var p3 = elements[ptr++];
                    var v1 = vertices[p1].Position;
                    var v2 = vertices[p2].Position;
                    var v3 = vertices[p3].Position;

                    //Console.WriteLine(@"textureCoords=[{0},{1}], [{2},{3}], [{4},{5}]",
                    //    v[p1].TexCoord.X, v[p1].TexCoord.Y,
                    //    v[p2].TexCoord.X, v[p2].TexCoord.Y,
                    //    v[p3].TexCoord.X, v[p3].TexCoord.Y);

                    Shape.FindNormal(ref v1, ref v2, ref v3, out Vector3 n);

                    if (float.IsNaN(n.X))
                    {
                        throw new Exception(@"Got NaN when creating a mesh normal");
                    }

                    buf[p1].Add(n);
                    buf[p2].Add(n);
                    buf[p3].Add(n);

                    /*
                     * if (tri == 0)
                     * {
                     *  Console.WriteLine(@"First tri");
                     *  Console.WriteLine(@"  p1={0} p2={1} p3={2}", p1, p2, p3);
                     *  Console.WriteLine(@"  v1={0} v2={1} v3={2}", v1, v2, v3);
                     *  Console.WriteLine(@"  n1={0} n2={1} n3={2}", v[p1].Normal, v[p2].Normal, v[p3].Normal);
                     *  Console.WriteLine(@"  calculated normal={0}", n);
                     *  Console.WriteLine();
                     * }
                     */

                    /*
                     * buf[p1].Add(v[p1].Normal);
                     * buf[p2].Add(v[p1].Normal);
                     * buf[p3].Add(v[p2].Normal);
                     */
                }
                for (var i = 0; i < VertexCount; i++)
                {
                    var norm = buf[i].Normal / buf[i].Count;
                    norm.Normalize();

                    /*
                     * if (i == 0 || i == 1 || i == 257)
                     * {
                     *  Console.WriteLine(@"point[{0}].Normal (before)={1}", i, v[i].Normal);
                     *  Console.WriteLine(@"new normal={0}", norm);
                     * }
                     */

                    vertices[i].Normal = norm;
                }
            }

            //Buffer = LoadVBO(vertices, elements, InterleavedArrayFormat.T2fN3fV3f);

            using (var ms = new MemoryStream())
            {
                using (var bw = new BinaryWriter(ms))
                {
                    for (var i = 0; i < vertices.Length; i++)
                    {
                        bw.Write((float)vertices[i].TexCoord.X);
                        bw.Write((float)vertices[i].TexCoord.Y);
                        bw.Write((float)vertices[i].Normal.X);
                        bw.Write((float)vertices[i].Normal.Y);
                        bw.Write((float)vertices[i].Normal.Z);
                        bw.Write((float)vertices[i].Position.X);
                        bw.Write((float)vertices[i].Position.Y);
                        bw.Write((float)vertices[i].Position.Z);
                    }
                }
                Vertices = ms.GetBuffer();
            }

            CornerVertices[0] = vertices[0 * Width + 0];
            CornerVertices[1] = vertices[0 * Width + (Width - 1)];
            CornerVertices[2] = vertices[(Height - 1) * Width + 0];
            CornerVertices[3] = vertices[(Height - 1) * Width + (Width - 1)];

            BoundingSphereRadius  = 100f; // Radius;
            BoundingSphereDefined = true;
        }
        static float EncodingToSlope(int encoding) => 4f * encoding / 1000000f;  // Haven't looked at the numerics closely to see whether these are exact inverses.  Don't care enough yet.

        /// <summary>
        /// Generate the near horizon for the patch, overwriting what was there.
        /// The horizons will be in slope format, not angles
        /// </summary>
        /// <param name="target"></param>
        public void AddNearHorizon(TerrainPatch target)
        {
            Debug.Assert(Terrain != null);
            Debug.Assert(target != null);
            if (target.Horizons == null)
            {
                target.InitializeHorizons();
            }
            using (var context = new Context())
            {
                AcceleratorId aid = Accelerator.Accelerators.Where(id => id.AcceleratorType == AcceleratorType.Cuda).FirstOrDefault();
                //AcceleratorId aid = Accelerator.Accelerators.Where(id => id.AcceleratorType == AcceleratorType.CPU).FirstOrDefault();
                if (aid.AcceleratorType != AcceleratorType.Cuda)
                {
                    Console.WriteLine(@"There is no CUDA accelerator present.  Doing nothing.");
                    return;
                }
                using (var accelerator = Accelerator.Create(context, aid))
                {
                    target.Matrices = null;  // Be sure!!
                    target.FillPoints(Terrain);
                    target.FillMatricesRelativeToPoint(Terrain, target.Points[0][0]);

                    // Matrices
                    var cpu_matrices_size = target.Height * target.Width * 12;
                    var basePoint         = target.Points[0][0];
                    var cpu_matrices      = MakeCPUMatrices(target);

                    // Horizon (load from target)
                    var cpu_horizon_size = target.Height * target.Width * Horizon.HorizonSamples;
                    var cpu_horizon      = new float[cpu_horizon_size];

                    // Initialize to float.MinValue.  Maintain horizon as slope
                    for (var i = 0; i < cpu_horizon_size; i++)
                    {
                        cpu_horizon[i] = float.MinValue;
                    }

                    // Caster array
                    const int dem_size    = TerrainPatch.DEM_size;
                    const int patch_size  = TerrainPatch.DefaultSize;
                    var       maxDistance = (float)TerrainPatch.MaximumLocalDistance;
                    var       border      = 2 + (int)Math.Ceiling(maxDistance); // the 2 is margin for the bilinear interpolation

                    var line_min    = Math.Max(0, target.Line - border);
                    var line_max    = Math.Min(dem_size - 1, target.Line + patch_size + border); // 1 more than the highest index
                    var line_size   = line_max - line_min;
                    var line_offset = target.Line - line_min;

                    var sample_min    = Math.Max(0, target.Sample - border);
                    var sample_max    = Math.Min(dem_size - 1, target.Sample + patch_size + border); // 1 more than the highest index
                    var sample_size   = sample_max - sample_min;
                    var sample_offset = target.Sample - sample_min;

                    var cpu_caster_points_size = line_size * sample_size * 3;
                    var cpu_caster_points      = new float[cpu_caster_points_size];
                    FillNearCasterArray(cpu_caster_points, target.Points[0][0], line_min, line_max, sample_min, sample_max);

                    //DumpNearFieldTestCsv(cpu_caster_points, cpu_matrices);

                    var test_floats_size = Horizon.HorizonSamples * TerrainPatch.NearHorizonOversample;
                    var test_floats      = new float[test_floats_size];

                    using (var gpu_matrices = accelerator.Allocate <float>(cpu_matrices_size))
                        using (var gpu_horizon = accelerator.Allocate <float>(cpu_horizon_size))
                            using (var gpu_caster_points = accelerator.Allocate <float>(cpu_caster_points_size))
                                using (var gpu_test_floats = accelerator.Allocate <float>(test_floats_size))
                                {
                                    gpu_matrices.CopyFrom(cpu_matrices, 0, 0, cpu_matrices_size);
                                    gpu_horizon.CopyFrom(cpu_horizon, 0, 0, cpu_horizon_size);
                                    gpu_caster_points.CopyFrom(cpu_caster_points, 0, 0, cpu_caster_points_size);
                                    gpu_test_floats.CopyFrom(test_floats, 0, 0, test_floats_size);

                                    var   groupSize       = accelerator.MaxNumThreadsPerGroup;
                                    Index launchDimension = Horizon.HorizonSamples * TerrainPatch.NearHorizonOversample;

                                    const float d_min  = 1f;
                                    var         d_max  = (float)TerrainPatch.MaximumLocalDistance;
                                    const float d_step = TerrainPatch.LocalStep;

                                    var kernel1 = accelerator.LoadAutoGroupedStreamKernel <Index, int, int, int, int, int, int, float, float, float, ArrayView <float>, ArrayView <float>, ArrayView <float>, ArrayView <float> >(NearHorizonKernel1);

                                    Console.WriteLine(@"Launching near horizon kernels ... ");
                                    if (true)
                                    {
                                        for (var target_line = 0; target_line < TerrainPatch.DefaultSize; target_line++)
                                        {
                                            for (var target_sample = 0; target_sample < TerrainPatch.DefaultSize; target_sample++)
                                            {
                                                kernel1(launchDimension, target_line, target_sample, line_offset, sample_offset, line_size, sample_size, d_min, d_max, d_step, gpu_caster_points, gpu_matrices, gpu_horizon, gpu_test_floats);
                                            }
                                        }
                                    }
                                    else
                                    {
                                        kernel1(launchDimension, 0, 0, line_offset, sample_offset, line_size, sample_size, d_min, d_max, d_step, gpu_caster_points, gpu_matrices, gpu_horizon, gpu_test_floats);
                                    }

                                    gpu_horizon.CopyTo(cpu_horizon, 0, 0, cpu_horizon_size);
                                    gpu_test_floats.CopyTo(test_floats, 0, 0, test_floats_size);

                                    // Update the horizons
                                    for (var line = 0; line < TerrainPatch.DefaultSize; line++)
                                    {
                                        for (var sample = 0; sample < TerrainPatch.DefaultSize; sample++)
                                        {
                                            var offset = (line * TerrainPatch.DefaultSize + sample) * Horizon.HorizonSamples;
                                            var buffer = target.Horizons[line][sample].Buffer;
                                            for (var i = 0; i < Horizon.HorizonSamples; i++)
                                            {
                                                buffer[i] = cpu_horizon[i + offset];
                                            }
                                        }
                                    }
                                }
                }
            }
        }
        /// <summary>
        /// Update the horizons of a patch based on a list of shadow casters.
        /// The horizons will be in slope, not angle, format
        /// </summary>
        /// <param name="target"></param>
        /// <param name="casters"></param>
        public void UpdateHorizons(TerrainPatch target, List <TerrainPatch> casters)
        {
            Debug.Assert(Terrain != null);
            if (casters.Count < 1)
            {
                return;
            }
            using (var context = new Context())
            {
                AcceleratorId aid = Accelerator.Accelerators.Where(id => id.AcceleratorType == AcceleratorType.Cuda).FirstOrDefault();
                if (aid.AcceleratorType != AcceleratorType.Cuda)
                {
                    Console.WriteLine(@"There is no CUDA accelerator present.  Doing nothing.");
                    return;
                }
                using (var accelerator = Accelerator.Create(context, aid))
                {
                    target.FillPoints(Terrain);
                    target.FillMatricesRelativeToPoint(Terrain, target.Points[0][0]);

                    // Matrices
                    var cpu_matrices_size = target.Height * target.Width * 12;
                    var basePoint         = target.Points[0][0];
                    var cpu_matrices      = MakeCPUMatrices(target);

                    // Horizon (load from target)
                    var cpu_horizon_size = target.Height * target.Width * Horizon.HorizonSamples;
                    var cpu_horizon      = new int[cpu_horizon_size];
                    for (var line = 0; line < TerrainPatch.DefaultSize; line++)
                    {
                        for (var sample = 0; sample < TerrainPatch.DefaultSize; sample++)
                        {
                            var offset = (line * TerrainPatch.DefaultSize + sample) * Horizon.HorizonSamples;
                            var buffer = target.Horizons[line][sample].Buffer;
                            for (var i = 0; i < Horizon.HorizonSamples; i++)
                            {
                                cpu_horizon[i + offset] = SlopeToEncoding(buffer[i]);
                            }
                        }
                    }

                    // Caster points
                    var cpu_caster_points_size = casters[0].Width * casters[0].Height * 3;
                    var cpu_caster_points      = new float[cpu_caster_points_size];

                    // test array
                    var cpu_test_array = new float[20];

                    using (var gpu_matrices = accelerator.Allocate <float>(cpu_matrices_size))
                        using (var gpu_horizon = accelerator.Allocate <int>(cpu_horizon_size))
                            using (var gpu_caster_points = accelerator.Allocate <float>(cpu_caster_points_size))
                                using (var gpu_test_array = accelerator.Allocate <float>(cpu_test_array.Length))
                                {
                                    gpu_matrices.CopyFrom(cpu_matrices, 0, 0, cpu_matrices_size);
                                    gpu_horizon.CopyFrom(cpu_horizon, 0, 0, cpu_horizon_size);

                                    var groupSize       = accelerator.MaxNumThreadsPerGroup;
                                    var launchDimension = new GroupedIndex2(
                                        new Index2(128, 128), // (data.Length + groupSize - 1) / groupSize,  // Compute the number of groups (round up)
                                        new Index2(1, 128));

                                    var kernel1 = accelerator.LoadSharedMemoryStreamKernel1 <GroupedIndex2, ArrayView <float>, ArrayView <float>, ArrayView <int>, ArrayView <float>, ArrayView <int> >(ShadowKernel1);

                                    //var stopwatch = new Stopwatch();
                                    //stopwatch.Start();

                                    foreach (var caster in casters)
                                    {
                                        caster.FillPoints(Terrain);
                                        CopyPointsToCpuArray(caster, basePoint, cpu_caster_points);
                                        gpu_caster_points.CopyFrom(cpu_caster_points, 0, 0, cpu_caster_points_size);

                                        kernel1(launchDimension, gpu_caster_points, gpu_matrices, gpu_horizon, gpu_test_array);

                                        accelerator.Synchronize();
                                    }

                                    // Copy out data
                                    gpu_horizon.CopyTo(cpu_horizon, 0, 0, cpu_horizon_size);
                                    gpu_test_array.CopyTo(cpu_test_array, 0, 0, cpu_test_array.Length);

                                    //stopwatch.Stop();
                                    //Console.WriteLine($"kernel time={stopwatch.Elapsed} cpu_horizon.Max()={cpu_horizon.Max()} cpu_horizon[0]={cpu_horizon[0]}");

                                    // Update the horizons
                                    for (var line = 0; line < TerrainPatch.DefaultSize; line++)
                                    {
                                        for (var sample = 0; sample < TerrainPatch.DefaultSize; sample++)
                                        {
                                            var offset = (line * TerrainPatch.DefaultSize + sample) * Horizon.HorizonSamples;
                                            var buffer = target.Horizons[line][sample].Buffer;
                                            for (var i = 0; i < Horizon.HorizonSamples; i++)
                                            {
                                                buffer[i] = EncodingToSlope(cpu_horizon[i + offset]);
                                            }
                                        }
                                    }

                                    //Console.WriteLine($"  max slope={cpu_horizon.Select(EncodingToSlope).Max()}");
                                }
                }
            }
        }