protected void Invalidate(TerrainPatch p) { var r = p.PixelBox(Transform); r.Inflate(2, 2); Invalidate(r); }
static float[,] GetHeightMap(TerrainPatch[] Terrain) { float average = 23; float[,] height = new float[256, 256]; for (int x = 0; x < 256; x++) { for (int y = 0; y < 256; y++) { int patchX = x / 16; int patchY = y / 16; TerrainPatch patch = Terrain[patchY * 16 + patchX]; if (patch != null) { height[x, y] = average = patch.Data[(y % 16) * 16 + (x % 16)]; } else { height[x, y] = average; } } } return(height); }
public void SaveTerrainRaw32(string path) { var patches = Client.Network.CurrentSim.Terrain; if (patches != null) { int count = 0; for (int i = 0; i < patches.Length; i++) { if (patches[i] != null) { ++count; } } Logger.Log(count + " terrain patches have been received for the current simulator", Helpers.LogLevel.Info); } else { Logger.Log("No terrain information received for the current simulator", Helpers.LogLevel.Info); return; } try { using ( FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write)) { for (int y = 0; y < 256; y++) { for (int x = 0; x < 256; x++) { int xBlock = x / 16; int yBlock = y / 16; int xOff = x - (xBlock * 16); int yOff = y - (yBlock * 16); TerrainPatch patch = patches[yBlock * 16 + xBlock]; float t = 0f; if (patch != null) { t = patch.Data[yOff * 16 + xOff]; } else { Logger.Log(String.Format("Skipping missing patch at {0},{1}", xBlock, yBlock), Helpers.LogLevel.Warning); } stream.Write(BitConverter.GetBytes(t), 0, 4); } } } } catch (Exception ex) { Logger.Log("Failed saving terrain: " + ex.Message, Helpers.LogLevel.Error); } }
/// <summary> Creates a CubeFace </summary> /// <param name="parent">Associated parent reference as a QuadTree sibling</param> /// <param name="pos">Position vector to render the cubeface at</param> /// <param name="left">The x axis of the plane</param> /// <param name="forward">The z axis of the plane</param> /// <param name="scale">The scale of the plane</param> /// <param name="size">The number of vertices in length/width to create (segments)</param> private void Initialize(TerrainPatch parent, Vector3 pos, Vector3 left, Vector3 forward, float scale) { pos.y = terrain.GetHeightAt(pos.x + terrain.scale / 2, pos.z + terrain.scale / 2) * Terrain.heightScalar; this.bound = new Bounds(pos, new Vector3(scale, scale, scale)); this.parent = parent; this.scale = scale; this.center = pos; this.position = pos; this.left = left; this.forward = forward; // Centre the plane! position -= left * (scale / 2); position -= forward * (scale / 2); gameObject = new GameObject("TerrainPatch_" + scale + "_" + 2 + "_" + pos.ToString()); gameObject.isStatic = true; gameObject.layer = 8; // To Raycast neighbours material = new Material(Shader.Find("Standard")); filter = gameObject.AddComponent <MeshFilter>(); renderer = gameObject.AddComponent <MeshRenderer>(); //collider = gameObject.AddComponent<MeshCollider>(); // Ensure hierarchy if (parent != null) { gameObject.transform.parent = parent.gameObject.transform; } //Generate(); }
public TerrainPatch ToTerrainPatch() { var tp = TerrainPatch.FromId(Id); tp.Step = Step; return(tp); }
internal static float[] MakeCPUMatrices(TerrainPatch target) { var cpu_matrixes_size = target.Height * target.Width * 12; var r = new float[cpu_matrixes_size]; var height = target.Height; var width = target.Width; for (var l = 0; l < height; l++) { for (var s = 0; s < width; s++) { var mat = target.Matrices[l][s]; var pos = (l * width + s) * 12; r[pos++] = (float)mat.Row0.X; r[pos++] = (float)mat.Row1.X; r[pos++] = (float)mat.Row2.X; r[pos++] = (float)mat.Row3.X; r[pos++] = (float)mat.Row0.Y; r[pos++] = (float)mat.Row1.Y; r[pos++] = (float)mat.Row2.Y; r[pos++] = (float)mat.Row3.Y; r[pos++] = (float)mat.Row0.Z; r[pos++] = (float)mat.Row1.Z; r[pos++] = (float)mat.Row2.Z; r[pos++] = (float)mat.Row3.Z; } } return(r); }
private void Load(List <TerrainPatch> patches) { if (patches.Count < 1) { return; } var patchSize = TerrainPatch.DefaultSize; var ids = patches.Select(p => p.Id).ToList(); var minline = ids.Min(p => p.Y); var maxline = ids.Max(p => p.Y); var minsample = ids.Min(p => p.X); var maxsample = ids.Max(p => p.X); PatchBounds = new Rectangle(minsample, minline, maxsample - minsample + 1, maxline - minline + 1); _patchArray = new TerrainPatch[PatchBounds.Height, PatchBounds.Width]; for (var line = 0; line < PatchBounds.Height; line++) { for (var sample = 0; sample < PatchBounds.Width; sample++) { _patchArray[line, sample] = new TerrainPatch { Line = (line + PatchBounds.Top) * patchSize, Sample = (sample + PatchBounds.Left) * patchSize, Height = patchSize, Width = patchSize, Step = 1 } } } ; pnlTiles.Size = new Size(PatchBounds.Width * patchSize, PatchBounds.Height * patchSize); _transform = new DisplayTransform { OffsetX = -PatchBounds.Left * patchSize, OffsetY = -PatchBounds.Top * patchSize, Scale = 1f }; pnlScroll.Invalidate(); pnlTiles.Invalidate(); }
public override void Run() { base.Run(); var patch = TerrainPatch.FromId(147, 72); Console.WriteLine(@"Generating safe havens"); var safe_haven_gen = new SafeHavenGenerator { Region = patch.Bounds, WriteHorizons = false }; safe_haven_gen.WriteSafeHavenGeotiffs(OutputPath); Console.WriteLine(@"Generating average Earth and average Sun patches"); var(start, stop, step) = StudyInterval.SiteStudyPeriod.GetInterval(); var gen = new TileLightingProductManager { //MainWindow = MainWindow, Selection = patch.Bounds, IntervalStart = start, IntervalStop = stop, IntervalStep = step, ObserverHeightInMeters = 0, EarthMultipathThreshold = 2f, GenerateAverageSun = true, GenerateAverageEarth = true }; gen.GenerateAverageSunEarthPatches(); Console.WriteLine(@"Finished."); }
/// <summary> /// Creates a terrain /// using 6 planes normalized around the origin /// </summary> /// <param name="name">The terrain name (duh)</param> /// <param name="scale">The scale of the terrain (radius)</param> public Terrain(string name, float scale, Texture2D heightmap, Transform parent, float detailLevel, float minResolution) { this.scale = scale; this.heightmap = heightmap; this.detailLevel = detailLevel; this.minResolution = minResolution; var hs = scale / 2; quadSize = (int)scale + 1; quadMatrix = new int[quadSize * quadSize]; for (int x = 0; x < quadSize; ++x) { for (int z = 0; z < quadSize; ++z) { quadMatrix[z * quadSize + x] = 1; } } // Chuck it into a single game object for neatness terrain = new GameObject(name); terrain.transform.parent = parent.transform; face = new TerrainPatch(new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(0, 0, 1), scale, this); face.gameObject.transform.parent = terrain.transform; PropagateRoughness(); }
/// <summary> /// Turns a plane's vertices into a segment of a sphere /// - Processes neighbour faces in order to remove gaps/tears /// </summary> /// <param name="face">The face in question (aint it an ugly one?)</param> private void FinalizeFace(TerrainPatch face) { if (face.finalized) { return; } var verts = face.mesh.vertices; for (int i = 0; i < verts.Length; i++) { // //verts[i] = verts[i].normalized * (scale + noise(verts[i], 2, 1.7f, 0.1f, scale / size)); //verts[i].y += noise(verts[i], 2, 1.7f, 0.1f, 10); float hs = scale / 2; //float u = (verts[i].x + hs) / scale; //float v = (verts[i].z + hs) / scale; //verts[i].y = heightmap.GetPixelBilinear(u, v).grayscale * 20; verts[i].y = GetHeightAt(verts[i].x + hs, verts[i].z + hs) * heightScalar; } face.mesh.vertices = verts; face.mesh.RecalculateNormals(); face.mesh.RecalculateBounds(); face.Texturize(); face.filter.sharedMesh = face.mesh; //face.collider.sharedMesh = face.mesh; face.finalized = true; }
void FillRangeLimit(TerrainPatch center) { var center_line = center.Line; var center_sample = center.Sample; Debug.Assert(cpu_range_limit.Length == cpu_slope_size); var min = (double)(2 * TerrainPatch.DefaultSize); var max = (double)(TerrainPatch.DEM_size - 2 * TerrainPatch.DefaultSize); for (var i = 0; i < cpu_range_limit.Length; i++) { var ray_rad = Math.PI * 2f * i / cpu_slope_size; // 0 deg in ME frame points toward the earth var ray_cos = Math.Cos(ray_rad); // varies the line var ray_sin = Math.Sin(ray_rad); // varies the sample var farthest = 0; for (var j = 0; j < cpu_range.Length; j++) { var range = cpu_range[j]; var caster_line = center_line + ray_sin * range; if (caster_line < min || caster_line > max) { break; } var caster_sample = center_sample + ray_cos * range; if (caster_sample < min || caster_sample > max) { break; } farthest = j; } cpu_range_limit[i] = farthest; } }
public void GenerateRiseHeight(string path, Rectangle region) { var queue = TerrainPatch.EnumerateIds(TerrainPatch.CoveringIdRectangle(region)).Select(id => TerrainPatch.FromId(id)).ToList(); var pairs = GenerateRiseHeightPatches(queue); var ary = new float[region.Height, region.Width]; var region_width = region.Width; var region_height = region.Height; foreach (var(patch, data) in pairs) { var xoffset = patch.Sample - region.Left; var yoffset = patch.Line - region.Top; for (var row_src = 0; row_src < TerrainPatch.DefaultSize; row_src++) { var row_dst = row_src + yoffset; if (row_dst >= region_height || row_dst < 0) { continue; } for (var col_src = 0; col_src < TerrainPatch.DefaultSize; col_src++) { var col_dst = col_src + xoffset; if (col_dst >= region_width || col_dst < 0) { continue; // This can be faster if I don't clip per pixel. Later. } ary[row_dst, col_dst] = data[row_src * TerrainPatch.DefaultSize + col_src]; } } } GeotiffHelper.WriteArrayAsGeotiff(ary, region, path); }
public void GenerateHeightField(TerrainPatch patch) { 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)) using (var gpu_dem = accelerator.Allocate <short>(ViperEnvironment.Terrain.Data.Length)) using (var gpu_range = accelerator.Allocate <float>(cpu_range.Length)) using (var gpu_slope = accelerator.Allocate <float>(cpu_slope.Length)) using (var gpu_rise = accelerator.Allocate <short>(TerrainPatch.DefaultSize * TerrainPatch.DefaultSize)) { gpu_dem.CopyFrom(ViperEnvironment.Terrain.Data, 0, 0, ViperEnvironment.Terrain.Data.Length); gpu_range.CopyFrom(cpu_range, 0, 0, cpu_range.Length); var launchDimension = new Index2(TerrainPatch.DefaultSize, TerrainPatch.DefaultSize); var kernel1 = accelerator.LoadStreamKernel <Index2, ArrayView <short>, ArrayView <float>, ArrayView <short>, int, int>(RiseKernel1); } } }
// Return all times from start to stop. Also return the indexes of the times at which the earth elevation reaches a minima void GetLowEarthTimes(DateTime outer_start, DateTime outer_stop, out List <DateTime> all_times, out List <int> indices_of_minima_earth_elevation) { var center = new Point(Region.Left + Region.Width / 2, Region.Top + Region.Height / 2); var centerPatch = ViperEnvironment.GetPatch(TerrainPatch.LineSampleToId(center)); centerPatch.FillMatrices(ViperEnvironment.Terrain); // Generate the time / elevation pairs all_times = new List <DateTime>(); for (var time = outer_start; time <= outer_stop; time += Step) { all_times.Add(time); } var earth_elevations = all_times.Select(t => { centerPatch.GetAzEl(CSpice.EarthPosition(t), 0, 0, out float _, out float earth_elevation_rad); return(earth_elevation_rad); }).ToList(); // Find minima indices_of_minima_earth_elevation = new List <int>(); for (var i = 1; i < earth_elevations.Count - 1; i++) { if (Start <= all_times[i] && all_times[i] <= Stop && earth_elevations[i - 1] > earth_elevations[i] && earth_elevations[i] < earth_elevations[i + 1]) { indices_of_minima_earth_elevation.Add(i); } } }
TerrainPatch GetCenterPatch() { var pt = new Point(TerrainPatch.DEM_size / 2, TerrainPatch.DEM_size / 2); var center = TerrainPatch.FromLineSample(pt.Y, pt.X); center.FillMatrices(ViperEnvironment.Terrain); return(center); }
/// <summary> /// Map over the fraction of the sun's disk visible times Cos(theta) - theta is the angle between the sun and normal /// </summary> /// <param name="line"></param> /// <param name="sample"></param> /// <param name="start"></param> /// <param name="stop"></param> /// <param name="step"></param> /// <param name="action"></param> /// <returns></returns> public bool MapOverSunContribution(int line, int sample, DateTime start, DateTime stop, TimeSpan step, Action <DateTime, float> action) { var id = TerrainPatch.LineSampleToId(line, sample); var patch = GetPatch(id); if (patch == null) { return(false); } var cross = GetSurfaceNormal(line, sample); var y_offset = line - patch.Line; var x_offset = sample - patch.Sample; var mat = patch.Matrices[y_offset][x_offset]; var horizon = patch.Horizons[y_offset][x_offset]; if (!horizon.IsDegrees) { lock (horizon) horizon.ConvertSlopeToDegrees(); } var cache = SunVectorCache.GetSingleton(); var temp = new Vector3d(); for (var time = start; time <= stop; time += step) { var sunvec = cache.SunPosition(time); TerrainPatch.Transform(ref sunvec, ref mat, ref temp); var sun_x = temp[0]; var sun_y = temp[1]; var sun_z = temp[2]; var azimuth_rad = Math.Atan2(sun_y, sun_x) + Math.PI; // [0,2PI] var azimuth_deg = (float)(azimuth_rad * 180d / Math.PI); var alen = Math.Sqrt(sun_x * sun_x + sun_y * sun_y); var slope = sun_z / alen; var elevation_deg = ((float)Math.Atan(slope)) * 180f / 3.141592653589f; var sunfrac = horizon.SunFraction2(azimuth_deg, elevation_deg); //if (line == 17522 && sample == 12388) //{ // Console.WriteLine($"{line},{sample}, time={time} elevation_deg={elevation_deg} sun={sunfrac}"); //} sunvec.NormalizeFast(); // Normalize before calculating surface temp var dot = Vector3d.Dot(sunvec, cross); var sun_contrib = dot < 0d ? 0d : sunfrac * dot; //if (sun_contrib > 0) // Console.WriteLine(sun_contrib); action(time, (float)sun_contrib); } return(true); }
public override void Run() { base.Run(); var patch = TerrainPatch.FromId(147, 72); var safe_haven_gen = new SafeHavenGenerator { Region = patch.Bounds, WriteHorizons = true }; safe_haven_gen.WriteSafeHavenGeotiffs(OutputPath); }
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 ExtendExistingPatches() { if (MaxSpread != 177) { throw new Exception($"Unexpected value of MaxSpread={MaxSpread}"); } var filenames = (new DirectoryInfo(LunarHorizon.HorizonsRoot)).EnumerateFiles("*.bin").Select(fi => fi.FullName).ToList(); foreach (var filename in filenames) { var patch = TerrainPatch.ReadFrom(filename); var stopwatch = new Stopwatch(); stopwatch.Start(); Console.WriteLine($"Starting [{patch.Line},{patch.Sample}] ..."); patch.FillPointsAndMatrices(Terrain); try { var far_field = new List <TerrainPatch>(); if (MapView != null) { MapView.ProcessingPatches = new List <TerrainPatch> { patch } } ; var options = new ParallelOptions { MaxDegreeOfParallelism = MaxDegreeOfParallelism }; for (var i = 1; i < MaxSpread; i++) { var other1 = patch.SurroundingPatches(i).Where(p => !patch.ShadowCasters.Contains(p.Id)).ToList(); Parallel.ForEach(other1, options, o => o.FillPoints(Terrain)); var other2 = other1.Where(patch.IsOverHorizon).ToList(); if (MapView != null) { far_field.AddRange(other2); MapView.FarPatches = far_field; MapView.Invalidate(); } Parallel.ForEach(other2, options, o => patch.UpdateHorizon(o)); } patch.Write(); patch.InitializeHorizons(); // Unload the horizon data (100MB) stopwatch.Stop(); var seconds_per_patch = far_field.Count == 0 ? 0f : (stopwatch.ElapsedMilliseconds / 1000f) / far_field.Count; Console.WriteLine($" Finished [{patch.Line},{patch.Sample}] time={stopwatch.Elapsed}. sec/patch={seconds_per_patch}"); } catch (Exception e1) { Console.WriteLine(e1); Console.WriteLine(e1.StackTrace); } } }
void DisableRenderer(TerrainPatch patch) { patch.renderer.enabled = false; if (patch.tree != null) { for (int i = 0; i < patch.tree.Length; i++) { DisableRenderer(patch.tree[i]); } } }
/// <summary> /// Build the terrain. /// </summary> public void BuildTerrain() { if (Heightmap == null) { return; } int width = Heightmap.Width; int depth = Heightmap.Depth; // Clear the terrain patches. Patches.Clear(); // Compute the world matrix to place the terrain in the middle of the scene. // _world = Matrix.Identity;//Matrix.CreateTranslation(width*-0.5f, 0.0f, depth*-0.5f); // Create the terrain patches. const int patchWidth = 16; const int patchDepth = 16; int patchCountX = width / patchWidth; int patchCountZ = depth / patchDepth; PatchRows = patchCountX; PatchColumns = patchCountZ; for (int x = 0; x < patchCountX; ++x) { for (int z = 0; z < patchCountZ; ++z) { // It is necessary to use patch width and depths of +1 otherwise there will be // gaps between the patches.. [0,15] and [16,31] have a gap of one unit! var patch = new TerrainPatch(Game, Heightmap, World, patchWidth + 1, patchDepth + 1, x * patchWidth, z * patchDepth); // x*(patchWidth - 1), z*(patchDepth - 1)); Patches.Add(patch); } } // Find the minimum bounding box that covers all patches BoundingBox box = Patches[0].BoundingBox; foreach (TerrainPatch patch in Patches) { box = BoundingBox.CreateMerged(box, patch.BoundingBox); } BoundingBox = box; PatchCount = Patches.Count; _vertexDeclaration = new VertexDeclaration(GraphicsDevice, VertexPositionNormalTexture.VertexElements); }
private static void PopulatePatch(ICollection <Vector3> trees, TerrainPatch patch) { int treeDistanceZ = patch.Depth / 1; int treeDistanceX = patch.Width / 1; for (int z = 0; z < patch.Depth; z += treeDistanceZ) { for (int x = 0; x < patch.Width; x += treeDistanceX) { int index = z * patch.Width + x; trees.Add(patch.Geometry[index].Position); } } }
public static bool EqualsMatrices(TerrainPatch a, TerrainPatch b) { for (var line = 0; line < TerrainPatch.DefaultSize; line++) { for (var sample = 0; sample < TerrainPatch.DefaultSize; sample++) { if (!a.Matrices[line][sample].Equals(b.Matrices[line][sample])) { return(false); } } } return(true); }
private void btnLoad_Click(object sender, EventArgs e) { if (lbPatches.SelectedItems.Count != 2) { MessageBox.Show("Please select exactly two patches"); return; } var selected = lbPatches.SelectedItems; patch1 = new TerrainPatch(); patch1.Load(Path.Combine(patch_dir, (selected[0] as string) + ".bin")); patch2 = new TerrainPatch(); patch2.Load(Path.Combine(patch_dir, (selected[1] as string) + ".bin")); }
Bitmap GetBitmap(TerrainPatch p) { switch (RenderMode) { case PatchRenderMode.AzEl: return(p.Horizons != null?p.GetShadows(azimuth_deg, elevation_deg) : p.GetHillshade()); case PatchRenderMode.ShadowCaster: return(p.Horizons != null?p.GetShadows(ShadowCaster) : p.GetHillshade()); case PatchRenderMode.Hillshade: default: return(p.GetHillshade()); } }
public override void Run() { base.Run(); var patch = TerrainPatch.FromId(147, 72); var queue = new List <TerrainPatch> { patch }; var gpu = new GPUHorizons { Terrain = ViperEnvironment.Terrain }; Console.WriteLine("Running the queue"); gpu.RunQueue(queue, unloadHorizons: false, writeHorizons: false); Console.WriteLine("Finished running the queue"); }
// maybe replace this with a transpiler that duplicates everything that is done to m_surfaceMapA? static void Postfix(TerrainPatch __instance) { if (Settings.TempDisable || Settings.EraseClipping.value) { return; } #if DEBUG Debug.Log("resizing patch (" + __instance.m_x + "|" + __instance.m_z + ")"); #endif Texture2D original = __instance.m_surfaceMapA; Texture2D replacement = SubstituteTextureManager.GetOrCreateSubstituteTexture(__instance); replacement.Resize(original.width, original.height, original.format, false); replacement.wrapMode = original.wrapMode; replacement.filterMode = original.filterMode; }
private void CopyPointsToCpuArray(TerrainPatch caster, Vector3d basePoint, float[] cpu_caster_points) { var ptr = 0; for (var line = 0; line < TerrainPatch.DefaultSize; line++) { var row = caster.Points[line]; for (var sample = 0; sample < TerrainPatch.DefaultSize; sample++) { var vec = row[sample] - basePoint; cpu_caster_points[ptr++] = (float)vec.X; cpu_caster_points[ptr++] = (float)vec.Y; cpu_caster_points[ptr++] = (float)vec.Z; } } }
void FillAzimuthElevation() { var center_patch = GetCenterPatch(); var center_pt = center_patch.PointInPatch(new Point(TerrainPatch.DEM_size / 2, TerrainPatch.DEM_size / 2)); var mat = center_patch.Matrices[center_pt.Y][center_pt.X]; const int per_degree = 4; const int count = 360 * per_degree; var slope_array = Enumerable.Range(0, count).Select(i => float.MinValue).ToArray(); var vec_array = new PointF[count]; var stop = TileLightingProductManager.MetonicCycleStop; var step = TileLightingProductManager.MetonicCycleStep; var temp = new Vector3d(); for (var time = TileLightingProductManager.MetonicCycleStart; time <= stop; time += step) { var sun = CSpice.SunPosition(time); TerrainPatch.Transform(ref sun, ref mat, ref temp); var sun_x = temp[0]; var sun_y = temp[1]; var sun_z = temp[2]; var azimuth_rad = Math.Atan2(sun_y, sun_x) + Math.PI; // [0,2PI] var azimuth_deg = azimuth_rad * 180d / Math.PI; var index = (int)(azimuth_deg * per_degree); var alen = Math.Sqrt(sun_x * sun_x + sun_y * sun_y); var slope = (float)(sun_z / alen); if (slope > slope_array[index]) { slope_array[index] = slope; vec_array[index] = new PointF((float)(sun_x / alen), (float)(sun_y / alen)); } } var slope_list = new List <float>(); var vec_list = new List <PointF>(); for (var i = 0; i < slope_array.Length; i++) { var s = slope_array[i]; if (s > float.MinValue) { slope_list.Add(s); vec_list.Add(vec_array[i]); } } cpu_slope = slope_array.ToArray(); cpu_vector_x = vec_list.Select(p => p.X).ToArray(); cpu_vector_y = vec_list.Select(p => p.Y).ToArray(); }
public void GetHorizonBoxes(List <Point> ids, Action <List <Rectangle> > callback) => Task.Run(() => { var gpuProcessor = MainWindow.Processor as GPUProcessor; var rectangles = new List <Rectangle>(); foreach (var id in ids) { gpuProcessor.RunQueue(new List <TerrainPatch> { TerrainPatch.FromId(id) }, null, writeHorizons: false); if (gpuProcessor.BoundingBox.HasValue) { rectangles.Add(gpuProcessor.BoundingBox.Value); } } callback(rectangles); });