using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;

namespace Dreamteck.Splines
{
    public class LevelTerrainTool : SplineTool
    {
        public override string GetName()
        {
            return "Level Terrain";
        }

        protected override string GetPrefix()
        {
            return "LevelTerrainTool";
        }

        public struct Point
        {
            public int x;
            public int y;

            public Vector2 vector
            {
                get { return new Vector2(x, y); }
                set {
                    x = (int)value.x;
                    y = (int)value.y;
                }
            }

            public Point(int newX, int newY)
            {
                x = newX;
                y = newY;
            }

            public Point(Vector2 input)
            {
                x = Mathf.RoundToInt(input.x);
                y = Mathf.RoundToInt(input.y);
            }
        }

        public class TerrainPaintPoint
        {
            public Point leftPoint;
            public Point rightPoint;
            public float leftHeight = 0f;
            public float rightHeight = 0f;
            public Point center;
            public float floatDiameter = 0f;

            public float GetHeight(float percent)
            {
                return Mathf.Lerp(leftHeight, rightHeight, percent);
            }
        }

        public float size = 1f;
        public int feather = 1;
        public float offset = 0f;
        public float clipFrom = 0f;
        public float clipTo = 1f;
        private float[,] heights = null;
        private Texture2D brushPreview = null;
        private Texture2D basePreview = null;
        private Texture2D drawPreview = null;

        private float maxDrawHeight = 0f;


        private bool init = false;

        Terrain terrain = null;


        void GetSplinesAndTerrain()
        {
            if(splines.Count == 0) GetSplines();
            for (int i = 0; i < Selection.gameObjects.Length; i++)
            {
                if (terrain == null)  terrain = Selection.gameObjects[i].GetComponent<Terrain>();
            }

            Terrain[] terrains = GameObject.FindObjectsOfType<Terrain>();
            if(terrains.Length == 1)
            {
                //if there is only one terrain in the scene, automatically select it
                terrain = terrains[0];
            }
        }

        void OnGUI()
        {
           // Draw();
        }

        public override void Open(EditorWindow window)
        {
            base.Open(window);
            GetSplinesAndTerrain();
        }

        public override void Close()
        {
            base.Close();
            if (promptSave)
            {
                if (EditorUtility.DisplayDialog("Apply changes?", "Changes to the terrain have been made. Do you want to keep them?", "Yes", "No"))
                {
                    SaveChanges();
                }
                else RevertToBase();
            }
        }

        public override void Draw(Rect windowRect)
        {
            base.Draw(windowRect);

            EditorGUILayout.Space();
            EditorGUI.BeginChangeCheck();
            terrain = (Terrain)EditorGUILayout.ObjectField("Terrain", terrain, typeof(Terrain), true);
            if (EditorGUI.EndChangeCheck())
            {
                heights = null;
            }

            if (splines.Count == 0) EditorGUILayout.HelpBox("No spline selected! Select an object with a SplineComputer component.", MessageType.Warning);
            if (terrain == null) EditorGUILayout.HelpBox("No terrain selected! You need to select a terrain.", MessageType.Warning);
            if (splines.Count == 0 || terrain == null) return;
            if (!init)
            {
                init = true;
                brushPreview = GenerateBrushThumbnail();
            }
            if (heights == null)
            {
                GetBase();
            }
            GUILayout.BeginHorizontal();
            GUILayout.BeginVertical();
            float lastSize = size;
            size = EditorGUILayout.FloatField("Brush radius", size);
            if (size < 0f) size = 0f;
            if(lastSize != size) brushPreview = GenerateBrushThumbnail();
            int lastBlur = feather;
            int maxFeatherCount = Mathf.Max(heights.GetLength(0)/64, 2);
            feather = EditorGUILayout.IntSlider("Feather", feather, 0, maxFeatherCount);
            if (lastBlur != feather) brushPreview = GenerateBrushThumbnail();
            GUILayout.EndVertical();
            GUILayout.Box("", GUILayout.Width(64), GUILayout.Height(64));
            Rect rect = GUILayoutUtility.GetLastRect();
            GUI.DrawTexture(rect, brushPreview);
            GUILayout.EndHorizontal();
            offset = EditorGUILayout.FloatField("Height offset", offset);
            EditorGUILayout.MinMaxSlider(new GUIContent("Spline range"), ref clipFrom, ref clipTo, 0f, 1f);
            if (GUILayout.Button("Level")) CarveTerrain();
            GUILayout.BeginHorizontal();
            GUILayout.Label("Terrain heightmap:");
            GUILayout.Label("Path heightmap:");
            GUILayout.EndHorizontal();
            GUILayout.BeginHorizontal();
            GUILayout.Box("", GUILayout.Width((windowRect.width-10)/2), GUILayout.Height((windowRect.width - 10) / 2));
            rect = GUILayoutUtility.GetLastRect();
            GUI.DrawTexture(rect, basePreview);
            GUILayout.Box("", GUILayout.Width((windowRect.width - 10) / 2), GUILayout.Height((windowRect.width - 10) / 2));
            rect = GUILayoutUtility.GetLastRect();
            GUI.DrawTexture(rect, drawPreview);
            GUILayout.EndHorizontal();

            if (promptSave)
            {
                GUILayout.BeginHorizontal();
                if (GUILayout.Button("Revert")) RevertToBase();
                if (GUILayout.Button("Apply")) SaveChanges();
                GUILayout.EndHorizontal();
            }
        }

