将鼠标手指约束到45度处理

问题描述

如何以交互方式将画线限制为45度? 想象一下在一个45格的下划线网格中,鼠标绘制也被磁化了。也许在mousedown上确定您的起始位置是多少,之后,Mouse.X和Mouse.Y位置与该起始单击之间的距离仅更新了45度?

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;
  }

解决方法

我想说我喜欢这个。而且,这个问题最终比我要难回答。

结果如下:

Drawing on an X pattern

首先,我自由地重组了您的示例代码。我不知道您的经验如何,也许我所做的某些更改是有目的的,但在我看来,您的示例中有很多奇怪的代码位悬而未决。这是我所做的更改的示例代码(请查看下一个代码块以获取答案,该代码更像是可以总体上帮助您的代码审查):

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);
}

注意我如何更改几个变量名称。通常,应该始终将变量命名为好像维护代码的人是一个生气的骑自行车的人,知道您的住所。

现在要解决您的实际问题:

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,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),origin.y + (distance * quadrant.y),easing));
  }
}

如果有任何疑问,我将回答您对此代码的问题。玩得开心!


编辑:

这部分可能需要更多的解释,所以让我们详细说明一下:

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

此检查的目的是要知道鼠标是否仍在与“铅笔”相同的象限中,这就是我们要绘制的点。

但是什么是“象限”?请记住,用户只能绘制45度线。这些线是相对于用户单击绘制的点定义的,我称之为“原点”:

Origin

这意味着屏幕始终分为4个不同的“象限”。为什么?因为我们可以绘制4个不同的方向。但是我们不希望用户必须坚持使用这些确切的像素才能进行绘制,对吗?我们可以,但是那不是算法的工作方式:它将铅笔放在页面上,然后跟随鼠标。因此,如果鼠标从原点​​向左上方移动,铅笔将在45度X线的左上方分支上绘制。这些假想线中的每条线都有其自己的“屏幕部分”,它们在其中控制铅笔有权绘画的位置,我称之为“象限”:

4 quadrants

现在,使用此算法,我们可以将铅笔强制移过45度线,但是如果鼠标从一个象限移动到另一象限会发生什么?我发现铅笔应该遵循,但不会违反“仅以45度线绘图”的规则,因此在更改象限之前,它必须经过中心:

Drawing on the lines

这里没有什么大秘密。很容易确定我们要应用的业务规则:

  1. 当鼠标和铅笔位于同一象限时,请遵循鼠标的位置进行绘制(但仅在直线上)。

  2. 如果鼠标与铅笔位于不同的象限中,请向中心画,然后再正常地向鼠标画。

这正是我们在这里做什么:

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

然后...如果这么简单,为什么要使用这种复杂的代码?

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

实际上,它可以以一种易于阅读的方式编写,但是会更长。让我们分解一下,看看为什么这样写:

我的操作逻辑如下:

  1. 您可能已经知道,点(0,0)在草图的左上方。计算机科学中的大多数绘图都是以这种方式计算坐标的。

  2. 与原点(用户单击以开始绘图的位置)有关的象限。

  3. 每个象限中的相对坐标将为正或负。如果X在原点右边,则为正。如果屏幕上的Y值比原点低,则Y为正值。

    Quadrant coordinates

  4. 如果按照我的逻辑,当鼠标和铅笔的相对坐标不是正或负时都不相同时,这意味着它们不在同一象限中。

所以:

distanceFromTheCenter > centerZone =>当我们离中心足够近时,我们可以让铅笔切换方向。如果铅笔不在中心附近,则无需计算其余部分。

relativeX > 0 ^ mouseQuadrant.x > 0 => relativeX实际上只是pos.x - origin.x。这是铅笔与原产地有关的地方。 mouseQuadrant“知道”鼠标相对于原点的位置(只是diffXdiffY,供以后使用,实际上我们完全可以使用diffXdiffY,因为我们只是比较它们是正数还是负数)

为什么XOR运算符这么简单?因此:

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)

...这也令人费解并且难看。因此,实际上,(relativeX > 0 ^ mouseQuadrant.x > 0 || relativeY > 0 ^ mouseQuadrant.y > 0)只是以下方面的简称:

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

我希望这是合理的!玩得开心!