Last active
August 29, 2015 14:08
-
-
Save kevinmartinjos/a9463090a8c69f8d4ff2 to your computer and use it in GitHub Desktop.
Sutherland-hogson polygon clipping javascript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!-- | |
| Sutherland-Hodgson polygon clipping algorithm using html5 canvas and javascript. Click to clip polygon sequentially | |
| Copyright (C) 2014 Kevin Martin Jose | |
| --> | |
| <!-- | |
| This program is free software: you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation, either version 3 of the License, or | |
| (at your option) any later version. | |
| This program is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU General Public License for more details. | |
| --> | |
| <html> | |
| <head> | |
| <title>CG assignment 1 Q2</title> | |
| </head> | |
| <body> | |
| <canvas width="800" height="800" id="canvas"></canvas> | |
| <script type="text/javascript"> | |
| /*HOW TO USE : | |
| Open this file in a modern browser (firefox version 26 and above) | |
| Click anywhere on the diagram to start clipping the polygon | |
| To see console log, press ctrl+shift+k | |
| */ | |
| //N.B : All variables declared with the prefix 'var' are global | |
| //viewport is a square of vertices a, b, c and d | |
| var ax = 200; | |
| var ay = 200; | |
| var bx = 500; | |
| var by = 200; | |
| var cx = 500; | |
| var cy = 500; | |
| var dx = 200; | |
| var dy = 500; | |
| //min max values required to know the bounds of the viewport | |
| var xmin = ax | |
| var xmax = cx | |
| var ymin = ay; | |
| var ymax = cy; | |
| //vertices in the polygon and the new clipped polygon | |
| var polygonPath=[]; | |
| var clippedPath=[]; | |
| //Determines which edge to clip when mouse is clicked | |
| var clipEdge=0; | |
| //Boilerplate code. Required to access the html5 canvas | |
| var canvas = document.getElementById("canvas"); | |
| var context = canvas.getContext("2d"); | |
| //Google "html5 compositing" | |
| context.globalCompositeOperation = 'source-over'; | |
| //The next 4 blocks draw our viewport | |
| //drawing line x=200, of length 300 units (pixels) | |
| context.beginPath(); | |
| context.moveTo(ax, ay); | |
| context.lineTo(bx, by); | |
| context.stroke(); | |
| //drawing line y=200, length 300 pixels | |
| context.beginPath(); | |
| context.moveTo(bx, by); | |
| context.lineTo(cx,cy); | |
| context.stroke(); | |
| //drawing line x=500 | |
| context.beginPath(); | |
| context.moveTo(cx, cy); | |
| context.lineTo(dx, dy); | |
| context.stroke(); | |
| //drawing line y=500 | |
| context.beginPath(); | |
| context.moveTo(dx, dy); | |
| context.lineTo(ax, ay); | |
| context.stroke() | |
| //Put the vertices of our original polygon into polygonPath | |
| polygonPath.push([100, 400]); | |
| polygonPath.push([350, 100]); | |
| polygonPath.push([600, 400]); | |
| polygonPath.push([400, 400]); | |
| polygonPath.push([380, 600]); | |
| polygonPath.push([320, 600]); | |
| polygonPath.push([300, 400]); | |
| polygonPath.push([100, 400]); | |
| //Draw the polygon filled with red | |
| context.fillStyle = "#ff0000"; | |
| drawPolygon(polygonPath); | |
| //Executes on every mouse click | |
| canvas.addEventListener('mousedown', function(){ | |
| // | |
| clipEdge++; | |
| clip(polygonPath); | |
| }); | |
| function clip(path) | |
| { | |
| switch(clipEdge) | |
| { | |
| case 1: | |
| //We draw a white polygon of same dimensions of the original one to erase it | |
| //Then we draw the new clipped polygon with respect to each edge | |
| clip_left(path); | |
| context.fillStyle = "#ffffff"; | |
| drawPolygon(path); | |
| context.fillStyle = "#00ff00"; | |
| drawPolygon(clippedPath); | |
| break; | |
| case 2: | |
| //slice() clones an array and returns a reference to it | |
| //After each clipping, the clipped polygon becomes the polygon to be clipped next | |
| path = clippedPath.slice(); | |
| clippedPath = []; | |
| clip_top(path); | |
| context.fillStyle = "#ffffff"; | |
| drawPolygon(path); | |
| context.fillStyle = "#0000ff"; | |
| drawPolygon(clippedPath); | |
| break; | |
| case 3: | |
| path = clippedPath.slice(); | |
| clippedPath = []; | |
| clip_right(path); | |
| context.fillStyle = "#ffffff"; | |
| drawPolygon(path); | |
| context.fillStyle = "#ffff00"; | |
| drawPolygon(clippedPath); | |
| break; | |
| case 4: | |
| path = clippedPath.slice(); | |
| clippedPath = []; | |
| clip_down(path); | |
| context.fillStyle = "#ffffff"; | |
| drawPolygon(path); | |
| context.fillStyle = "#ff0000"; | |
| drawPolygon(clippedPath); | |
| break; | |
| } | |
| } | |
| function clip_left(path) | |
| { | |
| console.log("clipping from left"); | |
| console.log("path : " + path); | |
| for(i=0; i<path.length-1; i++) | |
| { | |
| //Both points outside | |
| if(!isInside(path[i], 'left') && !isInside(path[i+1], 'left')) | |
| { | |
| console.log('Both ' + path[i] + " and " + path[i+1] + " are outside the clipping area"); | |
| //No points are added to the clipped polygon | |
| } | |
| //Both points inside | |
| else if(isInside(path[i], 'left') && isInside(path[i+1], 'left')) | |
| { | |
| console.log('Both ' + path[i] + " and " + path[i+1] + " are inside the clipping area"); | |
| //The ith point is added to the clipped polygon | |
| clippedPath.push(path[i]); | |
| } | |
| //ith point is inside and i+1t point is outside | |
| else if(isInside(path[i], 'left')) | |
| { | |
| if(!isInside(path[i+1], 'left')) | |
| { | |
| console.log('Point ' + path[i] + ' is inside and ' + path[i+1] + ' is outside'); | |
| //Find the intersection of the point outside left edge and the left edge | |
| endpoints = [path[i], path[i+1]]; | |
| intersection = find_intersection(endpoints, 'left'); | |
| console.log("intersects at " + intersection); | |
| clippedPath.push(path[i]); | |
| clippedPath.push(intersection); | |
| } | |
| } | |
| else if(!isInside(path[i], 'left')) | |
| { | |
| if(isInside(path[i+1], 'left')) | |
| { | |
| console.log('Point ' + path[i] + ' is outside and ' + path[i+1] + ' is inside'); | |
| endpoints = [path[i], path[i+1]]; | |
| intersection = find_intersection(endpoints, 'left'); | |
| console.log("intersects at " + intersection); | |
| clippedPath.push(intersection); | |
| clippedPath.push(path[i+1]); | |
| } | |
| } | |
| } | |
| //So that the clipped polygon is closed | |
| clippedPath.push(clippedPath[0]); | |
| } | |
| function clip_top(path) | |
| { | |
| for(i=0; i<path.length-1; i++) | |
| { | |
| if(!isInside(path[i], 'top') && !isInside(path[i+1], 'top')) | |
| { | |
| //No points are added to the clipped polygon | |
| } | |
| else if(isInside(path[i], 'top') && isInside(path[i+1], 'top')) | |
| { | |
| clippedPath.push(path[i]); | |
| } | |
| else if(isInside(path[i], 'top')) | |
| { | |
| if(!isInside(path[i+1], 'top')) | |
| { | |
| endpoints = [path[i], path[i+1]]; | |
| intersection = find_intersection(endpoints, 'top'); | |
| clippedPath.push(path[i]); | |
| clippedPath.push(intersection); | |
| } | |
| } | |
| else if(!isInside(path[i], 'top')) | |
| { | |
| if(isInside(path[i+1], 'top')) | |
| { | |
| endpoints = [path[i], path[i+1]]; | |
| intersection = find_intersection(endpoints, 'top'); | |
| clippedPath.push(intersection); | |
| clippedPath.push(path[i+1]); | |
| } | |
| } | |
| } | |
| clippedPath.push(clippedPath[0]); | |
| } | |
| function clip_right(path) | |
| { | |
| console.log("clippingfrom right"); | |
| for(i=0; i<path.length-1; i++) | |
| { | |
| if(!isInside(path[i], 'right') && !isInside(path[i+1], 'right')) | |
| { | |
| } | |
| else if(isInside(path[i], 'right') && isInside(path[i+1], 'right')) | |
| { | |
| clippedPath.push(path[i]); | |
| } | |
| else if(isInside(path[i], 'right')) | |
| { | |
| if(!isInside(path[i+1], 'right')) | |
| { | |
| endpoints = [path[i], path[i+1]]; | |
| intersection = find_intersection(endpoints, 'right'); | |
| clippedPath.push(path[i]); | |
| clippedPath.push(intersection); | |
| } | |
| } | |
| else if(!isInside(path[i], 'right')) | |
| { | |
| if(isInside(path[i+1], 'right')) | |
| { | |
| endpoints = [path[i], path[i+1]]; | |
| intersection = find_intersection(endpoints, 'right'); | |
| clippedPath.push(intersection); | |
| clippedPath.push(path[i+1]); | |
| } | |
| } | |
| } | |
| clippedPath.push(clippedPath[0]); | |
| } | |
| function clip_down(path) | |
| { | |
| console.log("clipping down"); | |
| for(i=0; i<path.length-1; i++) | |
| { | |
| if(!isInside(path[i], 'down') && !isInside(path[i+1], 'down')) | |
| { | |
| } | |
| else if(isInside(path[i], 'down') && isInside(path[i+1], 'down')) | |
| { | |
| clippedPath.push(path[i]); | |
| } | |
| else if(isInside(path[i], 'down')) | |
| { | |
| if(!isInside(path[i+1], 'down')) | |
| { | |
| endpoints = [path[i], path[i+1]]; | |
| intersection = find_intersection(endpoints, 'down'); | |
| clippedPath.push(path[i]); | |
| clippedPath.push(intersection); | |
| } | |
| } | |
| else if(!isInside(path[i], 'down')) | |
| { | |
| if(isInside(path[i+1], 'down')) | |
| { | |
| endpoints = [path[i], path[i+1]]; | |
| intersection = find_intersection(endpoints, 'down'); | |
| clippedPath.push(intersection); | |
| clippedPath.push(path[i+1]); | |
| } | |
| } | |
| } | |
| clippedPath.push(clippedPath[0]); | |
| } | |
| function find_intersection(endpoints, edge) | |
| { | |
| //endpoints - the end points of an edge of the polygon | |
| //edge - the edge with which we want to calculate the intersection with | |
| intersection = []; | |
| //all lines are of the form y = mx + c | |
| //m = (y2-y1)/(x2-x1) | |
| start = endpoints[0]; | |
| end = endpoints[1]; | |
| x1 = start[0]; | |
| y1 = start[1]; | |
| x2 = end[0]; | |
| y2 = end[1]; | |
| m = (y2-y1)/(x2-x1); | |
| //find the constant c | |
| c = y1 - m*x1; | |
| //To find intersection with left edge | |
| if(edge == 'left') | |
| { | |
| intersection[0] = xmin; | |
| intersection[1] = m*xmin + c; | |
| } | |
| else if(edge == 'right') | |
| { | |
| intersection[0] = xmax; | |
| intersection[1] = m*xmax + c; | |
| } | |
| else if(edge == 'top') | |
| { | |
| intersection[0] = (ymin - c)/m; | |
| intersection[1] = ymin; | |
| } | |
| else if(edge == 'down') | |
| { | |
| intersection[0] = (ymax - c)/m; | |
| intersection[1] = ymax; | |
| } | |
| return intersection; | |
| } | |
| //returns true if the point is inside with respect to a particular edge | |
| function isInside(point, orientation) | |
| { | |
| x = point[0]; | |
| y = point[1]; | |
| if(orientation == 'left') | |
| { | |
| if(x > xmin) | |
| return true; | |
| else | |
| return false; | |
| } | |
| else if(orientation == 'top') | |
| { | |
| if(y > ymin) | |
| return true; | |
| else | |
| return false; | |
| } | |
| else if(orientation == 'right') | |
| { | |
| if(x < xmax) | |
| return true; | |
| else | |
| return false; | |
| } | |
| else if(orientation == 'down') | |
| { | |
| if(y < ymax) | |
| return true; | |
| else | |
| return false; | |
| } | |
| } | |
| function drawPolygon(path) | |
| { | |
| context.beginPath(); | |
| context.moveTo(path[0][0], path[0][1]); | |
| for(i=1; i<path.length; i++) | |
| { | |
| context.lineTo(path[i][0], path[i][1]); | |
| } | |
| context.fill(); | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment