📜  剪线|组合2(Cyrus Beck算法)

📅  最后修改于: 2021-04-21 23:19:23             🧑  作者: Mango

背景
赛勒斯·贝克(Cyrus Beck)是针对凸多边形制作的线裁剪算法。与Cohen Sutherland或Nicholl Le Nicholl不同,它允许对非矩形窗口进行剪裁。它还消除了科恩·萨瑟兰(Cohen Sutherland)所需的重复剪辑。

Input: 
 1. Convex area of interest 
    which is defined by a set of coordinates
    given in a clockwise fashion.
 2. vertices which are an array of coordinates: 
    consisting of pairs (x, y)
 3. n which is the number of vertices
 4. A line to be clipped 
    given by a set of coordinates.
 5. line which is an array of coordinates: 
    consisting of two pairs, (x0, y0) and (x1, y1)
Output:
 1. Coordinates of line clipping which is the Accepted clipping
 2. Coordinates (-1, -1) which is the Rejected clipping

算法:

  • 计算每个边缘的法线。
  • 计算剪裁线的向量。
  • 计算每个边缘一个顶点与剪切线的一个选定端点的差与边缘法线之间的点积(对于所有边缘)。
  • 计算剪裁线的向量和边缘法线(对于所有边缘)之间的点积。
  • 前一个点积除以后一个点积并乘以-1。这是“ t”。
  • “ t”的值通过观察其分母(后乘积)而分类为(从所有边开始)进入或离开。
  • 从每个组中选择一个值“ t”,并将其放入线的参数形式以计算坐标。
  • 如果输入的“ t”值大于输出的“ t”值,则剪切线将被拒绝。

情况

  1. 情况1:行部分位于剪切窗口内:
    0 < tE < tL < 1
    
    where tE is 't' value for entering intersection point
          tL is 't' value for exiting intersection point
    
  2. 情况2:线在窗口内有一个点或在窗口的两侧,或者相交点在该线的端点上
    0 ≤ tE ≤ tL ≤ 1
  3. 情况3:该行完全在窗口之外:
    tL < tE

伪代码:

