diff --git a/.vscode/launch.json b/.vscode/launch.json index 6853278d..9cfcde95 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,13 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "type": "msedge", + "name": "Edge Vite Debug", + "request": "launch", + "url": "http://localhost:5173/", + "webRoot": "${workspaceFolder}/src" + }, { "type": "chrome", "request": "launch", diff --git a/.vscode/settings.json b/.vscode/settings.json index c4b098d1..564d4040 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,8 @@ "jamesoswald", "peaceiris", "peircemyheart", + "radx", + "rady", "styleguidelines", "Subrina", "Tiwari", diff --git a/package-lock.json b/package-lock.json index 6dd60eb5..9ab15ba7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "peircemyheart", "version": "0.0.2", "license": "MIT", + "dependencies": { + "nomial": "^1.0.11" + }, "devDependencies": { "@types/node": "20.8.0", "gts": "^5.0.1", @@ -3202,6 +3205,11 @@ "ncp": "bin/ncp" } }, + "node_modules/nomial": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/nomial/-/nomial-1.0.11.tgz", + "integrity": "sha512-pEw8C0E5xfHvGdWoiKeyB/kdVFZ9MfzgR62++sPxwcinSWlt9FhdRqCCj6ud2OvuHCP4Y06moesWZP0N4mC/tA==" + }, "node_modules/normalize-package-data": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", diff --git a/package.json b/package.json index 21a5388e..8057fc83 100644 --- a/package.json +++ b/package.json @@ -37,5 +37,8 @@ "typescript": "~5.2.2", "vite": "^4.4.9", "vitest": "^0.34.6" + }, + "dependencies": { + "nomial": "^1.0.11" } } diff --git a/src/AEG/AEGTree.ts b/src/AEG/AEGTree.ts index b0ce14ef..e4c8a431 100644 --- a/src/AEG/AEGTree.ts +++ b/src/AEG/AEGTree.ts @@ -55,12 +55,13 @@ export class AEGTree { } /** - * Checks whether the given node can be inserted into this tree - * without overlapping any bounding boxes. + * Method that checks whether the given node can be inserted into this tree + * at a given point without overlapping any bounding boxes. * @param incomingNode The node to be inserted. * @returns True, if the node can be inserted. Else, false */ public canInsert(incomingNode: AtomNode | CutNode): boolean { + console.log("checking can insert"); const currentCut: CutNode = this.sheet.getCurrentCut(incomingNode); for (let i = 0; i < currentCut.children.length; i++) { if (this.overlaps(incomingNode, currentCut.children[i])) { @@ -81,7 +82,9 @@ export class AEGTree { } const currentCut: CutNode = this.sheet.getCurrentCut(incomingNode); - const originalChildren: (AtomNode | CutNode)[] = currentCut.children; + //const originalChildren: (AtomNode | CutNode)[] = currentCut.children; + //==============CHANGEDDDD========= + const originalChildren: (AtomNode | CutNode)[] = [...currentCut.children]; currentCut.children.push(incomingNode); if (incomingNode instanceof CutNode) { @@ -134,4 +137,8 @@ export class AEGTree { } } } + + public toString(): string { + return this.sheet.toFormulaString(); + } } diff --git a/src/AEG/CutNode.ts b/src/AEG/CutNode.ts index b504b515..fd6cd83c 100644 --- a/src/AEG/CutNode.ts +++ b/src/AEG/CutNode.ts @@ -6,12 +6,13 @@ import {Point} from "./Point"; * Class that defines a Cut in AEGTree. * @author Anusha Tiwari * @author Ryan Reilly + * @author James Oswald */ export class CutNode { /** * The boundary of this node. */ - ellipse: Ellipse | null; //Null for sheet + ellipse: Ellipse | null; //Null for sheet of assertion /** * Contains the list of child nodes nested within this node. @@ -35,9 +36,12 @@ export class CutNode { */ public getCurrentCut(newNode: CutNode | AtomNode): CutNode { for (let i = 0; i < this.children.length; i++) { - if (this.children[i] instanceof CutNode && this.children[i].containsNode(newNode)) { + const child: CutNode | AtomNode = this.children[i]; + if (child instanceof CutNode && this.children[i].containsNode(newNode)) { + //======DEBUGGG======= + console.log("current cut: " + this.children[i]); //newNode can be placed at least one layer deeper - return this.getCurrentCut(this.children[i]); + return child.getCurrentCut(newNode); } } return this; //we are at the deepest valid level that newNode can be placed @@ -71,10 +75,12 @@ export class CutNode { } if (otherNode instanceof AtomNode) { - return this.ellipse.containsShape((otherNode as AtomNode).rect); - } else { + return this.ellipse.containsShape(otherNode.rect); + } else if (otherNode instanceof CutNode) { //ELLIPSE TO BE IMPLEMENTED ACCURATELY - return this.ellipse.containsShape((otherNode as CutNode).ellipse as Ellipse); + return this.ellipse.containsShape(otherNode.ellipse as Ellipse); + } else { + throw Error("containsNode expected AtomNode or CutNode"); } } @@ -130,4 +136,23 @@ export class CutNode { } return str; } + + public toFormulaString(): string { + let formulaString = ""; + for (const child of this.children) { + if (child instanceof AtomNode) { + formulaString += child.identifier; + } else if (child instanceof CutNode) { + formulaString += child.toFormulaString(); + } + formulaString += " "; + } + formulaString = formulaString.slice(0, -1); + if (this.ellipse === null) { + formulaString = "[" + formulaString + "]"; + } else { + formulaString = "(" + formulaString + ")"; + } + return formulaString; + } } diff --git a/src/AEG/Ellipse.ts b/src/AEG/Ellipse.ts index 21f231d2..bb872bbd 100644 --- a/src/AEG/Ellipse.ts +++ b/src/AEG/Ellipse.ts @@ -1,5 +1,6 @@ import {Point} from "./Point"; import {Rectangle} from "./Rectangle"; +//import {Polynomial, polynomialRoots} from "nomial"; /** * Class that defines an Ellipse. @@ -46,15 +47,49 @@ export class Ellipse { this.boundingBox = new Rectangle(boundingVertex, this.radiusX * 2, this.radiusY * 2); } + /** + * Method that returns the string representation of an ellipse. + * @returns The coordinates and radii for the ellipse. + */ + public toString(): string { + return ( + "An ellipse with Center at: " + + this.center.toString() + + ", \n" + + "Horizontal Radius of: " + + this.radiusX + + ", \n" + + "Vertical Radius of: " + + this.radiusY + + ", \n" + + "Bounding box: " + + this.boundingBox.toString() + ); + } + /** * Method that checks whether a point is within this ellipse. * @param otherPoint The point that might be inside this ellipse. * @returns True, if the point is inside this ellipse. Else, false */ public containsPoint(otherPoint: Point): boolean { - //ELLIPSE TO BE IMPLEMENTED ACCURATELY - //return this.boundingBox.containsPoint(otherPoint); - return false; + //(x-h)^2/rx^2 + (y-k)^2/ry^2 <= 1 + //(x, y) = new point + //(h, k) = center + + const p: number = Math.round( + Math.pow(otherPoint.x - this.center.x, 2) / Math.pow(this.radiusX, 2) + + Math.pow(otherPoint.y - this.center.y, 2) / Math.pow(this.radiusY, 2) + ); + + return p <= 1; + + //Method 2: scaling eclipse to check for containment + /* const scale_y = this.radiusX / this.radiusY; + const dx = otherPoint.x - this.center.x; + const dy = (otherPoint.y - this.center.y) * scale_y; + + return Math.pow(dx, 2) + Math.pow(dy, 2) <= Math.pow(this.radiusX, 2); */ } /** @@ -63,9 +98,27 @@ export class Ellipse { * @returns True, if there is an overlap. Else, false. */ public overlaps(otherShape: Rectangle | Ellipse): boolean { - //ELLIPSE TO BE IMPLEMENTED ACCURATELY //return this.boundingBox.overlaps(otherShape); - return false; + if (otherShape instanceof Rectangle) { + for (let i = 0; i < 4; i++) { + if (this.containsPoint(otherShape.getCorners()[i])) { + return true; + } + } + + return false; + } else { + //check if the rectangular bounding boxes of the ellipse overlap + if (this.boundingBox.overlaps((otherShape as Ellipse).boundingBox)) { + console.log("ellipse boxes overlap"); + return true; + //if there is an overlap, check if points along the ellipse curve overlap + //this can be done by checking if points along the curve of the other ellipse + //are within this ellipse + //return this.checkQuadrantOverlap(otherShape); + } + return false; + } } /** @@ -74,25 +127,139 @@ export class Ellipse { * @returns True, if the shape is within this ellipse. Else, false. */ public containsShape(otherShape: Rectangle | Ellipse): boolean { - //ELLIPSE TO BE IMPLEMENTED ACCURATELY - //this.boundingBox.containsShape(otherShape); + if (otherShape instanceof Rectangle) { + for (let i = 0; i < 4; i++) { + if (!this.containsPoint(otherShape.getCorners()[i])) { + return false; + } + } + return true; + } else if (otherShape instanceof Ellipse) { + //If all the widest coordinates of the other ellipse are within this ellipse, + //This ellipse contains the other ellipse + const points = otherShape.getWidestCoordinates(); + for (let j = 0; j < 4; j++) { + if (!this.containsPoint(points[j])) { + return false; + } + } + return true; + } + throw Error("Invalid Shape passed to containsShape, must be a Rectangle | Ellipse"); + } + + /** + * Method that checks if any quadrant of another ellipse overlaps with this ellipse. + * This can be done by checking if a point on the curve of the ellipse is within this ellipse. + * @param otherEllipse The other ellipse that might be overlapping with this ellipse + * @returns True, if there is an overlap. Else, false + */ + private checkQuadrantOverlap(otherEllipse: Ellipse): boolean { + //Get the quadrant which might be overlapping with this ellipse. + //To do so, check which corner of the rectangular bounding box of the other ellipse + //is within this ellipse. + for (let i = 0; i < 4; i++) { + if (this.containsPoint(otherEllipse.boundingBox.getCorners()[i])) { + //Get the points on the curve of the ellipse in that quadrant + const points: Point[] = otherEllipse.getQuadrantPoints(i); + + console.log("has corner " + i); + //If any points along the curve are within this ellipse, the other ellipse overlaps + //with this ellipse. Return true. + for (let j = 0; j < 6; j++) { + if (this.containsPoint(points[j])) { + console.log("Has overlap"); + return true; + } + } + } + } return false; } /** - * Method that returns the string representation of an ellipse. - * @returns The coordinates and radii for the ellipse. + * An array containing the widest coordinates of the ellipse, i.e. the coordinates along the + * x-axis and y-axis of the ellipse. + * The coordinates are in clockwise order such that: + * 0 - Top most coordinate (Top along y-axis). + * 1 - Right most coordinate (Right along x-axis). + * 2 - Bottom most coordinate (Bottom along y-axis). + * 3 - Left most coordinate (Left along x-axis). + * @returns */ - public toString(): string { + public getWidestCoordinates(): Point[] { + return [ + new Point(this.center.x, this.center.y - this.radiusY), + new Point(this.center.x + this.radiusX, this.center.y), + new Point(this.center.x, this.center.y + this.radiusY), + new Point(this.center.x - this.radiusX, this.center.y), + ]; + } + + /** + * Method that returns the points on the curve of the ellipse in a specific quadrant + * @param quadrant The quadrant which we want the points in + * @returns An array of points along the curve of the ellipse + */ + private getQuadrantPoints(quadrant: number): Point[] { + //==========DEBUGGG========= + console.log("Getting points for: " + this + "\n" + "In quad: " + quadrant); + + const points: Point[] = []; + const quadDistance = this.radiusX; + let curve = 1; + + if (quadrant === 0) { + //top left quadrant + points[0] = this.getWidestCoordinates()[3]; + points[1] = this.getWidestCoordinates()[0]; + } else if (quadrant === 1) { + //top right quadrant + points[0] = this.getWidestCoordinates()[0]; + points[1] = this.getWidestCoordinates()[1]; + } else if (quadrant === 2) { + //bottom right quadrant + points[0] = this.getWidestCoordinates()[1]; + points[1] = this.getWidestCoordinates()[2]; + + curve = -1; + } else if (quadrant === 3) { + //bottom left quadrant + points[0] = this.getWidestCoordinates()[2]; + points[1] = this.getWidestCoordinates()[3]; + + curve = -1; + } + + for (let i = 2; i < 6; i++) { + let x = points[0].x; + if (curve === 1) { + x = x + (i - 1) * (quadDistance / 5); + } else { + x = x - (i - 1) * (quadDistance / 5); + } + const y = this.getCurvePoint(x, curve); + points[i] = new Point(x, y); + } + + //========DEBUGGGG======== + console.log(points); + return points; + } + + /** + * Method that returns a point on the curve of the ellipse for a given x coordinate + * @param x The x coordinate of the point + * @param curveHalf Flag signifying the curve of the ellipse. + * 1 for top curve, -1 for bottom curve + * @returns A point along the curve + */ + private getCurvePoint(x: number, curveHalf: number): number { return ( - "An ellipse with Center at: " + - this.center.toString() + - ", " + - "Horizontal Radius of: " + - this.radiusX + - ", " + - "Vertical Radius of: " + - this.radiusY + curveHalf * + this.radiusY * + Math.sqrt(1 - Math.pow((x - this.center.x) / this.radiusX, 2)) + + this.center.y ); } } diff --git a/src/AEG/Rectangle.ts b/src/AEG/Rectangle.ts index 379ef6a4..37b6b2fb 100644 --- a/src/AEG/Rectangle.ts +++ b/src/AEG/Rectangle.ts @@ -93,9 +93,13 @@ export class Rectangle { } return false; } else { - //ELLIPSE TO BE IMPLEMENTED ACCURATELY //const ellipseBoundary = (otherShape as Ellipse).boundingBox; //return this.overlaps(ellipseBoundary); + for (let i = 0; i < 4; i++) { + if ((otherShape as Ellipse).containsPoint(this.getCorners()[i])) { + return true; + } + } return false; } } @@ -112,9 +116,14 @@ export class Rectangle { //Other rectangle is within this rectangle if its opposite corners are within return this.containsPoint(otherCorners[0]) && this.containsPoint(otherCorners[2]); } else { - //ELLIPSE TO BE IMPLEMENTED ACCURATELY - //const ellipseBoundary = (otherShape as Ellipse).boundingBox; - //return this.containsShape(ellipseBoundary); + //Check if the coordinates of the ellipse along the axes are within the rectangle. + //If they are within, it means that all other points along the curve of the ellipse + //are also within the rectangle. + for (let i = 0; i < 4; i++) { + if (!this.containsPoint((otherShape as Ellipse).getWidestCoordinates()[i])) { + return false; + } + } return true; } } diff --git a/src/AtomCreation.ts b/src/AtomCreation.ts index 2f40535a..639b3a94 100644 --- a/src/AtomCreation.ts +++ b/src/AtomCreation.ts @@ -74,6 +74,7 @@ function atomUp() { ); const newAtom: AtomNode = new AtomNode(atom, currentPoint, newRect); tree.insert(newAtom); + console.log(tree.toString()); canvas.removeEventListener("mousemove", moveAtom); canvas.removeEventListener("mouseup", atomUp); canvas.removeEventListener("mouseOut", mouseOut); diff --git a/src/EllipseCreation.ts b/src/EllipseCreation.ts index 3f4f5b5e..495a69bf 100644 --- a/src/EllipseCreation.ts +++ b/src/EllipseCreation.ts @@ -54,11 +54,17 @@ export function createEllipse(original: Point, current: Point): Ellipse { ctx.stroke(); } + const currentEllipse = new Ellipse(center, rx, ry); + if (tree.canInsert(new CutNode(currentEllipse))) { + ctx.strokeStyle = "#00FF00"; + } else { + ctx.strokeStyle = "#FF0000"; + } ctx.beginPath(); ctx.ellipse(center.x, center.y, rx, ry, 0, 0, 2 * Math.PI); //I know this is stupid to constantly make a new ellipse but my brain hurts I'm sorry ctx.stroke(); - currentEllipse = new Ellipse(center, rx, ry); + return currentEllipse; } @@ -104,9 +110,12 @@ function mouseUp() { if (tree.canInsert(newCut)) { tree.insert(newCut); } + console.log(tree.toString()); canvas.removeEventListener("mousemove", mouseMoving); canvas.removeEventListener("mouseup", mouseUp); canvas.removeEventListener("mouseout", mouseOut); + ctx.clearRect(0, 0, canvas.width, canvas.height); + redrawCut(tree.sheet); } /** diff --git a/src/index.html b/src/index.html index a8815fc7..91986b17 100644 --- a/src/index.html +++ b/src/index.html @@ -46,6 +46,9 @@ +