Open In App

Line Clipping | Set 2 (Cyrus Beck Algorithm)

Background

:

Cyrus Beck is a line clipping algorithm that is made for convex polygons. It allows line clipping for non-rectangular windows, unlike

Cohen Sutherland

or

Nicholl Le Nicholl

. It also removes the repeated clipping needed in

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



Algorithm:

Cases

:

  1. Case 1: The line is partially inside the clipping window:
    0 < tE < tL < 1
    where tE is 't' value for entering intersection point
    tL is 't' value for exiting intersection point



  2. Case 2: The line has one point inside or both sides inside the window or the intersection points are on the end points of the line:
    0 ≤ tE ≤ tL ≤ 1



  3. Case 3: The line is completely outside the window:
    tL < tE



Pseudocode:

First, calculate the parametric form of the line to be clipped and then follow the algorithm.

  • Choose a point called P1 from the two points of the line (P0P1).
  • Now for each edge of the polygon, calculate the normal pointing away from the centre of the polygon, namely N1, N2, etc.
  • Now for each edge choose PEi (i -> ith edge) (choose any of the vertices of the corresponding edge, eg.: For polygon ABCD, for side AB, PEi can be either point A or point B) and calculate
    P0 - PEi 



  • Then calculate
    P1 - P0



  • Then calculate the following dot products for each edge:
    Ni . (P0 - PEi)
    Ni . (P1 - P0)
    where i -> ith edge of the convex polygon



  • Then calculate the corresponding 't' values for each edge by:
        Ni . (P0 - PEi)
    t = ------------------
    -(Ni . (P1 - P0))



  • Then club the 't' values for which the Ni . (P1 - P0) came out to be negative and take the minimum of all of them and 1.
  • Similarly club all the 't' values for which the Ni . (P1 - P0) came out to be positive and take the maximum of all of the clubbed 't' values and 0.
  • Now the two 't' values obtained from this algorithm are plugged into the parametric form of the 'to be clipped' line and the resulting two points obtained are the clipped points.
    1. Implementation
    2. :
    3. Here is an implementation of the above steps in SFML C++ Graphics Library. You can also press any key to unclip the line and press any key to clip the line.
    4. // C++ Program to implement Cyrus Beck
      
      #include <SFML/Graphics.hpp>
      #include <iostream>
      #include <utility>
      #include <vector>
      
      using namespace std;
      using namespace sf;
      
      // Function to draw a line in SFML
      void drawline(RenderWindow* window, pair<int, int> p0, pair<int, int> 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<int, int> 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<int, int> p0, pair<int, int> p1)
      {
          return p0.first * p1.first + p0.second * p1.second;
      }
      
      // Function to calculate the max from a vector of floats
      float max(vector<float> 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<float> 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<int, int>* CyrusBeck(pair<int, int> vertices[],
                              pair<int, int> line[], int n)
      {
      
          // Temporary holder value that will be returned
          pair<int, int>* newPair = new pair<int, int>[2];
      
          // Normals initialized dynamically(can do it statically also, doesn't matter)
          pair<int, int>* normal = new pair<int, int>[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<int, int> P1_P0
              = make_pair(line[1].first - line[0].first,
                          line[1].second - line[0].second);
      
          // Initializing all values of P0 - PEi
          pair<int, int>* P0_PEi = new pair<int, int>[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<float> 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].first
              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<int, int> 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<int, int> line[] = { make_pair(10, 10), make_pair(450, 200) };
          pair<int, int>* temp1 = CyrusBeck(vertices, line, n);
          pair<int, int> 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;
      }
      
      // Import necessary JavaFX classes
      import javafx.application.Application;
      import javafx.scene.Scene;
      import javafx.scene.layout.Pane;
      import javafx.scene.shape.Line;
      import javafx.scene.shape.Polygon;
      import javafx.stage.Stage;
      
      // Main class that extends from JavaFX Application class
      public class Main extends Application {
      
          // Main function that launches the application
          public static void main(String[] args) {
              launch(args);
          }
      
          // Override the start method in the Application class
          @Override
          public void start(Stage primaryStage) {
              // Set the title of the window
              primaryStage.setTitle("Cyrus Beck Algorithm");
      
              // Create a new Polygon object
              Polygon polygon = new Polygon();
              // Add points to the polygon
              polygon.getPoints().addAll(new Double[]{
                      200.0, 50.0,
                      250.0, 100.0,
                      200.0, 150.0,
                      100.0, 150.0,
                      50.0, 100.0,
                      100.0, 50.0
              });
      
              // Create a new Line object
              Line line = new Line(0, 0, 500, 500);
      
              // Create a new Pane (container) object
              Pane root = new Pane();
              // Add the polygon and line to the pane
              root.getChildren().add(polygon);
              root.getChildren().add(line);
      
              // Create a new Scene with the pane and set it on the stage
              primaryStage.setScene(new Scene(root, 600, 400));
              // Display the stage
              primaryStage.show();
          }
      }
      
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using SFML.Graphics;
      using SFML.System;
      using SFML.Window;
      
      class Program
      {
          // Function to draw a line in SFML
          static void DrawLine(RenderWindow window, Vector2i p0, Vector2i p1)
          {
              VertexArray line = new VertexArray(PrimitiveType.Lines, 2);
              line[0] = new Vertex(new Vector2f(p0.X, p0.Y));
              line[1] = new Vertex(new Vector2f(p1.X, p1.Y));
              window.Draw(line);
          }
      
          // Function to draw a polygon, given vertices
          static void DrawPolygon(RenderWindow window, Vector2i[] vertices)
          {
              for (int i = 0; i < vertices.Length - 1; i++)
                  DrawLine(window, vertices[i], vertices[i + 1]);
              DrawLine(window, vertices[0], vertices[vertices.Length - 1]);
          }
      
          // Function to take dot product
          static int Dot(Vector2i p0, Vector2i p1)
          {
              return p0.X * p1.X + p0.Y * p1.Y;
          }
      
          // Function to calculate the max from a list of floats
          static float Max(List<float> t)
          {
              return t.Max();
          }
      
          // Function to calculate the min from a list of floats
          static float Min(List<float> t)
          {
              return t.Min();
          }
      
          // Cyrus Beck function, returns a pair of values
          // that are then displayed as a line
          static Vector2i[] CyrusBeck(Vector2i[] vertices, Vector2i[] line)
          {
              // Temporary holder value that will be returned
              Vector2i[] newPair = new Vector2i[2];
      
              // Normals initialized dynamically(can do it statically also, doesn't matter)
              Vector2i[] normal = new Vector2i[vertices.Length];
      
              // Calculating the normals
              for (int i = 0; i < vertices.Length; i++)
              {
                  normal[i].Y = vertices[(i + 1) % vertices.Length].X - vertices[i].X;
                  normal[i].X = vertices[i].Y - vertices[(i + 1) % vertices.Length].Y;
              }
      
              // Calculating P1 - P0
              Vector2i P1_P0 = new Vector2i(line[1].X - line[0].X, line[1].Y - line[0].Y);
      
              // Initializing all values of P0 - PEi
              Vector2i[] P0_PEi = new Vector2i[vertices.Length];
      
              // Calculating the values of P0 - PEi for all edges
              for (int i = 0; i < vertices.Length; i++)
              {
                  // Calculating PEi - P0, so that the
                  // denominator won't have to multiply by -1
                  P0_PEi[i].X = vertices[i].X - line[0].X;
      
                  // while calculating 't'
                  P0_PEi[i].Y = vertices[i].Y - line[0].Y;
              }
      
              int[] numerator = new int[vertices.Length], denominator = new int[vertices.Length];
      
              // Calculating the numerator and denominators
              // using the dot function
              for (int i = 0; i < vertices.Length; 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[vertices.Length];
      
              // Making two vectors called 't entering'
              // and 't leaving' to group the 't's
              List<float> tE = new List<float>(), tL = new List<float>();
      
              // Calculating 't' and grouping them accordingly
              for (int i = 0; i < vertices.Length; i++)
              {
                  t[i] = (float)numerator[i] / (float)denominator[i];
      
                  if (denominator[i] > 0)
                      tE.Add(t[i]);
                  else
                      tL.Add(t[i]);
              }
      
              // Initializing the final two values of 't'
              float[] temp = new float[2];
      
              // Taking the max of all 'tE' and 0, so pushing 0
              tE.Add(0f);
              temp[0] = Max(tE);
      
              // Taking the min of all 'tL' and 1, so pushing 1
              tL.Add(1f);
              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] = new Vector2i(-1, -1);
                  newPair[1] = new Vector2i(-1, -1);
                  return newPair;
              }
      
              // Calculating the coordinates in terms of x and y
              newPair[0] = new Vector2i((int)(line[0].X + P1_P0.X * temp[0]), (int)(line[0].Y + P1_P0.Y * temp[0]));
              newPair[1] = new Vector2i((int)(line[0].X + P1_P0.X * temp[1]), (int)(line[0].Y + P1_P0.Y * temp[1]));
      
              return newPair;
          }
      
          static void Main(string[] args)
          {
              // Setting up a window and loop
              // and the vertices of the polygon and line
              RenderWindow window = new RenderWindow(new VideoMode(500, 500), "Cyrus Beck");
              Vector2i[] vertices = new Vector2i[]
              {
                  new Vector2i(200, 50),
                  new Vector2i(250, 100),
                  new Vector2i(200, 150),
                  new Vector2i(100, 150),
                  new Vector2i(50, 100),
                  new Vector2i(100, 50)
              };
      
              // Make sure that the vertices
              // are put in a clockwise order
              Vector2i[] line = new Vector2i[] { new Vector2i(10, 10), new Vector2i(450, 200) };
              Vector2i[] temp1 = CyrusBeck(vertices, line);
              Vector2i[] temp2 = new Vector2i[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();
                  window.DispatchEvents();
      
                  if (Keyboard.IsKeyPressed(Keyboard.Key.Space))
                      trigger = !trigger;
      
                  DrawPolygon(window, vertices);
      
                  // 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();
              }
          }
      }
      
      // Import plotly.js
      var Plotly = require('plotly.js-dist');
      
      // Function to draw a line in plotly.js
      function drawline(p0, p1) {
          var trace = {
              x: [p0[0], p1[0]],
              y: [p0[1], p1[1]],
              mode: 'lines',
              line: {color: 'black'}
          };
          return trace;
      }
      
      // Function to draw a polygon, given vertices
      function drawPolygon(vertices) {
          vertices.push(vertices[0]); // repeat the first point to create a 'closed loop'
          var xs = vertices.map(v => v[0]); // create list of x values
          var ys = vertices.map(v => v[1]); // create list of y values
          var trace = {
              x: xs,
              y: ys,
              fill: 'toself',
              fillcolor: 'rgba(255, 0, 0, 0.5)',
              line: {color: 'red'},
              mode: 'lines'
          };
          return trace;
      }
      
      // Function to take dot product
      function dot(p0, p1) {
          return p0[0] * p1[0] + p0[1] * p1[1];
      }
      
      // Function to calculate the max from a list of floats
      function max(t) {
          return Math.max(...t);
      }
      
      // Function to calculate the min from a list of floats
      function min(t) {
          return Math.min(...t);
      }
      
      // Cyrus Beck function, returns a pair of values
      // that are then displayed as a line
      function CyrusBeck(vertices, line) {
          var n = vertices.length;
          var P1_P0 = [line[1][0] - line[0][0], line[1][1] - line[0][1]];
          var normal = vertices.map((v, i) => [vertices[i][1] - vertices[(i + 1) % n][1], vertices[(i + 1) % n][0] - vertices[i][0]]);
          var P0_PEi = vertices.map(v => [v[0] - line[0][0], v[1] - line[0][1]]);
          var numerator = normal.map((n, i) => dot(n, P0_PEi[i]));
          var denominator = normal.map(n => dot(n, P1_P0));
          var t = denominator.map((d, i) => d != 0 ? numerator[i] / d : 0);
          var tE = t.filter((t, i) => denominator[i] > 0);
          var tL = t.filter((t, i) => denominator[i] < 0);
          tE.push(0);
          tL.push(1);
          var temp = [max(tE), min(tL)];
          if (temp[0] > temp[1]) {
              return null;
          }
          var newPair = [[line[0][0] + P1_P0[0] * temp[0], line[0][1] + P1_P0[1] * temp[0]], 
                         [line[0][0] + P1_P0[0] * temp[1], line[0][1] + P1_P0[1] * temp[1]]];
          return newPair;
      }
      
      // Driver code
      var vertices = [[200, 50], [250, 100], [200, 150], [100, 150], [50, 100], [100, 50]];
      var line = [[0, 0], [500, 500]];  // New line coordinates
      
      // Before Clipping
      var data = [drawPolygon(vertices), drawline(line[0], line[1])];  // Draw the original line
      Plotly.newPlot('myDiv', data, {title: 'Before Clipping', xaxis: {range: [0, 500]}, yaxis: {range: [0, 500]}});
      
      // After Clipping
      var newPair = CyrusBeck(vertices, line);
      if (newPair !== null) {
          data = [drawPolygon(vertices), drawline(newPair[0], newPair[1])];  // Draw the clipped line
          Plotly.newPlot('myDiv', data, {title: 'After Clipping', xaxis: {range: [0, 500]}, yaxis: {range: [0, 500]}});
      }
      
      import matplotlib.pyplot as plt
      import numpy as np
      
      # Function to draw a line in matplotlib
      def drawline(p0, p1):
          plt.plot([p0[0], p1[0]], [p0[1], p1[1]], 'k-')
      
      # Function to draw a polygon, given vertices
      def drawPolygon(vertices):
          vertices.append(vertices[0]) # repeat the first point to create a 'closed loop'
          xs, ys = zip(*vertices) # create lists of x and y values
          plt.fill(xs, ys, edgecolor='r', fill=False)
      
      # Function to take dot product
      def dot(p0, p1):
          return p0[0] * p1[0] + p0[1] * p1[1]
      
      # Function to calculate the max from a list of floats
      def max(t):
          return np.max(t)
      
      # Function to calculate the min from a list of floats
      def min(t):
          return np.min(t)
      
      # Cyrus Beck function, returns a pair of values
      # that are then displayed as a line
      def CyrusBeck(vertices, line):
          n = len(vertices)
          P1_P0 = (line[1][0] - line[0][0], line[1][1] - line[0][1])
          normal = [(vertices[i][1] - vertices[(i + 1) % n][1], vertices[(i + 1) % n][0] - vertices[i][0]) for i in range(n)]
          P0_PEi = [(vertices[i][0] - line[0][0], vertices[i][1] - line[0][1]) for i in range(n)]
          numerator = [dot(normal[i], P0_PEi[i]) for i in range(n)]
          denominator = [dot(normal[i], P1_P0) for i in range(n)]
          t = [numerator[i] / denominator[i] if denominator[i] != 0 else 0 for i in range(n)]
          tE = [t[i] for i in range(n) if denominator[i] > 0]
          tL = [t[i] for i in range(n) if denominator[i] < 0]
          tE.append(0)
          tL.append(1)
          temp = [max(tE), min(tL)]
          if temp[0] > temp[1]:
              return None
          newPair = [(line[0][0] + P1_P0[0] * temp[0], line[0][1] + P1_P0[1] * temp[0]), 
                     (line[0][0] + P1_P0[0] * temp[1], line[0][1] + P1_P0[1] * temp[1])]
          return newPair
      
      # Driver code
      if __name__ == "__main__":
          vertices = [(200, 50), (250, 100), (200, 150), (100, 150), (50, 100), (100, 50)]
          line = [(0, 0), (500, 500)]  # New line coordinates
      
          # Before Clipping
          plt.figure(figsize=(6, 6))
          plt.title('Before Clipping')
          drawPolygon(vertices)
          drawline(line[0], line[1])  # Draw the original line
          plt.xlim(0, 500)
          plt.ylim(0, 500)
          plt.show()
      
          # After Clipping
          newPair = CyrusBeck(vertices, line)
          if newPair is not None:
              plt.figure(figsize=(6, 6))
              plt.title('After Clipping')
              drawPolygon(vertices)
              drawline(newPair[0], newPair[1])  # Draw the clipped line
              plt.xlim(0, 500)
              plt.ylim(0, 500)
              plt.show()
      
    5. Output:
    6. (102, 50) (240, 109)



      • Before Clipping:
      • After Clipping:
    Article Tags :