Used pre-existing Graham Scan library to make custom configurations look nicer
This commit is contained in:
		| @ -110,7 +110,8 @@ | ||||
|         </section> | ||||
|     </main> | ||||
|  | ||||
|     <script src="script.js"></script> | ||||
|     <script type="module" src="vendor/lucio/graham-scan.mjs"></script> | ||||
|     <script type="module" src="script.js"></script> | ||||
| </body> | ||||
|  | ||||
| </html> | ||||
							
								
								
									
										24
									
								
								script.js
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								script.js
									
									
									
									
									
								
							| @ -2,6 +2,8 @@ | ||||
| * BEGIN CLASS DECLARATIONS | ||||
| */ | ||||
|  | ||||
| import GrahamScan from "./vendor/lucio/graham-scan.mjs"; | ||||
|  | ||||
| // 2D vector class to make some of the math easier | ||||
| class Vec2D { | ||||
|     constructor(x, y) { | ||||
| @ -489,6 +491,7 @@ function drawModule(ctx, module) { | ||||
|     ctx.restore(); | ||||
| } | ||||
|  | ||||
|  | ||||
| function drawRobot(ctx, robot, heading) { | ||||
|     ctx.save(); // Save current state before rotation | ||||
|  | ||||
| @ -498,18 +501,29 @@ function drawRobot(ctx, robot, heading) { | ||||
|     ctx.fillStyle = rootStyles.getPropertyValue('--robot-fill-color'); | ||||
|     ctx.lineWidth = 4; | ||||
|  | ||||
|     const modules = robot.modules.sort((a, b) => Math.atan2(a.position.y, a.position.x) - Math.atan2(b.position.y, b.position.x)); | ||||
|     let hull = []; | ||||
|     // Get the convex hull of the robot if there are more than 3 modules | ||||
|     if (robot.modules.length > 3) { | ||||
|         const grahamScan = new GrahamScan(); | ||||
|         grahamScan.setPoints(robot.modules.map((module) => [module.position.x, module.position.y])); | ||||
|         hull = grahamScan.getHull(); | ||||
|     } else { | ||||
|         hull = robot.modules.map((module) => [module.position.x, module.position.y]); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // Draw the convex hull as the robot frame | ||||
|     ctx.beginPath(); | ||||
|     ctx.moveTo(modules[0].position.x, modules[0].position.y); | ||||
|     for (let i = 1; i < modules.length; i++) { | ||||
|         ctx.lineTo(modules[i].position.x, modules[i].position.y); | ||||
|     ctx.moveTo(hull[0][0], hull[0][1]); | ||||
|     for (let i = 1; i < hull.length; i++) { | ||||
|         ctx.lineTo(hull[i][0], hull[i][1]); | ||||
|     } | ||||
|     ctx.closePath(); | ||||
|     ctx.fill(); | ||||
|     ctx.stroke(); | ||||
|  | ||||
|     modules.forEach(module => drawModule(ctx, module)); | ||||
|     // Draw all modules (not just hull modules) | ||||
|     robot.modules.forEach(module => drawModule(ctx, module, heading)); | ||||
|  | ||||
|     ctx.restore(); // Restore to remove rotation | ||||
| } | ||||
|  | ||||
							
								
								
									
										148
									
								
								vendor/lucio/graham-scan.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								vendor/lucio/graham-scan.mjs
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,148 @@ | ||||
| /* | ||||
| This module is not by me, it was found at the following github with MIT license: | ||||
| https://github.com/luciopaiva/graham-scan/tree/master | ||||
| ========= | ||||
| Copyright 2020 Lucio Paiva | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| ========= | ||||
| */ | ||||
|  | ||||
|  | ||||
| const X = 0; | ||||
| const Y = 1; | ||||
| const REMOVED = -1; | ||||
|  | ||||
| export default class GrahamScan { | ||||
|  | ||||
|     constructor() { | ||||
|         /** @type {[Number, Number][]} */ | ||||
|         this.points = []; | ||||
|     } | ||||
|  | ||||
|     clear() { | ||||
|         this.points = []; | ||||
|     } | ||||
|  | ||||
|     getPoints() { | ||||
|         return this.points; | ||||
|     } | ||||
|  | ||||
|     setPoints(points) { | ||||
|         this.points = points.slice();  // copy | ||||
|     } | ||||
|  | ||||
|     addPoint(point) { | ||||
|         this.points.push(point); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the smallest convex hull of a given set of points. Runs in O(n log n). | ||||
|      * | ||||
|      * @return {[Number, Number][]} | ||||
|      */ | ||||
|     getHull() { | ||||
|         const pivot = this.preparePivotPoint(); | ||||
|  | ||||
|         let indexes = Array.from(this.points, (point, i) => i); | ||||
|         const angles = Array.from(this.points, (point) => this.getAngle(pivot, point)); | ||||
|         const distances = Array.from(this.points, (point) => this.euclideanDistanceSquared(pivot, point)); | ||||
|  | ||||
|         // sort by angle and distance | ||||
|         indexes.sort((i, j) => { | ||||
|             const angleA = angles[i]; | ||||
|             const angleB = angles[j]; | ||||
|             if (angleA === angleB) { | ||||
|                 const distanceA = distances[i]; | ||||
|                 const distanceB = distances[j]; | ||||
|                 return distanceA - distanceB; | ||||
|             } | ||||
|             return angleA - angleB; | ||||
|         }); | ||||
|  | ||||
|         // remove points with repeated angle (but never the pivot, so start from i=1) | ||||
|         for (let i = 1; i < indexes.length - 1; i++) { | ||||
|             if (angles[indexes[i]] === angles[indexes[i + 1]]) {  // next one has same angle and is farther | ||||
|                 indexes[i] = REMOVED;  // remove it logically to avoid O(n) operation to physically remove it | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         const hull = []; | ||||
|         for (let i = 0; i < indexes.length; i++) { | ||||
|             const index = indexes[i]; | ||||
|             const point = this.points[index]; | ||||
|  | ||||
|             if (index !== REMOVED) { | ||||
|                 if (hull.length < 3) { | ||||
|                     hull.push(point); | ||||
|                 } else { | ||||
|                     while (this.checkOrientation(hull[hull.length - 2], hull[hull.length - 1], point) > 0) { | ||||
|                         hull.pop(); | ||||
|                     } | ||||
|                     hull.push(point); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return hull.length < 3 ? [] : hull; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check the orientation of 3 points in the order given. | ||||
|      * | ||||
|      * It works by comparing the slope of P1->P2 vs P2->P3. If P1->P2 > P2->P3, orientation is clockwise; if | ||||
|      * P1->P2 < P2->P3, counter-clockwise. If P1->P2 == P2->P3, points are co-linear. | ||||
|      * | ||||
|      * @param {[Number, Number]} p1 | ||||
|      * @param {[Number, Number]} p2 | ||||
|      * @param {[Number, Number]} p3 | ||||
|      * @return {Number} positive if orientation is clockwise, negative if counter-clockwise, 0 if co-linear | ||||
|      */ | ||||
|     checkOrientation(p1, p2, p3) { | ||||
|         return (p2[Y] - p1[Y]) * (p3[X] - p2[X]) - (p3[Y] - p2[Y]) * (p2[X] - p1[X]); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      * @param {[Number, Number]} a | ||||
|      * @param {[Number, Number]} b | ||||
|      * @return Number | ||||
|      */ | ||||
|     getAngle(a, b) { | ||||
|         return Math.atan2(b[Y] - a[Y], b[X] - a[X]); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      * @param {[Number, Number]} p1 | ||||
|      * @param {[Number, Number]} p2 | ||||
|      * @return {Number} | ||||
|      */ | ||||
|     euclideanDistanceSquared(p1, p2) { | ||||
|         const a = p2[X] - p1[X]; | ||||
|         const b = p2[Y] - p1[Y]; | ||||
|         return a * a + b * b; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @private | ||||
|      * @return {[Number, Number]} | ||||
|      */ | ||||
|     preparePivotPoint() { | ||||
|         let pivot = this.points[0]; | ||||
|         let pivotIndex = 0; | ||||
|         for (let i = 1; i < this.points.length; i++) { | ||||
|             const point = this.points[i]; | ||||
|             if (point[Y] < pivot[Y] || point[Y] === pivot[Y] && point[X] < pivot[X]) { | ||||
|                 pivot = point; | ||||
|                 pivotIndex = i; | ||||
|             } | ||||
|         } | ||||
|         return pivot; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user