Manually drawing an oval, in response to a straight mouse drag

I wrote a sketch for penning letters and digits on a touchscreen, and then manipulating them. I use it as a teaching tool for young kids. The penning part is trivial, just drawing lines in response to mouse events.

I need to add a feature where a single, roughly horizontal swipe, from right to left appears to pen a complete ‘0’ (zero). So, as I swipe, it would start at the 90-degree point, split to draw up toward 0 and down toward 180 (simultaneously) and then converge back down toward a single point, completing at 270. Ideally, it would be nice to add a little noise() so the end results appear finger-penned and slightly unique, but still smooth. (some samples below)

I need to draw the 0’s in different sizes, relative to other objects on the screen. The desired width and height are calculated in mouseDown() at the beginning of the swipe.

I have it running, gradually copying from saved image files of penned 0’s, but they blur too much when resize()'d, even if it’s just a small amount around the edges. Hence, Plan B, doing the drawing in real time.

Any help greatly appreciated. I’m super solid in code/processing, but always struggle with radians/trig/spacial stuff.

zeros

1 Like

drag mouse slowly from right to left

The center of the “0”:

  • x : from mouse position when clicking
  • y always the same: height/2

Chrisir



// gather all points 
ArrayList<PVector> list0=new ArrayList();

final float RADIUS_X = 150; 
final float RADIUS_Y = 300; 

// this happens when you click and hold mouse 
boolean hold=false;
float mouseStartX, mouseStartY; 

void setup() {
  size(800, 800);
}

void draw() {
  // show entire list
  noStroke(); 
  for (PVector pv : list0) {
    ellipse(pv.x, pv.y, 
      6, 6);
  }//for

  // During mouse drag: 
  if (hold) {
    //list0.add(new PVector(mouseX, mouseY));
    storeLeftAndRightArmInList();
  }//if
}//draw()

// -----------------------------------------------------------------------
// Inputs

void mousePressed() {
  hold=true;
  mouseStartX=mouseX;
  mouseStartY=mouseY;
}

void mouseReleased () {
  hold=false;
}

// -----------------------------------------------------------------------
// Tools  

void storeLeftAndRightArmInList() {


  // right arm ---
  float dist1=map(dist(mouseX, 0, mouseStartX, 0), 
    0, width/2, 
    0, PI);

  float dist2 = dist1-PI/2;
  dist2=constrain(dist2, -PI/2, PI-PI/2);
  float newX=cos(dist2) * RADIUS_X + mouseStartX;
  float newY=sin(dist2) * RADIUS_Y + height/2;

  list0.add(new PVector(newX, newY));

  // left arm ---
  dist1=map(dist(mouseX, 0, mouseStartX, 0), 
    0, width/2, 
    TWO_PI-PI/2, PI-PI/2);
  newX=cos(dist1) * RADIUS_X + mouseStartX;
  newY=sin(dist1) * RADIUS_Y + height/2;

  list0.add(new PVector(newX, newY));
}
//
1 Like

Chrisir! That is spectacular! I can’t thank you enough, and I’ve got to find a way to send you a small Starbuck’s card (or similar)!

I need to do some tweaks, and will give them a shot tomorrow. Mainly, I want to draw the oval from right to left, instead of top to bottom. I was picturing 90-degrees at 3:00, and 270 at 9:00, my mistake in the description. So the oval would begin and end matching the x-coordinate with the mouse. So basically changing left/right-Arms to top/bottom-Arms.

What a huge help though! Easiest way to drop you a cup of coffee?

Mike

2 Likes

No big deal.

I can rotate this tomorrow

0 degrees in processing is to the right (east) by the way

A couple of tweaks:
using line() instead of ellipse() (requires separate lists)
placing the right edge at mouseDown
finishing the left edge at (mouseDown - diameter)
clearing and drawing more than once

Still drawing top to bottom, can give that a try tomorrow afternoon.

Thanks again Chrisir! Would love to get you a coffee!


// gather all points 
ArrayList<PVector> leftList=new ArrayList();
ArrayList<PVector> rightList=new ArrayList();

final float RADIUS_X = 150; 
final float RADIUS_Y = 300; 

