Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Painting/Drawing on mesh/terrain

Discussion in 'Scripting' started by slashdotray, Jun 12, 2009.

  1. slashdotray

    slashdotray

    Joined:
    Dec 3, 2006
    Posts:
    77
    Hi everybody,

    i posted this some weeks ago.

    We are building a tractor/agrar game, now the 2nd version.

    I´m a little lost since i don´t get how to do.
    I try to simulate ploughing and things like that by "painting" on a mesh(dont have to be the terrain) since our fields are extra meshes.

    I tried the SetPixel (it is paining now in yellow/black/red) only one pixel in width) but we want maybe 2 or 5 widht painting and it should be a texture to simulate the ploughing or something where you need a drawing on an object.


    Second one we tried we bought the starmanta Decalmaker(which is working well but in our case it slows down the system extremely)

    If there is somebody out there who could help us on that please help, we can if needed talk about a payment.
    You can also send a PM

    Thanks alot in advance
    Marc
     
  2. Bampf

    Bampf

    Joined:
    Oct 21, 2005
    Posts:
    369
    I did something similar for my game A Tack!, which features pens and pencils skating over a piece of paper, leaving lines behind. Screenshots can be seen here:
    http://www.mindthecube.com/A_Tack.html

    For starters try looking at the documentation for SetPixel/SetPixels:
    http://unity3d.com/support/documentation/ScriptReference/Texture2D.SetPixels.html
    In particular, look at the version of SetPixels at the very bottom, which lets you paint a block of pixels in one go. The array you are painting with would be set up ahead of time, assuming your brush doesn't change, and then the only trick is finding the texture coordinates to paint at. But it sounds like you already have that info.

    A classy thing to do is to define a public Texture2D member on the tractor's painter script. Then create a brush texture in your paint program of choice (or borrow one of the fire/smoke textures that come with Unity's standard assets.) In the Unity inspector, drag the texture to the tractor's public member to set it. Now your code can use this texture as your brush! The first time you need it, read the array of Colors from that texture using Texture2D:GetPixels. (Probably want to do this only once.) This gives you the array of Colors that you can pass to SetPixels.

    This makes it easy to try out different brush shapes/colors and see what looks best on your terrain, just by replacing the texture in the Inspector and rerunning your game. (You could also have multiple brushes to use for different terrain, or different types of tractor.)

    If you want to paint with a partially transparent brush, it gets a little more tricky. This is from memory, but I believe you will first have to read the pixels from the ground you are going to paint on, using GetPixels. Mix each pixel color of that array with a color from your brush texture and store it back in the same array. Now paint it back to your ground texture with SetPixels. Not too hard, but it gets trickier at the edges of the ground texture, so avoid painting off the edges if you can. (OTEE used to have example source code available that showed how to handle the clipping but I can't seem to find it on their site right now.)

    Hope this helps!
     
  3. slashdotray

    slashdotray

    Joined:
    Dec 3, 2006
    Posts:
    77
    Thanks for your answer.

    Quite a cool game BTW.

    So here is the modified example i have, and it´s painting like it should do black pixel.


    Code (csharp):
    1.  
    2.  
    3.  
    4. // Attach this script to a object and it will paint black pixels in 3D
    5. // on whatever Raycast hits.
    6.  
    7. function Update () {
    8.    
    9.    
    10.     // Only if we hit something, do we continue
    11.     var hit : RaycastHit;
    12.     var ray = transform.TransformDirection (-Vector3.up);
    13.     if (Physics.Raycast (transform.position,ray, hit))
    14.  
    15.    
    16.     // Just in case, also make sure the collider also has a renderer
    17.     // material and texture. Also we should ignore primitive colliders.
    18.     var renderer : Renderer = hit.collider.renderer;
    19.     var meshCollider = hit.collider as MeshCollider;
    20.  
    21.     // Now draw a pixel where we hit the object
    22.     var tex : Texture2D = renderer.material.mainTexture;
    23.     var pixelUV = hit.textureCoord;
    24.     pixelUV.x *= tex.width;
    25.     pixelUV.y *= tex.height;
    26.    
    27.     tex.SetPixel(pixelUV.x, pixelUV.y, Color.black);
    28.  
    29.     tex.Apply();
    30. }
    31.  
    changing it to tex.SetPixels gives following error:

    Assets/936Vario/A Scripts/SetPixels.js(24,18): BCE0023: No appropriate version of 'UnityEngine.Texture2D.SetPixels' for the argument list '(float, float, UnityEngine.Color)' was found.


    The question is, am i on the right way to paint a new texture to the Object hitted by the Raycast?
     
  4. Bampf

    Bampf

    Joined:
    Oct 21, 2005
    Posts:
    369
    Instead of passing a single Color to SetPixels, you need to pass in a few new things:

    - an array of colors, one for each pixel of the square you wish to paint
    - the width and height of the square you wish to paint

    So for a 5 by 5 array of black pixels, something like the following (this is not tested code)

    Code (csharp):
    1.  
    2. var colors = new Color[5*5];
    3. // set brush to black
    4. for (var i = 0; i < 5*5; i++)
    5. {
    6.    colors[i] = Color.black;
    7. }
    8. tex.SetPixels(pixelUV.x, pixelUV.y, 5, 5, colors);
    9.  
    Obviously in your debugged game you wouldn't initialize your brush in your Update function, as it only needs to be done once.

    Once you get this working, you can build on it with what I suggested earlier, which is to set up a public texture and use that as your brush. Or if you prefer, build up the brush with code like I do above.

    Hope this helps,
    Matt
     
  5. Ramen Sama

    Ramen Sama

    Joined:
    Mar 28, 2009
    Posts:
    561
    How do i make this code do something? i've applied the code to an object, given it a texture and what not but it gives me an error.

    NullReferenceException: Object reference not set to an instance of an object
    NewBehaviourScript.Update () (at Assets\NewBehaviourScript.js:21)

    which is this line
    Code (csharp):
    1. var tex : Texture2D = renderer.material.mainTexture;
     
  6. slashdotray

    slashdotray

    Joined:
    Dec 3, 2006
    Posts:
    77
    Thanks,


    @ Bampf: currently it´s doing no 5 x 5 pixels, i try to get that work.
    More help is still neede :oops:


    @Ramen Sama: I have a Object casting down a ray, and on that position it changes the pixels, so i have two objects, the raycaster with the script on and a plane where the pixels will be changed...i do not have the error.
     
  7. slashdotray

    slashdotray

    Joined:
    Dec 3, 2006
    Posts:
    77
    Hi,

    so i´m using now the bloodsplatter.js which is working very well.

    One more question:

    Can i count the polygons of my field and flag the already "hitted" polys as hitted?

    Thanks
    slashdotray
     
  8. Bampf

    Bampf

    Joined:
    Oct 21, 2005
    Posts:
    369
    That's an interesting question. It depends on what you are trying to accomplish. Here are a couple of ideas though.

    1) If your field is not currently using the alpha channel of the texture, you could "paint" a particular alpha value when you paint your tractor's position. Later on, to see if the tractor has been somewhere, just read back the alpha value from that spot on the field.

    2) You could just keep a separate two-dimensional array where you keep track the places the tractor has dug. This is especially appealing if your texture is large but the tractor can only dig in certain places and in certain directions. For example, maybe the field can be divided into smaller squares, each of which is either painted (dug) or not dug. (You could track partially dug areas if you wanted.)

    It's not quite clear what you mean by polygons. Is the field divided up into smaller squares/triangles? If so, then you might want to use my 2nd method. Each polygon would correspond to a single element of the array you are using to track dug/not-dug. You just need a formula to convert the U-V coordinates of the hit into the index of that section of field, so you could mark that it was painted.

    For example, if a 100x100 field was divided into smaller squares, each one 5x5, then you could store the information in a 20x20 array. If the tractor painted at UV (28, 55) you would set fieldIsDug[5][11] = true.

    Hope this helps.
     
  9. slashdotray

    slashdotray

    Joined:
    Dec 3, 2006
    Posts:
    77
    Here is for more explanation a image.

    so i want to mark or flag the hitted polygons as ploughed.

    and i want to count these polygons with script
     

    Attached Files:

  10. Bampf

    Bampf

    Joined:
    Oct 21, 2005
    Posts:
    369
    It would be possible to access the mesh data and track ploughed-ness for each polygon. But I think all you really need is to divide the field up into squares (10 x 10, say) and keep track of which squares have been ploughed in a separate array.
     
  11. Rispat-Momit

    Rispat-Momit

    Joined:
    Feb 14, 2013
    Posts:
    265
    I have been trying to figure this out for a while and now I feel I am on a good path! :)

    Here is my code :) Add the script to the camera, Set you texture to Read/Write enabled and to RGBA 32 bit and add a mesh collider to your object :)

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class DrawOnMesh : MonoBehaviour {
    6.  
    7.  
    8.     public Camera cam;
    9.     public int PixelsSize = 10;
    10.  
    11.     void Start()
    12.     {
    13.         cam = GetComponent<Camera>();
    14.     }
    15.  
    16.     void Update()
    17.     {
    18.         if (!Input.GetMouseButton(0))
    19.             return;
    20.  
    21.         RaycastHit hit;
    22.         if (!Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition), out hit))
    23.             return;
    24.  
    25.         Renderer rend = hit.transform.GetComponent<Renderer>();
    26.         MeshCollider meshCollider = hit.collider as MeshCollider;
    27.  
    28.         if (rend == null || rend.sharedMaterial == null || rend.sharedMaterial.mainTexture == null || meshCollider == null)
    29.             return;
    30.  
    31.         Texture2D tex = rend.material.mainTexture as Texture2D;
    32.         Vector2 pixelUV = hit.textureCoord;
    33.         pixelUV.x *= tex.width;
    34.         pixelUV.y *= tex.height;
    35.  
    36.  
    37.         var colors = new Color[PixelsSize*PixelsSize];
    38.         // set brush to black
    39.         for (var i = 0; i < PixelsSize*PixelsSize; i++)
    40.         {
    41.             colors[i] = Color.black;
    42.         }
    43.  
    44.  
    45.         tex.SetPixels((int)pixelUV.x, (int)pixelUV.y, PixelsSize, PixelsSize, colors);
    46.  
    47.  
    48.         //tex.SetPixel((int)pixelUV.x, (int)pixelUV.y, Color.black);
    49.         tex.Apply();
    50.     }
    51. }
     
    yeagob likes this.
  12. yeagob

    yeagob

    Joined:
    Jan 27, 2014
    Posts:
    18
    It give me an error:
    Unsupported GraphicsFormat(97) for SetPixel operations.