Draw Circle on Texture with World-Coords

I’m trying to make a 2d destructible terrain and i’m doing that by drawing on an image and using it with a 2d polygon collider. I found something online where i could draw at my mouse position and that worked fine. I tried to rewrite it a bit, so it would use the position of an other sprite instead of the mouses but i cant get it to work.
Any help would be greatly appreciated, here’s the code:

public SpriteRenderer _sr;
    PolygonCollider2D _collider;


    void Start()
    {
        _sr = GetComponent<SpriteRenderer>();
        _collider = GetComponent<PolygonCollider2D>();
    }

    public void Destruct(Vector2 worldPos, float radius)
    {
        Vector2 hitPoint;
        if (GetSpritePixelCoordsAtWorldPos(_sr, worldPos, out hitPoint))
        {
            DrawCircle(_sr.sprite.texture, Mathf.RoundToInt(hitPoint.x), Mathf.RoundToInt(hitPoint.y), (int)radius, new Color(255, 0, 0, 255));
            Destroy(_collider);
            _collider = gameObject.AddComponent<PolygonCollider2D>();
        }

    }

    void DrawCircle(Texture2D tex, int cx, int cy, int r, Color col)
    {
        int x, y, px, nx, py, ny, d;
        Color32[] tempArray = tex.GetPixels32();

        for (x = 0; x <= r; x++)
        {
            d = (int)Mathf.Ceil(Mathf.Sqrt(r * r - x * x));
            for (y = 0; y <= d; y++)
            {   
                px = cx + x;
                nx = cx - x;
                py = cy + y;
                ny = cy - y;

                if (tempArray.Length > py * tex.width + px)
                    tempArray[py * tex.width + px] = col;

                if (tempArray.Length > py * tex.width + nx)
                    tempArray[py * tex.width + nx] = col;

                if (tempArray.Length > ny * tex.width + px)
                    tempArray[ny * tex.width + px] = col;

                if (tempArray.Length > ny * tex.width + nx)
                    tempArray[ny * tex.width + nx] = col;          
            }
        }

        tex.SetPixels32(tempArray);
        tex.Apply();
    }

    public bool GetSpritePixelCoordsAtWorldPos(SpriteRenderer spriteRenderer, Vector2 orgPos, out Vector2 vec)
    {
        vec = Vector2.zero;

        Vector3 dir = new Vector3(orgPos.y, orgPos.y, 5F);
        Ray ray = new Ray(dir, new Vector3(0, 0, -1F));

        return getTexPos(spriteRenderer, ray, out vec);
    }

    bool getTexPos(SpriteRenderer spriteRenderer, Ray ray, out Vector2 vec)
    {
        vec = Vector2.zero;

        if (spriteRenderer == null) return false;
        Sprite sprite = spriteRenderer.sprite;
        if (sprite == null) return false;
        Texture2D texture = sprite.texture;
        if (texture == null) return false;
        // Check atlas packing mode
        if (sprite.packed && sprite.packingMode == SpritePackingMode.Tight)
        {
            // Cannot use textureRect on tightly packed sprites
            Debug.LogError("SpritePackingMode.Tight atlas packing is not supported!");
            // TODO: support tightly packed sprites
            return false;
        }
        // Craete a plane so it has the same orientation as the sprite transform
        Plane plane = new Plane(transform.forward, transform.position);
        // Intersect the ray and the plane
        float rayIntersectDist; // the distance from the ray origin to the intersection point
        if (!plane.Raycast(ray, out rayIntersectDist)) return false; // no intersection
                                                                     // Convert world position to sprite position
                                                                     // worldToLocalMatrix.MultiplyPoint3x4 returns a value from based on the texture dimensions (+/- half texDimension / pixelsPerUnit) )
                                                                     // 0, 0 corresponds to the center of the TEXTURE ITSELF, not the center of the trimmed sprite textureRect
        Vector3 spritePos = spriteRenderer.worldToLocalMatrix.MultiplyPoint3x4(ray.origin + (ray.direction * rayIntersectDist));
        Rect textureRect = sprite.textureRect;
        float pixelsPerUnit = sprite.pixelsPerUnit;
        float halfRealTexWidth = texture.width * 0.5f; // use the real texture width here because center is based on this -- probably won't work right for atlases
        float halfRealTexHeight = texture.height * 0.5f;
        // Convert to pixel position, offsetting so 0,0 is in lower left instead of center
        int texPosX = (int)(spritePos.x * pixelsPerUnit + halfRealTexWidth);
        int texPosY = (int)(spritePos.y * pixelsPerUnit + halfRealTexHeight);

        // Check if pixel is within texture
        if (texPosX < 0 || texPosX < textureRect.x || texPosX >= Mathf.FloorToInt(textureRect.xMax)) return false; // out of bounds
        if (texPosY < 0 || texPosY < textureRect.y || texPosY >= Mathf.FloorToInt(textureRect.yMax)) return false; // out of bounds

        vec = new Vector2(texPosX, texPosY);

        return true;
    }

That’s a lot of code! I gather you’re trying to do something like this, but I don’t understand what’s not working for you. Can you explain what happens with your code, and how that’s different from what you wanted?

1 Like

Basicly i am trying to draw a circle onto another texture, but need to convert the position of an gameobject to the position of this sprites texture (which is the part i’m having trouble with). The code above is an example i found online where i could draw on said sprite with clicking the mouse, i tried to change it so it doesnt take the mouse-coordinates but the position of another gameobject. Your link does what i wanted but also alot more, i dont need pixelated physic and the other stuff. I assume my mistake lies here, but i’m not sure:

That’s really odd code — the origin is called “dir”, and the direction isn’t called anything (but is always in the -Z direction).

But, oddness aside, it looks like it ought to work. I can’t see where you’re getting the mouse position, so the error could be there.

(You still haven’t actually said what goes wrong; maybe it doesn’t draw at all, maybe it draws at the wrong position, maybe it throws an exception… only you know, and apparently you’re not telling!)

The error is that it’s being drawn at the wrong position.

How do you choose the position ? (Vector2 worldPos) Do you pick a gameobject and use its transform.position?
What exactly happens? Is the sprite drawn on always at the same spot even if the chosen position is different?
Is the sprite drawn on on different spots but not the spot you were aiming for?

Yes it’s the transform.position of a gameobject, when the gameobjects collides with anything (i’m shooting a bazooka on a 2d game like in worms armageddon), it calls the terrains function to destroy terrain at this position. The circle on the texture doesnt gets drawn at all because its coordinates are way off the texture width and heights limit.

Did you add a Debug.Log to check
The coordinates of the missile when it explodes / The coordinates where the raycast start / the coordinates where the terrain should get destroyed?

Yes I checked them, but that is the problem. I don’t understand why they dont match at all. I guess i made a small mistake converting the coordinates from gameobject to the sprites texture, but i cant figure out where.

Usually, when this kind of thing happens to me, i rerwite again the script from scratch trying to do it in a more elegant way, else i just spend days trying billions of little modifications without any improvement.

Well, i guess I’ll do that then …

Let us know if you get stuck somewhere. Destructible terrain is always an interesting feature.

1 Like