        void OnFocus()
        {
            GetSplinesAndTerrain();
            if (promptSave)
            {
                bool isChanged = false;
                float[,] newHeights = terrain.terrainData.GetHeights(0, 0, terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution);
                if (newHeights.GetLength(0) != heights.GetLength(0) || newHeights.GetLength(1) != heights.GetLength(1))
                {
                    isChanged = true;
                } else {
                    for (int x = 0; x < heights.GetLength(0); x++)
                    {
                        for (int y = 0; y < heights.GetLength(1); y++)
                        {
                            if (heights[x,y] != newHeights[x, y])
                            {
                                isChanged = true;
                                break;
                            }
                        }
                    }
                }
                if (isChanged)
                {
                    if (EditorUtility.DisplayDialog("Preserve terrain ?", "The terrain has been edited from outside. Do you want to load the new height data? \r\n WARNING: Doing so will apply your leveled data to the terrain.", "Yes", "No"))
                    {
                        GetBase();
                    }
                }
            }
          
        }

        void OnLostFocus()
        {
           // RevertToBase();
        }

        void CarveTerrain()
        {
            float[,] drawLayer = new float[terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution];
            float[,] alphaLayer = new float[terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution];
            Undo.RecordObject(terrain, "Carve");
            for (int i = 0; i < splines.Count; i++)
            {
                PaintHeightMap(terrain, splines[i], ref drawLayer, ref alphaLayer);
            }

            float[,] blurLayer = new float[drawLayer.GetLength(0), drawLayer.GetLength(1)];
            GaussBlur(ref drawLayer, ref blurLayer, feather);
            float[,] blurAlphaLayer = new float[drawLayer.GetLength(0), drawLayer.GetLength(1)];
            GaussBlur(ref alphaLayer, ref blurAlphaLayer, feather);
            float[,] finalLayer = new float[drawLayer.GetLength(0), drawLayer.GetLength(1)];


            Color[] pixels = drawPreview.GetPixels();


            drawPreview = new Texture2D(drawLayer.GetLength(0), drawLayer.GetLength(1));
            for (int x = 0; x < drawLayer.GetLength(0); x++)
            {
                for (int y = 0; y < drawLayer.GetLength(1); y++)
                {
                    finalLayer[x, y] = Mathf.Lerp(heights[x, y], blurLayer[x, y], blurAlphaLayer[x,y]);
                    pixels[x * drawPreview.width + y] = Color.Lerp(Color.black, Color.white, blurLayer[x, y]/maxDrawHeight*blurAlphaLayer[x,y]);
                } 
            }
            terrain.terrainData.SetHeights(0, 0, finalLayer);
            drawPreview.SetPixels(pixels);
            drawPreview.Apply();
        }

        Texture2D GenerateBrushThumbnail()
        {
            Texture2D tex = new Texture2D(65, 65, TextureFormat.RGB24, false);
            Color[] colors = tex.GetPixels();
            for (int i = 0; i < colors.Length; i++)
            {
                colors[i] = Color.white;
            }
            //Get the brush size, compared to the blur amount
            int hmSize = ToHeightmapSize(size);
            float percent = 1f;
            if(hmSize > 0) percent = Mathf.Clamp01((float)(feather* feather) / hmSize);
            int r = Mathf.RoundToInt(30 * (1f - percent));
            int center = 32;
            for (int x = center - 30; x <= center; x++)
            {
                for (int y = center - 30; y <= center; y++)
                {
                    float value = (x - center) * (x - center) + (y - center) * (y - center);
                    int xSym = center - (x - center);
                    int ySym = center - (y - center);

                    if (value <= r * r)
                    {
                        colors[x * tex.width + y] = Color.black;
                        colors[xSym * tex.width + y] = Color.black;
                        colors[x * tex.width + ySym] = Color.black;
                        colors[xSym * tex.width + ySym] = Color.black;
                    } else 
                    if (value <= 30 * 30 && value > r*r)
                    {
                        float rr = r * r;
                        float val = value - rr;
                        float div = 30 * 30 - rr;
                        float alpha = Mathf.Clamp01(val / div);
                        //Debug.Log(val + "/" + div + " = " + alpha);
                        Color col = Color.Lerp(Color.black, Color.white, alpha);
                        colors[x * tex.width + y] = col;
                        colors[xSym * tex.width + y] = col;
                        colors[x * tex.width + ySym] = col;
                        colors[xSym * tex.width + ySym] = col;
                    }
                    if (value <= 30 * 30 && value >= 29 * 29)
                    {
                        Color col = Color.Lerp(Color.gray, Color.white, 1f-percent);
                        colors[x * tex.width + y] = col;
                        colors[xSym * tex.width + y] = col;
                        colors[x * tex.width + ySym] = col;
                        colors[xSym * tex.width + ySym] = col;
                    }
                }
            }


            tex.SetPixels(colors);
            tex.Apply();
            return tex;
        }

        void GetBase()
        {
            GetSplinesAndTerrain();
            if (terrain == null) return;
            heights = terrain.terrainData.GetHeights(0, 0, terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution);
            basePreview = new Texture2D(heights.GetLength(0), heights.GetLength(1));
            drawPreview = new Texture2D(heights.GetLength(0), heights.GetLength(1));
            Color[] pixels = new Color[basePreview.width * basePreview.height];
            Color[] blackPixels = new Color[basePreview.width * basePreview.height];
            float maxHeight = 0f;
            for (int x = 0; x < basePreview.width; x++)
            {
                for(int y = 0; y < basePreview.height; y++)
                {
                    if (heights[x, y] > maxHeight) maxHeight = heights[x, y];
                    pixels[x * basePreview.width + y] = Color.Lerp(Color.black, Color.white, heights[x, y]);
                    blackPixels[x * basePreview.width + y] = Color.black;
                }
            }
            if(maxHeight > 0f)
            {
                for (int x = 0; x < basePreview.width; x++)
                {
                    for (int y = 0; y < basePreview.height; y++)
                    {
                        pixels[x * basePreview.width + y] /= maxHeight;
                    }
                }
            }
            basePreview.SetPixels(pixels);
            basePreview.Apply();
            drawPreview.SetPixels(blackPixels);
            drawPreview.Apply();
            promptSave = false;
        }

        void SaveChanges()
        {
            GetBase();
        }

        void RevertToBase()
        {
            if (terrain == null) return;
            terrain.terrainData.SetHeights(0, 0, heights);
            heights = null;
        }

        void PaintHeightMap(Terrain terrain, SplineComputer computer, ref float[,] drawLayer, ref float[,] alphaLayer)
        {
            if (heights == null) GetBase();
            SplineSample[] results = new SplineSample[computer.iterations];
            computer.Evaluate(ref results, clipFrom, clipTo);
            Draw(results, ref drawLayer, ref alphaLayer);          
        }


        int ToHeightmapSize(float value)
        {
            float avgSize = (terrain.terrainData.size.x + terrain.terrainData.size.z) / 2f;
            int result = Mathf.RoundToInt(value / avgSize * terrain.terrainData.heightmapResolution);
            return result;
        }

        Point ToHeightmapCoords(Vector3 pos)
        {
            Vector3 terrainPos = pos - terrain.transform.position;
            terrainPos.x /= terrain.terrainData.size.x;
            terrainPos.z /= terrain.terrainData.size.z;
            terrainPos.x = Mathf.Clamp01(terrainPos.x);
            terrainPos.z = Mathf.Clamp01(terrainPos.z);
            int x = Mathf.RoundToInt(terrainPos.z * terrain.terrainData.heightmapResolution);
            int y = Mathf.RoundToInt(terrainPos.x * terrain.terrainData.heightmapResolution);
            return new Point(x, y);
        }

        float ToHeightmapValue(float y)
        {
            float terrainHeight = y - terrain.transform.position.y;
            terrainHeight /= terrain.terrainData.size.y;
            return terrainHeight;
        }

        void PaintSegment(TerrainPaintPoint fromPoint, TerrainPaintPoint toPoint, ref float[,] layer, ref float[,] alphaLayer, bool writeAlpha = true, bool overWriteHeight = true)
        {
            //Flip the points if the forward one has a bigger radius so the lerp can work well
            if (Vector2.Distance(fromPoint.leftPoint.vector, fromPoint.rightPoint.vector) < Vector2.Distance(toPoint.leftPoint.vector, toPoint.rightPoint.vector))
            {
                TerrainPaintPoint temp = fromPoint;
                fromPoint = toPoint;
                toPoint = temp;
            }

            List<Point> drawn = new List<Point>();
            Vector2 currentPosition = fromPoint.leftPoint.vector;
            Vector2 fromRight = fromPoint.rightPoint.vector;

            float alphaStartPercent = 0f;
            float alphaEndPercent = 1f;
            if(feather > 0)
            {
                currentPosition += (fromPoint.leftPoint.vector - fromPoint.center.vector).normalized * feather * 4f;
                fromRight += (fromPoint.rightPoint.vector - fromPoint.center.vector).normalized * feather * 4f;
                float span = (fromPoint.leftPoint.vector - fromPoint.rightPoint.vector).magnitude / (fromRight - currentPosition).magnitude;
                float rest = (1f - span) / 2f;
                alphaStartPercent = rest;
                alphaEndPercent = 1f - rest;
            }
            float armLength = Vector2.Distance(currentPosition, fromRight);
            if (armLength < 1f) return;
            while (true)
            {
                float armDistance = Vector2.Distance(currentPosition, fromRight);
                float armPercent = 1f-armDistance / armLength;
                //This can be optimized, take it outside of the cycle
                Point fromPos = new Point(currentPosition);
                Vector2 leftvector = toPoint.leftPoint.vector;
                Vector2 rightVector = toPoint.rightPoint.vector;
                if (feather > 0)
                {
                    leftvector += (toPoint.leftPoint.vector - toPoint.center.vector).normalized * feather * 4f;
                    rightVector += (toPoint.rightPoint.vector - toPoint.center.vector).normalized * feather * 4f;
                }
                Vector2 toArm = Vector2.Lerp(leftvector, rightVector, armPercent);

                Point toPos = new Point(toArm);
                int dx = Mathf.Abs(toPos.x - fromPos.x), sx = fromPos.x < toPos.x ? 1 : -1;
                int dy = -Mathf.Abs(toPos.y - fromPos.y), sy = fromPos.y < toPos.y ? 1 : -1;
                int err = dx + dy, e2;
                Point current = fromPos;
                Vector2 target = new Vector2(toPos.x - fromPos.x, toPos.y - fromPos.y);
                
                float fromHeight = fromPoint.GetHeight(armPercent);
                float toHeight = toPoint.GetHeight(armPercent);
                while (true)
                {
                    if (current.x >= 0 && current.x < layer.GetLength(0) && current.y >= 0 && current.y < layer.GetLength(1))
                    {
                        if (overWriteHeight || layer[current.x, current.y] == 0f)
                        {
                            if (!ContainsPoint(ref drawn, current))
                            {
                                Vector2 currentDist = new Vector2(current.x - fromPos.x, current.y - fromPos.y);
                                float positionPercent = Mathf.Clamp01(currentDist.magnitude / target.magnitude);
                                float height = Mathf.Lerp(fromHeight, toHeight, positionPercent);
                                float alphaValue = 0f;
                                if (armPercent >= alphaStartPercent && armPercent <= alphaEndPercent) alphaValue = 1f;
                                if (writeAlpha) Plot(current.x, current.y, height, alphaValue, ref alphaLayer, ref layer);
                                else Plot(current.x, current.y, height, alphaLayer[current.x, current.y], ref alphaLayer, ref layer);
                                drawn.Add(current);
                            }
                        }
                    }
                    if (current.x == toPos.x && current.y == toPos.y) break;
                    e2 = 2 * err;
                    if (e2 > dy)
                    {
                        err += dy;
                        current.x += sx;
                    } else if (e2 < dx)
                    {
                        err += dx;
                        current.y += sy;
                    }
                }
                if (currentPosition == fromRight) break;
                currentPosition = Vector2.MoveTowards(currentPosition, fromRight, 1f);
            }
         }

        private bool ContainsPoint(ref List<Point> list, Point point)
        {
            for(int i = 0; i < list.Count; i++)
            {
                if (list[i].x == point.x && list[i].y == point.y) return true;
            }
            return false;
        }

        void Draw(SplineSample[] points, ref float[,] drawLayer, ref float[,] alphaLayer)
        {
            List<SplineSample> selectedPoints = new List<SplineSample>();
            Point last = new Point();
            //Filter out points that are too close to each other
            for (int i = 0; i < points.Length; i++)
            {
                Point current = ToHeightmapCoords(points[i].position + points[i].up * offset);
                if (i == 0 || i == points.Length-1)
                {
                    last = new Point(current.x, current.y);
                    selectedPoints.Add(points[i]);
                } else if (Vector2.Distance(new Vector2(current.x, current.y), new Vector2(last.x, last.y)) >= 1.5f)
                {
                    selectedPoints.Add(points[i]);
                    last = new Point(current.x, current.y);
                }
            }
            if (selectedPoints.Count <= 1) return;
            TerrainPaintPoint[] paintPoints = new TerrainPaintPoint[selectedPoints.Count];
            for (int i = 0; i < selectedPoints.Count; i++)
            {
                ConvertToPaintPoint(selectedPoints[i], ref paintPoints[i]);
            }
            //Paint the points
            for (int i = 0; i < paintPoints.Length - 1; i++)
            {
                promptSave = true;
                PaintSegment(paintPoints[i], paintPoints[i + 1], ref drawLayer, ref alphaLayer);
            }

            SplineSample exResult = selectedPoints[0];
            exResult.position += exResult.position - selectedPoints[1].position;
            TerrainPaintPoint exPoint = null;
            ConvertToPaintPoint(exResult, ref exPoint);
            PaintSegment(paintPoints[0], exPoint, ref drawLayer, ref alphaLayer, false, false);

            exResult = selectedPoints[selectedPoints.Count-1];
            exResult.position += exResult.position - selectedPoints[selectedPoints.Count - 2].position;
            ConvertToPaintPoint(exResult, ref exPoint);
            PaintSegment(paintPoints[paintPoints.Length-1], exPoint, ref drawLayer, ref alphaLayer, false, false);
            //Extrapolate the ending and the begining
        }

        TerrainPaintPoint ConvertToPaintPoint(SplineSample result, ref TerrainPaintPoint paintPoint)
        {
            paintPoint = new TerrainPaintPoint();
            Vector3 right = -Vector3.Cross(result.forward, result.up).normalized * size * 0.5f * result.size;
            Vector3 leftPoint = result.position - right + result.up * offset;
            Vector3 rightPoint = result.position + right + result.up * offset;
            paintPoint.center = ToHeightmapCoords(result.position + result.up * offset);
            paintPoint.leftPoint = ToHeightmapCoords(leftPoint);
            paintPoint.rightPoint = ToHeightmapCoords(rightPoint);
            paintPoint.leftHeight = ToHeightmapValue(leftPoint.y);
            paintPoint.rightHeight = ToHeightmapValue(rightPoint.y);
            paintPoint.floatDiameter = Vector2.Distance(new Vector2(leftPoint.x, leftPoint.z), new Vector2(rightPoint.x, rightPoint.z));
            if (paintPoint.leftHeight > maxDrawHeight) maxDrawHeight = paintPoint.leftHeight;
            if (paintPoint.rightHeight > maxDrawHeight) maxDrawHeight = paintPoint.rightHeight;
            return paintPoint;
        }


        Point Project(Point fromPoint, Point toPoint, int x, int y)
        {
            Vector2 dir = toPoint.vector - fromPoint.vector;
            Vector2 point = new Vector2(x, y);
            dir.Normalize();
            Vector2 v = point - fromPoint.vector;
            float d = Vector2.Dot(v, dir);
            return new Point(fromPoint.vector + dir * d);
        }

        void GaussBlur(ref float[,] source, ref float[,] target, int r)
        {
            int w = source.GetLength(0);
            int h = source.GetLength(1);
            int[] bxs = GBGetBoxes(r, 3);
            float[] flatSource = new float[source.GetLength(0) * source.GetLength(1)];
            float[] flatTarget = new float[source.GetLength(0) * source.GetLength(1)];
            for (int x = 0; x < source.GetLength(0); x++)
            {
                for (int y = 0; y < source.GetLength(1); y++)
                {
                    if (r == 0) target[x, y] = source[x, y];
                    else flatSource[x * source.GetLength(0) + y] = source[x, y];
                }
            }
            if (r == 0) return;
            BoxBlur(ref flatSource, ref flatTarget, w, h, (bxs[0] - 1) / 2);
            BoxBlur(ref flatTarget, ref flatSource, w, h, (bxs[1] - 1) / 2);
            BoxBlur(ref flatSource, ref flatTarget, w, h, (bxs[2] - 1) / 2);

            for (int i = 0; i < flatSource.Length; i++)
            {
                int x = Mathf.FloorToInt(i / source.GetLength(0));
                int y = i - x * source.GetLength(0);
                target[x, y] = flatTarget[i];
            }
        }

        int[] GBGetBoxes(int sigma, int n)
        {
            float wIdeal = Mathf.Sqrt((12 * sigma * sigma / n) + 1);
            int wl = Mathf.FloorToInt(wIdeal); if (wl % 2 == 0) wl--;
            int wu = wl + 2;

            float mIdeal = (12 * sigma * sigma - n * wl * wl - 4 * n * wl - 3 * n) / (-4 * wl - 4);
            float m = Mathf.Round(mIdeal);

            int[] sizes = new int[n];
            for (int i = 0; i < n; i++) sizes[i] = i < m ? wl : wu;
            return sizes;
        }

        void BoxBlur(ref float[] source, ref float[] target, int w, int h, int r)
        {
            for (int i = 0; i < source.Length; i++) target[i] = source[i];
            HorizontalBlur(ref target, ref source, w, h, r);
            VerticalBlur(ref source, ref target, w, h, r);
        }

        void HorizontalBlur(ref float[] source, ref float[] target, int w, int h, int r)
        {
            float iarr = 1f / (r*2f + 1f);
            for (int i = 0; i < h; i++)
            {
                int ti = i * w, li = ti, ri = ti + r;
                float fv = source[ti], lv = source[ti + w - 1], val = (r + 1) * fv;
                for (int j = 0; j < r; j++) val += source[ti + j];
                for (int j = 0; j <= r; j++) { val += source[ri++] - fv; target[ti++] = val * iarr; }
                for (int j = r + 1; j < w - r; j++) { val += source[ri++] - source[li++]; target[ti++] = val * iarr; }
                for (int j = w - r; j < w; j++) { val += lv - source[li++]; target[ti++] = val * iarr; }
            }
        }

        void VerticalBlur(ref float[] source, ref float[] target, int w, int h, int r)
        {
            float iarr = 1f / (r * 2f + 1f);
            for (int i = 0; i < w; i++)
            {
                int ti = i, li = ti, ri = ti + r * w;
                float fv = source[ti], lv = source[ti + w * (h - 1)], val = (r + 1) * fv;
                for (var j = 0; j < r; j++) val += source[ti + j * w];
                for (var j = 0; j <= r; j++) { val += source[ri] - fv; target[ti] = val * iarr; ri += w; ti += w; }
                for (var j = r + 1; j < h - r; j++) { val += source[ri] - source[li]; target[ti] = val * iarr; li += w; ri += w; ti += w; }
                for (var j = h - r; j < h; j++) { val += lv - source[li]; target[ti] = val * iarr; li += w; ti += w; }
            }
        }


        private void Plot(int x, int y, float value, float alpha, ref float[,] alphaTarget, ref float[,] target)
        {
            if (x < 0 || x >= target.GetLength(0)) return;
            if (y < 0 || y >= target.GetLength(1)) return;
            if (value > target[x, y])
            {
                target[x, y] = value;
                alphaTarget[x, y] = alpha;
            }
        }
    }
}