Shading a trapezoid with points

Hi all!
I’ve been working lately on some generative drawing tools that generate SVG files for plotting with the Axidraw pen plotter.
For a (fake 2D) isometric drawing tool i’m doing (https://twitter.com/sntvlnv/status/1234844525144743937/photo/1), i just found some problems that exceed my knowledge, and i’ve been struggling with them for days.

The question i’d like to address in this post is as follows:

-I want to “shade” a trapezoid using points, distributed exponentially.
-This points should not exceed the boundaries of the trapezoid.
-Because the final output needs to be a vectorial image (svg or pdf), i can’t use Pgraphics, so no bitmap masking possible.

I just arrived to this code so far, which is obviously not working.

float x=200;
float y=200;
float w=100;
float h=100;

int numpoints=90;
int exp=2;


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

void draw(){
  
  w=mouseX;
  h=mouseY;
  
background (255);
  
  
strokeWeight(1);

beginShape();
vertex(x, y);
vertex(x+w, y+w/2);
vertex(x+w, h+y+w/2);
vertex(x, h+y);
endShape(CLOSE);
  
  
  for(int i=0; i<numpoints; i++){
    for(int k=0; k<numpoints; k++){
      
      float growx=pow(((i*(w/numpoints))/w), exp)*w; //normalizing, exponentiating, and multiplying back to range
      float growy=pow(((k*(h/numpoints))/h), exp)*h;
      
      float pointx=x+growx+(random(growx)-growx/2);
      float pointy=y+growy+growx/2+(random(growy)-growy/2);
      pointx=constrain(pointx,x,x+w);
      pointy=constrain(pointy,y,h+y+w/2);
      
      point(pointx,pointy);  
    }
  }
  
}

It is very probable that my approach is totally wrong!
Has anyone faced a similar problem, or can help me on finding a solution for this?

Thank you very much!!!

Hi @eltrem – were you able to resolve this question. I wasn’t sure what you meant by “distributed exponentially” – could you explain more what specifically you are trying to achieve?

Hi Jeremy!
Yes, I asked for some help to a friend and he came up with a nice solution.
He programmed the whole thing using p5.js.
To be honest, i still need to understand what he did, and then translate from the p5 syntax to regular processing, but a giant step has been made.

You can check the working code here:
https://editor.p5js.org/eduardfrigola/full/FsoC-R5n

Anyhow, i’d still be interested in alternative solutions using the regular processing syntax,
so any contributions would be welcome!

1 Like
  1. You mentioned using points, but the “hatcher” demo uses black lines – are black lines what you wanted?

  2. The second slider does not keep the jagged lines within the bounds of the rectangle – is that okay?

  1. You’re right! I’m mixing both things in my head as they are related to my final application for drawing geometric gradients. What i Was asking in this forum was related to points, not lines

  2. No it’s not. the jagged line should be kept within the limits of the square. But that was an extra feature my friend came up with, so not relevant at the moment

So coming back to the original request, i’ve found a way to solve what i wanted, but i had to use a collision algorithm. It is kind of unefficient as i want to draw thousands of points, and i guess there sould be a simpler solution. Anyhow, this is what I came up with.

Again, a more elegant, simpler solution would be really appreciated

float x=200;
float y=200;
float w=300;
float h=300;

int numpoints=190;
int expx=2;
int expy=2;


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

void draw(){
  
  w=mouseX;
  h=mouseY;
  
background (255);
  
  
strokeWeight(1);

PVector[] vertices = new PVector[4];
vertices[0] = new PVector(x,y);
vertices[1] = new PVector(x+w,y+w/2);
vertices[2] = new PVector(x+w,h+y+w/2);
vertices[3] = new PVector(x,h+y);

float area=((((vertices[0].x)*(vertices[1].y))-((vertices[1].x)*(vertices[0].y)))+(((vertices[1].x)*(vertices[2].y))-((vertices[2].x)*(vertices[1].y)))+(((vertices[2].x)*(vertices[3].y))-((vertices[3].x)*(vertices[2].y)))+(((vertices[0].x)*(vertices[1].y))-((vertices[1].x)*(vertices[0].y))))/2;
numpoints=80+(int(area)/550);
println(area);
/*
beginShape();
  for (PVector v : vertices) {
    vertex(v.x, v.y);
  }
  endShape(CLOSE);
 */ 
  
  for(int i=0; i<numpoints; i++){
    for(int k=0; k<numpoints; k++){
      
      float growx=pow(((i*(w/numpoints))/w), expx)*w; //normalizing, exponentiating, and multiplying back to range
      float growy=pow(((k*(h/numpoints))/h), expy)*h;
      //growy=100;
      
      float jitterX=random(-growx, growx);
      float jitterY=random(-growy,growy);
      
      float pointx=x+growx+jitterX;
      float pointy=y+growy+(growx/2)+jitterY;
      
      boolean hit = polyPoint(vertices, pointx,pointy);
      if (hit) point(pointx,pointy);
      else ;
    }
  }
  
}

boolean polyPoint(PVector[] vertices, float px, float py) {
  boolean collision = false;

  // go through each of the vertices, plus
  // the next vertex in the list
  int next = 0;
  for (int current=0; current<vertices.length; current++) {

    // get next vertex in list
    // if we've hit the end, wrap around to 0
    next = current+1;
    if (next == vertices.length) next = 0;

    // get the PVectors at our current position
    // this makes our if statement a little cleaner
    PVector vc = vertices[current];    // c for "current"
    PVector vn = vertices[next];       // n for "next"

    // compare position, flip 'collision' variable
    // back and forth
    if (((vc.y >= py && vn.y < py) || (vc.y < py && vn.y >= py)) &&
         (px < (vn.x-vc.x)*(py-vc.y) / (vn.y-vc.y)+vc.x)) {
            collision = !collision;
    }
  }
  return collision;
}
1 Like

So, coming back to this question:

You want to arrange points randomly on the surface as a texture a way that simulates lighting / light falloff?

Are you generating a lot of points and then cropping them onto a polygon? You could just draw the points on a PGraphics and then use PImage.mask() to cut polygon shapes out of your texture.

Another idea: you could switch to P2D mode and use a shader with PShader.

Yes the idea is to simulate roughly light falloff, although it doesn’t need to be realistic. Think of it as an “artistic” feature for doing gradients rather than to simulate reality.

The thing here is that i can’t use Pgraphics or PShader (or anything bitmap related) because i want this geometry to be plottable… i need id to be exportable as a vector graphic on a svg or a pdf…

If you are trying to optimize, can you explain what specifically polyPoint is accomplishing? As opposed to what happens if you disable it entirely, for example.

here is mine


// see https://discourse.processing.org/t/question-about-rotating-quad/1423

size(512, 512);
//smooth(8);
background(255);
fill(0);
noStroke();

int count = 6;

// Translation
float centerX = width * 0.5;
float centerY = height * 0.5;

// Rotation
float rotation = HALF_PI;
float arcLength = PI / 8.0;
float halfArcLength = arcLength * 0.5;
float iToTheta = TWO_PI / float(count);

// Scale
float shortEdge = min(width, height);
float outerRadius = shortEdge * 0.45;
float innerRadius = shortEdge * 0.2;

//for (int i = 0; i < count; ++i) {
int i = 0; 
float angle = rotation + i * iToTheta;
float t0 = angle - halfArcLength;
float t1 = angle + halfArcLength;

float ct0 = cos(t0);
float st0 = sin(t0);

float ct1 = cos(t1);
float st1 = sin(t1);

float p0x = centerX + ct0 * innerRadius;
float p0y = centerY + st0 * innerRadius;

float p1x = centerX + ct1 * innerRadius;
float p1y = centerY + st1 * innerRadius;

float p2x = centerX + ct1 * outerRadius;
float p2y = centerY + st1 * outerRadius;

float p3x = centerX + ct0 * outerRadius;
float p3y = centerY + st0 * outerRadius;

text("p0", p0x, p0y-3);
text("p1", p1x, p1y-3);
text("p2", p2x, p2y+12);
text("p3", p3x, p3y+12);

noFill();
stroke(2);

quad(p0x, p0y, 
  p1x, p1y, 
  p2x, p2y, 
  p3x, p3y);


//--------------------------------------------------

stroke(2);
strokeWeight(3); 

int ub = 10; 

for (int i2 = 0; i2 <= ub; i2++) {
  float x1 = lerp(p2x, p3x, i2/10.0);
  float x2 = lerp(p1x, p0x, i2/10.0);

  for (int i3 = 0; i3 <= ub; i3++) {
    float x3 = lerp(x1, x2, i3/10.0);  
    float y1 = lerp(p2y, p1y, i3/10.0);
    point(x3, y1);
  }
}
//
2 Likes

I managed a slight change of the density for the area




// see https://discourse.processing.org/t/question-about-rotating-quad/1423

size(512, 512);
smooth(8);
background(255);
fill(0);
noStroke();

int count = 6;

// Translation
float centerX = width * 0.5;
float centerY = 1;

// Rotation
float rotation = HALF_PI;
float arcLength = PI / 8.0;
float halfArcLength = arcLength * 0.5;
float iToTheta = TWO_PI / float(count);

// Scale
float shortEdge = min(width, height);
float outerRadius = shortEdge * 0.45 + 270;
float innerRadius = shortEdge * 0.2 + 110;

int i = 0; 
float angle = rotation + i * iToTheta;
float t0 = angle - halfArcLength;
float t1 = angle + halfArcLength;

float ct0 = cos(t0);
float st0 = sin(t0);

float ct1 = cos(t1);
float st1 = sin(t1);

float p0x = centerX + ct0 * innerRadius;
float p0y = centerY + st0 * innerRadius;

float p1x = centerX + ct1 * innerRadius;
float p1y = centerY + st1 * innerRadius;

float p2x = centerX + ct1 * outerRadius;
float p2y = centerY + st1 * outerRadius;

float p3x = centerX + ct0 * outerRadius;
float p3y = centerY + st0 * outerRadius;

text("p0", p0x, p0y-3);
text("p1", p1x, p1y-3);
text("p2", p2x, p2y+12);
text("p3", p3x, p3y+12);

noFill();
stroke(2);

quad(p0x, p0y, 
  p1x, p1y, 
  p2x, p2y, 
  p3x, p3y);


//--------------------------------------------------

stroke(2);
strokeWeight(3); 

int ub = 120; 
float ub2 = 120.0; 

float i2 = 0; 
while (i2<=ub2) {

  float x1 = lerp(p2x, p3x, i2/ub2);
  float x2 = lerp(p1x, p0x, i2/ub2);

  float i3 = 0; 
  while (i3<=ub2) {
    float x3 = lerp(x1, x2, i3/ub2);  
    float y1 = lerp(p2y, p1y, i3/ub2);
    point(x3, y1);

    //-----------------------------------------

    // increase 
    i3 += map(i2, 
      0, ub2, 
      1.621, 5.7 );
  }//while
  i2 += map(i2, 
    0, ub2, 
    0.31, 4 );
}//outer while  
//
1 Like

wow! thanks Chrisir!
Let’s see if i’m capable to understand how you arrived there… luckily i have days for studying now with the whole coronavirus mess… ommmmm…

1 Like

the first part is the same in both sketches. It is from he link I included in the sketch. It’s over complicate for our purpose and just generates 4 corner points of the quad.

The nested while loops are interesting

1 Like

polypoint is evaluating if the points are inside the polygon or not. If the point is not inside the polygon, it won’t be drawn.

The code in my first message shows clearly what happens without using the polypoint function: there are points which fall outside the polygon boundaries.

Sadly i haven’t found the way to constrain properly the coordinates of the points using simple statements…

Is the trapezoid always axis-aligned – e.g. with two sides straight up-down? Or could it be any quadrilateral?

The nice thing about a homography approach is that then you will fewer points to test, and you will only have to test some in one dimension, not two.

1 Like

at the end i want to draw an isometric cube, therefore i’ll have to draw 3 trapezoids. I guess one of them, then, won’t be axis aligned?

Hello,

I’m a bit late on your question and I do not answer it completely since I am using squares instead of circles but I would still like to share what I have done because I had a lot of fun doing it and it can maybe gives you some ideas.
Also it is possible to change the fill shape.

The end result looks like this:
ShadingExemple

Here is the code. It is dynamic: you can drag some points to change the shadow’s direction, start and end point. It is also displaying construction lines.
I admit that my code is a real nightmare to go through so if you have any questions please do not hesitate =)

// PVector squareDir;
Quadrilateral q;

void setup() {
  size(500, 500);
  background(240);

  //squareDir = new PVector(1/sqrt(2), 1/sqrt(2));
  q = new Quadrilateral(350, 70, 110, 150, 153, 418, 348, 300);

  //drawSquare(30, 30, 10, squareDir);
  //q.Show();
}


void draw() {
  background(230);
  q.Update();
  q.Show();
}

void mousePressed() {
  q.OnMousePressed();
}


void mouseReleased() {
  q.OnMouseReleased();
}







void drawSquare(float x, float y, float size, PVector dir) {
  float diagSize = size / sqrt(2);
  fill(20);
  noStroke();
  beginShape();
  vertex(x + dir.x * diagSize, y + dir.y * diagSize);
  vertex(x - dir.y * diagSize, y + dir.x * diagSize);
  vertex(x - dir.x * diagSize, y - dir.y * diagSize);
  vertex(x + dir.y * diagSize, y - dir.x * diagSize);
  endShape();
}


PVector[] getSquarePt(float x, float y, float size, PVector dir) {
  PVector[] result = new PVector[4];
  float diagSize = size / sqrt(2);
  result[0] = new PVector(x + dir.x * diagSize, y + dir.y * diagSize);
  result[1] = new PVector(x - dir.y * diagSize, y + dir.x * diagSize);
  result[2] = new PVector(x - dir.x * diagSize, y - dir.y * diagSize);
  result[3] = new PVector(x + dir.y * diagSize, y - dir.x * diagSize);
  return result;
}


void drawSquareInQuadrilateral(float x, float y, float size, PVector dir, Point[] quad) {
  // Prepare the quadrilateral points
  PVector[] quadPt = new PVector[4];
  for (int i = 0; i < 4; i++) {
    quadPt[i] = quad[i].getPos();
  }

  // Prepare the square points
  PVector[] squarePt = getSquarePt(x, y, size, dir);

  // Array to store all the points we'll need
  ArrayList<PVector> finalPt = new ArrayList<PVector>();

  // Add point of square that are in the quadrilateral
  // If all points are inside => the square can be drawn
  // If no points are inside => Nothing will need to be drawn
  for (int i = 0; i<4; i++) {
    if (ptInQuad(squarePt[i], quadPt)) {
      finalPt.add(squarePt[i]);
    }
  }
  if (finalPt.size() == 0) {
    return;
  }
  if (finalPt.size() == 4) {
    drawSquare(x, y, size, dir);
    return;
  }

  // Add point of quadrilateral that are in the square
  for (int i = 0; i<4; i++) {
    if (ptInQuad(quadPt[i], squarePt)) {
      finalPt.add(quadPt[i]);
    }
  }

  // Add all intersecting points
  for (int i = 0; i < 4; i++) {
    int idx1 = i;
    int idx2 = i + 1;
    if (idx2 > 3) {
      idx2 = 0;
    }
    for (int j = 0; j < 4; j++) {
      int idx3 = j;
      int idx4 = j + 1;
      if (idx4 > 3) {
        idx4 = 0;
      }
      intersectSolution sol = intersectingPt(squarePt[idx1], squarePt[idx2], quadPt[idx3], quadPt[idx4]);
      if (sol.haveSolution()) {
        finalPt.add(sol.getIntersection());
      }
    }
  }

  // Sort all points in correct order to draw them
  // Start by computing center point
  float mx = 0, my = 0;
  for (int i = 0; i < finalPt.size(); i++) {
    mx += finalPt.get(i).x;
    my += finalPt.get(i).y;
  }
  mx /= finalPt.size();
  my /= finalPt.size();

  // Then get the atan2 value
  float[] arcTanVal = new float[finalPt.size()];
  int[] indices = new int[finalPt.size()]; // To track the sorting
  for (int i = 0; i < finalPt.size(); i++) {
    arcTanVal[i] = atan2(finalPt.get(i).y - my, finalPt.get(i).x - mx);
    indices[i] = i;
  }

  // Sort from lower to higher atan2
  int n = arcTanVal.length; 
  for (int i = 0; i < n-1; i++) {
    for (int j = 0; j < n-i-1; j++) {
      if (arcTanVal[j] > arcTanVal[j+1]) 
      { 
        // swap arr[j+1] and arr[i] 
        float temp1 = arcTanVal[j];
        int temp2 = indices[j];
        arcTanVal[j] = arcTanVal[j+1]; 
        arcTanVal[j+1] = temp1;
        
        indices[j] = indices[j+1]; 
        indices[j+1] = temp2;
      }
    }
  }
  
  // Draw new polygon
  fill(20);
  noStroke();
  beginShape();
  for (int i = 0; i < n; i++) {
    vertex(finalPt.get(indices[i]).x, finalPt.get(indices[i]).y);
  }
  endShape();
}

intersectSolution intersectingPt(PVector A1, PVector A2, PVector B1, PVector B2) {
  PVector result = new PVector(0, 0);
  
  // first line go through A1 and A2 : y = a1 * x + b1
  // second line go thourgh B1 and B2 : y = a2 * x + b2
  float a1 = (A2.y - A1.y) / (A2.x - A1.x);
  float a2 = (B2.y - B1.y) / (B2.x - B1.x);
  
  if (abs(a1 - a2) < 0.000000000001) {
    return new intersectSolution(false, result);
  }
  
  float b1 = A1.y - (a1 * A1.x);
  float b2 = B1.y - (a2 * B1.x);
  
  float x = (b2-b1) / (a1-a2);
  
  float minAx, maxAx, minBx, maxBx;
  minAx = min(A1.x, A2.x);
  maxAx = max(A1.x, A2.x);
  minBx = min(B1.x, B2.x);
  maxBx = max(B1.x, B2.x);
  
  if ( (x > minAx) && (x < maxAx) && (x > minBx) && (x < maxBx)) {
    result.set(x, a1 * x + b1);
    return new intersectSolution(true, result);
  }
  
  return new intersectSolution(false, result);
}

class intersectSolution {
  boolean haveSolution;
  PVector p;

  intersectSolution(boolean b, PVector point) {
    haveSolution = b;
    p = point;
  }

  boolean haveSolution() {
    return haveSolution;
  }

  PVector getIntersection() {
    return p;
  }
}


boolean ptInQuad(PVector pt, PVector[] quad) {
  if (ptInTriangle(pt, quad[0], quad[1], quad[2]) || ptInTriangle(pt, quad[0], quad[2], quad[3])) {
    return true;
  }
  return false;
}


boolean ptInTriangle(PVector p, PVector a, PVector b, PVector c) {
  if (isSameSide(p, a, b, c) && isSameSide(p, b, a, c) && isSameSide(p, c, a, b)) {
    return true;
  }
  return false;
}

boolean isSameSide(PVector p1, PVector p2, PVector a, PVector b) {
  PVector v1 = b.copy().add(a.copy().mult(-1)); // b - a
  PVector v2 = p1.copy().add(a.copy().mult(-1)); // p1 - a
  PVector v3 = p2.copy().add(a.copy().mult(-1)); // p2 - a

  PVector cp1 = v1.cross(v2);
  PVector cp2 = v1.cross(v3);

  if (cp1.dot(cp2) >= 0) {
    return true;
  }

  return false;
}






class Quadrilateral {
  Point[] points = new Point[4]; 
  Shading shading;
  boolean displayShading;

  Quadrilateral(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) {
    points[0] = new Point(x1, y1, color(40, 40, 40), 8);
    points[1] = new Point(x2, y2, color(40, 40, 40), 8);
    points[2] = new Point(x3, y3, color(40, 40, 40), 8);
    points[3] = new Point(x4, y4, color(40, 40, 40), 8);
    shading = new Shading(10, 10, 40, 40);
    displayShading = true;
  }

  void DisplayShading(boolean b) {
    displayShading = b;
  }

  void OnMousePressed() {
    if (displayShading) {
      shading.OnMousePressed();
    }
  }

  void OnMouseReleased() {
    shading.OnMouseReleased();
  }

  void Update() {
    shading.Update();
  }

  void Show() {
    shading.DrawShading(points);

    noFill();
    stroke(60, 60, 60);
    strokeWeight(2);
    for (int i = 0; i < 3; i++) {
      line(points[i].getPos().x, points[i].getPos().y, points[i+1].getPos().x, points[i+1].getPos().y);
    }
    line(points[3].getPos().x, points[3].getPos().y, points[0].getPos().x, points[0].getPos().y);

    fill(60, 60, 60);
    noStroke();
    for (int i = 0; i < 4; i++) {
      points[i].Show();
    }

    if (displayShading) {
      shading.Show();
    }
  }

  void Shade() {
  }
}




class Shading {
  Point start, end;
  PVector dir; // Direction of the shading
  float patternSize;

  Shading(int x1, int y1, int x2, int y2) {
    start = new Point(x1, y1, color(230, 20, 20), 10);
    start.setMovable(true);
    end = new Point(x2, y2, color(20, 20, 230), 10);
    end.setMovable(true);
    dir = new PVector(x2 - x1, y2 - y1);
    dir.normalize();
    patternSize = 10;
  }

  void setPatternSize(float s) {
    patternSize = s;
  }

  void OnMousePressed() {
    if (end.isHoovered()) {
      end.setSelect(true);
      end.computeDeltaFromMouse();
      return;
    }

    if (start.isHoovered()) {
      start.setSelect(true);
      start.computeDeltaFromMouse();
    }
  }

  void OnMouseReleased() {
    start.setSelect(false);
    end.setSelect(false);
  }

  void Update() {
    start.Update();
    end.Update();
    dir.set(end.getPos().x - start.getPos().x, end.getPos().y - start.getPos().y);
    dir.normalize();
  }

  void Show() {
    DrawArrow(start.getPos().x, start.getPos().y, end.getPos().x, end.getPos().y);
    start.Show();
    end.Show();
  }

  void DrawShading(Point[] points) {
    // Chose a perpendicular direction to the shading direction to draw the shading
    // dir = direction of the shading
    // drawingDir = direction in which the shading is drawn. It is perpendicular to the shading dir
    PVector drawingDir = new PVector(-dir.y, dir.x);
    DrawArrow(start.getPos().x, start.getPos().y, start.getPos().x + 40 * drawingDir.x, start.getPos().y + 40 * drawingDir.y);

    // Create the shading line
    Line shadingLine = new Line(start.getPos().x, start.getPos().y, dir.x, dir.y);
    shadingLine.Show();

    // FIND TOP AND BOTTOM OF DRAWING BOX
    // create the start line: line that go trough the start point and is perpendicular to the direction of the shading
    Line startLine = new Line(start.getPos().x, start.getPos().y, drawingDir.x, drawingDir.y);
    startLine.Show();

    // Compute signed distance of each points of the quadrilateral to the start line
    float[] sDist = new float[4];
    for (int i = 0; i < 4; i++) {
      sDist[i] = points[i].getSignedDistFrom(startLine, dir);
    }

    // Get the closest and furthest point of the quadrilateral to the start line to know bottom of the drawing box and the top of the drawing box
    int idxBot = 0, idxTop = 0;
    for (int i = 1; i < 4; i++) {
      if (sDist[i] < sDist[idxBot]) {
        idxBot = i;
      }
      if (sDist[i] > sDist[idxTop]) {
        idxTop = i;
      }
    }

    // Create the lower bounding line and higher bounding line
    Line drawingBoxBot = new Line(points[idxBot].getPos().x, points[idxBot].getPos().y, drawingDir.x, drawingDir.y);
    Line drawingBoxTop = new Line(points[idxTop].getPos().x, points[idxTop].getPos().y, drawingDir.x, drawingDir.y);
    drawingBoxBot.setColor(color(230, 20, 20));
    drawingBoxTop.setColor(color(20, 20, 230));
    drawingBoxBot.Show();
    drawingBoxTop.Show();
    float drawingBoxHeight = abs(sDist[idxTop] - sDist[idxBot]);

    // FIND LEFT AND RIGHT OF DRAWING BOX
    // Compute signed distance of each points of the quadrilateral to the start line
    for (int i = 0; i < 4; i++) {
      sDist[i] = points[i].getSignedDistFrom(shadingLine, drawingDir);
    }

    // Get the closest and furthest point of the quadrilateral to the start line to know bottom of the drawing box and the top of the drawing box
    int idxLeft = 0, idxRight = 0;
    for (int i = 1; i < 4; i++) {
      if (sDist[i] < sDist[idxLeft]) {
        idxLeft = i;
      }
      if (sDist[i] > sDist[idxRight]) {
        idxRight = i;
      }
    }

    // Create the lower bounding line and higher bounding line
    Line drawingBoxLeft = new Line(points[idxLeft].getPos().x, points[idxLeft].getPos().y, dir.x, dir.y);
    Line drawingBoxRight = new Line(points[idxRight].getPos().x, points[idxRight].getPos().y, dir.x, dir.y);
    drawingBoxLeft.setColor(color(230, 20, 230));
    drawingBoxRight.setColor(color(20, 230, 230));
    drawingBoxLeft.Show();
    drawingBoxRight.Show();
    float drawingBoxWidth = abs(sDist[idxRight] - sDist[idxLeft]);

    // Get the start drawing point
    PVector startDrawPt = drawingBoxLeft.intersect(drawingBoxBot);

    // Fille in the shape
    noStroke();
    fill(255, 0, 0);
    float maxDist = end.getSignedDistFrom(startLine, dir); // Get the distance between the start point and the end point
    for (float j = -1; j < (drawingBoxHeight / patternSize) + 1; j+=0.5) {
      Point firstRowPt = new Point(startDrawPt.x + j * patternSize * dir.x, startDrawPt.y + j * patternSize * dir.y);
      float offset = 0;
      if (floor(j) != j) {
        offset = 0.5;
      }
      float currRowDist = firstRowPt.getSignedDistFrom(startLine, dir);
      float sizeRatio = constrain(currRowDist / maxDist, 0, 1);
      float fadeOut = -1;
      float currPatternSize = (1 - (exp(fadeOut * sizeRatio) - 1) / (exp(fadeOut) - 1)) * patternSize;
      for (int i = 0; i < (drawingBoxWidth / patternSize) + 1; i++) {
        //ellipse(startDrawPt.x + i * patternSize * drawingDir.x + j * patternSize * dir.x, startDrawPt.y + i * patternSize * drawingDir.y + j * patternSize * dir.y, currPatternSize, currPatternSize);
        //drawSquare(startDrawPt.x + (i + offset) * patternSize * drawingDir.x + j * patternSize * dir.x, startDrawPt.y + (i + offset) * patternSize * drawingDir.y + j * patternSize * dir.y, currPatternSize, dir);
        drawSquareInQuadrilateral(startDrawPt.x + (i + offset) * patternSize * drawingDir.x + j * patternSize * dir.x, startDrawPt.y + (i + offset) * patternSize * drawingDir.y + j * patternSize * dir.y, currPatternSize, dir, points);
      }
    }
  }
}



void DrawArrow(float x1, float y1, float x2, float y2) {
  noFill();
  stroke(50);
  strokeWeight(1);

  float arrowSize = 20;

  line(x1, y1, x2, y2);

  PVector dir = new PVector(x1 - x2, y1 - y2);
  dir.normalize();

  dir.rotate(PI / 6);
  line(x2, y2, x2 + arrowSize * dir.x, y2 + arrowSize * dir.y);

  dir.rotate(-PI / 3);
  line(x2, y2, x2 + arrowSize * dir.x, y2 + arrowSize * dir.y);
}



class Point {
  PVector pos, deltaFromMouse;
  color col;
  float size;
  boolean isSelected;
  boolean movable;

  Point(float x, float y) {
    pos = new PVector(x, y);
    col = color(0);
    size = 0;
    isSelected = false;
    movable = false;
    deltaFromMouse = new PVector(0, 0);
  }

  Point(float x, float y, color c, float s) {
    pos = new PVector(x, y);
    col = c;
    size = s;
    isSelected = false;
    movable = false;
    deltaFromMouse = new PVector(0, 0);
  }

  PVector getPos() {
    return pos;
  }

  void setMovable(boolean b) {
    movable = b;
  }

  boolean isHoovered() {
    return ((mouseX - pos.x) * (mouseX - pos.x) + (mouseY - pos.y) * (mouseY - pos.y) - (size/2) * (size/2) < 0);
  }

  void setSelect(boolean b) {
    isSelected = b;
  }

  void computeDeltaFromMouse() {
    deltaFromMouse.x = mouseX - pos.x;
    deltaFromMouse.y = mouseY - pos.y;
  }

  void Update() {
    if (isSelected == false || movable == false) {
      return;
    }

    pos.x = mouseX - deltaFromMouse.x;
    pos.y = mouseY - deltaFromMouse.y;
  }

  void Show() {
    if (movable && isHoovered()) {
      noStroke();
      int r = (col >> 16) & 0xFF;
      int g = (col >> 8) & 0xFF;
      int b = col & 0xFF; 
      fill(color(r, g, b, 100));
      ellipse(pos.x, pos.y, size * 1.4, size * 1.4);
    }

    noStroke();
    fill(col);
    ellipse(pos.x, pos.y, size, size);
  }


  float getSignedDistFrom(Line l, PVector signDir) {
    PVector projectedPt = new PVector(0, 0);
    PVector linePt = l.getPt();
    PVector lineDir = l.getDir();

    // find projected point on line
    // Line dir vector is unit vector so sqrt(x² + y²) = 1 and can be omited in below formulas
    projectedPt.set(linePt.x + ((pos.x - linePt.x) * lineDir.x + (pos.y - linePt.y) * lineDir.y) * lineDir.x, 
      linePt.y + ((pos.x - linePt.x) * lineDir.x + (pos.y - linePt.y) * lineDir.y) * lineDir.y);

    // Get distance from line
    float dist = (pos.x - projectedPt.x) * (pos.x - projectedPt.x) + (pos.y - projectedPt.y) * (pos.y - projectedPt.y);

    // Get sign of distance with dot product
    float sign = 1;
    if ((pos.x - projectedPt.x) * signDir.x + (pos.y - projectedPt.y) * signDir.y < 0) {
      sign = -1;
    }

    return sign * sqrt(dist);
  }
}


class Line {
  PVector p, dir; // p = point of the line, dir = unit vector
  float a, b; // y = ax+b
  color col;

  Line(float x1, float y1, PVector d) {
    p = new PVector(x1, y1);
    dir = d;
    init();
  }

  Line(float x1, float y1, float x2, float y2) {
    p = new PVector(x1, y1);
    dir = new PVector(x2, y2);
    init();
  }

  void init() {
    dir.normalize();
    a = dir.y / dir.x;
    b = p.y - a * p.x;
    col = color(50, 50, 50);
  }

  float getA() {
    return a;
  }

  float getB() {
    return b;
  }

  void setColor(color c) {
    col = c;
  }

  PVector getPt() {
    return p;
  }

  PVector getDir() {
    return dir;
  }

  void Show() {
    PVector[] p = new PVector[2];
    int i = 0;
    float currValue;

    p[0] = new PVector(0, 0);
    p[1] = new PVector(0, 0);

    if (b > 0 && b < height) {
      p[i].set(0, b);
      i++;
    }

    currValue = -b/a;
    if (currValue > 0 && currValue < width) {
      p[i].set(currValue, 0);
      i++;
    }

    currValue = a * width + b;
    if (i < 2 && currValue > 0 && currValue < height) {
      p[i].set(width, currValue);
      i++;
    }

    currValue = (height - b) / a;
    if (i < 2 && currValue > 0 && currValue < width) {
      p[i].set(currValue, height);
    }

    PVector dir = new PVector(p[1].x - p[0].x, p[1].y - p[0].y);
    dir.normalize();
    float dist = sqrt((p[1].x - p[0].x) * (p[1].x - p[0].x) + (p[1].y - p[0].y) * (p[1].y - p[0].y));
    float dashSize = 10;
    PVector currPoint = p[0].copy();
    stroke(col);
    strokeWeight(1);
    for (int j = 0; j < dist / (dashSize * 2); j++) {
      line(currPoint.x, currPoint.y, currPoint.x + dashSize * dir.x, currPoint.y + dashSize * dir.y);
      currPoint.set(currPoint.x + 2 * dashSize * dir.x, currPoint.y + 2 * dashSize * dir.y);
    }
  }


  PVector intersect(Line line) {
    float x, y;
    x = (line.getB() - b) / (a - line.getA());
    y = a * x + b;
    return new PVector(x, y);
  }
}
2 Likes