【问题标题】:Separate shapes as lines split the canvas + Select points to draw shapes from arrays将形状分离为线条分割画布 + 选择点以从数组中绘制形状
【发布时间】:2021-08-08 17:35:11
【问题描述】:

我想检测不同的形状,因为随机生成的线条会分割画布。我将线交点保存在单独的数组中,用于 x 和 y 位置(相同的顺序),但不知道如何连接 完成多个形状的点。

  1. 是否有任何方法可以检测附近的点以闭合可能的最小形状,无论它是三角形、矩形还是多边形(例如,通过使用 beginShape 和 endShape)?
  2. 如果 1) 太复杂,有什么方法可以从数组中选择 3 个或更多随机点?

这是一个示例图像,其中有 4 条线将画布分开,它们的交点用红色标记。我还将每条随机生成的线的顶部和底部点(标记为黑色)以及画布的四个角分别保存在相同的数组中,分别用于 x 和 y 位置(px、py)。

多行分割画布。

如何在Processing中得到按线分割的形状?

我能够得到所有的交点,但是在将它们连接成单独的形状时遇到了问题。这是我正在处理的处理代码:

//Run in Processing.
//Press r to refresh.
//Top and bottom points are added to px and py when refreshed (filled in black).
//Intersection points are added to px and py when detected (filled in red).
int l = 4; //set number of lines
float[] r1 = new float[l];
float[] r2 = new float[l];
float[] px = {}; //array to save x positions of all possible points
float[] py = {}; //array to save y positions of all possible points
boolean added = false;
void setup(){
  size(800, 800);
  background(255);
  
  refresh();
}
void draw(){ 
  background(255);
  stroke(0, 150, 255, 150);
  strokeWeight(1);
  for(int i=0; i < r1.length; i++){
    for(int j=0; j < r1.length; j++){
      if(i>j){
      boolean hit = lineLine(r1[i], 0, r2[i], height, r1[j], 0, r2[j], height);
      if (hit) stroke(255, 150, 0, 150);
      else stroke(0, 150, 255, 150);
      }
    line(r1[i], 0, r2[i], height);
    }
  }
  added = true;
  print(px.length);
}
//source: http://jeffreythompson.org/collision-detection/line-line.php
boolean lineLine(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) {
  // calculate the distance to intersection point
  float uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
  float uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
  // if uA and uB are between 0-1, lines are colliding
  if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
    // optionally, draw a circle where the lines meet
    float intersectionX = x1 + (uA * (x2-x1));
    float intersectionY = y1 + (uA * (y2-y1));
    fill(255,0,0);
    noStroke();
    ellipse(intersectionX,intersectionY, 20,20);
    if(added==false){
      px = append(px, intersectionX);
      py = append(py, intersectionY);
    }
    return true;
  }
  return false;
}
void refresh(){
  added = false;
  px = new float[0];
  py = new float[0];
  r1 = new float[l];
  r2 = new float[l];
  
  px = append(px, 0);
  py = append(py, 0);
  px = append(px, 0);
  py = append(py, height);
  px = append(px, width);
  py = append(py, 0);
  px = append(px, width);
  py = append(py, height);
  
  for(int i=0; i< r1.length; i++){
    r1[i] = random(800);
  }
  for(int i=0; i< r2.length; i++){
    r2[i] = random(800);
  }
  for(int i=0; i < r1.length; i++){
      stroke(0);
      line(r1[i], 0, r2[i], height);
      px = append(px, r1[i]);
      py = append(py, 0);
      px = append(px, r2[i]);
      py = append(py, height);
  }
}
void keyReleased() {
  if (key == 'r') refresh();
}

【问题讨论】:

  • 我建议您在标签中添加您的编程语言。
  • 我无法理解您的问题。你有一些特定的编程问题吗?这是一个通用的编程问题吗?或者这是一个几何/线性代数问题?
  • 感谢您调查问题并提出旅行商问题。我正在使用处理 IDE,问题一般是关于处理语法和几何:如何检测单独的形状,因为线条彼此相交。我在源代码中的方法可能不正确,但最终我想获得单独的形状,因为线条分割画布(为每个分割的形状应用不同的颜色)。我添加了一张新图片来澄清我的问题。
  • 哦,我的错。我什至不知道那种语言,所以processing 在我看来只是一个随机的广泛关键字。 :) 所以这是一个我无法回答的具体问题。不过,祝你好运。 :)

