Remove Condensation from a Window (C#)

Hi there,

Lately i’ve been working on a mini-game in which you remove condensation from a window. The idea is to allow the player to draw/write on a window with his finger/cursor (brush).

As i see it, i need to be able to select Pixels in a given zone and fade alpha to 0.

I spent some time looking for a way to do it and quickly found several options :

1 - Shaders
—> Nop, beyond my coding competence

2 - Get/SetPixels with render textures
I found a nice asset on the asset store, but soon i realized it didn’t fit my needs : this asset as most of “painting” assets just add sprites based on UV coordinates.

What i’m trying to do is different : i need to fade alpha from a given range of texture coordinates.

Here is how i think i should do it :

STEP 1:

So first : mouse raycast hit → convert to UV coordinates
This way i know where the player is pointing his finger.

STEP 2 : Select UV coordinates based on a brush texture

My Brush :


This is where i struggle… How can i tell the program :

" Here is my texture, place it on the UV coordinate (inherited from STEP 1) of the canvas, then getpixels (uv coordinates) that are inside the black dot ?"

My guess :

1 - Apply brush on canvas texture
2 - Check all pixels color, get black ones and add to array…
3 - Change alpha of the model uv based on this array…

Is this a way to do it ? It feels… Is it going to be fast enough ?

STEP 3 : Use SetPixel to set alpha to 0.

I should be able to code step 1 and 3, but how can i achieve step 2 ?

Thank you,

If you always have a circular “brush” I would simply calculate the affected pixels.
Transform the UV-Coordinates of your “finger position” into pixel coordinates by multiplying with texture size.
Then make 2 loops to loop over x and y, starting from finger postition minus the radius up to finger position + radius. effectively you are iterating over the pixels of the bounding rect of your circle. within these loops calculate the distance from xy to your fingerposition. if its smaller than your radius set your alpha value.

remarks:
you can also use an animation curve to “ease” the alpha with the radius so you dont have a sharp edge but a soft one.
imo you don’t need rendertextures. a fullscreenquad should do. dont use unity plain as afaik it consists of many more vertices. build one procedurally.
don’t retrive the data of your texture and write it back. have a persistent 2d array of color32 values of texture size which you modify directly and then only modify and apply this each frame a click/touch is registered.
dont forget to clamp your xy coordinates to texture size. otherwise xy gets out of bound when you draw at the border of your texture. alternatively have a large enough invisible border meaning the camera shows only the region around the center and not the full mesh.
If you want to use a shaped brush later on you can directly provide a similar array fe with bools and then simply look up the pixels there and apply them to your large “canvas” array.
Dont ever use SetPixel (without postfix s), always modify the data and apply it once in SetPixels.

This seems the most straightfoward, short and basic (meaning does not require special knowledge) approach to me. Hope this helps.

Kudos for asking a clear and structured question, providing all required information and showing own effort. This is too rare these days in this forum.

Good luck.

Hey,

Thanks a lot for your answer ! It’ll definitely help a lot !

In the meantime i get it working with SetPixel and this math formula :

    private bool isInCircle(Vector2 coordinate){

        float a = Mathf.Pow((coordinate.x - circleCenter.x),2) + Mathf.Pow((coordinate.y - circleCenter.y),2) ;
        float b = Mathf.Pow( circleRadius * 2,2)/4;
 
        if(a < b){
            Debug.Log ("Is IN");
            return true;
        } else {
            Debug.Log ("Is OUT");
            return false;
        }

    }

But as you pointed it out SetPixel is SUPER slow… I’ll check how SetPixels works this afternoon !

Thanks again :wink:

If performance concerns you some hints:

  • only “draw” if the finger position actually changes regarding to last frame (or if there is a press at all). You could also consider not redrawing every frame but only 20 times per second for example. Just store Time.RealTimeSinceStartup each frame you calculate and in update check if this is larger than its current calue + 1/20;
  • as mentioned use a persistent 2d color32 array to modify and use setpixels with it.
  • your isincirlce method is a method call and thus has a higher cost compared to “inline” code. do the check in the loop itself.
  • as mentioned don’t iterate over the whole 2d array/texture but only the coordinate range which can be affected. so when your finger is at 200, 300 and your radius is 32 loop from (168, 268) to (232, 332).
  • avoid the squareroot (costly) by squaring your radius and compare to that instead.
  • since you deal with pixels you should also directly calculate in integer space and not in float space. you need ints for indexing anyway so do the transition early since ints should be faster to calculate with.
float radiussq = circleradius * circleradius;
int ycoord = (int)coordinate.y;   // cache this
int xcoord = (int)coordinate.x;   // cache this
for(int y = ycoord - circleradius; y < ycoord + circleradius; ++y)
{
   int ydist = ycoord - y;
   for(int x = xcoord - circleradius; xcoord + circleradius; ++x)
   {
       int xdist = xcoord - x;
       int distsq = xdist * xdist + ydist * ydist;
       if(distsq < radiussq)
       {// the pixel is within the radius, set your color/alpha here
       }
   }
}

Note: code is untested but should give you a start. also check the < <= etc as i didn’t give caution to proper indices here.

Hey !

It’s kind of working with alpha now :slight_smile: #HAPPY

Thanks for the performance tips, i’ve edited my code using some of them (i’ll add the cursor position check).
I’ve got 2 questions nonetheless :

- as mentioned use a persistent 2d color32 array to modify and use setpixels with it.
I’m using SetPixels now (see my code below). But why should i use a 2D array ? What should a store in this 2D Array ? I’m using a simple Vector2 Array to store coordinates and it seems OK.

- as mentioned don’t iterate over the whole 2d array/texture but only the coordinate range which can be affected. so when your finger is at 200, 300 and your radius is 32 loop from (168, 268) to (232, 332).
Sounds smart ! Let me guess, should i use blockWidth*blockHeight ? :slight_smile:

Here is my code for science purposes :

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

public class GetCirclePixels : MonoBehaviour {

    public int circleRadius;
    private Renderer rend;
    private Texture2D tex;
    private Color[] pixelUVArray;
    public void Start(){
        rend = transform.GetComponent<Renderer> ();
        tex = rend.material.mainTexture as Texture2D;
        pixelUVArray = new Color[tex.width * tex.height];
        pixelUVArray = tex.GetPixels();
    }

    public void Update(){
        if(Input.GetMouseButton(0)){
            RaycastHit hit;
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if(Physics.Raycast(ray,out hit)){
                Vector2 centerCoor = hit.textureCoord;
                centerCoor.x *= tex.width;
                centerCoor.x = Mathf.RoundToInt (centerCoor.x);
                centerCoor.y *= tex.height;
                centerCoor.y = Mathf.RoundToInt (centerCoor.y);
                DrawCircle (centerCoor);
            }
        }
    }

    private void DrawCircle(Vector2 centerCoor){
        for(int x = Mathf.RoundToInt(centerCoor.x) - circleRadius; x<Mathf.RoundToInt(centerCoor.x) + circleRadius;x += 1 ){
            for (int y = Mathf.RoundToInt(centerCoor.y) - circleRadius; y < Mathf.RoundToInt(centerCoor.y) + circleRadius; y += 1) {
                Vector2 currentPixel = new Vector2 (x,y);
                if (Vector2.Distance(currentPixel,centerCoor) < circleRadius) {
                    int myCoor = tex.height * y + x  ;
                    pixelUVArray [Mathf.RoundToInt(myCoor)].a = 0f;
                } else
                    Debug.Log ("Not in circle");
            }
        }
        tex.SetPixels(pixelUVArray);
        tex.Apply ();

    }
     
}

One speed improvement jumps out at me:

                if (Vector2.Distance(currentPixel,centerCoor) < circleRadius) {
replace with:
                if ((currentPixel - centerCoor).sqrMagnitude < circleRadius * circleRadius) {

In distance calculations, the last step of the formula is to take the square root. Square root is slooooow. .sqrMagnitude skips that step, and you can still get 100% accurate results if you are just comparing distances for > or <.

(And of course, remove that Debug.Log on line 41 - Logs are pretty slow)

1 Like

Technically the array used by SetPixels is a “flattened” 2d array. So it only has one dimension in layout but its treated as a 2d array (btw all arrays are 1d in memory layout since memory is 1dimesional). so you have to calculate the index by y * width + x;
Note that accoding to Documentation its from bottom to top. so you need to iterate from y = height; y > -1; --y instead of from zero upwards as i posted above. sorry for that.

Also the documentation says that setpixels32 is faster than setpixels so consider to use that instead together with color32.

        pixelUVArray = new Color[tex.width * tex.height];
        pixelUVArray = tex.GetPixels();

this is “useless” since the second one “replaces” the first array. if the texture already contains some data/image just use the second.

also you are constantly converting float to int and then back int to float (Vector2). conversions which could be avoided. Also Vector2Distance is a method call and thus slower than a direct calculation. whats wrong with my code? :wink:

If I were you I would:
use Color32[ ] and SetPixels32 instead of Color[ ] and SetPixels.
switch the x + y loops since the array layout is “linewise” and thus iterating over a line should be faster than iterating over the columns.
do the calculation directly and avoid the squareroot like StarManta and me suggested.

Hey guys !

Thanks a lot, i got something i’m happy with (for now) ! :wink:

@exiguous : sorry ! Nothing was wrong ! I didn’t see your code, i was in a hurry at work ! It solved almost everything !

For now i’m happy with the result. I’m moving to other programming/GD stuff that need advancement.

BUT i know i’ll need to improve it because when i move the cursor too fast there is a distance between two circles. This is not an optimization issue. This is because if draw my pattern based on the position of the cursor during a given frame. I thought about it and i’ll probably try to draw an additional pattern in between two circles if they are too far from each other. If distance is x2, then 2 additional patterns etc.

I’ll try this and come back to post the result !