首先,计算要剪切的线的参数形式,然后遵循算法。

  • 从直线的两个点(P 0 P 1 )中选择一个称为P 1的点。
  • 现在,对于多边形的每个边缘,计算远离多边形中心的法线指向,即N 1 ,N 2等。
  • 现在为每个边选择P Ei (i-> i th边)(选择相应边的任意一个顶点,例如:对于多边形ABCD,对于边AB,P Ei可以是点A或点B)并计算
    P0 - PEi 
  • 然后计算
    P1 - P0
  • 然后为每个边计算以下点积:
    Ni . (P0 - PEi)
    Ni . (P1 - P0) 
    
    where i -> ith edge of the convex polygon
  • 然后通过以下方式为每个边缘计算相应的“ t”值:
    Ni . (P0 - PEi)
    t = ------------------
        -(Ni . (P1 - P0))
  • 然后将N i的“ t”值取整。 (P 1 – P 0 )变为负数,并取所有最小值和1。
  • 类似地,将所有N i的“ t”值归并。 (P 1 – P 0 )表示为正,并取所有杆状’t’值的最大值和0。
  • 现在,将从该算法获得的两个“ t”值插入“待剪切”线的参数形式,并且获得的结果两个点就是剪切点。

      实现这是SFML C++图形库中上述步骤的实现。您也可以按任意键以取消剪切线,并按任意键以剪切线。

      // C++ Program to implement Cyrus Beck
        
      #include 
      #include 
      #include 
      #include 
        
      using namespace std;
      using namespace sf;
        
      // Function to draw a line in SFML
      void drawline(RenderWindow* window, pair p0, pair p1)
      {
          Vertex line[] = {
              Vertex(Vector2f(p0.first, p0.second)),
              Vertex(Vector2f(p1.first, p1.second))
          };
          window->draw(line, 2, Lines);
      }
        
      // Function to draw a polygon, given vertices
      void drawPolygon(RenderWindow* window, pair vertices[], int n)
      {
          for (int i = 0; i < n - 1; i++)
              drawline(window, vertices[i], vertices[i + 1]);
          drawline(window, vertices[0], vertices[n - 1]);
      }
        
      // Function to take dot product
      int dot(pair p0, pair p1)
      {
          return p0.first * p1.first + p0.second * p1.second;
      }
        
      // Function to calculate the max from a vector of floats
      float max(vector t)
      {
          float maximum = INT_MIN;
          for (int i = 0; i < t.size(); i++)
              if (t[i] > maximum)
                  maximum = t[i];
          return maximum;
      }
        
      // Function to calculate the min from a vector of floats
      float min(vector t)
      {
          float minimum = INT_MAX;
          for (int i = 0; i < t.size(); i++)
              if (t[i] < minimum)
                  minimum = t[i];
          return minimum;
      }
        
      // Cyrus Beck function, returns a pair of values
      // that are then displayed as a line
      pair* CyrusBeck(pair vertices[],
                                pair line[], int n)
      {
        
          // Temporary holder value that will be returned
          pair* newPair = new pair[2];
        
          // Normals initialized dynamically(can do it statically also, doesn't matter)
          pair* normal = new pair[n];
        
          // Calculating the normals
          for (int i = 0; i < n; i++) {
              normal[i].second = vertices[(i + 1) % n].first - vertices[i].first;
              normal[i].first = vertices[i].second - vertices[(i + 1) % n].second;
          }
        
          // Calculating P1 - P0
          pair P1_P0
              = make_pair(line[1].first - line[0].first,
                          line[1].second - line[0].second);
        
          // Initializing all values of P0 - PEi
          pair* P0_PEi = new pair[n];
        
          // Calculating the values of P0 - PEi for all edges
          for (int i = 0; i < n; i++) {
        
              // Calculating PEi - P0, so that the
              // denominator won't have to multiply by -1
              P0_PEi[i].first
                  = vertices[i].first - line[0].first;
        
              // while calculating 't'
              P0_PEi[i].second = vertices[i].second - line[0].second;
          }
        
          int *numerator = new int[n], *denominator = new int[n];
        
          // Calculating the numerator and denominators
          // using the dot function
          for (int i = 0; i < n; i++) {
              numerator[i] = dot(normal[i], P0_PEi[i]);
              denominator[i] = dot(normal[i], P1_P0);
          }
        
          // Initializing the 't' values dynamically
          float* t = new float[n];
        
          // Making two vectors called 't entering'
          // and 't leaving' to group the 't's
          // according to their denominators
          vector tE, tL;
        
          // Calculating 't' and grouping them accordingly
          for (int i = 0; i < n; i++) {
        
              t[i] = (float)(numerator[i]) / (float)(denominator[i]);
        
              if (denominator[i] > 0)
                  tE.push_back(t[i]);
              else
                  tL.push_back(t[i]);
          }
        
          // Initializing the final two values of 't'
          float temp[2];
        
          // Taking the max of all 'tE' and 0, so pushing 0
          tE.push_back(0.f);
          temp[0] = max(tE);
        
          // Taking the min of all 'tL' and 1, so pushing 1
          tL.push_back(1.f);
          temp[1] = min(tL);
        
          // Entering 't' value cannot be
          // greater than exiting 't' value,
          // hence, this is the case when the line
          // is completely outside
          if (temp[0] > temp[1]) {
              newPair[0] = make_pair(-1, -1);
              newPair[1] = make_pair(-1, -1);
              return newPair;
          }
        
          // Calculating the coordinates in terms of x and y
          newPair[0].firs
              t
              = (float)line[0].first
                + (float)P1_P0.first * (float)temp[0];
          newPair[0].second
              = (float)line[0].second
                + (float)P1_P0.second * (float)temp[0];
          newPair[1].first
              = (float)line[0].first
                + (float)P1_P0.first * (float)temp[1];
          newPair[1].second
              = (float)line[0].second
                + (float)P1_P0.second * (float)temp[1];
          cout << '(' << newPair[0].first << ", "
               << newPair[0].second << ") ("
               << newPair[1].first << ", "
               << newPair[1].second << ")";
        
          return newPair;
      }
        
      // Driver code
      int main()
      {
        
          // Setting up a window and loop
          // and the vertices of the polygon and line
          RenderWindow window(VideoMode(500, 500), "Cyrus Beck");
          pair vertices[]
              = { make_pair(200, 50),
                  make_pair(250, 100),
                  make_pair(200, 150),
                  make_pair(100, 150),
                  make_pair(50, 100),
                  make_pair(100, 50) };
        
          // Make sure that the vertices
          // are put in a clockwise order
          int n = sizeof(vertices) / sizeof(vertices[0]);
          pair line[] = { make_pair(10, 10), make_pair(450, 200) };
          pair* temp1 = CyrusBeck(vertices, line, n);
          pair temp2[2];
          temp2[0] = line[0];
          temp2[1] = line[1];
        
          // To allow clipping and unclipping
          // of the line by just pressing a key
          bool trigger = false;
          while (window.isOpen()) {
              window.clear();
              Event event;
              if (window.pollEvent(event)) {
                  if (event.type == Event::Closed)
                      window.close();
                  if (event.type == Event::KeyPressed)
                      trigger = !trigger;
              }
              drawPolygon(&window, vertices, n);
        
              // Using the trigger value to clip
              // and unclip a line
              if (trigger) {
                  line[0] = temp1[0];
                  line[1] = temp1[1];
              }
              else {
                  line[0] = temp2[0];
                  line[1] = temp2[1];
              }
              drawline(&window, line[0], line[1]);
              window.display();
          }
          return 0;
      }
      

      输出:

      (102, 50) (240, 109)
    • 剪裁之前:

    • 剪裁后: