Итак, я работаю в 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;
}
}
}