// this happens when you click and hold mouse 
boolean hold=false;
float mouseStartX, mouseStopX, mouseStartY;

void setup() {
  size(800, 800);
  smooth();
}

void draw() {
  // show lists
  for (int i=1; i<leftList.size(); i++) { // start at 1
    line(leftList.get(i-1).x, leftList.get(i-1).y, leftList.get(i).x, leftList.get(i).y);
  }//for

  for (int i=1; i<rightList.size(); i++) { // start at 1
    line(rightList.get(i-1).x, rightList.get(i-1).y, rightList.get(i).x, rightList.get(i).y);
  }//for

  // During mouse drag: 
  if (hold) {
    storeLeftAndRightArmInList();
  }//if
}//draw()

// -----------------------------------------------------------------------
// Inputs

void mousePressed() {
  // clear and start over
  background(#CCCCCC);
  leftList.clear();
  rightList.clear();

  hold=true;
  mouseStartX=mouseX;
  mouseStopX=mouseX-RADIUS_X*2;
  mouseStartY=mouseY-RADIUS_Y;
}

void mouseReleased () {
  hold=false;
}

// -----------------------------------------------------------------------
// Tools  

void storeLeftAndRightArmInList() {
  // right arm ---
  float dist1=map(mouseX, mouseStartX, mouseStopX, 0, PI);

  float dist2 = dist1-PI/2;
  dist2=constrain(dist2, -PI/2, PI-PI/2);
  float newX=cos(dist2) * RADIUS_X + mouseStartX;
  float newY=sin(dist2) * RADIUS_Y + height/2;

  leftList.add(new PVector(newX-RADIUS_X, newY+mouseStartY));

  // left arm ---
  dist1=map(mouseX, mouseStartX, mouseStopX, TWO_PI-PI/2, PI-PI/2);
  newX=cos(dist1) * RADIUS_X + mouseStartX;
  newY=sin(dist1) * RADIUS_Y + height/2;

  rightList.add(new PVector(newX-RADIUS_X, newY+mouseStartY));
}
//
2 Likes

instead of using line() you could also store the old
angle and make a for-loop from old to new angle, adding all points in between to the list
(I haven’t done this).

Chrisir :wink:



// gather all points 
ArrayList<PVector> listLowerArm=new ArrayList();
ArrayList<PVector> listUpperArm=new ArrayList();

final float RADIUS_X = 300; 
final float RADIUS_Y = 150; 

// this happens when you click and hold mouse 
boolean hold=false;
float mouseStartX, mouseStopX, mouseStartY;

void setup() {
  size(800, 800);
  smooth();
}

void draw() {

  background(#CCCCCC);

  // show lists
  for (int i=1; i<listLowerArm.size(); i++) { // start at 1
    line(listLowerArm.get(i-1).x, listLowerArm.get(i-1).y, 
      listLowerArm.get(i).x, listLowerArm.get(i).y);
  }//for

  for (int i=1; i<listUpperArm.size(); i++) { // start at 1
    line(listUpperArm.get(i-1).x, listUpperArm.get(i-1).y, 
      listUpperArm.get(i).x, listUpperArm.get(i).y);
  }//for

  // During mouse drag: 
  if (hold) {
    storeArmsInList();
  }//if
}//draw()

// -----------------------------------------------------------------------
// Inputs

void mousePressed() {
  // clear and start over
  listLowerArm.clear();
  listUpperArm.clear();

  hold=true;
  mouseStartX=mouseX;
  mouseStopX=mouseX-RADIUS_X*2;
  mouseStartY=mouseY-RADIUS_Y;
}

void mouseReleased () {
  hold=false;
}

// -----------------------------------------------------------------------
// Tools  

void storeArmsInList() {
  // lower arm ---
  float angleLowerArm=map(mouseX, mouseStartX, mouseStopX, 0, PI);

  // float dist2 = dist1-PI/2;
  angleLowerArm=constrain(angleLowerArm, 0, PI);


  float newX=cos(angleLowerArm) * RADIUS_X + mouseStartX;
  float newY=sin(angleLowerArm) * RADIUS_Y + height/2;

  listLowerArm.add(new PVector(newX-RADIUS_X, newY+mouseStartY));

  // upper arm ---
  float angleUpperArm=map(mouseX, mouseStartX, mouseStopX, TWO_PI, PI);
  angleUpperArm=constrain(angleUpperArm, PI, TWO_PI);
  newX=cos(angleUpperArm) * RADIUS_X + mouseStartX;
  newY=sin(angleUpperArm) * RADIUS_Y + height/2;

  listUpperArm.add(new PVector(newX-RADIUS_X, newY+mouseStartY));
}
//

1 Like

here is a totally new version just allowing storage of any ellipse

// gather all data 
ArrayList<EllipseData> list=new ArrayList();

// this happens when you click and hold mouse 
boolean hold=false;
float mouseStartX, mouseStartY;

void setup() {
  size(800, 800);
}

void draw() {
  background(#CCCCCC);

  // show lists
  for (EllipseData ed : list) {
    ed.display();
  }//for

  // During mouse drag: 
  if (hold) {
    fill(255); 
    ellipseMode(CENTER);
    ellipse(mouseStartX, mouseStartY, 
      (mouseX-mouseStartX)*2, (mouseY-mouseStartY)*2);
  }//if
}//draw()

// -----------------------------------------------------------------------
// Inputs

void mousePressed() {
  hold=true;
  mouseStartX=mouseX;
  // mouseStopX=mouseX-RADIUS_X*2;
  mouseStartY=mouseY;
}

void mouseReleased () {
  hold=false;
  EllipseData ed=new EllipseData ( new PVector(mouseStartX, mouseStartY), 
    new PVector( (mouseX-mouseStartX)*2, (mouseY-mouseStartY)*2) );
  list.add(ed);
}

// -----------------------------------------------------------------------
// Tools  

class EllipseData {

  PVector posEll; 
  PVector sizeEll; 
  color colEll = color (random(255), random(255), random(255));

  EllipseData(PVector p_, 
    PVector s_) {
    posEll  = p_.copy(); 
    sizeEll = s_.copy();
  }

  void display() {
    ellipseMode(CENTER);
    fill(colEll);
    ellipse(posEll.x, posEll.y, 
      sizeEll.x, sizeEll.y ) ;
  }
} //class
//
1 Like

Thanks a million Chrisir! Just what I was looking for!

Here’s the version I reworked to fit into my original app. Some funky requirements (like only drawing each point once and drawing from mouseDragged) but got everything I needed thanks to you.


PVector mouseStart, topXY, bottomXY;
float mouseStopX;

float zWidth = 150; // width of zero
float zHeight = 250; // height of zero

public void setup() {
  size(800, 600);
  smooth();
  strokeWeight(3);
}

public void draw() {
  // drawn inside extendZeroSwipe (triggered from mouseDragged)
}

public void mouseReleased() {
  mouseStart = null; // flag to start over
}

public void mouseDragged() {
  if (mouseStart == null)
    mouseStart = startZeroSwipe();
  extendZeroSwipe(max(mouseX, mouseStopX));
}

public void extendZeroSwipe(float x) {
  PVector nextTop = nextXY(x, 2 * PI, PI);
  PVector nextBottom = nextXY(x, 0, PI);
  line(topXY.x, topXY.y, nextTop.x, nextTop.y);
  line(bottomXY.x, bottomXY.y, nextBottom.x, nextBottom.y);
  topXY = nextTop;
  bottomXY = nextBottom;
}

public PVector nextXY(float mouseX, float startRadians, float stopRadians) {
  float radians = map(mouseX, mouseStart.x, mouseStopX, startRadians, stopRadians);
  float x = cos(radians) * zWidth + mouseStart.x;
  float y = sin(radians) * zHeight + mouseStart.y;
  return new PVector(x - zWidth, y);
}

public PVector startZeroSwipe() { // start/restart oval
  background(204);
  mouseStart = new PVector(mouseX, mouseY);
  mouseStopX = mouseX - zWidth * 2;
  topXY = nextXY(mouseStart.x, 2 * PI, PI);
  bottomXY = nextXY(mouseStart.x, 0, PI);
  return topXY;
}
2 Likes