Advertisement
NPSF3000

TextureFromPoints4

Jul 22nd, 2012
334
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 13.29 KB | None | 0 0
  1. //  http://forum.unity3d.com/threads/144235-A-Fast-KD-Tree-or-Unity-Alternative
  2. //  All rights reserved. Copyright NPSF3000 2012  Attribution Required.
  3.  
  4. //  I think I have enough data to give an example of how I'd tackle the problem.
  5. //  This is written as a console application in VS because it has a few more features
  6. //  and niceties to reduce my prototyping time.  Feel free to use it as a console app,
  7. //  or port it unity or even windows forms!
  8.  
  9. //  Tested on a i3 2120, windows 7 64, x86 build, plenty of ram and a SSD.
  10.  
  11. //  This version has had multithreading implemented.
  12. //  Version 4 : Optimisations [Fast BMP Generation, Flexible Save Format] resulting in 2000% speedup
  13.  
  14. using System;
  15. using System.Collections.Generic;
  16. using System.Diagnostics;
  17. using System.Drawing;
  18. using System.Drawing.Imaging;
  19. using System.Linq;
  20. using System.Runtime.InteropServices;
  21. using System.Threading.Tasks;
  22. using System.IO;
  23.  
  24. namespace TextureFromPoints4
  25. {
  26.     class Program
  27.     {
  28.         const int numSensors = 70000;
  29.         const int textureSize = 1024;
  30.         const int intervals = 200;
  31.  
  32.         const bool useFastBMPGenerator = true;
  33.  
  34.         //BMP is much faster than PNG to save, but at the cost of no compression.
  35.         //Simple tests show a 530% time saving [7ms vs 44ms] but 100% larger file. [1MB vs 500KB per 1024x1024 image, 70k poins]
  36.         static ImageFormat imageType = ImageFormat.Bmp;
  37.  
  38.         //Disabling debug mode removes some console output that can slow the program by 1s [25%]
  39.         const bool debugMode = false;
  40.  
  41.         const string directory = @"C:\TextureFromPoints4\";
  42.         //Deletes all files in the data directory at start
  43.         const bool cleanDirAtStart = true;
  44.  
  45.         //Changing hash size can have dramatic effect on the my Spacial Collection's performance.
  46.         //Ideal hash size relies upon the densisty of sensors:pixels -
  47.         //For example the hash size of 2 will fill a 1024x1024 texture with 70,000 points in 0.5s.
  48.         //However, if you reduce the number of points to 700 it will take 13 seconds.
  49.         //Setting hash to less than 1 will use a simple formula to guess the correct size.
  50.         //Otherwise the code will use whatever variable you choose.
  51.         static int hash = -1;
  52.  
  53.         //If maxThreads is under 1 the system default will be used.
  54.         static int maxThreads = -1;
  55.  
  56.         static Random rnd = new Random();
  57.  
  58.         static void Main(string[] args)
  59.         {
  60.             var pOptions = new ParallelOptions();
  61.             if (maxThreads > 0) pOptions.MaxDegreeOfParallelism = maxThreads;
  62.  
  63.             if (hash < 1)
  64.             {
  65.                 var magic = Math.Sqrt(textureSize * textureSize / numSensors) / 2;  //Magic Formula
  66.                 hash = (int)Math.Pow(2, (int)(Math.Log(magic - 1, 2)) + 1);  //Rounds up to nearest power of 2.
  67.                 if (hash < 1) hash = 1;
  68.                 Console.WriteLine("Rough Hash Generated: {0}  [Magic Density : {1}]", hash, magic);
  69.             }
  70.             else
  71.                 Console.WriteLine("Using Harcoded Hash: {0}", hash);
  72.             Console.WriteLine("");
  73.  
  74.             while (true)
  75.             {
  76.                 var dir = Directory.CreateDirectory(directory);
  77.                 if (cleanDirAtStart) foreach (FileInfo file in dir.GetFiles()) file.Delete();
  78.  
  79.  
  80.                 Console.WriteLine("Starting");
  81.                 Console.WriteLine("");
  82.  
  83.                 var swTotalTime = Stopwatch.StartNew();
  84.  
  85.                 //Step one: get the raw data:
  86.                 var sensors = GetData();
  87.  
  88.                 //Step 2&3 - generate the sensor texture.  We create a new spacial hash for every thread.
  89.                 //This decreases texture geration time from 700ms to about 500ms.
  90.                 //As such, at these settings the improvement is minimal - tough different workloads may vary.
  91.  
  92.                 var sw = Stopwatch.StartNew();
  93.                 int splitCount = Environment.ProcessorCount / 2;
  94.  
  95.                 Sensor[,] sensorTexture = new Sensor[textureSize, textureSize];
  96.  
  97.                 Parallel.For(0, splitCount, pOptions, i =>
  98.                 {
  99.                     //Generate the spacial hash
  100.                     var spacialhash = new SimpleSpacialCollection(textureSize, hash);
  101.                     foreach (var sensor in sensors) spacialhash.AddSensor(sensor);
  102.  
  103.                     //remember the offset
  104.                     int offsetSize = textureSize / splitCount;
  105.                     int offset = offsetSize * i;
  106.                     //Create the array to store the elements
  107.                     var result = new Sensor[offsetSize, textureSize];
  108.  
  109.                     //Fill the result
  110.                     for (int x = 0; x < offsetSize; x++)
  111.                         for (int y = 0; y < textureSize; y++)
  112.                             result[x, y] = spacialhash.GetNearest(x + offset, y);
  113.  
  114.                     //Copies the result to the sensorTexture.
  115.                     Array.Copy(result, 0, sensorTexture, offset * textureSize, result.Length);
  116.                 });
  117.                 sw.Stop();
  118.                 Console.WriteLine("Mapped {0} sensors to {1}x{1} pixels in {2}ms", numSensors, textureSize, sw.ElapsedMilliseconds);
  119.  
  120.                 //Step 4&5 - Creating and saving textures.
  121.                 //It makes sense to combine IO and Compute bound tasks together - so IO isn't idling while computing and vice vera
  122.                 var sw5 = Stopwatch.StartNew();
  123.                 Parallel.For(0, intervals, pOptions, i =>
  124.                 {
  125.                     var sw1 = Stopwatch.StartNew();
  126.                     var bmp = useFastBMPGenerator ? GenerateBMPv2(sensorTexture, i) : GenerateBMP(sensorTexture, i);
  127.                     sw1.Stop();
  128.                     if (debugMode) Console.WriteLine("Created Texture {0} in {1}ms", i, sw1.ElapsedMilliseconds);
  129.  
  130.                     sw1 = Stopwatch.StartNew();
  131.                     bmp.Save(directory + i + "." + imageType.ToString().ToLower(), imageType);
  132.                     bmp.Dispose();  //This is new - keeps memory consumption down!
  133.                     sw1.Stop();
  134.                     if (debugMode) Console.WriteLine("Saved Texture {0} in {1}ms", i, sw1.ElapsedMilliseconds);
  135.                 });
  136.                 sw5.Stop();
  137.  
  138.                 Console.WriteLine("Created & Saved {0} Textures as {2} in {1}ms", intervals, sw5.ElapsedMilliseconds, imageType.ToString().ToLower());
  139.  
  140.                 swTotalTime.Stop();
  141.  
  142.                 Console.WriteLine("");
  143.                 Console.WriteLine("All finished in {0}ms", swTotalTime.ElapsedMilliseconds);
  144.                 Console.ReadLine();
  145.  
  146.                 //With Multithreading 200 images, 70k points, 1024 by 1024 takes 86s - faster than the single threaded version at 188.7s.
  147.                 //With new v4 optimisations, this now down to ~4 seconds.
  148.             }
  149.         }
  150.  
  151.         private static Bitmap GenerateBMP(Sensor[,] sensorTexture, long i)
  152.         {
  153.             var bmp = new Bitmap(textureSize, textureSize);
  154.             for (int x = 0; x < textureSize; x++)
  155.                 for (int y = 0; y < textureSize; y++)
  156.                 {
  157.                     var sensor = sensorTexture[x, y];
  158.                     bmp.SetPixel(x, y, Color.FromArgb((int)(255 * sensor.data[i]), (int)(255 * sensor.data[i]), (int)(255 * sensor.data[i])));
  159.                 }
  160.             return bmp;
  161.         }
  162.  
  163.         //http://stackoverflow.com/questions/6782489/create-bitmap-from-byte-array-of-pixel-data
  164.         private static Bitmap GenerateBMPv2(Sensor[,] sensorTexture, long i)
  165.         {
  166.             var b = new Bitmap(textureSize, textureSize, PixelFormat.Format8bppIndexed);
  167.             ColorPalette ncp = b.Palette;
  168.             for (int k = 0; k < 256; k++)
  169.                 ncp.Entries[k] = Color.FromArgb(255, k, k, k);
  170.             b.Palette = ncp;
  171.  
  172.             var BoundsRect = new Rectangle(0, 0, textureSize, textureSize);
  173.             BitmapData bmpData = b.LockBits(BoundsRect, ImageLockMode.WriteOnly, b.PixelFormat);
  174.             IntPtr ptr = bmpData.Scan0;
  175.  
  176.             var bytes = new byte[bmpData.Stride * b.Height];
  177.  
  178.             var width = bmpData.Width;
  179.             var height = bmpData.Height;
  180.             var stride = bmpData.Stride;
  181.  
  182.             //Console.WriteLine(" {0} : {1} : {2} ", width, height, stride);
  183.             for (int x = 0; x < width; x++)
  184.                 for (int y = 0; y < height; y++)
  185.                 {
  186.                     var sensor = sensorTexture[x, y];
  187.                     bytes[x + y * stride] = (byte)(255 * sensor.data[i]);
  188.                 }
  189.  
  190.             Marshal.Copy(bytes, 0, ptr, bytes.Length);
  191.             b.UnlockBits(bmpData);
  192.             return b;
  193.         }
  194.  
  195.         private static Sensor[] GetData()
  196.         {
  197.             //  I don't care how you manage it, but I figure you need a sensor
  198.             //  that has a 2d position [corrisponding with the texture co-ords]
  199.             //  and has a array of values that shows teh sensor's value every interval.
  200.             //  So I faked itL
  201.             var sensors = new Sensor[numSensors];
  202.  
  203.             for (int i = 0; i < numSensors; i++)
  204.             {
  205.                 var sensor = new Sensor();
  206.                 sensor.position = new Vector2(textureSize);
  207.                 sensor.data = Enumerable.Range(0, intervals).Select(_ => (float)rnd.NextDouble()).ToArray();
  208.                 sensors[i] = sensor;
  209.             }
  210.             return sensors;
  211.         }
  212.  
  213.         public struct Sensor
  214.         {
  215.             public Vector2 position;
  216.             public float[] data;
  217.         }
  218.  
  219.         public struct Vector2
  220.         {
  221.             public float x;
  222.             public float y;
  223.  
  224.             public Vector2(float x, float y)
  225.             {
  226.                 this.x = x;
  227.                 this.y = y;
  228.             }
  229.  
  230.             public Vector2(float randomDistance)
  231.             {
  232.                 this.x = (float)rnd.NextDouble() * randomDistance;
  233.                 this.y = (float)rnd.NextDouble() * randomDistance;
  234.             }
  235.  
  236.             public static Vector2 operator -(Vector2 a, Vector2 b)
  237.             {
  238.                 return new Vector2(a.x - b.x, a.y - b.y);
  239.             }
  240.  
  241.             public float sqrMagnitude()
  242.             {
  243.                 return x * x + y * y;
  244.             }
  245.  
  246.             public float sqrDistanceToPoint(Vector2 point)
  247.             {
  248.                 var x = this.x - point.x;
  249.                 var y = this.y - point.y;
  250.                 return x * x + y * y;
  251.             }
  252.         }
  253.  
  254.         public class SimpleSpacialCollection
  255.         {
  256.             List<Sensor>[,] buckets;
  257.             public readonly int bounds;
  258.             public readonly int hash;
  259.  
  260.             public int count { get; private set; }
  261.  
  262.             public SimpleSpacialCollection(int bounds, int hash)
  263.             {
  264.                 this.bounds = bounds;
  265.                 this.hash = hash;
  266.                 buckets = new List<Sensor>[bounds / hash + 1, bounds / hash + 1];
  267.                 count = 0;
  268.             }
  269.  
  270.             public void AddSensor(Sensor sensor)
  271.             {
  272.                 GetBucket(sensor.position).Add(sensor);
  273.                 count++;
  274.             }
  275.  
  276.             List<Sensor> GetBucket(Vector2 v2)
  277.             {
  278.                 return GetBucket((int)v2.x, (int)v2.y);
  279.             }
  280.  
  281.             List<Sensor> GetBucket(int x, int y)
  282.             {
  283.                 var bucket = buckets[x / hash, y / hash];
  284.                 if (bucket == null)
  285.                 {
  286.                     bucket = new List<Sensor>();
  287.                     buckets[x / hash, y / hash] = bucket;
  288.                 }
  289.                 return bucket;
  290.             }
  291.  
  292.             public Sensor GetNearest(int pointX, int pointY)
  293.             {
  294.                 if (count == 0) throw new Exception("Need more than 0 elements!");
  295.  
  296.                 var candidates = new List<Sensor>();
  297.  
  298.                 int maxX, minX, maxY, minY;
  299.  
  300.                 maxX = minX = pointX / hash;
  301.                 maxY = minY = pointY / hash;
  302.  
  303.                 do
  304.                 {
  305.                     maxX++; maxY++; minX--; minY--;
  306.  
  307.                     if (minX < 0) minX = 0;
  308.                     if (minY < 0) minY = 0;
  309.                     if (maxX > buckets.GetLength(0)) maxX = buckets.GetLength(0);
  310.                     if (maxY > buckets.GetLength(1)) maxY = buckets.GetLength(1);
  311.  
  312.                     for (int x = minX; x < maxX; x++)
  313.                         for (int y = minY; y < maxY; y++)
  314.                         {
  315.                             var bucket = buckets[x, y];
  316.                             if (bucket != null) candidates.AddRange(bucket);
  317.                         }
  318.                 } while (candidates.Count == 0);
  319.  
  320.                 var point = new Vector2(pointX, pointY);
  321.  
  322.                 Sensor nearest = candidates[0];
  323.                 float nearestDistance = nearest.position.sqrDistanceToPoint(point);
  324.  
  325.                 for (int i = 1; i < candidates.Count; i++)
  326.                 {
  327.                     var candidate = candidates[i];
  328.                     if (candidate.position.sqrDistanceToPoint(point) < nearestDistance)
  329.                     {
  330.                         nearest = candidate;
  331.                         nearestDistance = candidate.position.sqrDistanceToPoint(point);
  332.                     }
  333.                 }
  334.                 return nearest;
  335.             }
  336.         }
  337.     }
  338. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement