Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constrain mouse daw to 45degrees Processing

Tags:

processing

How does one constrain interactively drawing a line to 45degrees? Imagine there in an underlining grid that's at 45 degees which the mouse draw is magnetized too. Perhaps on mousedown decides which your starting position is and after that your Mouse.X and Mouse.Y position only update in 45 degrees from that starting click?

float dif = 10;
float easing = 0.05;
boolean current = false;
boolean started = false;
float rainbow, x, y;
float colbase;
PVector pos, prev_pos, update_pos;
float step = 25;

void setup(){
  size(400, 400);
  background(100);
  colorMode(HSB);
}
 
void draw(){

  if (rainbow >= 255)  rainbow=0;  else  rainbow+=5;
  if (frameCount % dif == 0) {
   colbase = rainbow; 
  }
  if(mousePressed){

    update_pos();
      
        if(current){
          started =  true;//start drawing
          pos =      update_pos;
        }else{
          prev_pos = update_pos;
        }
       current = !current;
      
  }else{
      update_pos();
      started   = false;
      pos       = update_pos;
      prev_pos  = update_pos;
  }
 
  if(started){
      //style for lines
      strokeWeight(step);
      stroke(colbase,255,255);
      noFill();
      line(prev_pos.x, prev_pos.y, pos.x, pos.y);
   }
  
 }

PVector update_pos(){
  x = lerp(x, mouseX, easing);
  y = lerp(y, mouseY, easing);
  update_pos = new PVector(x, y);
  return update_pos;
  }
like image 340
sixfeet Avatar asked May 25 '26 01:05

sixfeet


1 Answers

I would like to say that I like this. Also, that this question was ultimately way harder to answer than I though it would be.

Here's the result:

Drawing on an X pattern

First, I took the liberty to reorganize your example code. I don't know how experienced you are, and maybe some of the things I changed were on purpose, but it seemed to me that your example had a lot of weird code bits dangling. Here's the example code with my changes (check the next code block for the answer, this one is more like a code review to help you in general):

float dif = 10;
float easing = 0.05;
boolean current, started; // automatically initializing as false unless stated otherwise
float rainbow;
float colbase;
PVector pos, prev_pos;
float step = 25;

void setup() {
  size(400, 400);
  background(100);
  colorMode(HSB); // clever!

  pos = prev_pos = new PVector(); // instanciating some non-null PVectors
}

void draw() {
  pos = prev_pos = update_pos(); // cascade attribution: it starts by the last one and goes back toward 'pos'

  if (mousePressed) {
    if (!started) {
      // initializing variables needed for drawing
      started = true;
      pos = prev_pos = new PVector(mouseX, mouseY);
    }
  } else {
    started = false;
  }

  if (started) {
    updateColor();
    strokeWeight(step);
    stroke(colbase, 255, 255);
    noFill();
    line(prev_pos.x, prev_pos.y, pos.x, pos.y);
  }
}

void updateColor() {
  if (rainbow >= 255) {
    rainbow=0;
  } else {
    rainbow+=5;
  }

  if (frameCount % dif == 0) {
    colbase = rainbow;
  }
}

PVector update_pos() {
  float x = lerp(pos.x, mouseX, easing);
  float y = lerp(pos.y, mouseY, easing);

  return new PVector(x, y);
}

Notice how I changed a couple variables names. As a general rule, you should always name your variables as if the guy maintaining your code was an angry biker who knows where you live.

Now to solve your actual question:

boolean isDrawing;
float rainbow, colbase, dif, easing, step, centerZone;
PVector pos, prev_pos, origin, quadrant;

void setup() {
  size(400, 400);
  background(100);
  colorMode(HSB); // clever!

  centerZone = 5; // determine how close to the original click you must be to change quadrant
  dif = 10;
  easing = 0.05;
  step = 25;

  origin = pos = prev_pos = new PVector();
}

void draw() {
  updatePosition();

  if (mousePressed) {
    if (!isDrawing) {
      // setting variables needed for drawing
      isDrawing = true;
      origin = pos = prev_pos = new PVector(mouseX, mouseY); // drawing should always start where the mouse currently is
    }
  } else {
    isDrawing = false;
  }

  if (isDrawing) {
    updateColor();
    strokeWeight(step);
    stroke(colbase, 255, 255);
    noFill();
    line(prev_pos.x, prev_pos.y, pos.x, pos.y);
  }
}

void updateColor() {
  if (rainbow >= 255) {
    rainbow=0;
  } else {
    rainbow+=5;
  }

  if (frameCount % dif == 0) {
    colbase = rainbow;
  }
}

void updatePosition() {
  float relativeX = pos.x - origin.x;
  float relativeY = pos.y - origin.y;
  float diffX = mouseX - origin.x;
  float diffY = mouseY - origin.y;
  float distance = abs(diffX) > abs(diffY) ? abs(diffX) : abs(diffY); // this is just inline if, the syntax being " condition ? return if true : return if false; "
  prev_pos = pos;
  
  // validating if the mouse is in the same quadrant as the pencil
  PVector mouseQuadrant = new PVector(diffX > 0 ? 1 : -1, diffY > 0 ? 1 : -1);
  
  // we can only change quadrant when near the center
  float distanceFromTheCenter = abs(relativeX) + abs(relativeY);
  if (quadrant == null || distanceFromTheCenter < centerZone) {
    quadrant = new PVector(diffX > 0 ? 1 : -1, diffY > 0 ? 1 : -1);
  }

  // if the mouse left it's quadrant, then draw toward the center until close enough to change direction
  // ^ is the XOR operator, which returns true only when one of the sides is different than the other (one true, one false)
  // if the quadrant info is positive and the diff coordinate is negative (or the other way around) we know the mouse has changed quadrant
  if (distanceFromTheCenter > centerZone && (relativeX > 0 ^ mouseQuadrant.x > 0 || relativeY > 0 ^ mouseQuadrant.y > 0)) {
    // going toward origin
    pos = new PVector(lerp(prev_pos.x, origin.x, easing), lerp(prev_pos.y, origin.y, easing));
  } else {
    // drawing normally
    pos = new PVector(lerp(prev_pos.x, origin.x + (distance * quadrant.x), easing), lerp(prev_pos.y, origin.y + (distance * quadrant.y), easing));
  }
}

I'll answer your questions on this code if you have any. Have fun!


EDIT:

This part could use more explainations, so let's elaborate a little bit:

  if (distanceFromTheCenter > centerZone && (relativeX > 0 ^ mouseQuadrant.x > 0 || relativeY > 0 ^ mouseQuadrant.y > 0)) {
    // going toward origin
  } else {
    // drawing normally
  }

The point of this check is to know if the mouse is still in the same quadrant than the "pencil", by which I mean the point where we're drawing.

But what are the "quadrants"? Remember, the user can only draw in 45 degrees lines. Theses lines are defines in relation to the point where the user clicked to draw, which I call "origin":

Origin

Which means that the screen is always divided into 4 different "quadrants". Why? Because there is 4 different directions where we can draw. But we don't want the user to have to stick to these exact pixels to be able to draw, do we? We could, but that's not how the algo is working: it poses a pencil on the page and then follows the mouse. So here if the mouse goes up-left from origin, the pencil will draw on the up-left branch of the 45 degrees X lines. Each of these imaginary lines has it's own "part of the screen" where they control where the pencil has the right to draw, which I call "quadrants":

4 quadrants

Now with this algorithm we can force the pencil over the 45 degrees lines, but what happens if the mouse goes from one quadrant to another one? I figured out that the pencil should follow, but without breaking the "only drawing in 45 degrees lines" rule, so it has to go through the center before changing quadrant:

Drawing on the lines

There is no big secret here. It's kind of easy to figure the business rules we want to apply:

  1. While the mouse and the pencil are in the same quadrant, follow the mouse's position to draw (but only on the line).

  2. If the mouse is in a different quadrant from the pencil, draw toward the center, then toward the mouse normally.

Which is exactly what we're doing here:

if (mouse and pencil are in different quadrants) {
  // draw toward origin
} else {
  // draw toward the mouse
}

Then... if it's so simple, why this convoluted code?

(distanceFromTheCenter > centerZone && (relativeX > 0 ^ mouseQuadrant.x > 0 || relativeY > 0 ^ mouseQuadrant.y > 0))

Well, really, it could be written in a waaay easier to read way, but it would be longer. Let's decompose it and see why it's written this way:

My operational logic here go as follow:

  1. As you probably know, point (0,0) is on the upper-left side of the sketch. Most drawing in computer science calculate coordinates this way.

  2. Quadrants exist in relation to the origin point (where the user clicked to start drawing).

  3. Relative coordinates in each quadrant will be either positive or negative. X will be positive if they are to the right of origin. Y will be positive if they are lower in the screen than origin:

    Quadrant coordinates

  4. If you follow my logic, when relative coordinate of the mouse and pencil aren't both positive or both negative in the same way, it would mean that they are not located in the same quadrant.

So:

distanceFromTheCenter > centerZone => when we're close enough to the center, we can let the pencil switch direction. No need to calculate the rest if the pencil isn't near the center.

relativeX > 0 ^ mouseQuadrant.x > 0 => relativeX is really just pos.x - origin.x. It's where the pencil is in relation to origin. mouseQuadrant "knows" where the mouse is in relation to the origin (it's just diffX and diffY as interpreted for later use, in fact we totally could have used diffX and diffY instead as we just compare if they are positive or negative numbers)

Why the XOR operator if it's so simple? Because of this:

relativeX > 0 ^ mouseQuadrant.x > 0
// is the same thing than this pseudocode:
if (relativeX sign is different than mousequadrant.x's sign)
// and the same thing than this more elaborated code:
!(relativeX > 0 && mouseQuadrant.x > 0) || !(relativeX < 0 && mouseQuadrant.x < 0)
// or, in a better writing:
(relativeX > 0 && mouseQuadrant.x < 0) || (relativeX < 0 && mouseQuadrant.x > 0)

...which is also convoluted and also ugly. So, really, (relativeX > 0 ^ mouseQuadrant.x > 0 || relativeY > 0 ^ mouseQuadrant.y > 0) is just a short hand for:

(!(relativeX > 0 && mouseQuadrant.x > 0) || !(relativeX < 0 && mouseQuadrant.x < 0) || !(relativeY > 0 && mouseQuadrant.y > 0) || !(relativeY < 0 && mouseQuadrant.y < 0))

I hope this just made sense! Have fun!

like image 72
laancelot Avatar answered Jun 04 '26 18:06

laancelot



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!