标签: processing line intersection shapes points


【解决方案1】:

如果你想绘制一个由交叉点组成的形状,那么你在beginShape()/endShape() 的正确轨道上。

目前,您似乎将所有点放在pxpy:交点以及定义用于首先计算交点的线的点。

您可能希望将两者分开,例如一对数组仅用于定义线的点,另一对 x,y 数组仅用于交点。您只需要遍历相交的坐标即可在beginShape()/endShape() 之间放置vertex(x, y) 调用。这是您代码的修改版本来说明这个想法:

//Run in Processing.
//Press r to refresh.
//Top and bottom points are added to px and py when refreshed (filled in black).
//Intersection points are added to px and py when detected (filled in red).
int l = 4; //set number of lines
float[] r1 = new float[l];
float[] r2 = new float[l];
float[] px = {}; //array to save x positions of all possible points
float[] py = {}; //array to save y positions of all possible points
float[] ipx = {}; // array to save x for intersections only
float[] ipy = {}; // array to save y for intersections only
boolean added = false;

void setup(){
  size(800, 800);
  background(255);
  
  refresh();
}
void draw(){ 
  background(255);
  stroke(0, 150, 255, 150);
  strokeWeight(1);
  for(int i=0; i < r1.length; i++){
    for(int j=0; j < r1.length; j++){
      if(i>j){
      boolean hit = lineLine(r1[i], 0, r2[i], height, r1[j], 0, r2[j], height);
      if (hit) stroke(255, 150, 0, 150);
      else stroke(0, 150, 255, 150);
      }
    line(r1[i], 0, r2[i], height);
    }
  }
  added = true;
  
  // draw intersections
  beginShape();
  for(int i = 0 ; i < ipx.length; i++){
    vertex(ipx[i], ipy[i]);
  }
  endShape();
  
  //print(px.length);
  //println(px.length, py.length);
}
//source: http://jeffreythompson.org/collision-detection/line-line.php
boolean lineLine(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) {
  // calculate the distance to intersection point
  float uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
  float uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
  // if uA and uB are between 0-1, lines are colliding
  if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
    // optionally, draw a circle where the lines meet
    float intersectionX = x1 + (uA * (x2-x1));
    float intersectionY = y1 + (uA * (y2-y1));
    fill(255,0,0);
    noStroke();
    ellipse(intersectionX,intersectionY, 20,20);
    if(added==false){
      px = append(px, intersectionX);
      py = append(py, intersectionY);
      
      // store intersections
      ipx = append(ipx, intersectionX);
      ipy = append(ipy, intersectionY);
      
    }
    return true;
  }
  return false;
}
void refresh(){
  added = false;
  px = new float[0];
  py = new float[0];
  ipx = new float[0];
  ipy = new float[0];
  r1 = new float[l];
  r2 = new float[l];
  
  px = append(px, 0);
  py = append(py, 0);
  px = append(px, 0);
  py = append(py, height);
  px = append(px, width);
  py = append(py, 0);
  px = append(px, width);
  py = append(py, height);
  
  for(int i=0; i< r1.length; i++){
    r1[i] = random(800);
  }
  for(int i=0; i< r2.length; i++){
    r2[i] = random(800);
  }
  for(int i=0; i < r1.length; i++){
      stroke(0);
      line(r1[i], 0, r2[i], height);
      px = append(px, r1[i]);
      py = append(py, 0);
      px = append(px, r2[i]);
      py = append(py, height);
  }
  
}
void keyReleased() {
  if (key == 'r') refresh();
}

请记住,这个 simlpy 按照计算交点的顺序绘制点。在美好的一天,你会得到这样的东西:

不排除多边形顶点顺序错误(缠绕)的可能性:

你也可能会得到凹多边形。

如果您只需要这些交点的外“外壳”,您可能需要 convex hull algorithm 之类的东西

至少在视觉上分割形状的一个选项可能是使用beginShape(TRIANGLES);endShape(CLOSE);,它应该遍历点并为每个坐标三元组绘制一个三角形,但是给定随机点和交叉点的数量您最终可能会丢失一两个三角形(例如 6 个点 = 2 个三角形、7 个点 = 2 个三角形和 1 个点且没有丢失对)

我唯一的其他注意事项是语法:数组可以开始使用,但您可能需要查看 ArrayListPVector。这将允许您使用具有 x、y 属性的 PVector 实例的单个动态数组。

更新

总体上可以简化代码。如果我们取出与线交叉点相关的代码,我们可以摆脱类似的情况:

int l = 4; //set number of random lines
float[] r1 = new float[l];  // random x top
float[] r2 = new float[l];  // random x bottom

void setup() {
  size(800, 800);
  strokeWeight(3);
  stroke(0, 150, 255, 150);
  
  refresh();
}

void draw() { 
  background(255);
  
  // random lines
  for (int i=0; i < r1.length; i++) {
    line(r1[i], 0, r2[i], height);
  }
  
  // borders
  line(0, 0, width, 0);
  line(width, 0, width - 1, height - 1);
  line(0, height - 1, width - 1, height - 1);
  line(0, 0, 0, height - 1);
}

void refresh() {
  r1 = new float[l];
  r2 = new float[l];

  for (int i=0; i< r1.length; i++) {
    r1[i] = random(800);
    r2[i] = random(800);
  }
}

void keyReleased() {
  if (key == 'r') refresh();
}

如果我们要使用基本的Line 类并使用PVectorArrayList,我们可以将上面的内容重写为:

int numRandomLines = 4;
ArrayList<PVector> points = new ArrayList<PVector>();


void setup() {
  size(800, 800);
  stroke(0, 150, 255, 150);
  strokeWeight(3);
  refresh();
}

void refresh(){
  // remove previous points
  points.clear();
  //add borders
  points.add(new PVector(0, 0)); points.add(new PVector(width, 0));
  points.add(new PVector(width, 0));points.add(new PVector(width - 1, height - 1));
  points.add(new PVector(0, height - 1));points.add(new PVector(width - 1, height - 1));
  points.add(new PVector(0, 0)); points.add(new PVector(0, height - 1));
  // add random lines
  for (int i=0; i< numRandomLines; i++) {
    points.add(new PVector(random(800), 0));  points.add(new PVector(random(800), height));
  }
}

void draw(){
  background(255);
  
  beginShape(LINES);
  for(PVector point : points) vertex(point.x, point.y);
  endShape();
}

void keyReleased() {
  if (key == 'r') refresh();
}

并将一对点 (PVector) 分组到 Line 类中:

int numRandomLines = 4;
ArrayList<Line> lines = new ArrayList<Line>();

void setup() {
  size(800, 800);
  stroke(0, 150, 255, 150);
  strokeWeight(3);
  refresh();
}

void refresh(){
  // remove previous points
  lines.clear();
  //add borders
  lines.add(new Line(new PVector(0, 0), new PVector(width, 0)));
  lines.add(new Line(new PVector(width, 0), new PVector(width - 1, height - 1)));
  lines.add(new Line(new PVector(0, height - 1), new PVector(width - 1, height - 1)));
  lines.add(new Line(new PVector(0, 0), new PVector(0, height - 1)));
  // add random lines
  for (int i=0; i< numRandomLines; i++) {
    lines.add(new Line(new PVector(random(800), 0), new PVector(random(800), height)));
  }
}

void draw(){
  background(255);
  
  for(Line line : lines) line.draw();
}

void keyReleased() {
  if (key == 'r') refresh();
}

class Line{
  
  PVector start;
  PVector end;
  
  Line(PVector start, PVector end){
    this.start = start;
    this.end = end;
  }
  
  void draw(){
    line(start.x, start.y, end.x, end.y);
  }
}

在这个阶段,要获得您的图表所描述的各个形状,我们可以作弊并使用像 OpenCV 这样的计算机视觉库。这当然是矫枉过正(因为我们将get() 一个PImage 绘图的副本,将其转换为OpenCV 图像)然后只需使用findContours() 来获取每个形状/轮廓。

回到原来的做法,线对线交集功能可以集成到Line类中:

int numRandomLines = 4;
ArrayList<Line> lines = new ArrayList<Line>();
ArrayList<PVector> intersections = new ArrayList<PVector>();

void setup() {
  size(800, 800);
  strokeWeight(3);
  refresh();
}

void refresh(){
  // remove previous points
  lines.clear();
  intersections.clear();
  //add borders
  lines.add(new Line(new PVector(0, 0), new PVector(width, 0)));
  lines.add(new Line(new PVector(width, 0), new PVector(width - 1, height - 1)));
  lines.add(new Line(new PVector(0, height - 1), new PVector(width - 1, height - 1)));
  lines.add(new Line(new PVector(0, 0), new PVector(0, height - 1)));
  // add random lines
  for (int i=0; i< numRandomLines; i++) {
    lines.add(new Line(new PVector(random(800), 0), new PVector(random(800), height)));
  }
  // compute intersections
  int numLines = lines.size();
  // when looping only check if lineA intersects lineB but not also if lineB intersects lineA (redundant)
  for (int i = 0; i < numLines - 1; i++){
    Line lineA = lines.get(i);
    for (int j = i + 1; j < numLines; j++){
      Line lineB = lines.get(j);
      // check intersection
      PVector intersection = lineA.intersect(lineB);
      // if there is one, append the intersection point to the list
      if(intersection != null){
        intersections.add(intersection);
      }
    }
  }
}

void draw(){
  background(255);
  stroke(0, 150, 255, 150);
  // draw lines
  for(Line line : lines) line.draw();
  stroke(255, 0, 0, 150);
  // draw intersections
  for(PVector intersection : intersections) ellipse(intersection.x, intersection.y, 9, 9);
}

void keyReleased() {
  if (key == 'r') refresh();
}

class Line{
  
  PVector start;
  PVector end;
  
  Line(PVector start, PVector end){
    this.start = start;
    this.end = end;
  }
  
  void draw(){
    line(start.x, start.y, end.x, end.y);
  }
  
  //source: http://jeffreythompson.org/collision-detection/line-line.php
  //boolean lineLine(float this.start.x, float this.start.y, float this.end.x, float this.end.y, 
                   //float other.start.x, float other.start.y, float other.end.x, float other.end.y) {
  PVector intersect(Line other) {
    // calculate the distance to intersection point
    float uA = ((other.end.x-other.start.x)*(this.start.y-other.start.y) - (other.end.y-other.start.y)*(this.start.x-other.start.x)) / ((other.end.y-other.start.y)*(this.end.x-this.start.x) - (other.end.x-other.start.x)*(this.end.y-this.start.y));
    float uB = ((this.end.x-this.start.x)*(this.start.y-other.start.y) - (this.end.y-this.start.y)*(this.start.x-other.start.x)) / ((other.end.y-other.start.y)*(this.end.x-this.start.x) - (other.end.x-other.start.x)*(this.end.y-this.start.y));
    // if uA and uB are between 0-1, lines are colliding
    if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
      // optionally, draw a circle where the lines meet
      float intersectionX = this.start.x + (uA * (this.end.x-this.start.x));
      float intersectionY = this.start.y + (uA * (this.end.y-this.start.y));
      
      return new PVector(intersectionX, intersectionY);
    }
    return null;
  }
}

下一步将是一个更复杂的算法,用于根据 x、y 位置(例如从上到下、从左到右)对点进行排序,迭代通过距离和角度比较第一个点与其余点并尝试工作如果具有最小距离和角度变化的连续点连接起来。

在网上快速浏览一下,我可以看到这样的算法,例如:

【讨论】:

  • 谢谢!这真的很有帮助。仅分离交点是有意义的。我将尝试使用 begin/endShape() 方法的不同选项。
  • 很高兴听到这个消息。我已经用更多代码 sn-ps 更新了答案,说明了另一种组织代码的方法。再看一下彩色多边形图,这是一个非平凡的算法,不幸的是我没有时间提供详细的处理实现,但我希望链接的论文可以成为一个很好的起点。
【解决方案2】:

我可以看到您的代码不是 javascript,但由于您没有指定语言,我假设您只需要一种方法并且可以转换为您的语言。

我处理这个问题的方法是为每一行分配一个行号。如果我可以识别一条线上的 2 个相邻点,那么我将通过检查它们不共享的线的交叉点是否存在一个点来知道第三个点是否存在。

示例: 有 3 行(第 1、2、3 行)

我在第 3 行和第 1 行之间有一个交点,现在我沿着第 3 行步行到一个相邻点。我找到了一个,它的交点是 3 和 2。我可以有一个三角形的唯一方法是通过线 1 和 2 交叉某处。所以我们可以通过编程方式进行检查。

请记住,我实际上从未为此使用过和角度。我确实在函数中计算了它们,但决定不使用它们,因为我使用上面解释的方法。我使用 0.1 的 alpha 值对三角形着色,以便您可以看到重叠的位置。

这只是检查三角形

let canvas = document.getElementById("canvas");
        let ctx = canvas.getContext("2d");
        canvas.width = 400;
        canvas.height = 400;

        let lines = []; //holds each line
        let points = []; //all intersection point are pushed here [{x: num, y: num}, {x: num, y: num},...]
        let sortedPts = []; //all points sorted bu first number are pushed here in 2d array.
        let lineNum = 15;

        class Lines {
            constructor(num) {
                this.x = Math.round(Math.random() * canvas.width);
                this.x2 = Math.round(Math.random() * canvas.width);
                this.pt1 = {
                    x: this.x,
                    y: 0
                };
                this.pt2 = {
                    x: this.x2,
                    y: canvas.height
                };
                this.num = num;
                this.rads = Math.atan2(this.pt2.y - this.pt1.y, this.pt2.x - this.pt1.x);
                this.angle = this.rads * (180 / Math.PI);
            }
            draw() {
                ctx.beginPath();
                ctx.moveTo(this.pt1.x, this.pt1.y);
                ctx.lineTo(this.pt2.x, this.pt2.y);
                ctx.stroke();
            }
        }

        //creates the lines. I also use this function to prepare the 2d array by pushing an empty array for each line into sortedPts.
        function createLines() {
            for (let i = 0; i < lineNum; i++) {
                lines.push(new Lines(i + 1));
                sortedPts.push([])
            }
        }
        createLines();

        //Visually draws lines on screen
        function drawLines() {
            for (let i = 0; i < lines.length; i++) {
                lines[i].draw();
            }
        }
        drawLines();

        //intersecting formula
        function lineSegmentsIntersect(line1, line2) {
            let a_dx = line1.pt2.x - line1.pt1.x;
            let a_dy = line1.pt2.y - line1.pt1.y;
            let b_dx = line2.pt2.x - line2.pt1.x;
            let b_dy = line2.pt2.y - line2.pt1.y;
            let s =
                (-a_dy * (line1.pt1.x - line2.pt1.x) + a_dx * (line1.pt1.y - line2.pt1.y)) /
                (-b_dx * a_dy + a_dx * b_dy);
            let t =
                (+b_dx * (line1.pt1.y - line2.pt1.y) - b_dy * (line1.pt1.x - line2.pt1.x)) /
                (-b_dx * a_dy + a_dx * b_dy);
            if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
                //this is where we create our array but we also add the line number of where each point intersects. I also add the angle but have not used it throughout the rest of this...yet.
                points.push({
                    x: Math.round(line1.pt1.x + t * (line1.pt2.x - line1.pt1.x)),
                    y: Math.round(line1.pt1.y + t * (line1.pt2.y - line1.pt1.y)),
                    num: {
                        first: line1.num,
                        second: line2.num
                    },
                    angle: {
                        a1: line1.angle,
                        a2: line2.angle
                    }
                });
            }
        }

        //just checks each line against the others by passing to lineSegmentsIntersect() function
        function callIntersect() {
            for (let i = 0; i < lines.length; i++) {
                for (let j = i + 1; j < lines.length; j++) {
                    lineSegmentsIntersect(lines[i], lines[j]);
                }
            }
        }
        callIntersect();

        function drawPoints() {
            //just draws the black points for reference
            for (let i = 0; i < points.length; i++) {
                ctx.beginPath();
                ctx.arc(points[i].x, points[i].y, 2, 0, Math.PI * 2);
                ctx.fill();
            }
        }
        drawPoints();

        function createSortedArray() {
            //Now we take the points array and sort the points by the first number to make using i and j below possible
            points.sort((a, b) => a.num.first - b.num.first)
            //We push each group of points into an array inside sortedPts creating the 2d array 
            for (let i = 0; i < lineNum; i++) {
                for (let j = 0; j < points.length; j++) {
                    if (points[j].num.first == (i + 1)) {
                        sortedPts[i].push(points[j]);
                    }
                }
            }
            //now sort the 2d arrays by y value. This allows or next check to go in order from point to point per line.
            sortedPts.forEach(arr => arr.sort((a, b) => a.y - b.y));

            fillTriangles();
        }
        createSortedArray();

        /*
        The last step iterates through each point in the original points array
        and check to see if either the first or second number matches the second
        number of a point in our sortedPts array AND do the first or second number
        match the next points in the sortedPtsd array. If so then we must have a
        triangle.

        Quick breakdown. If we have 3 lines (line 1, 2, 3) and I have a points on lines
        2 & 3. I also have another point on lines 2 & 1. Then in order to have a triangle
        the last point must be on lines 1 & 3. 

        That's all this is doing.
        */
        function fillTriangles() {
            //iterate through each array inside sortedPts array
            for (let i = 0; i < sortedPts.length; i++) {
                //iterate through all points inside each array of points inside the sortedPts array
                for (let j = 0; j < sortedPts[i].length - 1; j++) {
                    //iterate over the original points and compare
                    for (let k = 0; k < points.length; k++) {
                        if (
                            (points[k].num.first == sortedPts[i][j].num.second ||
                                points[k].num.second == sortedPts[i][j].num.second) &&
                            (points[k].num.first == sortedPts[i][j + 1].num.second ||
                                points[k].num.second == sortedPts[i][j + 1].num.second)
                        ) {
                            ctx.fillStyle = "rgba(200, 100, 0, 0.1)";
                            ctx.beginPath();
                            ctx.moveTo(sortedPts[i][j].x, sortedPts[i][j].y);
                            ctx.lineTo(sortedPts[i][j + 1].x, sortedPts[i][j + 1].y);
                            ctx.lineTo(points[k].x, points[k].y);
                            ctx.closePath();
                            ctx.fill();
                        }
                    }
                }
            }
        }
&lt;canvas id="canvas"&gt;&lt;/canvas&gt;

我也认为有一个很好的方法可以通过交叉线的角度来做到这一点,并且正在努力做到这一点。我希望我可以让它根据边数确定形状的类型,但我认为这不是一个快速的项目。

【讨论】:

  • 谢谢!为行分配 id 是个好主意。我不熟悉 javascript,但能够按照您的步骤进行操作。我会将它们应用于处理代码!
【解决方案3】:

我不清楚你的目标。您可以以任意顺序连接任意一组点并将其称为形状。你的标准是什么?

如果你想找到连接给定子集所有点的最短路径,我建议寻找旅行推销员问题。

【讨论】:

    猜你喜欢
    • 2011-10-27
    • 2015-05-19
    • 2020-12-31
    • 2014-02-11
    • 2021-09-19
    • 1970-01-01
    • 2019-04-23
    • 1970-01-01
    • 2013-10-02
    相关资源
    最近更新 更多