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:

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