Drawing with mouse on GUI or world texture

Hello,

I would like to be able to fluidly draw on top of any unity scene.
I have tried to do this on the Gui as well as on a texture in a world object.

The code below works, but the mouse drawing is slow and jerky. The problem is that I don’t get real time pos from Input.mousePosition. I tried to use Texture2D.SetPixels and Texture2D.Apply as little as possible.

My questions:

  1. How can I draw fluidly on a texture ? GUI or world object. I’m new to Unity so I am not sure the approach I chose is the correct one. Feel free to suggest different methods. I know there are other ways to get mouse input, but Input.mousePosition seemed the most precise.

  2. Is there a way to freeze the Unity3D engine and give the GUI priority ? I don’t mind freezing the scene in some cases, while in others I would like the action to continue in the background.

  3. I also tried without the GUI, putting a semi transparent world object in front of the camera and drawing on its texture, thinking that function Update() might update faster than function OnGUI() but Input.mousePosition was just as slow.

  4. If I reduce the game world window, mouse tracking is more fluid. But I need this to run on a high resolution.

  5. I don’t necessarily have to draw 1 pixel dots, I just used that as the first attempt. Eventually I would like to use brushes of various sizes. I saw [here][1] that textures can be linked to brushes in the inspector.

  6. Also, eventually I would like to draw not with the mouse but with gestures on an iPad. Will it be easy to replace Input.mousePosition with gesture inputs ? For now I don’t have Unity Pro or the iOS addon, but plan to buy them later on.

  7. Is it possible to make a game like crayon physics with Unity ? or is it optimized for 3D rendering ? Just wondering if this is the right tool for what I need to do.

Notes:

I’m not making a game, but just used crayon physics as a reference of something that has fluid drawing capabilities.

I’m new to all this so please bare with me. Also, thought this might be useful for other people, so I will edit to add references to other answers.

System info: win7 64bit, decent graphic card.

Here is my code

#pragma strict

private var canvasBackground: Texture2D;
private var locked: boolean = true;

// initialize the canvas
canvasBackground = new Texture2D(Screen.width, Screen.height);

// get the size of the screen
private	var pixels = Screen.width * Screen.height;

// create a color array 
private	var colors = new Color[pixels];

private	var pencolor = Color.black;

// buffer is used to avoid setting pixels on each onGUI call
private var buffer = 0;

// last detected mouse position
private var lastpos:Vector3;

private var reset:boolean = true;

// fill color array with a BG color -- optional -- commented out to leave it transparent
/* for (var i=0;i<pixels;i++)
{
	colors *= Color.black;*

} */

function OnGUI() {

  • // mouse 1 shows the ‘canvas’*

  • if (locked && (Input.GetMouseButton(1)) ) {*

  •  locked = false;*
    
  • }*

  • if (locked)*

  •  return;*
    
  • // mouse 2 hides the ‘canvas’*

  • if (!locked && (Input.GetMouseButton(2)) ) {*

  •  locked = true;*
    
  •  return;*
    
  • }*

  • // mouse 0 ‘paints’*

  • if (Input.GetMouseButton(0)){*

  •  var currpos:Vector3 = Input.mousePosition;*
    
  •  if (reset){*
    
  •  	reset = false;*
    
  •  	lastpos = currpos;*
    
  •  }*
    
  •  line(lastpos.x, lastpos.y, currpos.x, currpos.y);*
    
  •  lastpos = currpos;*
    
  •  buffer++;*
    
  •  if (buffer > 9){*
    
  •  	buffer = 0;*
    
  •  	canvasBackground.SetPixels(colors,0);*
    
  •  	canvasBackground.Apply(false);*
    
  •  }*
    
  • } else {*

  •  reset = true;*
    
  • }*

  • // GUI.Box(Rect(xpos, ypos, 120, 120), “”);*

  • GUI.Box(Rect(0, 0, canvasBackground.width, canvasBackground.height), “”);*

  • // Override the default texture with the custom texture.*

  • GUI.skin.box.normal.background = canvasBackground;*

}

