Don’t do a check against a point but against a line segment. The only difference is that before you do the distance check you have to project the coordinate of the current pixel you’re testing onto the line segment. Of course this projected point need to be clamped between the start and end points of the line. Once you have that point you can simply subtract the projected point from x and y instead of just the new point. This will naturally draw a “line” with a width of twice your radius. It’s basically the shadow of a capsule.
So all you need is to save the last position while dragging so you have that line segment you need. Note that currently you iterate over all pixels in the image. This could be optimised by just iterating over the bounding rectangle of your line segment. So just figure out the min and max values of your start and end coordinates and add / subract the radius and finally clamp the result to the image resolution. Finally you shouldn’t use SetPixel
in a loop. It’s much more efficient to use GetPixels
once in Start
, modify the array and use SetPixels
after your processing.
Also you shouldn’t create a new texture each time. Unused Textures need to be destroyed or you will run out of memory. You may also want to create a single texture which you are going to modify in start and reuse that texture…
EDIT
I just created a script based on your approach but heavily modified it with the changes i’ve mentioned.
using UnityEngine;
public class DrawLines : MonoBehaviour
{
private Texture2D m_Texture;
private Color[] m_Colors;
RaycastHit2D hit;
SpriteRenderer spriteRend;
Color zeroAlpha = Color.clear;
public int erSize;
public Vector2Int lastPos;
public bool Drawing = false;
void Start ()
{
spriteRend = gameObject.GetComponent<SpriteRenderer>();
var tex = spriteRend.sprite.texture;
m_Texture = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false);
m_Texture.filterMode = FilterMode.Bilinear;
m_Texture.wrapMode = TextureWrapMode.Clamp;
m_Colors = tex.GetPixels();
m_Texture.SetPixels(m_Colors);
m_Texture.Apply();
spriteRend.sprite = Sprite.Create(m_Texture, spriteRend.sprite.rect, new Vector2(0.5f, 0.5f));
}
void Update()
{
if (Input.GetMouseButton(0))
{
hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
if (hit.collider != null)
{
UpdateTexture();
Drawing = true;
}
}
else
Drawing = false;
}
public void UpdateTexture()
{
int w = m_Texture.width;
int h = m_Texture.height;
var mousePos = hit.point - (Vector2)hit.collider.bounds.min;
mousePos.x *= w / hit.collider.bounds.size.x;
mousePos.y *= h / hit.collider.bounds.size.y;
Vector2Int p = new Vector2Int((int)mousePos.x, (int)mousePos.y);
Vector2Int start = new Vector2Int();
Vector2Int end = new Vector2Int();
if(!Drawing)
lastPos = p;
start.x = Mathf.Clamp(Mathf.Min(p.x, lastPos.x) - erSize, 0, w);
start.y = Mathf.Clamp(Mathf.Min(p.y, lastPos.y) - erSize, 0, h);
end.x = Mathf.Clamp(Mathf.Max(p.x, lastPos.x) + erSize, 0, w);
end.y = Mathf.Clamp(Mathf.Max(p.y, lastPos.y) + erSize, 0, h);
Vector2 dir = p - lastPos;
for (int x = start.x; x < end.x; x++)
{
for (int y = start.y; y < end.y; y++)
{
Vector2 pixel = new Vector2(x, y);
Vector2 linePos = p;
if (Drawing)
{
float d = Vector2.Dot(pixel - lastPos, dir) / dir.sqrMagnitude;
d = Mathf.Clamp01(d);
linePos = Vector2.Lerp(lastPos, p, d);
}
if ((pixel - linePos).sqrMagnitude <= erSize * erSize)
{
m_Colors[x + y * w] = zeroAlpha;
}
}
}
lastPos = p;
m_Texture.SetPixels(m_Colors);
m_Texture.Apply();
spriteRend.sprite = Sprite.Create(m_Texture, spriteRend.sprite.rect, new Vector2(0.5f, 0.5f));
}
}
Here’s an example of the script in action. The first radius is 10, the second is 40
original gif link
Note the image used here is “1024x240” and works smoothly. Though if you have a larger image you may run into performance issues as updating such large images is always a problem as it has to be uploaded to the GPU each time. You might get better performance when creating an actual mask mesh and only apply the drawn lines every now and then. Though for relatively small images the current approach seems to work fine.