4

I make a code to be able to draw and generate a sprite of this drawing. So I get a sprite with white background and my drawing (which is in a different color).

My question : How could I remove the white background at runtime ?(with C# code)

My problem is : I want to generated mesh using the drawing, but with white background I have 4 vertices (the fourth corners of the sprite) and I want to get all the vertices from the real shape I draw on my sprite (so much more than 4 vertices)

My current idea is to convert the drawing into having a transparent background and then use unity's sprite packer to generate a mesh from that.

My project: It’s a game, where we can create his own game circuit : user draw a black and white sprite —> I convert it to a mesh with collider and generated the new game circuit.

I already thin to clean all white pixels, but I don't think I will get many vertices with that technic.

Thanks for help, Axel the real shape

Ruzihm
  • 19,749
  • 5
  • 36
  • 48
AKIRK
  • 109
  • 1
  • 14
  • What are you trying to accomplish with this? There is probably a better way than this, which would be very difficult to account for all possible edge cases. – Ruzihm Dec 26 '18 at 18:11
  • I want to generate a circuit game (like car circuit) from this drawing. User should be able to create his own circuit . – AKIRK Dec 26 '18 at 18:12
  • SO user make a drawing -> I generated a png --> I generate mesh with vertice from png --> add collider ... create field game and let's play – AKIRK Dec 26 '18 at 18:13
  • There is not enough information in the image to distinguish between intersections or two separate turns that share a location . A better approach would be to collect user input in your game (such as by tracking where the pointer goes when they go to level creation) and then using that information to generate your mesh. – Ruzihm Dec 26 '18 at 18:22
  • In fact, I begin with that, but as the user have to be able to get specific design it‘s better to use this way. It work if i import png which already have transparency. – AKIRK Dec 26 '18 at 18:29
  • When you import a png without a white background , you could have all vertices and generate the shape mesh – AKIRK Dec 26 '18 at 18:52

4 Answers4

3
using System.IO;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.Networking;
public class scri : MonoBehaviour
{
    // For saving the mesh------------------------ 
    public KeyCode saveKey = KeyCode.F12;
    public string saveName = "SavedMesh";

    // Concerning mesher--------------------------
    public GameObject mesher; //require

    public List<Vector3> vertices;
    public  List<int> triangles;

    public Vector3 point0;
    public Vector3 point1;
    public Vector3 point2;
    public Vector3 point3;
    
    public int loop;
    public float size;

    public Mesh meshFilterMesh;
    public Mesh meshColliderMesh;

    // Sprite work
    public Color[] pixels;

    public Texture2D newTexture;  
    public Texture2D oldTexture;  //require

    private Sprite mySprite;
    private SpriteRenderer spriteRenderer;

    public int pathCount;

    public GameObject displayerComponent; //require

    public PolygonCollider2D polygonColliderAdded; //require

    void Start()
    {
        // Mesher
        vertices = new List<Vector3> (); 
        triangles = new List<int> (); 
        meshFilterMesh= mesher.GetComponent<MeshFilter>().mesh;
        meshColliderMesh= mesher.GetComponent<MeshCollider>().sharedMesh;
        size = 10; // lenght of the mesh in Z direction
        loop=0;

        // Sprite
        pixels = oldTexture.GetPixels();
        newTexture =new Texture2D(oldTexture.width,oldTexture.height,TextureFormat.ARGB32, false);
        spriteRenderer = gameObject.AddComponent<SpriteRenderer>();
        
        ConvertSpriteAndCreateCollider (pixels);
        BrowseColliderToCreateMesh (polygonColliderAdded);
        
    }

    void Update()
    {
        // Save if F12 press
        if (Input.GetKeyDown(saveKey)){SaveAsset();}
    }

    public void ConvertSpriteAndCreateCollider (Color[] pixels) {
        for (int i = 0 ; i < pixels.Length ; i++ ) 
        { 
            // delete all black pixel (black is the circuit, white is the walls)
            if ((pixels[i].r==0 && pixels[i].g==0 && pixels[i].b==0 && pixels[i].a==1)) {
                pixels[i] = Color.clear;
            }
        }
        // Set a new texture with this pixel list
        newTexture.SetPixels(pixels);
        newTexture.Apply();

        // Create a sprite from this texture
        mySprite = Sprite.Create(newTexture, new Rect(0, 0, newTexture.width, newTexture.height), new Vector2(10.0f,10.0f), 10.0f, 0, SpriteMeshType.Tight,new Vector4(0,0,0,0),false);

        // Add it to our displayerComponent
        displayerComponent.GetComponent<SpriteRenderer>().sprite=mySprite;

        // Add the polygon collider to our displayer Component and get his path count
        polygonColliderAdded = displayerComponent.AddComponent<PolygonCollider2D>();

    }

    // Method to browse the collider and launch makemesh
    public void BrowseColliderToCreateMesh (PolygonCollider2D polygonColliderAdded){
        //browse all path from collider
        pathCount=polygonColliderAdded.pathCount;
        for (int i = 0; i < pathCount; i++)
        {
            Vector2[] path = polygonColliderAdded.GetPath(i);

            // browse all path point
            for (int j = 1; j < path.Length; j++)
            {
                if (j != (path.Length - 1)) // if we aren't at the last point
                {
                point0 = new Vector3(path[j-1].x ,path[j-1].y ,0);
                point1 = new Vector3(path[j-1].x ,path[j-1].y ,size);
                point2 = new Vector3(path[j].x ,path[j].y ,size);
                point3 = new Vector3(path[j].x ,path[j].y ,0);
                    MakeMesh(point0,point1,point2,point3);

                } 
                else if(j == (path.Length - 1))// if we are at the last point, we need to close the loop with the first point
                {
                point0 = new Vector3(path[j-1].x ,path[j-1].y ,0);
                point1 = new Vector3(path[j-1].x ,path[j-1].y ,size);
                point2 = new Vector3(path[j].x ,path[j].y ,size);
                point3 = new Vector3(path[j].x ,path[j].y ,0);
                    MakeMesh(point0,point1,point2,point3);
                point0 = new Vector3(path[j].x ,path[j].y ,0);
                point1 = new Vector3(path[j].x ,path[j].y ,size);
                point2 = new Vector3(path[0].x ,path[0].y ,size); // First point
                point3 = new Vector3(path[0].x ,path[0].y ,0); // First point
                    MakeMesh(point0,point1,point2,point3);
                }
            }
        }
    }


    //Method to generate 2 triangles mesh from the 4 points 0 1 2 3 and add it to the collider
    public void MakeMesh (Vector3 point0,Vector3 point1,Vector3 point2, Vector3 point3){
        
        // Vertice add
        vertices.Add(point0);
        vertices.Add(point1);
        vertices.Add(point2);
        vertices.Add(point3);

        //Triangle order
        triangles.Add(0+loop*4);
        triangles.Add(2+loop*4);
        triangles.Add(1+loop*4);
        triangles.Add(0+loop*4);
        triangles.Add(3+loop*4);
        triangles.Add(2+loop*4);
        loop = loop + 1; 

        // create mesh 
        meshFilterMesh.vertices=vertices.ToArray();
        meshFilterMesh.triangles=triangles.ToArray();

        // add this mesh to the MeshCollider
        mesher.GetComponent<MeshCollider>().sharedMesh=meshFilterMesh;
    }

    // Save if F12 press
    public void SaveAsset() 
    {
        var mf = mesher.GetComponent<MeshFilter>();
        if (mf)
        {
            var savePath = "Assets/" + saveName + ".asset";
            Debug.Log("Saved Mesh to:" + savePath);
            AssetDatabase.CreateAsset(mf.mesh, savePath);
        }
    }
}

enter image description here

DropDrage
  • 725
  • 8
  • 9
AKIRK
  • 109
  • 1
  • 14
  • Thank you! This code almost work for me! I encountered a problem: the generated mesh always has an offset, which made it far from the pivot. Do you have any ideas about it? – Denny Jun 17 '22 at 08:03
  • I find the answer of my problem! And If anyone else want to create the mesh just at the position of the gameobject, we should fulfill the parameters of Sprite.Create carefull! (like, set the pivot to (0.5, 0.5), calculate the PPU by texture resolution and mesh width, set the meshType to FullRect, etc) – Denny Jun 17 '22 at 09:52
0

One approach is to generate the mesh directly on your own terms. The pro of this is that you can have very fine control of exactly what you want pixel boundaries to look like, and you have better information do your own triangulation of the mesh. The downside is that you have to do all of this yourself.

One way of implementing this is to use the Marching Squares algorithm to generate isobands from the pixel data (You can use the blue/green/alpha channel to get the isovalue depending on if the background is white or transparent), and then generate a piece of the mesh from each of the 2x2 pixel grounds that have a part of the isoband.

To get the pixel data from the image you can use Texture2D.GetPixels. Then you can use the marching squares algorithm on that information to determine how to represent every 2x2 cluster of pixels in the mesh. Then you would use that information to find the vertices of each triangle that represents that quad of pixels.

Once you convert each quad of pixels into triangles, arrange the vertices of those triangles into an array (make sure you order the vertices of each triangle in a clockwise direction from the visible side) and use Mesh.SetVertices to create a mesh with those vertices.

Ruzihm
  • 19,749
  • 5
  • 36
  • 48
0

Another approach is to set the alpha of any non-red pixel to zero, and let Unity's sprite packer generate the mesh for you.

Here is one way to do that:

  1. If it is an asset and you want to modify it, set the texture asset to have Read/Write enabled checked. If the texture is created at runtime (and is therefore not an asset) this step can be skipped.

  2. Get the pixel data with Texture2D.GetPixels. This will get you an array of pixels in the form of Color[] pixels:

    public Texture2D tex;  
    
    ...
    
    Color[] pixels = tex.GetPixels();
    
  3. Iterate through each index and replace pixels with any amount of blue (such as white pixels) with clear pixels:

    for (int i = 0 ; i < pixels.Length ; i++ ) 
    {
        if (
            pixels[i].r != 1f 
            || pixels[i].g != 0f
            || pixels[i].b != 0f) 
            pixels[i] = Color.clear;
    }
    
  4. Set the texture pixel data with the modified pixel array:

    tex.SetPixels(pixels);
    tex.Apply();
    

The downside to this approach is that I do not know if you can use the Unity spritepacker to pack textures created at run time onto the sprite atlas. If it can not, then a different tool would be needed for this approach to generate meshes from sprites at run time.

Ruzihm
  • 19,749
  • 5
  • 36
  • 48
  • 1
    That‘s what I was thinking about, I have to make a try. I do not know too if unity could generate a new vertices array after that manipulation. I have to try it – AKIRK Dec 26 '18 at 19:37
  • It color in black all the white pixel. I'm thinking to create a new texture with the red pixel. --> How could we create a new getpixels array and add pixel each time the for loop is on a red red pixel ? Then I will have a new texture. – AKIRK Dec 26 '18 at 20:56
  • Make sure the asset is configured to use the alpha channel correctly. `Color.clear` sets r,g,b,a = 0,0,0,0 = black with 0 alpha. – Ruzihm Dec 26 '18 at 20:58
  • Anyway how could we manipulate this array Color[], it's not a list so I don't know how to create a new one, add some pixel and set it to a new texture. – AKIRK Dec 26 '18 at 21:13
  • I have Also a defaut-sprite shader on my sprite – AKIRK Dec 26 '18 at 21:17
  • I edited my question to apply the pixel change to any non-red pixel.What happens when you render that sprite with a spriterenderer? Also, you don't need to copy `Color[]`, you can just modify its contents in-place and use it in `SetPixels`. – Ruzihm Dec 26 '18 at 21:41
  • Hi, sry for delay. I change but It's still black. Now i try to create directly a new sprite with the selected pixel ( all the red pixel for example) but I have some error "Array size must be at least width*height UnityEngine.Texture2D:SetPixels(Int32, Int32, Int32, Int32, Color[])" – AKIRK Jan 04 '19 at 07:53
0

Ok I've made something :

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

public class scri : MonoBehaviour
{

    public Texture2D tex;  
    public Texture2D newText;  
    public Sprite sprite;
    public List<Color> colorList;

    private Sprite mySprite;
    private SpriteRenderer sr;

    // Start is called before the first frame update
    void Start()
    {
    sr = gameObject.AddComponent<SpriteRenderer>() as SpriteRenderer;
    newText =new Texture2D(tex.width,tex.height,TextureFormat.ARGB32, false);
    Color[] pixels = sprite.texture.GetPixels();
    for (int i = 0 ; i < pixels.Length ; i++ ) 
    { 
    Debug.Log(pixels[i]);
        if (pixels[i].r==1) {
            pixels[i] = Color.clear;
        }
    }
        newText.SetPixels(pixels);
        newText.Apply();
        mySprite = Sprite.Create(newText, new Rect(0.0f, 0.0f, newText.width, newText.height), new Vector2(0.5f, 0.5f), 100.0f);
        sr.sprite = mySprite;
    }

    // Update is called once per frame
    // void Update()
    // {
    //     Debug.Log(sprite.triangles.Length);
    //     Debug.Log(sprite.vertices.Length);
    // }
}

Usefull link : https://forum.unity.com/threads/setting-pixel-to-transparent-turns-out-black.172375/ https://docs.unity3d.com/ScriptReference/Sprite.Create.html https://forum.unity.com/threads/is-it-possible-to-convert-a-texture2d-from-one-format-to-another-in-standalone-run-time.327141/ https://forum.unity.com/threads/texture-setpixels.431177/

But I don't know why, if the png has a white background at the begining it doesn't work well ... : With svg it's ok since the begining without my code. enter image description here

But in sprite Editor I could generate a custom physic shape:enter image description here

AKIRK
  • 109
  • 1
  • 14