// Bresenham’s line algorithm
function line(x0:int, y0:int, x1:int, y1:int){
var dx = Mathf.Abs(x1-x0);
var dy = Mathf.Abs(y1-y0);
var sx = (x0 < x1) ? 1 : -1;
var sy = (y0 < y1) ? 1 : -1;
var err = dx-dy;

while(true){

// Draw the ‘pixels’ in the color array
_ colors[x0 + Screen.width * y0] = pencolor;_

if ((x0==x1) && (y0==y1)) break;
var e2 = 2*err;
if (e2>-dy){
err -= dy;
x0 += sx;
}
if (e2 < dx){
err += dx;
y0 += sy;
}
}
}
[1]: http://unity3d.com/support/documentation/ScriptReference/GUI.DrawTexture.html

This is not really an answer, just an extended comment.

I rewrote the code to track the mouse in update() instead of onGUI() but that hasn’t made any difference.

I think that I should not use SetPixel or SetPixels.
Any other way to do this ?

I like Berenger’s trick, it’s very fluid, but I need it to look more like real pen and brush painting.
Also, Berenger’s code is quite complex for me to replicate quickly. I would have to study C# and Unity more :slight_smile:

Here is my code anyway, separated in two files now.

Driver.js

#pragma strict

private var cc : canvas;

private var locked : boolean = true;

// get the size of the screen
private var pixels = Screen.width * Screen.height;

// create a color array
private var colors = new Color[pixels];

private var pencolor = Color.black;

// counter is used to avoid setting pixels on each onGUI call
private var counter = 101;

// last detected mouse position
private var lastpos : Vector3;

private var reset : boolean = true;

function Start() {
	cc = GetComponent(canvas);

	// fill color array with a BG color
	for (var i = 0; i < pixels; i++) {
		colors *= Color(1.0, 1.0, 1.0, 0.45); // Color.white; // canvasBackground.GetPixel(0, 0);*
  • }*

}

function Update() {

  • // ctrl toggles locking of the ‘canvas’*

  • if (Input.GetKeyUp(KeyCode.LeftControl)) {*

  •  locked = !locked;*
    
  •  // print(locked);*
    
  • }*

  • if (locked) {*

  •  cc.draw = false;*
    
  •  return;*
    
  • }*

  • cc.draw = true;*

  • // mouse 0 ‘paints’*

  • if (Input.GetMouseButton(0)) {*

  •  var currpos : Vector3 = Input.mousePosition;*
    
  •  if (reset) {*
    
  •  	reset = false;*
    
  •  	lastpos = currpos;*
    
  •  }*
    
  •  line(lastpos.x, lastpos.y, currpos.x, currpos.y);*
    
  •  lastpos = currpos;*
    
  •  counter++;*
    
  •  if (counter > 3) {*
    
  •  	counter = 0;*
    
  •  	cc.setColors(colors);*
    
  •  }*
    
  • } else {*

  •  reset = true;*
    
  • }*
    }

// Bresenham’s line algorithm
function line(x0 : int, y0 : int, x1 : int, y1 : int) {

  • var dx = Mathf.Abs(x1 - x0);*

  • var dy = Mathf.Abs(y1 - y0);*

  • var sx = (x0 < x1) ? 1 : -1;*

  • var sy = (y0 < y1) ? 1 : -1;*

  • var err = dx - dy;*

  • while (true) {*

  •  // Do what you need to for this*
    
  •  // setPixel(x0,y0);*
    

_ colors[x0 + Screen.width * y0] = pencolor;_

  •  if ((x0 == x1) && (y0 == y1))*
    
  •  	break;*
    

_ var e2 = 2 * err;_

  •  if (e2 > -dy) {*
    
  •  	err -= dy;*
    
  •  	x0 += sx;*
    
  •  }*
    
  •  if (e2 < dx) {*
    
  •  	err += dx;*
    
  •  	y0 += sy;*
    
  •  }*
    
  • }*
    }
    and canvas.js
    #pragma strict

public var draw : boolean = false;
private var canvasBackground : Texture2D;

// initialize the canvas
canvasBackground = new Texture2D(Screen.width, Screen.height);

function setColors(newColors:Color[]) {

  • // print(“set new colors”);*

  • canvasBackground.SetPixels(newColors, 0); // rem 0*

  • canvasBackground.Apply(false);*
    }

function OnGUI() {

  • if (!draw)*

  •  return;*
    
  • // GUI.Box(Rect(xpos, ypos, 120, 120), “”);*

  • GUI.Box(Rect(0, 0, canvasBackground.width, canvasBackground.height), “”);*

  • // Override the default texture with the custom texture.*

  • GUI.skin.box.normal.background = canvasBackground;*

}

function Start() {}

function Update() {}