This JavaScript program demonstrates how to generate a convext hull of a set of points using a gift wrapping algorithm with a dot product on a canvas element. The time complexity is O(mn), where n is the number points and m is the number of points in the hull.
<!DOCTYPE html>
<html>
<head>
<title>XoaX.net's Javascript</title>
<script type="text/javascript" src="ConvexHullDotProductGiftWrap.js"></script>
</head>
<body onload="Draw()">
<canvas id="idCanvas" width="640" height ="480" style="background-color: #F0F0F0;"></canvas>
</body>
</html>var gqCanvas = null;
var gqContext = null;
var gqPoints = null;
class CPoint2D {
constructor(dX, dY) {
this.mdX = dX;
this.mdY = dY;
}
DrawPoint(qContext2D) {
qContext2D.fillStyle = "gray";
qContext2D.beginPath();
qContext2D.arc(this.mdX, this.mdY, 3, 0, 2.0*Math.PI, true);
qContext2D.fill();
}
static Swap(qP1, qP2) {
var dSwap = qP1.mdX;
qP1.mdX = qP2.mdX;
qP2.mdX = dSwap;
dSwap = qP1.mdY;
qP1.mdY = qP2.mdY;
qP2.mdY = dSwap;
}
}
class CUnitVector {
constructor(qP1, qP2) {
this.mdX = qP2.mdX - qP1.mdX;
this.mdY = qP2.mdY - qP1.mdY;
var dMag = Math.sqrt(this.mdX*this.mdX + this.mdY*this.mdY);
this.mdX /= dMag;
this.mdY /= dMag;
}
Dot(qV) {
return (this.mdX*qV.mdX + this.mdY*qV.mdY);
}
}
function ConvexHull(qaPoints) {
// Find a point on the hull. Find the index of the lowest left most point.
var iLL = 0;
for (var i = 1; i < qaPoints.length; ++i) {
// If the comparison point is lower or as low and to the left
if ((qaPoints[i].mdY > qaPoints[iLL].mdY) ||
((qaPoints[i].mdY == qaPoints[iLL].mdY) && (qaPoints[i].mdX < qaPoints[iLL].mdX))) {
iLL = i;
}
}
// Put the first hull point in the last place
CPoint2D.Swap(qaPoints[qaPoints.length-1], qaPoints[iLL]);
var qInitDir = new CUnitVector(new CPoint2D(0.0, 0.0), new CPoint2D(1.0, 0.0));
// Get a second point using the dot product to get the nearest point
var qDir = new CUnitVector(qaPoints[qaPoints.length-1], qaPoints[0]);
var dMaxDot = qInitDir.Dot(qDir);
var iMaxIndex = 0;
for (var i = 1; i < qaPoints.length; ++i) {
// Make unit direction vector
qDir = new CUnitVector(qaPoints[qaPoints.length-1], qaPoints[i]);
var dCurrDot = qInitDir.Dot(qDir);
if (dMaxDot < dCurrDot) {
dMaxDot = dCurrDot;
iMaxIndex = i;
}
}
// Put the second point at the zero index
CPoint2D.Swap(qaPoints[0], qaPoints[iMaxIndex]);
var bDone = false;
var iNextHullIndex = 1;
qInitDir = new CUnitVector(qaPoints[qaPoints.length-1], qaPoints[0]);
qDir = new CUnitVector(qaPoints[qaPoints.length-1], qaPoints[0]);
while (!bDone) {
iMaxIndex = -1;
dMaxDot = -2;
// Find the next point in the hull
for (var i = iNextHullIndex; i < qaPoints.length; ++i) {
qDir = new CUnitVector(qaPoints[iNextHullIndex-1], qaPoints[i]);
var dCurrDot = qInitDir.Dot(qDir);
if (dCurrDot > dMaxDot) {
dMaxDot = dCurrDot;
iMaxIndex = i;
}
}
// Swap the point into the next index
CPoint2D.Swap(qaPoints[iNextHullIndex], qaPoints[iMaxIndex]);
if (iMaxIndex == qaPoints.length-1) {
bDone = true;
} else {
// Reset the initial direction
qInitDir = new CUnitVector(qaPoints[iNextHullIndex-1], qaPoints[iNextHullIndex]);
++iNextHullIndex;
}
}
// Return the hull size
return iNextHullIndex + 1;
}
function Initialize() {
gqCanvas = document.getElementById("idCanvas");
gqContext = gqCanvas.getContext("2d");
gqPoints = [];
// Get the size of the canvas
const kiWidth = gqCanvas.width;
const kiHeight = gqCanvas.height;
// Generate the set of points
for (var i = 0; i < 100; ++i) {
gqPoints.push(new CPoint2D(20 + (kiWidth - 40)*Math.random(),20 + (kiHeight - 40)*Math.random()));
}
}
function Draw() {
Initialize();
// Draw the full set of points in gray
for(var qPoint of gqPoints) {
qPoint.DrawPoint(gqContext);
}
const kiHullSize = ConvexHull(gqPoints);
// Draw the hull polygon path
gqContext.beginPath();
gqContext.moveTo(gqPoints[0].mdX, gqPoints[0].mdY);
for (var i = 1; i < kiHullSize; ++i) {
gqContext.lineTo(gqPoints[i].mdX, gqPoints[i].mdY);
}
gqContext.closePath();
gqContext.strokeStyle = "red";
gqContext.lineWidth = 1;
gqContext.stroke();
// Draw the vertices of the hull in red
for (var i = 0; i < kiHullSize; ++i) {
gqContext.fillStyle = "red";
gqContext.beginPath();
gqContext.arc(gqPoints[i].mdX, gqPoints[i].mdY, 3, 0, 2.0*Math.PI, true);
gqContext.fill();
}
}
© 20072025 XoaX.net LLC. All rights reserved.