Поиск полигонов на 2D текстуре (C#, Unity) - PullRequest
0 голосов
/ 07 марта 2020

Итак, я работаю в Unity. То, чего я пытаюсь достичь, - это возможность подавать текстуру в скрипт. Вся текстура белая, за исключением нескольких фигур, многоугольников (от четырех до двенадцати точек). Эти формы будут (обводка и заливка) только одной формой шкалы серого (серый, черный, между ними). ​​

Сценарий должен уметь обнаруживать различные формы, добавлять их в список с координатами их углы. После этого я хочу построить трехмерные сетки на основе этих координат, используя уровень серого для формы, чтобы указать ее высоту.

Я в основном использую текстуру 2D в оттенках серого, чтобы построить трехмерный город с простыми формами. .

Я гуглил и искал указания. Я нашел этот скрипт. Это работает очень хорошо и позволяет мне находить любые замкнутые формы. Тем не менее, он ищет не многоугольники, а любые формы и, таким образом, будет хранить все точек. Но я ищу только углы.

Я не нуждаюсь в помощи для 3D-мешей. Я только ищу алгоритм или способ найти фигуры на 2d текстуре, кэшировать их цвет и координаты их углов.

Спасибо!

using UnityEngine;
using System.Collections.Generic;

namespace B83.UA.ImageTools
{
    public class DetectImageShapes : MonoBehaviour
    {
        public Texture2D texture;
        List<List<Vector2i>> shapes = null;
        Material m;
        void Start()
        {
            shapes = FindShapes(texture, true); // true--> black on white | false --> white on black
            m = new Material(Shader.Find("Particles/Alpha Blended Premultiply"));
            Debug.Log("shapeCount: " + shapes.Count);
        }
        void OnRenderObject()
        {
            m.SetPass(0);
            GL.modelview = Camera.main.worldToCameraMatrix * transform.localToWorldMatrix;
            GL.Begin(GL.LINES);
            UnityEngine.Random.seed = 0;
            for(int i = 0; i < shapes.Count; i++)
            {
                var shape = shapes[i];
                GL.Color(UnityEngine.Random.ColorHSV(0,1,1,1,1,1,1,1));
                Vector2 last = (Vector2)shape[shape.Count - 1];
                for(int n = 0; n < shape.Count; n++)
                {
                    Vector2 p = (Vector2)shape[n];
                    GL.Vertex(last);
                    GL.Vertex(p);
                    last = p;
                }
            }
            GL.End();
        }


        public static List<List<Vector2i>> FindShapes(Texture2D aTex, bool aInvertColors)
        {
            int width = aTex.width;
            byte[] data = CreateBWData(aTex, 0.5f, aInvertColors);
            var res = new List<List<Vector2i>>();
            int maxCount = 20000; // safety switch
            for (int start = FindShapeStart(data, 0); start != -1 && maxCount > 0; start = FindShapeStart(data, start+1), --maxCount)
            {
                var shape = GetShapePoints(data, start, width);
                res.Add(shape);
                RemoveShape(data, width, shape);
            }
            return res;
        }
        // converts the pixel data into a pure data array
        static byte[] CreateBWData(Texture2D aTexture, float aBWThreshold, bool aInvertColors)
        {
            var tmp = aTexture.GetPixels();
            int count = tmp.Length;
            byte[] data = new byte[count];
            for (int i = 0; i < count; i++)
                data[i] = (byte)(((tmp[i].grayscale > aBWThreshold) ^ aInvertColors) ? 1 : 0);
            return data;
        }
        // This simply iterates the image row by row from the bottom to find the next "filled" point
        static int FindShapeStart(byte[] aData, int aStartingIndex)
        {
            int count = aData.Length;
            for (int i = aStartingIndex; i < count; i++)
                if (aData[i] > 0)
                    return i;
            return -1;
        }
        // this is just a helper that translates a point into an index and checks if the point is "filled".
        static bool TestPoint(byte[] aData, int aWidth, Vector2i aPoint)
        {
            int index = aPoint.x + aPoint.y * aWidth;
            if (index > 0 && index < aData.Length)
                return aData[index] > 0;
            return false;
        }

        static List<Vector2i> GetShapePoints(byte[] aData, int aIndex, int aWidth)
        {
            List<Vector2i> res = new List<Vector2i>();
            HashSet<int> pointCache = new HashSet<int>();
            Vector2i dir = new Vector2i(1,0);
            Vector2i start = new Vector2i(aIndex % aWidth, aIndex / aWidth);
            Vector2i p = start;
            res.Add(p);
            bool done = false;
            int maxPoints = 40000;
            while (!done && --maxPoints > 0)
            {
                var d = dir.right;
                for(int i = 0; i < 7; i++)
                {
                    var current = p + d;
                    int index = current.x + current.y * aWidth;
                    // If we are back to the start or if we reached a point we already included
                    if (current == start || pointCache.Contains(index))
                    {
                        done = true;
                        break;
                    }
                    if (TestPoint(aData, aWidth, current))
                    {
                        // valid point, so move on to that point and update dir
                        // so we know where we were coming from
                        p = current;
                        dir = d;
                        res.Add(current);
                        pointCache.Add(index);
                        break;
                    }
                    // rotate 45° each iteration
                    d = d.left45;
                }
            }
            return res;
        }
        // removes the pixels of a shape from the data array
        static void RemoveShape(byte[] aData, int aWidth, List<Vector2i> aPoints)
        {
            int count = aPoints.Count;
            var one = new Vector2i(1, 1);
            // image resolution constraints.
            var min = new Vector2i(0, 0);
            var max = new Vector2i(aWidth - 1, aData.Length / aWidth - 1);
            for (int i = 0; i < count; i++)
            {
                // construct valid bottom-left and top-right coordinates
                Vector2i s = Vector2i.Clamp(aPoints[i] - one, min, max);
                Vector2i e = Vector2i.Clamp(aPoints[i] + one, min, max);
                // remove the whole 3x3 area around each point
                for(int y = s.y; y <= e.y; y++)
                {
                    for (int x = s.x; x <= e.x; x++)
                        aData[x + y * aWidth] = 0;
                }
            }
        }
    }


    public struct Vector2i
    {
        public int x;
        public int y;
        public Vector2i(int aX, int aY)
        {
            x = aX; y = aY;
        }

        public static Vector2i operator +(Vector2i aV1, Vector2i aV2)
        {
            return new Vector2i(aV1.x + aV2.x, aV1.y + aV2.y);
        }
        public static Vector2i operator -(Vector2i aV1, Vector2i aV2)
        {
            return new Vector2i(aV1.x - aV2.x, aV1.y - aV2.y);
        }
        public static Vector2i operator +(Vector2i aV1, int aScalar)
        {
            return new Vector2i(aV1.x + aScalar, aV1.y + aScalar);
        }
        public static Vector2i operator +(int aScalar, Vector2i aV1)
        {
            return new Vector2i(aV1.x + aScalar, aV1.y + aScalar);
        }
        public static Vector2i operator -(Vector2i aV1, int aScalar)
        {
            return new Vector2i(aV1.x - aScalar, aV1.y - aScalar);
        }
        public static Vector2i operator -(int aScalar, Vector2i aV1)
        {
            return new Vector2i(-aV1.x + aScalar, -aV1.y + aScalar);
        }
        public static Vector2i operator -(Vector2i aV1)
        {
            return new Vector2i(-aV1.x, -aV1.y);
        }
        public static Vector2i operator *(Vector2i aV1, int aScalar)
        {
            return new Vector2i(aV1.x * aScalar, aV1.y * aScalar);
        }
        public static Vector2i operator *(int aScalar, Vector2i aV1)
        {
            return new Vector2i(aV1.x * aScalar, aV1.y * aScalar);
        }
        public static Vector2i operator /(Vector2i aV1, int aScalar)
        {
            return new Vector2i(aV1.x / aScalar, aV1.y / aScalar);
        }
        public static implicit operator Vector2(Vector2i aV1)
        {
            return new Vector2(aV1.x, aV1.y);
        }
        public static explicit operator Vector2i(Vector2 aV1)
        {
            return new Vector2i(Mathf.RoundToInt(aV1.x), Mathf.RoundToInt(aV1.y));
        }
        public static bool operator ==(Vector2i aV1, Vector2i aV2)
        {
            return aV1.x == aV2.x && aV1.y == aV2.y;
        }
        public static bool operator !=(Vector2i aV1, Vector2i aV2)
        {
            return aV1.x != aV2.x || aV1.y != aV2.y;
        }
        public override bool Equals(object obj)
        {
            if (obj is Vector2i)
                return this == (Vector2i)obj;
            return false;
        }
        public override int GetHashCode()
        {
            return (x.GetHashCode()+y).GetHashCode();
        }
        public override string ToString()
        {
            return string.Format("({0},{1})", x, y);
        }

        // This only reduces the vector uniformly by the largest component
        // So (2,2) --> (1,1) but (2,1) --> (1,0)
        public Vector2i normalized
        {
            get
            {
                int len = Mathf.Max(Mathf.Abs(x), Mathf.Abs(y));
                return this / len;
            }
        }

        public Vector2i right
        {
            get { return new Vector2i(y, -x); }
        }
        public Vector2i left
        {
            get { return new Vector2i(-y, x); }
        }
        public Vector2i right45
        {
            // (this + right).normalized
            get { return new Vector2i(x + y, y - x).normalized; }
        }
        public Vector2i left45
        {
            // (this + left).normalized
            get { return new Vector2i(x - y, y + x).normalized; }
        }
        // performs a component-wise min operation
        public static Vector2i Min(Vector2i aV1, Vector2i aV2)
        {
            return new Vector2i(Mathf.Min(aV1.x, aV2.x), Mathf.Min(aV1.y, aV2.y));
        }
        // performs a component-wise max operation
        public static Vector2i Max(Vector2i aV1, Vector2i aV2)
        {
            return new Vector2i(Mathf.Max(aV1.x, aV2.x), Mathf.Max(aV1.y, aV2.y));
        }
        // Keep the vector inside the box defined by min and max
        public static Vector2i Clamp(Vector2i aVec, Vector2i aMin, Vector2i aMax)
        {
            aVec.x = Mathf.Clamp(aVec.x, aMin.x, aMax.x);
            aVec.y = Mathf.Clamp(aVec.y, aMin.y, aMax.y);
            return aVec;
        }
    }
}
...