From dbf1b94fa2b6037de5a940df8041089da9bb3296 Mon Sep 17 00:00:00 2001 From: AnushaTiwari5 Date: Mon, 9 Oct 2023 17:58:56 -0400 Subject: [PATCH 1/3] changed node members to private, added setters/getters --- src/AEG/AEGTree.ts | 46 +++++++++++++++++++++------------------- src/AEG/AtomNode.ts | 46 ++++++++++++++++++++++++++++++---------- src/AEG/CutNode.ts | 51 ++++++++++++++++++++++++++++++++++++++------- src/AEG/Ellipse.ts | 2 -- 4 files changed, 102 insertions(+), 43 deletions(-) diff --git a/src/AEG/AEGTree.ts b/src/AEG/AEGTree.ts index 287ed7de..c0ec3674 100644 --- a/src/AEG/AEGTree.ts +++ b/src/AEG/AEGTree.ts @@ -7,8 +7,7 @@ export class AEGTree { sheet: CutNode; public constructor(sheet?: CutNode) { - this.sheet = sheet ?? new CutNode(); - this.sheet.ellipse = null; + this.sheet = sheet ?? new CutNode(null); } /** @@ -29,24 +28,24 @@ export class AEGTree { * @returns True, if the structure is structurally consistent. Else, false. */ private verifyAEG(currentCut: CutNode): boolean { - for (let i = 0; i < currentCut.children.length; i++) { + for (let i = 0; i < currentCut.Children.length; i++) { //Check that all children, in this level, are in currentCut - if (!currentCut.containsNode(currentCut.children[i])) { + if (!currentCut.containsNode(currentCut.Children[i])) { return false; } //Check for overlaps on the same level - for (let j = i + 1; j < currentCut.children.length; j++) { - if (this.overlaps(currentCut.children[i], currentCut.children[j])) { + for (let j = i + 1; j < currentCut.Children.length; j++) { + if (this.overlaps(currentCut.Children[i], currentCut.Children[j])) { return false; } } } - for (let i = 0; i < currentCut.children.length; i++) { + for (let i = 0; i < currentCut.Children.length; i++) { //Check one level deeper if the child is a CutNode. Recursive case if ( - currentCut.children[i] instanceof CutNode && - !this.verifyAEG(currentCut.children[i] as CutNode) + currentCut.Children[i] instanceof CutNode && + !this.verifyAEG(currentCut.Children[i] as CutNode) ) { return false; } @@ -62,8 +61,8 @@ export class AEGTree { */ public canInsert(incomingNode: AtomNode | CutNode): boolean { const currentCut: CutNode = this.sheet.getCurrentCut(incomingNode); - for (let i = 0; i < currentCut.children.length; i++) { - if (this.overlaps(incomingNode, currentCut.children[i])) { + for (let i = 0; i < currentCut.Children.length; i++) { + if (this.overlaps(incomingNode, currentCut.Children[i])) { return false; } } @@ -81,14 +80,15 @@ export class AEGTree { } const currentCut: CutNode = this.sheet.getCurrentCut(incomingNode); - const originalChildren: (AtomNode | CutNode)[] = [...currentCut.children]; - currentCut.children.push(incomingNode); + const originalChildren: (AtomNode | CutNode)[] = [...currentCut.Children]; + currentCut.Child = incomingNode; if (incomingNode instanceof CutNode) { for (let i = 0; i < originalChildren.length; i++) { if (incomingNode.containsNode(originalChildren[i])) { - incomingNode.children.push(originalChildren[i]); - currentCut.children = currentCut.children.splice(i, 1); + //incomingNode.children.push(originalChildren[i]); + incomingNode.Child = originalChildren[i]; + currentCut.Children = currentCut.Children.splice(i, 1); } } } @@ -115,21 +115,23 @@ export class AEGTree { if (incomingNode instanceof AtomNode) { if (otherNode instanceof AtomNode) { - return (incomingNode as AtomNode).rect.overlaps((otherNode as AtomNode).rect); + return (incomingNode as AtomNode).Rectangle.overlaps( + (otherNode as AtomNode).Rectangle + ); } else { //the case where otherNode is the sheet is handled in canInsert() //and all child.ellipse[i] will never be null. this is the reason for ! below - ellipse1 = (otherNode as CutNode).ellipse!; - return (incomingNode as AtomNode).rect.overlaps(ellipse1); + ellipse1 = (otherNode as CutNode).Ellipse!; + return (incomingNode as AtomNode).Rectangle.overlaps(ellipse1); } } else { if (otherNode instanceof AtomNode) { - ellipse1 = (incomingNode as CutNode).ellipse!; - return ellipse1.overlaps((otherNode as AtomNode).rect); + ellipse1 = (incomingNode as CutNode).Ellipse!; + return ellipse1.overlaps((otherNode as AtomNode).Rectangle); } else { - ellipse1 = (incomingNode as CutNode).ellipse!; - ellipse2 = (otherNode as CutNode).ellipse!; + ellipse1 = (incomingNode as CutNode).Ellipse!; + ellipse2 = (otherNode as CutNode).Ellipse!; return ellipse1.overlaps(ellipse2); } } diff --git a/src/AEG/AtomNode.ts b/src/AEG/AtomNode.ts index 29722f94..5ae71f3f 100644 --- a/src/AEG/AtomNode.ts +++ b/src/AEG/AtomNode.ts @@ -12,27 +12,51 @@ export class AtomNode { /** * The rectangle signifying the boundary box of this node. */ - rect: Rectangle; + private rect: Rectangle; /** * The string value of the proposition represented by this node. */ - identifier: string; + private identifier: string; /** * The point the atom is initially placed. */ - origin: Point; + private origin: Point; /** * Construct an atom node with given boundary box and proposition. - * @param rect The rectangle to be set as the boundary box of this node. - * @param val The value of the proposition represented by this node. + * @param rect (Required) The rectangle to be set as the boundary box of this node. + * @param val (Required) The value of the proposition represented by this node. */ - public constructor(val?: string, origin?: Point, rect?: Rectangle) { - this.rect = rect ?? new Rectangle(); - this.identifier = val ?? ""; - this.origin = origin ?? new Point(); + public constructor(val: string, origin: Point, rect: Rectangle) { + this.rect = rect; + this.identifier = val; + this.origin = origin; + } + + /** + * Accessor to get the bounding rectangle of the Atom Node. + * @returns The bounding rectangle of this Atom Node + */ + public get Rectangle(): Rectangle { + return this.rect; + } + + /** + * Accessor to get the identifier of the Atom Node. + * @returns The identifier of this Atom Node + */ + public get Identifier(): string { + return this.identifier; + } + + /** + * Accessor to get the origin (top left) vertex of the bounding rectangle of the Atom Node. + * @returns The origin of the bounding rectangle + */ + public get Origin(): Point { + return this.origin; } /** @@ -51,10 +75,10 @@ export class AtomNode { */ public containsNode(otherNode: AtomNode | CutNode): boolean { if (otherNode instanceof AtomNode) { - return this.rect.containsShape((otherNode as AtomNode).rect); + return this.rect.containsShape((otherNode as AtomNode).Rectangle); } else { //ELLIPSE TO BE IMPLEMENTED ACCURATELY - return this.rect.containsShape((otherNode as CutNode).ellipse as Ellipse); + return this.rect.containsShape((otherNode as CutNode).Ellipse as Ellipse); } } diff --git a/src/AEG/CutNode.ts b/src/AEG/CutNode.ts index 686632dc..1c90471b 100644 --- a/src/AEG/CutNode.ts +++ b/src/AEG/CutNode.ts @@ -12,23 +12,58 @@ export class CutNode { /** * The boundary of this node. */ - ellipse: Ellipse | null; //Null for sheet of assertion + private ellipse: Ellipse | null; //Null for sheet of assertion /** * Contains the list of child nodes nested within this node. */ - children: (AtomNode | CutNode)[]; + private children: (AtomNode | CutNode)[]; /** * Constructs a CutNode with the incoming Ellipse as its boundary box. - * @param ellipse The ellipse to be set as the boundary box of this node. + * @param ellipse (Required) The ellipse to be set as the boundary box of this node. + * For Sheet of Assertion, this should be passed as null. * @param childList The list of children nodes nested within this node. + * If not passed, defaults to an empty array. */ - public constructor(ellipse?: Ellipse, childList?: (AtomNode | CutNode)[]) { - this.ellipse = ellipse ?? new Ellipse(); + public constructor(ellipse: Ellipse | null, childList?: (AtomNode | CutNode)[]) { + this.ellipse = ellipse; this.children = childList ?? []; } + /** + * Accessor to get the bounding ellipse of the Cut Node. + * @returns The bounding ellipse of this Cut Node + * Returns null for Sheet of Assertion + */ + public get Ellipse(): Ellipse | null { + return this.ellipse; + } + + /** + * Accessor to get the children (array of nodes nested within) of the Cut Node. + * @returns The children of the Cut Node + */ + public get Children(): (AtomNode | CutNode)[] { + return this.children; + } + + /** + * Modifier that sets the children of the Cut Node. + * @param list The list of nodes to be added as the children of the Cut Node + */ + public set Children(list: (AtomNode | CutNode)[]) { + this.children = list; + } + + /** + * Modifier that adds a child to the Cut Node. + * @param child The node to be added as a child of the Cut Node + */ + public set Child(child: AtomNode | CutNode) { + this.children.push(child); + } + /** * Determines the deepest CutNode in which newNode can fit. * @param newNode the new node @@ -73,9 +108,9 @@ export class CutNode { } if (otherNode instanceof AtomNode) { - return this.ellipse.containsShape(otherNode.rect); + return this.ellipse.containsShape(otherNode.Rectangle); } else if (otherNode instanceof CutNode) { - return this.ellipse.containsShape(otherNode.ellipse as Ellipse); + return this.ellipse.containsShape(otherNode.Ellipse as Ellipse); } else { throw Error("containsNode expected AtomNode or CutNode"); } @@ -138,7 +173,7 @@ export class CutNode { let formulaString = ""; for (const child of this.children) { if (child instanceof AtomNode) { - formulaString += child.identifier; + formulaString += child.Identifier; } else if (child instanceof CutNode) { formulaString += child.toFormulaString(); } diff --git a/src/AEG/Ellipse.ts b/src/AEG/Ellipse.ts index f53e2145..cd5e2d3b 100644 --- a/src/AEG/Ellipse.ts +++ b/src/AEG/Ellipse.ts @@ -102,10 +102,8 @@ export class Ellipse { //check if the rectangular bounding boxes of the ellipse overlap if ( this.boundingBox.overlaps((otherShape as Ellipse).boundingBox) || - //this.boundingBox.containsShape((otherShape as Ellipse).boundingBox) || (otherShape as Ellipse).boundingBox.containsShape(this.boundingBox) ) { - //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 this ellipse //are within the other ellipse From 7234e133072127e572ba5dd577dd8dc48344ebbb Mon Sep 17 00:00:00 2001 From: AnushaTiwari5 Date: Tue, 10 Oct 2023 01:43:58 -0400 Subject: [PATCH 2/3] collision functionality in single function, all others call this. Can draw cuts around existing nodes. Private members for Cut and Atom, with setters and getters --- .vscode/settings.json | 2 + src/AEG/AEGTree.ts | 2 +- src/AEG/AEGUtils.ts | 364 ++++++++++++++++++++++++++++++++++++++++++ src/AEG/AtomNode.ts | 32 +++- src/AEG/CutNode.ts | 15 +- src/AEG/Ellipse.ts | 122 +------------- src/AEG/Rectangle.ts | 190 +--------------------- src/AtomMode.ts | 22 +-- src/CutMode.ts | 6 +- 9 files changed, 431 insertions(+), 324 deletions(-) create mode 100644 src/AEG/AEGUtils.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 24e4181f..8d7ffd13 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,8 @@ { "cSpell.words": [ "Anusha", + "atomnode", + "hasmousedown", "Huda", "jamesoswald", "mosueout", diff --git a/src/AEG/AEGTree.ts b/src/AEG/AEGTree.ts index c0ec3674..fc69360e 100644 --- a/src/AEG/AEGTree.ts +++ b/src/AEG/AEGTree.ts @@ -88,7 +88,7 @@ export class AEGTree { if (incomingNode.containsNode(originalChildren[i])) { //incomingNode.children.push(originalChildren[i]); incomingNode.Child = originalChildren[i]; - currentCut.Children = currentCut.Children.splice(i, 1); + currentCut.Children.splice(i, 1); } } } diff --git a/src/AEG/AEGUtils.ts b/src/AEG/AEGUtils.ts new file mode 100644 index 00000000..8a581ba8 --- /dev/null +++ b/src/AEG/AEGUtils.ts @@ -0,0 +1,364 @@ +import {Ellipse} from "./Ellipse"; +import {Point} from "./Point"; +import {Rectangle} from "./Rectangle"; + +/** + * Method that checks whether one shape overlaps another + * @param newShape The new shape that might be overlapping the existing shape + * @param existingShape The existing shape + * @returns True, if the new shape overlaps the existing shape. Else, false + */ +export function shapesOverlaps( + newShape: Rectangle | Ellipse, + existingShape: Rectangle | Ellipse +): boolean { + if (newShape instanceof Rectangle) { + if (existingShape instanceof Rectangle) { + //For rectangle-rectangle, check if their edges intersect + return edgesIntersect(newShape, existingShape); + } else { + //For ellipse-rectangle collision, check if points on the ellipse are + //within the rectangle + const points: Point[] = getEllipsePoints(existingShape as Ellipse, 64); + for (let i = 0; i < points.length; i++) { + if (pointInRect(newShape as Rectangle, points[i])) { + return true; + } + } + return false; + } + } else { + if (existingShape instanceof Rectangle) { + //For ellipse-rectangle collision, check if points on the ellipse are + //within the rectangle + const points: Point[] = getEllipsePoints(newShape as Ellipse, 64); + for (let i = 0; i < points.length; i++) { + if (pointInRect(existingShape as Rectangle, points[i])) { + return true; + } + } + return false; + } else { + //For ellipse-ellipse collision, check if the rectangular bounding boxes intersect. + //If they do, check if points of the new ellipse are within the current ellipse + if ( + shapesOverlaps( + (newShape as Ellipse).boundingBox, + (existingShape as Ellipse).boundingBox + ) || + shapeContains( + (existingShape as Ellipse).boundingBox, + (newShape as Ellipse).boundingBox + ) + ) { + //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 this ellipse + //are within the other ellipse + const points: Point[] = getEllipsePoints(newShape, 64); + for (let i = 0; i < points.length; i++) { + if (pointInELlipse(existingShape as Ellipse, points[i])) { + return true; + } + } + } + return false; + } + } +} + +/** + * Method that checks whether a shape is contained within another + * @param outerShape The outer shape + * @param innerShape The inner shape, that might be contained in the outer shape + * @return True, if shape1 contains shape2. Else, false. + */ +export function shapeContains( + outerShape: Rectangle | Ellipse, + innerShape: Rectangle | Ellipse +): boolean { + if (outerShape instanceof Rectangle) { + if (innerShape instanceof Rectangle) { + //A rectangle contains a rectangle if all the corners of the inner rectangle are + //within the outer rectangle + const innerCorners: Point[] = (innerShape as Rectangle).getCorners(); + for (let i = 0; i < 4; i++) { + if (!pointInRect(outerShape as Rectangle, innerCorners[i])) { + return false; + } + } + return true; + } else { + //A rectangle contains an ellipse if all the widest coordinates of the ellipse are + //within the rectangle + const innerCoords: Point[] = getWidestCoordinates(innerShape as Ellipse); + for (let i = 0; i < 4; i++) { + if (!pointInRect(outerShape as Rectangle, innerCoords[i])) { + return false; + } + } + return true; + } + } else { + if (innerShape instanceof Rectangle) { + //An ellipse contains a rectangle if all the corners of the rectangle are within + //the ellipse + const innerCorners = (innerShape as Rectangle).getCorners(); + for (let i = 0; i < 4; i++) { + if (!pointInELlipse(outerShape as Ellipse, innerCorners[i])) { + return false; + } + } + return true; + } else { + //An ellipse contains an ellipse if all the widest coordinates of the inner ellipse + //are within the outer ellipse + const innerCoords: Point[] = getEllipsePoints(innerShape as Ellipse, 64); + //= getWidestCoordinates(innerShape as Ellipse); + for (let i = 0; i < innerCoords.length; i++) { + if (!pointInELlipse(outerShape as Ellipse, innerCoords[i])) { + return false; + } + } + return true; + } + } +} + +/** + * Method that checks if any edges of this rectangle overlap with the other rectangle. + * @param otherRect The other rectangle to be checked. + * @returns True, if edges overlap. Else, false. + * @todo This algo can and should be simplified to be less than 10 lines of code. -James-Oswald + */ +function edgesIntersect(shape1: Rectangle, shape2: Rectangle): boolean { + const thisCorners = shape1.getCorners(); + const otherCorners = shape2.getCorners(); + + if (thisCorners[0].y <= otherCorners[0].y && thisCorners[2].y >= otherCorners[0].y) { + //The top edge of the other rectangle is within the horizontal boundaries + //of this rectangle + if (thisCorners[0].x <= otherCorners[0].x && thisCorners[1].x >= otherCorners[0].x) { + //The left edge of the other rectangle is within the vertical boundaries + //of this rectangle + return true; + } else if ( + //The left edge of this rectangle is within the vertical boundaries + //of the other rectangle + otherCorners[0].x <= thisCorners[0].x && + otherCorners[1].x >= thisCorners[0].x + ) { + return true; + } else if ( + //The right edge of the other rectangle is within the vertical boundaries + //of this rectangle + thisCorners[0].x <= otherCorners[1].x && + thisCorners[1].x >= otherCorners[1].x + ) { + return true; + } else if ( + //The right edge of this rectangle is within the vertical boundaries + //of the other rectangle + otherCorners[0].x <= thisCorners[1].x && + otherCorners[1].x >= thisCorners[1].x + ) { + return true; + } + + return false; + } else if (otherCorners[0].y <= thisCorners[0].y && otherCorners[2].y >= thisCorners[0].y) { + //The top edge of this rectangle is within the horizontal boundaries + //of the other rectangle + if (thisCorners[0].x <= otherCorners[0].x && thisCorners[1].x >= otherCorners[0].x) { + //The left edge of the other rectangle is within the vertical boundaries + //of this rectangle + return true; + } else if ( + //The left edge of this rectangle is within the vertical boundaries + //of the other rectangle + otherCorners[0].x <= thisCorners[0].x && + otherCorners[1].x >= thisCorners[0].x + ) { + return true; + } else if ( + //The right edge of the other rectangle is within the vertical boundaries + //of this rectangle + thisCorners[0].x <= otherCorners[1].x && + thisCorners[1].x >= otherCorners[1].x + ) { + return true; + } else if ( + //The right edge of this rectangle is within the vertical boundaries + //of the other rectangle + otherCorners[0].x <= thisCorners[1].x && + otherCorners[1].x >= thisCorners[1].x + ) { + return true; + } + + return false; + } else if (thisCorners[0].y <= otherCorners[2].y && thisCorners[2].y >= otherCorners[2].y) { + //The bottom edge of the other rectangle is within the horizontal boundaries + //of this rectangle + if (thisCorners[0].x <= otherCorners[0].x && thisCorners[1].x >= otherCorners[0].x) { + //The left edge of the other rectangle is within the vertical boundaries + //of this rectangle + return true; + } else if ( + //The left edge of this rectangle is within the vertical boundaries + //of the other rectangle + otherCorners[0].x <= thisCorners[0].x && + otherCorners[1].x >= thisCorners[0].x + ) { + return true; + } else if ( + //The right edge of the other rectangle is within the vertical boundaries + //of this rectangle + thisCorners[0].x <= otherCorners[1].x && + thisCorners[1].x >= otherCorners[1].x + ) { + return true; + } else if ( + //The right edge of this rectangle is within the vertical boundaries + //of the other rectangle + otherCorners[0].x <= thisCorners[1].x && + otherCorners[1].x >= thisCorners[1].x + ) { + return true; + } + + return false; + } else if (otherCorners[0].y <= thisCorners[2].y && otherCorners[2].y >= thisCorners[2].y) { + //The bottom edge of this rectangle is within the horizontal boundaries + //of the other rectangle + if (thisCorners[0].x <= otherCorners[0].x && thisCorners[1].x >= otherCorners[0].x) { + //The left edge of the other rectangle is within the vertical boundaries + //of this rectangle + return true; + } else if ( + //The left edge of this rectangle is within the vertical boundaries + //of the other rectangle + otherCorners[0].x <= thisCorners[0].x && + otherCorners[1].x >= thisCorners[0].x + ) { + return true; + } else if ( + //The right edge of the other rectangle is within the vertical boundaries + //of this rectangle + thisCorners[0].x <= otherCorners[1].x && + thisCorners[1].x >= otherCorners[1].x + ) { + return true; + } else if ( + //The right edge of this rectangle is within the vertical boundaries + //of the other rectangle + otherCorners[0].x <= thisCorners[1].x && + otherCorners[1].x >= thisCorners[1].x + ) { + return true; + } + + return false; + } + + return false; +} + +/** + * Method that checks whether there is a point inside the given rectangle. + * @param rect The given rectangle. + * @param otherPoint The point that might be inside the given rectangle. + * @returns True, if the point is completely inside the rectangle. Else, false. + */ +export function pointInRect(rect: Rectangle, point: Point): boolean { + const rectCorners = rect.getCorners(); + + return ( + rectCorners[0].x < point.x && + rectCorners[1].x > point.x && + rectCorners[0].y < point.y && + rectCorners[2].y > point.y + ); +} + +/** + * Method that checks whether a point is within the given ellipse. + * @param ellipse The given ellipse + * @param otherPoint The point that might be inside the given ellipse. + * @returns True, if the point is inside the given ellipse. Else, false + */ +export function pointInELlipse(ellipse: Ellipse, point: Point): boolean { + //(x-h)^2/rx^2 + (y-k)^2/ry^2 <= 1 + //(x, y) = new point + //(h, k) = center + + const p: number = //Math.ceil( + Math.pow(point.x - ellipse.center.x, 2) / Math.pow(ellipse.radiusX, 2) + + Math.pow(point.y - ellipse.center.y, 2) / Math.pow(ellipse.radiusY, 2); + //); + + return p < 1; +} + +/** + * An array containing the widest coordinates of the given ellipse, i.e. the coordinates along the + * x-axis and y-axis of the ellipse. + * @param ellipse The given ellipse. + * @returns The coordinates 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). + */ +function getWidestCoordinates(ellipse: Ellipse): Point[] { + return [ + new Point(ellipse.center.x, ellipse.center.y - ellipse.radiusY), + new Point(ellipse.center.x + ellipse.radiusX, ellipse.center.y), + new Point(ellipse.center.x, ellipse.center.y + ellipse.radiusY), + new Point(ellipse.center.x - ellipse.radiusX, ellipse.center.y), + ]; +} + +/** + * Method to get the points along the bounding curve of the given ellipse + * @param ellipse The given ellipse + * @returns An array of points along the bounding curve of the ellipse + */ +function getEllipsePoints(ellipse: Ellipse, amount: number): Point[] { + const points: Point[] = []; + const pointDist = ellipse.radiusX / (amount / 4); + + points[0] = getWidestCoordinates(ellipse)[3]; + let x: number; + let y: number; + + for (let i = 1; i < amount; i++) { + if (i < amount / 2 + 1) { + x = points[i - 1].x + pointDist; + y = getCurvePoint(ellipse, x, 1); + } else { + x = points[i - 1].x - pointDist; + y = getCurvePoint(ellipse, x, -1); + } + points[i] = new Point(x, y); + } + + return points; +} + +/** + * Method that returns a point on the curve of the given ellipse for a given x coordinate + * @param ellipse The given ellipse + * @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 + */ +function getCurvePoint(ellipse: Ellipse, x: number, curveHalf: number): number { + return ( + curveHalf * + ellipse.radiusY * + Math.sqrt(1 - Math.pow((x - ellipse.center.x) / ellipse.radiusX, 2)) + + ellipse.center.y + ); +} diff --git a/src/AEG/AtomNode.ts b/src/AEG/AtomNode.ts index 5ae71f3f..848977e6 100644 --- a/src/AEG/AtomNode.ts +++ b/src/AEG/AtomNode.ts @@ -29,10 +29,10 @@ export class AtomNode { * @param rect (Required) The rectangle to be set as the boundary box of this node. * @param val (Required) The value of the proposition represented by this node. */ - public constructor(val: string, origin: Point, rect: Rectangle) { - this.rect = rect; + public constructor(val: string, origin?: Point, rect?: Rectangle) { + this.rect = rect ?? new Rectangle(); this.identifier = val; - this.origin = origin; + this.origin = origin ?? new Point(); } /** @@ -43,6 +43,13 @@ export class AtomNode { return this.rect; } + /** + * Modifier to set the bounding rectangle of the Atom Node. + */ + public set Rectangle(rect: Rectangle) { + this.rect = rect; + } + /** * Accessor to get the identifier of the Atom Node. * @returns The identifier of this Atom Node @@ -51,6 +58,13 @@ export class AtomNode { return this.identifier; } + /** + * Modifier to set the identifier of the Atom Node + */ + public set Identifier(identifier: string) { + this.identifier = identifier; + } + /** * Accessor to get the origin (top left) vertex of the bounding rectangle of the Atom Node. * @returns The origin of the bounding rectangle @@ -59,6 +73,13 @@ export class AtomNode { return this.origin; } + /** + * Modifier to set the origin (top left) vertex of the bounding rectangle of the Atom Node. + */ + public set Origin(point: Point) { + this.origin = point; + } + /** * Method that checks whether a point is contained within this node. * @param otherPoint The point that might be within this node. @@ -75,10 +96,9 @@ export class AtomNode { */ public containsNode(otherNode: AtomNode | CutNode): boolean { if (otherNode instanceof AtomNode) { - return this.rect.containsShape((otherNode as AtomNode).Rectangle); + return this.rect.contains((otherNode as AtomNode).Rectangle); } else { - //ELLIPSE TO BE IMPLEMENTED ACCURATELY - return this.rect.containsShape((otherNode as CutNode).Ellipse as Ellipse); + return this.rect.contains((otherNode as CutNode).Ellipse as Ellipse); } } diff --git a/src/AEG/CutNode.ts b/src/AEG/CutNode.ts index 1c90471b..85dbde64 100644 --- a/src/AEG/CutNode.ts +++ b/src/AEG/CutNode.ts @@ -40,6 +40,13 @@ export class CutNode { return this.ellipse; } + /** + * Modifier to set the bounding ellipse of this Cut Node + */ + public set Ellipse(ellipse: Ellipse) { + this.ellipse = ellipse; + } + /** * Accessor to get the children (array of nodes nested within) of the Cut Node. * @returns The children of the Cut Node @@ -72,7 +79,7 @@ export class CutNode { public getCurrentCut(newNode: CutNode | AtomNode): CutNode { for (let i = 0; i < this.children.length; i++) { const child: CutNode | AtomNode = this.children[i]; - if (child instanceof CutNode && this.children[i].containsNode(newNode)) { + if (child instanceof CutNode && child.containsNode(newNode)) { //newNode can be placed at least one layer deeper return child.getCurrentCut(newNode); } @@ -108,11 +115,9 @@ export class CutNode { } if (otherNode instanceof AtomNode) { - return this.ellipse.containsShape(otherNode.Rectangle); - } else if (otherNode instanceof CutNode) { - return this.ellipse.containsShape(otherNode.Ellipse as Ellipse); + return this.ellipse.contains(otherNode.Rectangle); } else { - throw Error("containsNode expected AtomNode or CutNode"); + return this.ellipse.contains(otherNode.Ellipse as Ellipse); } } diff --git a/src/AEG/Ellipse.ts b/src/AEG/Ellipse.ts index cd5e2d3b..3300b1fb 100644 --- a/src/AEG/Ellipse.ts +++ b/src/AEG/Ellipse.ts @@ -1,5 +1,6 @@ import {Point} from "./Point"; import {Rectangle} from "./Rectangle"; +import {shapesOverlaps, shapeContains, pointInELlipse} from "./AEGUtils"; /** * Class that defines an Ellipse. @@ -67,21 +68,12 @@ export class Ellipse { } /** - * Method that checks whether a point is within this ellipse. + * Method that checks whether a point is within the given 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 { - //(x-h)^2/rx^2 + (y-k)^2/ry^2 <= 1 - //(x, y) = new point - //(h, k) = center - - const p: number = Math.ceil( - 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; + public containsPoint(point: Point): boolean { + return pointInELlipse(this, point); } /** @@ -90,32 +82,7 @@ export class Ellipse { * @returns True, if there is an overlap. Else, false. */ public overlaps(otherShape: Rectangle | Ellipse): boolean { - 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) || - (otherShape as Ellipse).boundingBox.containsShape(this.boundingBox) - ) { - //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 this ellipse - //are within the other ellipse - const points: Point[] = this.getEllipsePoints(); - for (let i = 0; i < points.length; i++) { - if (otherShape.containsPoint(points[i])) { - return true; - } - } - } - return false; - } + return shapesOverlaps(this, otherShape); } /** @@ -123,82 +90,7 @@ export class Ellipse { * @param otherShape The shape that might be within this ellipse. * @returns True, if the shape is within this ellipse. Else, false. */ - public containsShape(otherShape: Rectangle | Ellipse): boolean { - 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"); - } - - /** - * 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 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), - ]; - } - - private getEllipsePoints(): Point[] { - const points: Point[] = []; - const pointDist = this.radiusX / 15; - - points[0] = this.getWidestCoordinates()[3]; - let x: number; - let y: number; - - for (let i = 1; i < 64; i++) { - if (i < 33) { - x = points[i - 1].x + pointDist; - y = this.getCurvePoint(x, 1); - } else { - x = points[i - 1].x - pointDist; - y = this.getCurvePoint(x, -1); - } - points[i] = new Point(x, y); - } - - 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 ( - curveHalf * - this.radiusY * - Math.sqrt(1 - Math.pow((x - this.center.x) / this.radiusX, 2)) + - this.center.y - ); + public contains(otherShape: Rectangle | Ellipse): boolean { + return shapeContains(this, otherShape); } } diff --git a/src/AEG/Rectangle.ts b/src/AEG/Rectangle.ts index 3d9d23dd..395be8db 100644 --- a/src/AEG/Rectangle.ts +++ b/src/AEG/Rectangle.ts @@ -1,5 +1,6 @@ import {Ellipse} from "./Ellipse"; import {Point} from "./Point"; +import {shapesOverlaps, shapeContains, pointInRect} from "./AEGUtils"; /** * Class that defines a Rectangle. @@ -57,183 +58,21 @@ export class Rectangle { } /** - * Method that checks whether there is a point inside this rectangle. + * Method that checks whether there is a point inside the given rectangle. * @param otherPoint The point that might be inside this rectangle. * @returns True, if the point is completely inside the rectangle. Else, false. */ - public containsPoint(otherPoint: Point): boolean { - const thisCorners = this.getCorners(); - - return ( - thisCorners[0].x <= otherPoint.x && - thisCorners[1].x >= otherPoint.x && - thisCorners[1].y <= otherPoint.y && - thisCorners[2].y >= otherPoint.y - ); + public containsPoint(point: Point): boolean { + return pointInRect(this, point); } /** * Method that checks whether there is an overlap between this rectangle and another shape. * @param otherShape The other shape that might be overlapping this rectangle. * @returns True, if there is an overlap. Else, false. - * @todo This method is wrong, the ellipse overlap portion functions as a contains or overlaps - * it should be replaced to just handle overlaps and not return true on contains. -James - * @todo this is basically the exact same method that needs to be implemented for - * Ellipse.overlaps, the implementations should be merged into a single helper function, - * which is called by both methods. -James */ public overlaps(otherShape: Rectangle | Ellipse): boolean { - if (otherShape instanceof Rectangle) { - return this.edgesOverlap(otherShape); - } else if (otherShape instanceof Ellipse) { - for (let i = 0; i < 4; i++) { - if (otherShape.containsPoint(this.getCorners()[i])) { - return true; - } - } - return false; - } else { - throw Error("Invalid Shape passed to overlaps, must be a Rectangle | Ellipse"); - } - } - - /** - * Method that checks if any edges of this rectangle overlap with the other rectangle. - * @param otherRect The other rectangle to be checked. - * @returns True, if edges overlap. Else, false. - * @todo This algo can and should be simplified to be less than 10 lines of code. -James-Oswald - */ - private edgesOverlap(otherRect: Rectangle): boolean { - const thisCorners = this.getCorners(); - const otherCorners = otherRect.getCorners(); - - if (thisCorners[0].y <= otherCorners[0].y && thisCorners[2].y >= otherCorners[0].y) { - //The top edge of the other rectangle is within the horizontal boundaries - //of this rectangle - if (thisCorners[0].x <= otherCorners[0].x && thisCorners[1].x >= otherCorners[0].x) { - //The left edge of the other rectangle is within the vertical boundaries - //of this rectangle - return true; - } else if ( - //The left edge of this rectangle is within the vertical boundaries - //of the other rectangle - otherCorners[0].x <= thisCorners[0].x && - otherCorners[1].x >= thisCorners[0].x - ) { - return true; - } else if ( - //The right edge of the other rectangle is within the vertical boundaries - //of this rectangle - thisCorners[0].x <= otherCorners[1].x && - thisCorners[1].x >= otherCorners[1].x - ) { - return true; - } else if ( - //The right edge of this rectangle is within the vertical boundaries - //of the other rectangle - otherCorners[0].x <= thisCorners[1].x && - otherCorners[1].x >= thisCorners[1].x - ) { - return true; - } - - return false; - } else if (otherCorners[0].y <= thisCorners[0].y && otherCorners[2].y >= thisCorners[0].y) { - //The top edge of this rectangle is within the horizontal boundaries - //of the other rectangle - if (thisCorners[0].x <= otherCorners[0].x && thisCorners[1].x >= otherCorners[0].x) { - //The left edge of the other rectangle is within the vertical boundaries - //of this rectangle - return true; - } else if ( - //The left edge of this rectangle is within the vertical boundaries - //of the other rectangle - otherCorners[0].x <= thisCorners[0].x && - otherCorners[1].x >= thisCorners[0].x - ) { - return true; - } else if ( - //The right edge of the other rectangle is within the vertical boundaries - //of this rectangle - thisCorners[0].x <= otherCorners[1].x && - thisCorners[1].x >= otherCorners[1].x - ) { - return true; - } else if ( - //The right edge of this rectangle is within the vertical boundaries - //of the other rectangle - otherCorners[0].x <= thisCorners[1].x && - otherCorners[1].x >= thisCorners[1].x - ) { - return true; - } - - return false; - } else if (thisCorners[0].y <= otherCorners[2].y && thisCorners[2].y >= otherCorners[2].y) { - //The bottom edge of the other rectangle is within the horizontal boundaries - //of this rectangle - if (thisCorners[0].x <= otherCorners[0].x && thisCorners[1].x >= otherCorners[0].x) { - //The left edge of the other rectangle is within the vertical boundaries - //of this rectangle - return true; - } else if ( - //The left edge of this rectangle is within the vertical boundaries - //of the other rectangle - otherCorners[0].x <= thisCorners[0].x && - otherCorners[1].x >= thisCorners[0].x - ) { - return true; - } else if ( - //The right edge of the other rectangle is within the vertical boundaries - //of this rectangle - thisCorners[0].x <= otherCorners[1].x && - thisCorners[1].x >= otherCorners[1].x - ) { - return true; - } else if ( - //The right edge of this rectangle is within the vertical boundaries - //of the other rectangle - otherCorners[0].x <= thisCorners[1].x && - otherCorners[1].x >= thisCorners[1].x - ) { - return true; - } - - return false; - } else if (otherCorners[0].y <= thisCorners[2].y && otherCorners[2].y >= thisCorners[2].y) { - //The bottom edge of this rectangle is within the horizontal boundaries - //of the other rectangle - if (thisCorners[0].x <= otherCorners[0].x && thisCorners[1].x >= otherCorners[0].x) { - //The left edge of the other rectangle is within the vertical boundaries - //of this rectangle - return true; - } else if ( - //The left edge of this rectangle is within the vertical boundaries - //of the other rectangle - otherCorners[0].x <= thisCorners[0].x && - otherCorners[1].x >= thisCorners[0].x - ) { - return true; - } else if ( - //The right edge of the other rectangle is within the vertical boundaries - //of this rectangle - thisCorners[0].x <= otherCorners[1].x && - thisCorners[1].x >= otherCorners[1].x - ) { - return true; - } else if ( - //The right edge of this rectangle is within the vertical boundaries - //of the other rectangle - otherCorners[0].x <= thisCorners[1].x && - otherCorners[1].x >= thisCorners[1].x - ) { - return true; - } - - return false; - } - - return false; + return shapesOverlaps(this, otherShape); } /** @@ -241,23 +80,8 @@ export class Rectangle { * @param otherShape The shape that might be within this rectangle. * @returns True, if the shape is within this rectangle. Else, false. */ - public containsShape(otherShape: Rectangle | Ellipse): boolean { - if (otherShape instanceof Rectangle) { - const otherCorners = otherShape.getCorners(); - - //Other rectangle is within this rectangle if its opposite corners are within - return this.containsPoint(otherCorners[0]) && this.containsPoint(otherCorners[2]); - } else { - //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; - } + public contains(otherShape: Rectangle | Ellipse): boolean { + return shapeContains(this, otherShape); } /** diff --git a/src/AtomMode.ts b/src/AtomMode.ts index 581404a9..276d6294 100644 --- a/src/AtomMode.ts +++ b/src/AtomMode.ts @@ -13,7 +13,7 @@ let atomMetrics: TextMetrics; let hasMouseDown: Boolean = false; let hasAtom: Boolean = false; -let currentAtom: AtomNode = new AtomNode(); +let currentAtom: AtomNode = new AtomNode("a"); //MAKING a THE DEFAULT IDENTIFIER FOR ATOMS /** * Will compare the event given with all possible events it could be. @@ -30,22 +30,22 @@ export function atomHandler(event: Event) { const thisEvent: KeyboardEvent = event; const regex = new RegExp(/^[A-Za-z]$/); if (regex.test(thisEvent.key)) { - currentAtom.identifier = thisEvent.key; + currentAtom.Identifier = thisEvent.key; hasAtom = true; } } else if (event.type === "mousedown" && hasAtom) { const thisEvent: MouseEvent = event; - atomMetrics = ctx.measureText(currentAtom.identifier); + atomMetrics = ctx.measureText(currentAtom.Identifier); const startVertex: Point = new Point( thisEvent.clientX, thisEvent.clientY - atomMetrics.actualBoundingBoxAscent ); - currentAtom.rect = new Rectangle( + currentAtom.Rectangle = new Rectangle( startVertex, atomMetrics.width, atomMetrics.fontBoundingBoxDescent + atomMetrics.actualBoundingBoxAscent ); - currentAtom.origin = new Point(thisEvent.clientX, thisEvent.clientY); + currentAtom.Origin = new Point(thisEvent.clientX, thisEvent.clientY); ctx.clearRect(0, 0, canvas.width, canvas.height); redrawCut(tree.sheet); @@ -57,8 +57,8 @@ export function atomHandler(event: Event) { hasMouseDown = true; } else if (event.type === "mousemove" && hasMouseDown) { const thisEvent: MouseEvent = event; - currentAtom.origin = new Point(thisEvent.clientX, thisEvent.clientY); - currentAtom.rect.startVertex = new Point( + currentAtom.Origin = new Point(thisEvent.clientX, thisEvent.clientY); + currentAtom.Rectangle.startVertex = new Point( thisEvent.clientX, thisEvent.clientY - atomMetrics.actualBoundingBoxAscent ); @@ -74,14 +74,14 @@ export function atomHandler(event: Event) { if (tree.canInsert(currentAtom)) { tree.insert(currentAtom); } - currentAtom = new AtomNode(currentAtom.identifier); + currentAtom = new AtomNode(currentAtom.Identifier); ctx.clearRect(0, 0, canvas.width, canvas.height); redrawCut(tree.sheet); hasMouseDown = false; console.log(tree.toString()); } else if (event.type === "mouseout" && hasMouseDown) { hasMouseDown = false; - currentAtom = new AtomNode(currentAtom.identifier); + currentAtom = new AtomNode(currentAtom.Identifier); ctx.clearRect(0, 0, canvas.width, canvas.height); redrawCut(tree.sheet); } @@ -95,9 +95,9 @@ export function atomHandler(event: Event) { function drawAtom(thisAtom: AtomNode, color: string) { ctx.fillStyle = color; ctx.strokeStyle = color; - const displayBox = thisAtom.rect; + const displayBox = thisAtom.Rectangle; ctx.beginPath(); - ctx.fillText(thisAtom.identifier, thisAtom.origin.x, thisAtom.origin.y); + ctx.fillText(thisAtom.Identifier, thisAtom.Origin.x, thisAtom.Origin.y); ctx.rect( displayBox.startVertex.x, displayBox.startVertex.y, diff --git a/src/CutMode.ts b/src/CutMode.ts index 657299f7..ac57965e 100644 --- a/src/CutMode.ts +++ b/src/CutMode.ts @@ -26,7 +26,7 @@ let startingPoint: Point = new Point(); * @param event The event that will be used */ export function cutHandler(event: MouseEvent) { - let newCut: CutNode = new CutNode(); + let newCut: CutNode = new CutNode(new Ellipse()); const currentPoint: Point = new Point(); if (event.type === "mousedown") { @@ -39,7 +39,7 @@ export function cutHandler(event: MouseEvent) { ctx.clearRect(0, 0, canvas.width, canvas.height); redrawCut(tree.sheet); currentEllipse = createEllipse(startingPoint, currentPoint); - newCut.ellipse = currentEllipse; + newCut.Ellipse = currentEllipse; if (tree.canInsert(newCut) && currentEllipse.radiusX > 15 && currentEllipse.radiusY > 15) { drawEllipse(newCut, "#00FF00"); @@ -108,7 +108,7 @@ export function createEllipse(original: Point, current: Point): Ellipse { */ function drawEllipse(thisCut: CutNode, color: string) { ctx.strokeStyle = color; - const ellipse: Ellipse = thisCut.ellipse; + const ellipse: Ellipse = thisCut.Ellipse; const center: Point = ellipse.center; ctx.beginPath(); ctx.ellipse(center.x, center.y, ellipse.radiusX, ellipse.radiusY, 0, 0, 2 * Math.PI); From af3f81202ef665d1c4afa490cb7368045a766286 Mon Sep 17 00:00:00 2001 From: AnushaTiwari5 Date: Tue, 10 Oct 2023 01:51:34 -0400 Subject: [PATCH 3/3] updated index.ts as per build fail --- src/AEG/AEGTree.ts | 1 - src/index.ts | 22 +++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/AEG/AEGTree.ts b/src/AEG/AEGTree.ts index fc69360e..c32b2c14 100644 --- a/src/AEG/AEGTree.ts +++ b/src/AEG/AEGTree.ts @@ -86,7 +86,6 @@ export class AEGTree { if (incomingNode instanceof CutNode) { for (let i = 0; i < originalChildren.length; i++) { if (incomingNode.containsNode(originalChildren[i])) { - //incomingNode.children.push(originalChildren[i]); incomingNode.Child = originalChildren[i]; currentCut.Children.splice(i, 1); } diff --git a/src/index.ts b/src/index.ts index 327d9ea8..f44d9012 100644 --- a/src/index.ts +++ b/src/index.ts @@ -101,21 +101,21 @@ function removeListeners() { */ export function redrawCut(incomingNode: CutNode) { cutDisplay.innerHTML = tree.toString(); - for (let i = 0; incomingNode.children.length > i; i++) { - if (incomingNode.children[i] instanceof AtomNode) { - redrawAtom(incomingNode.children[i]); + for (let i = 0; incomingNode.Children.length > i; i++) { + if (incomingNode.Children[i] instanceof AtomNode) { + redrawAtom(incomingNode.Children[i]); } else { - redrawCut(incomingNode.children[i]); + redrawCut(incomingNode.Children[i]); } } - if (incomingNode.ellipse instanceof Ellipse) { + if (incomingNode.Ellipse instanceof Ellipse) { ctx.strokeStyle = "#000000"; ctx.beginPath(); ctx.ellipse( - incomingNode.ellipse.center.x, - incomingNode.ellipse.center.y, - incomingNode.ellipse.radiusX, - incomingNode.ellipse.radiusY, + incomingNode.Ellipse.center.x, + incomingNode.Ellipse.center.y, + incomingNode.Ellipse.radiusX, + incomingNode.Ellipse.radiusY, 0, 0, 2 * Math.PI @@ -129,7 +129,7 @@ export function redrawCut(incomingNode: CutNode) { * @param incomingNode The Atom Node to be redrawn */ function redrawAtom(incomingNode: AtomNode) { - const displayBox = incomingNode.rect; + const displayBox = incomingNode.Rectangle; ctx.strokeStyle = "#000000"; ctx.fillStyle = "#000000"; ctx.beginPath(); @@ -139,6 +139,6 @@ function redrawAtom(incomingNode: AtomNode) { displayBox.width, displayBox.height ); - ctx.fillText(incomingNode.identifier, incomingNode.origin.x, incomingNode.origin.y); + ctx.fillText(incomingNode.Identifier, incomingNode.Origin.x, incomingNode.Origin.y); ctx.stroke(); }