Background
:
Cyrus Beck is a line clipping algorithm that is made for convex polygons. It allows line clipping for non-rectangular windows, unlike
Cohen Sutherlandor
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:
- Normals of every edge is calculated.
- Vector for the clipping line is calculated.
- Dot product between the difference of one vertex per edge and one selected end point of the clipping line and the normal of the edge is calculated (for all edges).
- Dot product between the vector of the clipping line and the normal of edge (for all edges) is calculated.
- The former dot product is divided by the latter dot product and multiplied by -1. This is 't'.
- The values of 't' are classified as entering or exiting (from all edges) by observing their denominators (latter dot product).
- One value of 't' is chosen from each group, and put into the parametric form of a line to calculate the coordinates.
- If the entering 't' value is greater than the exiting 't' value, then the clipping line is rejected.
Cases
:
- 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 - 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
- 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.
P0 - PEi
P1 - P0
Ni . (P0 - PEi)
Ni . (P1 - P0)
where i -> ith edge of the convex polygon
Ni . (P0 - PEi)
t = ------------------
-(Ni . (P1 - P0))
- Implementation
- :
- 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.
- CPP
// 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; }
Java// 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(); } }
C#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(); } } }
Javascript// 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]}}); }
Python3import 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()
- Output:
(102, 50) (240, 109)
- Before Clipping:
- After Clipping: