From e2bc1e2956e93ecb24abbb4db4cc3307f36b11d7 Mon Sep 17 00:00:00 2001 From: BurntWaffleCake Date: Mon, 23 Oct 2023 09:46:30 -0500 Subject: [PATCH 1/4] optimization branch creation --- CS-III/Projects/AdvancedSorts.html | 16 +- CS-III/Projects/ConspiracyTheory.html | 226 ++++++++--- .../articleGlobalNavObject.html | 1 + .../articleGlobalNavObjectHome.html | 1 + .../articleNavObjectTemplate.html | 1 + .../classesArticleNavObject.html | 1 + .../csArticleNavObject.html | 2 + .../statArticleNavObject.html | 1 + .../articlestylesheettemp.css | 4 + index.html | 2 +- javascript/PhysicsEngine/Polygon.js | 365 +++++++++++++----- javascript/PhysicsEngine/main.js | 113 ++++-- mainPages/computerScience.html | 8 +- mainPages/index.html | 14 + mainPages/index.js | 66 ++++ mainPages/why.html | 57 +-- 16 files changed, 672 insertions(+), 206 deletions(-) rename {styles => deprecated}/articlestylesheettemp.css (98%) create mode 100644 mainPages/index.js diff --git a/CS-III/Projects/AdvancedSorts.html b/CS-III/Projects/AdvancedSorts.html index 628ef17..a28eb9a 100644 --- a/CS-III/Projects/AdvancedSorts.html +++ b/CS-III/Projects/AdvancedSorts.html @@ -14,19 +14,19 @@ + diff --git a/CS-III/Projects/ConspiracyTheory.html b/CS-III/Projects/ConspiracyTheory.html index 0d3df4f..9e8d2c3 100644 --- a/CS-III/Projects/ConspiracyTheory.html +++ b/CS-III/Projects/ConspiracyTheory.html @@ -3,59 +3,189 @@ - - - - - - - + + + + + + + + .image-holder { + max-width: 100%; + } + + .image-holder img { + max-width: 100%; + height: auto; + background-color: white; + } + + .article-nav-header { + font-size: larger; + } + - - -
- - -
- -
-

Conspiracy Theories

-

The Basics of Recommendation Algorithms

-

The Feedback Loop

-

Conspiracy Theories & Algorithmic Amplification

-

Platform Incentives

-

Potential for Exploitation

-
- -
- - -
+ + +
+ + +
+ +
+

Conspiracy Theories

+

The Basics of Recommendation Algorithms

+

+ Recommendation algorithms are commonly used in online applications such as social media, ads, and marketing + sites. These algorithms are designed to find the best fitting data sets and match them to people most likely to + relate and invest in them. Companies use the algorithms for applications such as recommending relevant products + in advertisements or finding related topics when searching for certain ideas (as Google does when you search for + things). Who main recommendation paradigms exist: collaborative and content. Each conform to different datasets + and have their own methods of categorizing information and compiling such information into results. +

+ +

Collaborative Filtering

+

+ Collaborative filtering uses past interactions between users and items to produce new recommendations and + relevant data. Past interactions are stored in a "user-item interactions matrix" which holds different metrics + regarding user's interactions with the item (rating, time spent on data, click interactions, etc). These + matrices are then used to estimate similar items and/or users to group data points into clusters of similar + information. +
+
+ + Collaborative filtering is divided into two subcategories: memory based and model based. Memory based approaches + work directly with the matrix's raw values and search for similar data (find similar users by comparing matrix + values and recommend items these neighbors find interest in). Model based approaches use an underlying + generative model that tries to predict new related data points using that model. +
+
+ + Some benefits that collaborative filtering has is that it requires no starting data to implement and instead + reads information that users generate as they interact with the product. This means that more interactions and + time result in better recommendations due to the dataset getting larger and more interactions are held. However, + this does mean that collaborative filtering is weak during the early stages of the product where no data about + its users are present meaning no recommendations can be made to new users. This problem is also present in users + with very little interactions which will result in inaccurate predictions by the algorithm. To alleviate this + effect, random or new data points can be chosen and given to random users which is called the random strategy. + Another strategy used is the maximum expectation strategy where generally popular data points or completely new + data points are given to the most active users. Along with this, the exploration strategy can also be used where + sets of various items with similar structures are given to new users or new items are given to similar user. +

+ +

Content Filtering

+

+ Content filtering uses additional available data such as age, sex, occupation, and other initial data to form + recommendations. Content filtering build models off of these available datasets to recommend data points using + observed user-item interactions. For example, if a person generally watches action oriented films and movies + more than other genres, the model will recommend more actions films to that person. +
+ The initial issues present in collaborative filtering is less pronounced in content filtering as new data points + can be recommended to new users using already available data on them. The problem of completely new users or + ones with previously unrecorded interests persists; however, these drawbacks are solved as more and more data is + added into the system. +

+ +

The Feedback Loop

+

What Is It?

+

A feedback loop is a part of a system where outputs of certain operations are used as inputs of the same + operation in the future. Recommendation algorithms use feedback loops to iteratively generate new data points + for the system to use as stepping stones to develop more accurate and relevant topics users may find interest + in.

+ +

Feedback Loops in Recommendation Algorithms

+

+ Most recommendation algorithms use some sort of built dataset to recommend new information to users. These + datasets are created using the already available data about the user or are generated and stored as the user + interacts with the product. The growth of these datasets are what cause feedback loops to form where new data is + closely related to older data forming biases and causing the algorithm to focus more on those similar topics. + This causes an effect referred to as the "filter bubble" or "echo chamber" where very similar topics are + reiterated to the user in increasing intensity causing the user interacts with the same topics and forming a + feedback loop. +

+ +

So What?

+

+ As companies try to get their users to engage more with their product, many users will find themselves getting + recommended biased or "generic" content that conforms to their interests. This may lead to tunnel visioning or + false belief in topics that fail to branch out into other relevant or new topics which may lead to + misinformation, misrepresentation, among many other problems. +

+ +

Conspiracy Theories & Algorithmic Amplification

+

Conspiracy Theory Catastrophe

+

As companies look for ways to increase user engagement and metrics such as look time, shares, + clicks, etc. a general trend that has emerged is that people tend to engage much more with sensationalistic + content such as conspiracy theories or conflict / internet drama. The fluctuating, diverse, and magnitudal cast + of user reactions to these types of content had resulted in an increased priority for such content to be shared + with newer and engaging people. As discussed in the paragraphs above talking about recommendation algorithms and + feedback loops, this increased level of engage builds up at rates much faster than regular articles and content + which in turn recommends these topics to newer and more individuals increasing engagement even more. +
+
+ What this means is that topics regarding very polarized opinions or outlandish and outright wrong opinions can + be spread extremely quickly through increased user interactions resulting in mass misinformation and + misunderstandings. This is further emphasized by the general laziness of the average internet surfer, many who + rely on single, unreliable sources, and who believe what others say without much thought. +

+

Platform Incentives

+

The Basis of the Problem

+

+ Many of the companies and applications where this problem exists use user interactions as their main source of + relevancy and income which only incentivize the promotion of these types of conversations and topics even more. + This type of behavior especially plagues information outlets such as news and article websites where titles are + often misleading, hyperbole is generously sprinkled throughout, and information is just outright false. The fact + that these types of articles are what people find most interesting and the strategies used to generate visits is + what fuels the engine for conspiracy theories and other types of misleading content to flourish. +

+

Potential for Exploitation

+

Escalation

+

+ In many cases, the misinformation spread throughout the internet through conspiracy theories and other forms of + sensationalistic articles are generally ridiculed, made fun of, and passed on as outlandish ideas that nobody + would believe in. However, the implications and powerful effects that these types of topics can have can easily + be used to exploit the human mind. Objectives such as propaganda, targeted attacks, and many other forms of + dangerous and destructive behavior can lead to devastating consequences. +

+

What Can We Do?

+

+ Most of the problems present in conspiracy theories and misleading information hubs can be alleviated through + thorough researching and exploration. As a matter of fact, many of these theories only require a line of + reasoning or common sense to solve. Making sure to check multiple credible sources of information, keep an open + mind, and carefully work through a concrete line of reasoning can easily knock out the most obvious falsities + and result in a deeper level of perception towards misleading information. +

+ +
+ +
+ + +
\ No newline at end of file diff --git a/articleTemplateObjects/articleGlobalNavObject.html b/articleTemplateObjects/articleGlobalNavObject.html index 5a45dd3..164baeb 100644 --- a/articleTemplateObjects/articleGlobalNavObject.html +++ b/articleTemplateObjects/articleGlobalNavObject.html @@ -1,3 +1,4 @@ + - +
diff --git a/javascript/PhysicsEngine/Polygon.js b/javascript/PhysicsEngine/Polygon.js index 238c2b0..5af6cfb 100644 --- a/javascript/PhysicsEngine/Polygon.js +++ b/javascript/PhysicsEngine/Polygon.js @@ -62,6 +62,177 @@ export class Face { } } +export class Model { + constructor(polygons, pos = new Vector2(0, 0), rot = 0, initVel = new Vector2(0, 0), initRotVel = 0) { + this.components = [] + polygons.forEach(element => { + this.components.push({ polygon: element, offset: element.pos.clone(), rotation: element.rot - rot }) + }); + + this.pos = pos + this.vel = initVel + this.rot = rot * Math.PI / 180 + this.rotVel = initRotVel * Math.PI / 180 + + this.e = 0 + this.df = .4 + this.sf = .6 + this.mTot = 0 + this.iTot = 0 + + polygons.forEach(element => { + this.mTot += element.mass + this.iTot += element.mass * element.pos.magnitude() ** 2 + }); + + this.invMTot = 1 / this.mTot + this.invITot = 1 / this.iTot + } + + render(ctx) { + this.components.forEach(element => { + let rot = element.rotation + let offset = element.offset + let polygon = element.polygon + + polygon.render(ctx) + }); + } + + tick(dt) { + if (this.anchored) { + this.vel.set(0, 0) + this.rotVel = 0 + return + } else if (this.lockRot) { + this.rotVel = 0 + return + } + this.pos.add(this.vel.clone().scale(dt)); + this.rot += this.rotVel * dt + + this.components.forEach(element => { + let rot = element.rotation + let offset = element.offset + let polygon = element.polygon + + polygon.rot = this.rot + rot + polygon.pos.set(this.pos.x + offset.x * Math.cos(this.rot) - offset.y * Math.sin(this.rot), this.pos.y + offset.x * Math.sin(this.rot) + offset.y * Math.cos(this.rot)) + }); + } + + testModelCollision() { + + } + + testPolygonCollision(colPoly, ctx) { + let results = [] + this.components.forEach(element => { + let rot = element.rotation + let offset = element.offset + let polygon = element.polygon + + let result = polygon.testCollision(colPoly, ctx) + if (result == false) { return } + results.push({polygon: colPoly, mvt: result.mvt, normal: result.normal, points: result.points}) + // this.resolveCollision(polygon, result.mvt, result.normal, result.point) + }); + return results + } + + applyImpulse(impulse, pos) { + let collArm = pos.clone().subtract(this.pos) + this.vel.add(impulse.clone().scale(this.invMTot)) + this.rotVel += this.invITot * collArm.cross(impulse) + } + + resolvePolygonCollision(polygon, mvt, normal, collisionPoints) { + if (this.anchored && polygon.anchored) { + return + } else if (this.anchored) { + polygon.pos.add(normal.clone().scale(mvt / 2)) + } else if (polygon.anchored) { + this.pos.subtract(normal.clone().scale(mvt / 2)) + } else { + this.pos.subtract(normal.clone().scale(mvt / 2)) + polygon.pos.add(normal.clone().scale(mvt / 2)) + } + + let impulses = [] + let collArm1s = [] + let collArm2s = [] + + collisionPoints.forEach(collPoint => { + let collArm1 = collPoint.clone().subtract(this.pos) + let rotVel1 = new Vector2(-this.rotVel * collArm1.y, this.rotVel * collArm1.x) + let closVel1 = this.vel.clone().add(rotVel1) + + let collArm2 = collPoint.clone().subtract(polygon.pos) + let rotVel2 = new Vector2(-polygon.rotVel * collArm2.y, polygon.rotVel * collArm2.x) + let closVel2 = polygon.vel.clone().add(rotVel2) + + collArm1s.push(collArm1) + collArm2s.push(collArm2) + + let impAug1 = collArm1.cross(normal) + impAug1 = impAug1 * impAug1 * this.invITot + let impAug2 = collArm2.cross(normal) + impAug2 = impAug2 * impAug2 * polygon.invI + + let relVel = closVel1.clone().subtract(closVel2) + let sepVel = relVel.dot(normal) + let newSepVel = -sepVel * Math.min(this.e, polygon.e) + let vSepDiff = newSepVel - sepVel + + let impulse = vSepDiff / (this.invMTot + polygon.invMass + impAug1 + impAug2) / collisionPoints.length + let impulseVec = normal.clone().scale(impulse) + + impulses.push(impulseVec) + + //Friction + let tangent = relVel.clone().subtract(normal.clone().scale(relVel.dot(normal))) + if (tangent.magnitude() < 0.005) { return } //tangent is near zero + tangent.normalize() + + let frictionAug1 = collArm1.cross(tangent) + frictionAug1 = frictionAug1 * frictionAug1 * this.invITot + let frictionAug2 = collArm2.cross(tangent) + frictionAug2 = frictionAug2 * frictionAug2 * polygon.invI + + let impulseFriction = relVel.dot(tangent) / (frictionAug1 + frictionAug2 + this.invMTot + polygon.invMass) / collisionPoints.length + + let sf = (this.sf + polygon.sf) * .5 + let df = (this.df + polygon.df) * .5 + + let impFricVec + if (impulseFriction <= impulse * sf) { + impFricVec = tangent.scale(-impulseFriction) + } else { + impFricVec = tangent.clone().scale(impulse * df) + } + + collArm1s.push(collArm1) + collArm2s.push(collArm2) + + impulses.push(impFricVec) + }); + + impulses.forEach((impulse, index) => { + let collArm1 = collArm1s[index] + let collArm2 = collArm2s[index] + + this.vel.add(impulse.clone().scale(this.invMTot)) + polygon.vel.subtract(impulse.clone().scale(polygon.invMass)) + + this.rotVel += this.invITot * collArm1.cross(impulse) + polygon.rotVel -= polygon.invI * collArm2.cross(impulse) + }); + + } + + +} + export class Polygon { constructor(points, pos = new Vector2(0, 0), rot = 0, initVel = new Vector2(0, 0), initRotVel = 0) { this.points = points; @@ -86,7 +257,9 @@ export class Polygon { this.rot = rot * Math.PI / 180; this.rotVel = initRotVel * Math.PI / 180; - this.e = .5 + this.e = .6 + this.df = .4 + this.sf = .6 this.lockRot = false this.anchored = false @@ -175,21 +348,22 @@ export class Polygon { ctx.lineTo(worldCoords[0].x, worldCoords[0].y); // ctx.fill() ctx.stroke() - + + //draw foward + ctx.beginPath() + ctx.strokeStyle = "rgb(255, 0, 0)" + ctx.moveTo(this.pos.x, this.pos.y); + ctx.lineTo(this.pos.x + 50 * Math.cos(this.rot), this.pos.y + 50 * Math.sin(this.rot)) + ctx.stroke(); + + ctx.beginPath() + ctx.strokeStyle = "rgb(0, 255, 0)" + ctx.moveTo(this.pos.x, this.pos.y); + ctx.lineTo(this.pos.x + 50 * Math.cos(this.rot - Math.PI / 2), this.pos.y + 50 * Math.sin(this.rot - Math.PI / 2)) + ctx.stroke(); if (debug) { - //draw foward - ctx.beginPath() - ctx.strokeStyle = "rgb(255, 0, 0)" - ctx.moveTo(this.pos.x, this.pos.y); - ctx.lineTo(this.pos.x + 50 * Math.cos(this.rot), this.pos.y + 50 * Math.sin(this.rot)) - ctx.stroke(); - ctx.beginPath() - ctx.strokeStyle = "rgb(0, 255, 0)" - ctx.moveTo(this.pos.x, this.pos.y); - ctx.lineTo(this.pos.x + 50 * Math.cos(this.rot - Math.PI / 2), this.pos.y + 50 * Math.sin(this.rot - Math.PI / 2)) - ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = "rgb(0, 255, 255)" @@ -248,41 +422,51 @@ export class Polygon { return false } + findCollidingPoint(polygon, ctx) { - let collPoint = undefined + let collPoints = [] let minDis = Number.MAX_SAFE_INTEGER for (let point of polygon.points) { for (let face of this.sides) { - let dis = minDisToLineSeg(this.toWorldSpace(face.from), this.toWorldSpace(face.to), polygon.toWorldSpace(point)) + let worldPoint = polygon.toWorldSpace(point) + + let dis = minDisToLineSeg(this.toWorldSpace(face.from), this.toWorldSpace(face.to), worldPoint) - if (dis < minDis) { + if (dis < minDis - 0.5) { minDis = dis; - collPoint = polygon.toWorldSpace(point) + collPoints = [worldPoint] + } else if (dis < minDis + 0.5) { + collPoints.push(worldPoint) } } } for (let point of this.points) { for (let face of polygon.sides) { - let dis = minDisToLineSeg(polygon.toWorldSpace(face.from), polygon.toWorldSpace(face.to), this.toWorldSpace(point)) + let worldPoint = this.toWorldSpace(point) + let dis = minDisToLineSeg(polygon.toWorldSpace(face.from), polygon.toWorldSpace(face.to), worldPoint) - if (dis < minDis) { + if (dis < minDis - 0.5) { minDis = dis; - collPoint = this.toWorldSpace(point) + collPoints = [worldPoint] + } else if (dis < minDis + 0.5) { + collPoints.push(worldPoint) } } } if (debug) { - ctx.fillStyle = "rgb(255,0,0)" - ctx.fillRect(collPoint.x, collPoint.y, 10, 10) + collPoints.forEach(point => { + ctx.fillStyle = "rgb(255,0,0)" + ctx.fillRect(point.x, point.y, 10, 10) + }); } - return collPoint + return collPoints } - resolveCollision(polygon, mvt, normal, collisionPoint, ctx) { + resolveCollision(polygon, mvt, normal, collisionPoints) { if (this.anchored && polygon.anchored) { // return } else if (this.anchored) { @@ -294,49 +478,79 @@ export class Polygon { polygon.pos.add(normal.clone().scale(mvt / 2)) } - let collArm1 = collisionPoint.clone().subtract(this.pos) - let rotVel1 = new Vector2(-this.rotVel * collArm1.y, this.rotVel * collArm1.x) - let closVel1 = this.vel.clone().add(rotVel1) + let impulses = [] + let collArm1s = [] + let collArm2s = [] - let collArm2 = collisionPoint.clone().subtract(polygon.pos) - let rotVel2 = new Vector2(-polygon.rotVel * collArm2.y, polygon.rotVel * collArm2.x) - let closVel2 = polygon.vel.clone().add(rotVel2) + collisionPoints.forEach(collPoint => { + let collArm1 = collPoint.clone().subtract(this.pos) + let rotVel1 = new Vector2(-this.rotVel * collArm1.y, this.rotVel * collArm1.x) + let closVel1 = this.vel.clone().add(rotVel1) - // console.log(collArm1, rotVel1, closVel1) - // console.log(collArm2, rotVel2, closVel2) + let collArm2 = collPoint.clone().subtract(polygon.pos) + let rotVel2 = new Vector2(-polygon.rotVel * collArm2.y, polygon.rotVel * collArm2.x) + let closVel2 = polygon.vel.clone().add(rotVel2) - let impAug1 = collArm1.cross(normal) - impAug1 = impAug1 * impAug1 * this.invI - let impAug2 = collArm2.cross(normal) - impAug2 = impAug2 * impAug2 * polygon.invI + collArm1s.push(collArm1) + collArm2s.push(collArm2) - // console.log(impAug1, impAug2) + let impAug1 = collArm1.cross(normal) + impAug1 = impAug1 * impAug1 * this.invI + let impAug2 = collArm2.cross(normal) + impAug2 = impAug2 * impAug2 * polygon.invI - let relVel = closVel1.clone().subtract(closVel2) - let sepVel = relVel.dot(normal) - let newSepVel = -sepVel * Math.min(this.e, polygon.e) - let vSepDiff = newSepVel - sepVel + let relVel = closVel1.clone().subtract(closVel2) + let sepVel = relVel.dot(normal) + let newSepVel = -sepVel * Math.min(this.e, polygon.e) + let vSepDiff = newSepVel - sepVel - // console.log(relVel, sepVel, newSepVel, vSepDiff) - let impulse = vSepDiff / (this.invMass + polygon.invMass + impAug1 + impAug2) - let impulseVec = normal.clone().scale(impulse) + let impulse = vSepDiff / (this.invMass + polygon.invMass + impAug1 + impAug2) / collisionPoints.length + let impulseVec = normal.clone().scale(impulse) - this.vel.add(impulseVec.clone().scale(this.invMass)) - polygon.vel.subtract(impulseVec.clone().scale(polygon.invMass)) + impulses.push(impulseVec) - this.rotVel += this.invI * collArm1.cross(impulseVec) - polygon.rotVel -= polygon.invI * collArm2.cross(impulseVec) + //Friction + let tangent = relVel.clone().subtract(normal.clone().scale(relVel.dot(normal))) + if (tangent.magnitude() < 0.005) { return } //tangent is near zero + tangent.normalize() + let frictionAug1 = collArm1.cross(tangent) + frictionAug1 = frictionAug1 * frictionAug1 * this.invI + let frictionAug2 = collArm2.cross(tangent) + frictionAug2 = frictionAug2 * frictionAug2 * polygon.invI - console.log(normal.dot(impulseVec)) - let normalForce = normal.dot(impulseVec) - let frictionDirection = new Vector2(-normal.y, normal.x) + let impulseFriction = relVel.dot(tangent) / (frictionAug1 + frictionAug2 + this.invMass + polygon.invMass) / collisionPoints.length + + let sf = (this.sf + polygon.sf) * .5 + let df = (this.df + polygon.df) * .5 + + let impFricVec + if (impulseFriction <= impulse * sf) { + impFricVec = tangent.scale(-impulseFriction) + } else { + impFricVec = tangent.clone().scale(impulse * df) + } + + collArm1s.push(collArm1) + collArm2s.push(collArm2) + + impulses.push(impFricVec) + }); + + impulses.forEach((impulse, index) => { + let collArm1 = collArm1s[index] + let collArm2 = collArm2s[index] + + this.vel.add(impulse.clone().scale(this.invMass)) + polygon.vel.subtract(impulse.clone().scale(polygon.invMass)) + + this.rotVel += this.invI * collArm1.cross(impulse) + polygon.rotVel -= polygon.invI * collArm2.cross(impulse) + }); - this.applyImpulse(frictionDirection.scale(normalForce), collisionPoint.clone()) - polygon.applyImpulse(frictionDirection.scale(-normalForce), collisionPoint.clone()) } - bruteForceTestCollision(polygon, ctx) { + testCollision(polygon, ctx) { let minOverlap = Number.MAX_VALUE let normal @@ -386,7 +600,7 @@ export class Polygon { let selfMaxAxis = axis.clone().scale(selfProj.maxMag / 5).add(polygon.pos) let compMinAxis = axis.clone().scale(compProj.minMag / 5).add(polygon.pos) let compMaxAxis = axis.clone().scale(compProj.maxMag / 5).add(polygon.pos) - + if (debug) { ctx.fillStyle = (overlap) ? "rgb(0,255,255)" : "rgb(0,0,255)" ctx.fillRect(selfMinAxis.x - 2.5, selfMinAxis.y - 2.5, 5, 5) @@ -408,45 +622,8 @@ export class Polygon { normal.scale(-1) } - let collisionPoint = this.findCollidingPoint(polygon, ctx) - return { mvt: minOverlap, normal: normal, point: collisionPoint } - } - - - testProjection(polygon) { - let minOverlap = Number.MAX_VALUE - let minFace - for (let i = 0; i < this.sides.length; i++) { - let face = this.sides[i] - let selfProj = this.selfProjection[i] - let compProj = polygon.projectToAxis(face.normal) - - let overlap = this.segmentOverlaps(selfProj.minMag, selfProj.maxMag, compProj.minMag, compProj.maxMag) - if (overlap == false) { - return false - } else if (minOverlap > overlap) { - minOverlap = overlap - minFace = face - } - } - return { overlap: minOverlap, face: minFace } - } - - testCollision(polygon) { - let selfOverlap = this.testProjection(polygon) - let compOverlap = polygon.testProjection(this) - - let minOverlap - let minFace - if (selfOverlap.overlap <= compOverlap.overlap) { - minOverlap = selfOverlap.overlap - minFace = selfOverlap.face - } else { - minOverlap = compOverlap.overlap - minFace = compOverlap.face - } - - if (selfOverlap == false || compOverlap == false) { return false } else { return { overlap: minOverlap, face: minFace } } + let collisionPoints = this.findCollidingPoint(polygon, ctx) + return { mvt: minOverlap, normal: normal, points: collisionPoints } } tick(dt, t) { diff --git a/javascript/PhysicsEngine/main.js b/javascript/PhysicsEngine/main.js index 9d1d87d..ac20135 100644 --- a/javascript/PhysicsEngine/main.js +++ b/javascript/PhysicsEngine/main.js @@ -6,8 +6,11 @@ let ctx = src.getContext("2d"); let poly1 let poly2 +let model +let bounds = [] let polygons = [] +let models = [] function clearCanvas(ctx) { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); @@ -15,6 +18,12 @@ function clearCanvas(ctx) { function render(dt) { + model.render(ctx) + + for (let model of models) { + model.render(ctx) + } + for (let polygon of polygons) { polygon.render(ctx) } @@ -47,29 +56,55 @@ function applyCollisionBounds(polygon) { } } + +let substeps = 4 function calculate(dt, t) { if (paused) { return } + for (let model of models) { + for (let i = 0; i < substeps; i++) { + let dti = dt / substeps + model.vel.y += 1000 * dti + model.tick(dti, t) + // if (!polygon.anchored) { + // // applyCollisionBounds(polygon) + // } + + for (let collPoly of polygons) { + let results = model.testPolygonCollision(collPoly, ctx) + + results.forEach(result => { + if (result == false) { return } + model.resolvePolygonCollision(result.polygon, result.mvt, result.normal, result.points) + }); + } + } + // console.log(model) + } + for (let polygon of polygons) { - for (let i = 0; i < 1; i++) { - let dti = dt / 1 - polygon.tick(dti, t) + for (let i = 0; i < substeps; i++) { + let dti = dt / substeps + polygon.vel.y += 1000 * dti + polygon.tick(dti, t) if (!polygon.anchored) { - applyCollisionBounds(polygon) + // applyCollisionBounds(polygon) } for (let collPoly of polygons) { if (collPoly === polygon) { continue } - let result = polygon.bruteForceTestCollision(collPoly, ctx) + let result = polygon.testCollision(collPoly, ctx) if (result == false) { continue } - polygon.resolveCollision(collPoly, result.mvt, result.normal, result.point, ctx) + polygon.resolveCollision(collPoly, result.mvt, result.normal, result.points) } } } + + } let time = 0; @@ -80,9 +115,9 @@ function loop(t) { let dt = (t / 1000) - time; if (mouse1down) { - let delta = new Vector2(prevMouseEvent.clientX, prevMouseEvent.clientY).subtract(poly1.pos) - // poly1.applyImpulse(delta.scale(poly1.mass / 2), poly1.pos) - poly1.pos.set(prevMouseEvent.clientX, prevMouseEvent.clientY) + let delta = new Vector2(prevMouseEvent.clientX, prevMouseEvent.clientY).subtract(model.pos) + // model.pos.set(prevMouseEvent.clientX, prevMouseEvent.clientY) + model.applyImpulse(delta.scale(model.mTot / 2), model.pos) } calculate(dt, t / 1000); @@ -97,33 +132,59 @@ function updateCanvasSize() { ctx.canvas.height = src.clientHeight; } +let boundThickness = 1000 function startup() { updateCanvasSize(); - // poly1 = new polyModule.Box(new Vector2(ctx.canvas.width / 2 , ctx.canvas.height / 2), new Vector2(300, 100), 65, undefined, -0) - // polygons.push(poly1) + let top = new polyModule.Box(new Vector2(ctx.canvas.width / 2, -boundThickness / 2), new Vector2(ctx.canvas.width + boundThickness * 2, boundThickness), 0, undefined, undefined) + top.anchored = true + polygons.push(top) + + let bottom = new polyModule.Box(new Vector2(ctx.canvas.width / 2, ctx.canvas.height + boundThickness / 2), new Vector2(ctx.canvas.width + boundThickness * 2, boundThickness), 0, undefined, undefined) + bottom.anchored = true + polygons.push(bottom) + + let left = new polyModule.Box(new Vector2(-boundThickness / 2, ctx.canvas.height / 2), new Vector2(boundThickness, ctx.canvas.height + boundThickness * 2), 0, undefined, undefined) + left.anchored = true + polygons.push(left) + + let right = new polyModule.Box(new Vector2(ctx.canvas.width + boundThickness / 2, ctx.canvas.height / 2), new Vector2(boundThickness, ctx.canvas.height + boundThickness * 2), 0, undefined, undefined) + right.anchored = true + polygons.push(right) + - poly1 = new polyModule.RegularPolygon(new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), new Vector2(200, 100), 30, 0, undefined, -20) - // poly1.anchored = true + model = new polyModule.Model([ + new polyModule.Box(new Vector2(0, 0), new Vector2(100, 50), 0), + new polyModule.Box(new Vector2(75, 0), new Vector2(50, 150), 0), + new polyModule.Box(new Vector2(-75, 0), new Vector2(50, 150), 0), + new polyModule.RegularPolygon(new Vector2(100, 0), new Vector2(100, 100), 30, 0, undefined, -20) + ], new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), 0) + console.log(model) + models.push(model) + poly1 = new polyModule.Box(new Vector2(ctx.canvas.width / 2 , ctx.canvas.height / 2), new Vector2(300, 100), 0, undefined, -0) polygons.push(poly1) - poly2 = new polyModule.Box(new Vector2(ctx.canvas.width / 2, ctx.canvas.height - 100), new Vector2(3000, 100), 5, undefined, 0) - polygons.push(poly2) + // poly1 = new polyModule.RegularPolygon(new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), new Vector2(100, 100), 30, 0, undefined, -20) + // polygons.push(poly1) + + poly2 = new polyModule.Box(new Vector2(ctx.canvas.width / 2, ctx.canvas.height - 500), new Vector2(500, 100), 30, undefined, 0) + // polygons.push(poly2) poly2.anchored = true // poly2 = new polyModule.RegularPolygon(new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), new Vector2(100, 150), 50, 0, undefined, 10) // poly2 = new polyModule.Wall(new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), 500, 0, new Vector2(0, 0), 0) - // polygons.push(poly2) - // for (let i = 0; i < 10; i++) { - // polygons.push( - // new polyModule.RegularPolygon( - // new Vector2(ctx.canvas.width * Math.random(), ctx.canvas.height * Math.random()), - // new Vector2(25 + Math.random() * 100, 25 + Math.random() * 100), - // 3 + Math.floor(Math.random() * 3), - // 0, - // undefined, - // 180 * Math.random() - // ) + polygons.push(poly2) + + for (let i = 0; i < 25; i++) { + polygons.push( + new polyModule.RegularPolygon( + new Vector2(ctx.canvas.width * Math.random(), ctx.canvas.height * Math.random()), + new Vector2(25 + Math.random() * 100, 25 + Math.random() * 100), + 3 + Math.floor(Math.random() * 3), + 0, + undefined, + 180 * Math.random() + ) // // new polyModule.Box( // // new Vector2(ctx.canvas.width * Math.random(), ctx.canvas.height * Math.random()), diff --git a/mainPages/computerScience.html b/mainPages/computerScience.html index 059036f..9ff3089 100644 --- a/mainPages/computerScience.html +++ b/mainPages/computerScience.html @@ -8,8 +8,8 @@ - - + + - +
- +
diff --git a/mainPages/index.html b/mainPages/index.html index c9cd2b2..bc3b430 100644 --- a/mainPages/index.html +++ b/mainPages/index.html @@ -34,6 +34,8 @@ color: lightgray; background-color: #282828; + background-color: #111111; + background-color: #282828; overflow: hidden; } @@ -95,6 +97,14 @@ /* border: 3px solid red; */ text-align: justify; } + + #background-effect { + position: absolute; + box-sizing: border-box; + width: 100vw; + height: 100vh; + z-index: -1; + } @@ -102,6 +112,8 @@
+ +

About Me

@@ -141,4 +153,6 @@

What Is This?

+ + \ No newline at end of file diff --git a/mainPages/index.js b/mainPages/index.js new file mode 100644 index 0000000..ed2059c --- /dev/null +++ b/mainPages/index.js @@ -0,0 +1,66 @@ +const heightRatio = 0.5 +const canvas = document.getElementById("background-effect") +const ctx = canvas.getContext("2d") +const content = document.getElementById("about-me-content") + +let ratio = 0 + +let resolution = 25 + +function clearCanvas() { + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height) +} + + +function drawFrame() { + clearCanvas() + + let size = ctx.canvas.width / 30 + let t = content.scrollTop / 1000 + .5 + for (let i = 0; i { + ratio = getScrollRatio() + drawFrame() +}) + +window.addEventListener("resize", (event) => { + console.log(event) + resizeCanvas() +}) +resizeCanvas() + + + diff --git a/mainPages/why.html b/mainPages/why.html index 99ba4b0..b8c447b 100644 --- a/mainPages/why.html +++ b/mainPages/why.html @@ -1,27 +1,34 @@ - - Computer Science I - - - - -
But Why?
- - -

But Why Have a Digital Portfolio?

-

A Digital Portfolio allows me and others to show off our accomplishments as well as aspects about ourselves that may be of interest. The portfolio can be used to show off a various number of things starting from projects to pictures and even programs. These items can then be presented to colleges or job sights in order to gain credit for the work you did. It also serves as a medium to represent your creativity whilst also providing you an avenue to learn about the intricacies of website development and design.

- - + + + Computer Science I + + + + + +
But Why?
+ + +

But Why Have a Digital Portfolio?

+

A Digital Portfolio allows me and others to show off our accomplishments as well as aspects about ourselves that + may be of interest. The portfolio can be used to show off a various number of things starting from projects to + pictures and even programs. These items can then be presented to colleges or job sights in order to gain credit for + the work you did. It also serves as a medium to represent your creativity whilst also providing you an avenue to + learn about the intricacies of website development and design.

+ + + \ No newline at end of file From 6f48f6d5ee668c023bb2fe296761f76be65a06f3 Mon Sep 17 00:00:00 2001 From: BurntWaffleCake Date: Mon, 23 Oct 2023 11:13:01 -0500 Subject: [PATCH 2/4] collision response change --- javascript/PhysicsEngine/Polygon.js | 79 ++++++++++++++++++++--------- javascript/PhysicsEngine/main.js | 56 ++++++++++++++------ 2 files changed, 95 insertions(+), 40 deletions(-) diff --git a/javascript/PhysicsEngine/Polygon.js b/javascript/PhysicsEngine/Polygon.js index 5af6cfb..d1549b9 100644 --- a/javascript/PhysicsEngine/Polygon.js +++ b/javascript/PhysicsEngine/Polygon.js @@ -1,6 +1,6 @@ import { Vector2 } from "./Vector2.js" -const debug = true +const debug = false function minDisToLineSeg(a, b, e) { let ab = b.clone().subtract(a) @@ -257,7 +257,7 @@ export class Polygon { this.rot = rot * Math.PI / 180; this.rotVel = initRotVel * Math.PI / 180; - this.e = .6 + this.e = 0.6 this.df = .4 this.sf = .6 @@ -269,8 +269,8 @@ export class Polygon { this.i = 1 / 2 * this.mass * this.radius * this.radius this.invI = 1 / this.i - // this.fillColor = "rgb("+ String(Math.floor(Math.random() * 255)) +","+ String(Math.floor(Math.random() * 255)) +","+ String(Math.floor(Math.random() * 255)) +")" - this.fillColor = "rgb(255, 255, 255)" + this.fillColor = "rgb("+ String(Math.floor(Math.random() * 255)) +","+ String(Math.floor(Math.random() * 255)) +","+ String(Math.floor(Math.random() * 255)) +")" + // this.fillColor = "rgb(255, 255, 255)" this.strokeColor = "rgb(255, 255, 255)" } @@ -291,9 +291,8 @@ export class Polygon { } toObjectSpace(vector = Vector2.prototype) { - let delta = vector.clone().subtract(this.pos) - return this.toWorldSpace(delta) - } + return vector.clone().subtract(this.pos).rotate(-this.rot) + } toWorldSpace(vector) { return new Vector2( @@ -322,7 +321,6 @@ export class Polygon { applyImpulse(impulse, pos) { let collArm = pos.clone().subtract(this.pos) - console.log(impulse.clone().scale(this.invMass)) this.vel.add(impulse.clone().scale(this.invMass)) this.rotVel += this.invI * collArm.cross(impulse) } @@ -346,7 +344,7 @@ export class Polygon { ctx.lineTo(point.x, point.y); } ctx.lineTo(worldCoords[0].x, worldCoords[0].y); - // ctx.fill() + ctx.fill() ctx.stroke() //draw foward @@ -433,10 +431,10 @@ export class Polygon { let dis = minDisToLineSeg(this.toWorldSpace(face.from), this.toWorldSpace(face.to), worldPoint) - if (dis < minDis - 0.5) { + if (dis < minDis - 0.25) { minDis = dis; collPoints = [worldPoint] - } else if (dis < minDis + 0.5) { + } else if (dis < minDis + 0.25) { collPoints.push(worldPoint) } } @@ -447,10 +445,10 @@ export class Polygon { let worldPoint = this.toWorldSpace(point) let dis = minDisToLineSeg(polygon.toWorldSpace(face.from), polygon.toWorldSpace(face.to), worldPoint) - if (dis < minDis - 0.5) { + if (dis < minDis - 0.25) { minDis = dis; collPoints = [worldPoint] - } else if (dis < minDis + 0.5) { + } else if (dis < minDis + 0.25) { collPoints.push(worldPoint) } } @@ -478,22 +476,29 @@ export class Polygon { polygon.pos.add(normal.clone().scale(mvt / 2)) } + if (polygon.mass <= 0 && this.mass <= 0) { + return + } + let impulses = [] + let impulseList = [] + let frictionImpulses = [] let collArm1s = [] let collArm2s = [] - collisionPoints.forEach(collPoint => { + collisionPoints.forEach((collPoint, index) => { let collArm1 = collPoint.clone().subtract(this.pos) - let rotVel1 = new Vector2(-this.rotVel * collArm1.y, this.rotVel * collArm1.x) - let closVel1 = this.vel.clone().add(rotVel1) - let collArm2 = collPoint.clone().subtract(polygon.pos) - let rotVel2 = new Vector2(-polygon.rotVel * collArm2.y, polygon.rotVel * collArm2.x) - let closVel2 = polygon.vel.clone().add(rotVel2) collArm1s.push(collArm1) collArm2s.push(collArm2) + let rotVel1 = new Vector2(-this.rotVel * collArm1.y, this.rotVel * collArm1.x) + let rotVel2 = new Vector2(-polygon.rotVel * collArm2.y, polygon.rotVel * collArm2.x) + + let closVel1 = this.vel.clone().add(rotVel1) + let closVel2 = polygon.vel.clone().add(rotVel2) + let impAug1 = collArm1.cross(normal) impAug1 = impAug1 * impAug1 * this.invI let impAug2 = collArm2.cross(normal) @@ -507,9 +512,35 @@ export class Polygon { let impulse = vSepDiff / (this.invMass + polygon.invMass + impAug1 + impAug2) / collisionPoints.length let impulseVec = normal.clone().scale(impulse) + impulseList.push(impulse) impulses.push(impulseVec) + }) - //Friction + impulses.forEach((impulse, index) => { + let collArm1 = collArm1s[index] + let collArm2 = collArm2s[index] + + this.vel.add(impulse.clone().scale(this.invMass)) + polygon.vel.subtract(impulse.clone().scale(polygon.invMass)) + + this.rotVel += this.invI * collArm1.cross(impulse) + polygon.rotVel -= polygon.invI * collArm2.cross(impulse) + }); + + collisionPoints.forEach((collPoint, index) => { + let collArm1 = collPoint.clone().subtract(this.pos) + let collArm2 = collPoint.clone().subtract(polygon.pos) + + collArm1s[index] = collArm1 + collArm2s[index] = collArm2 + + let rotVel1 = new Vector2(-this.rotVel * collArm1.y, this.rotVel * collArm1.x) + let rotVel2 = new Vector2(-polygon.rotVel * collArm2.y, polygon.rotVel * collArm2.x) + + let closVel1 = this.vel.clone().add(rotVel1) + let closVel2 = polygon.vel.clone().add(rotVel2) + + let relVel = closVel1.clone().subtract(closVel2) let tangent = relVel.clone().subtract(normal.clone().scale(relVel.dot(normal))) if (tangent.magnitude() < 0.005) { return } //tangent is near zero tangent.normalize() @@ -525,19 +556,17 @@ export class Polygon { let df = (this.df + polygon.df) * .5 let impFricVec + let impulse = impulseList[index] if (impulseFriction <= impulse * sf) { impFricVec = tangent.scale(-impulseFriction) } else { impFricVec = tangent.clone().scale(impulse * df) } - collArm1s.push(collArm1) - collArm2s.push(collArm2) - - impulses.push(impFricVec) + frictionImpulses.push(impFricVec) }); - impulses.forEach((impulse, index) => { + frictionImpulses.forEach((impulse, index) => { let collArm1 = collArm1s[index] let collArm2 = collArm2s[index] diff --git a/javascript/PhysicsEngine/main.js b/javascript/PhysicsEngine/main.js index ac20135..f9d395b 100644 --- a/javascript/PhysicsEngine/main.js +++ b/javascript/PhysicsEngine/main.js @@ -18,7 +18,6 @@ function clearCanvas(ctx) { function render(dt) { - model.render(ctx) for (let model of models) { model.render(ctx) @@ -28,6 +27,11 @@ function render(dt) { polygon.render(ctx) } + let delta = poly2.pos.clone().add(poly2.toObjectSpace(poly1.pos)) + ctx.fillStyle = "rbg(0,0,255)" + ctx.fillRect(delta.x, delta.y, 10, 10) + + // newPoly.render(ctx); } @@ -57,14 +61,19 @@ function applyCollisionBounds(polygon) { } +let gravity = new Vector2(1000, 0) let substeps = 4 function calculate(dt, t) { if (paused) { return } + gravity.set(1000 * Math.cos(t), 1000 * Math.sin(t)) + + poly2.rot += dt * (10 * Math.PI / 180) + for (let model of models) { for (let i = 0; i < substeps; i++) { let dti = dt / substeps - model.vel.y += 1000 * dti + model.vel.add(gravity.clone().scale(dti)) model.tick(dti, t) // if (!polygon.anchored) { // // applyCollisionBounds(polygon) @@ -85,8 +94,9 @@ function calculate(dt, t) { for (let polygon of polygons) { for (let i = 0; i < substeps; i++) { let dti = dt / substeps + polygon.vel.add(gravity.clone().scale(dti)) - polygon.vel.y += 1000 * dti + // model.vel.add(gravity.clone().scale(dti)) polygon.tick(dti, t) if (!polygon.anchored) { @@ -115,9 +125,9 @@ function loop(t) { let dt = (t / 1000) - time; if (mouse1down) { - let delta = new Vector2(prevMouseEvent.clientX, prevMouseEvent.clientY).subtract(model.pos) + let delta = new Vector2(prevMouseEvent.clientX, prevMouseEvent.clientY).subtract(poly1.pos) // model.pos.set(prevMouseEvent.clientX, prevMouseEvent.clientY) - model.applyImpulse(delta.scale(model.mTot / 2), model.pos) + poly1.applyImpulse(delta.scale(poly1.mass / 2), poly1.pos) } calculate(dt, t / 1000); @@ -138,29 +148,45 @@ function startup() { let top = new polyModule.Box(new Vector2(ctx.canvas.width / 2, -boundThickness / 2), new Vector2(ctx.canvas.width + boundThickness * 2, boundThickness), 0, undefined, undefined) top.anchored = true + top.mass = 0 + top.invMass = 0 + top.i = 0 + top.invI = 0 polygons.push(top) let bottom = new polyModule.Box(new Vector2(ctx.canvas.width / 2, ctx.canvas.height + boundThickness / 2), new Vector2(ctx.canvas.width + boundThickness * 2, boundThickness), 0, undefined, undefined) bottom.anchored = true + bottom.mass = 0 + bottom.invMass = 0 + bottom.i = 0 + bottom.invI = 0 polygons.push(bottom) let left = new polyModule.Box(new Vector2(-boundThickness / 2, ctx.canvas.height / 2), new Vector2(boundThickness, ctx.canvas.height + boundThickness * 2), 0, undefined, undefined) left.anchored = true + left.mass = 0 + left.invMass = 0 + left.i = 0 + left.invI = 0 polygons.push(left) let right = new polyModule.Box(new Vector2(ctx.canvas.width + boundThickness / 2, ctx.canvas.height / 2), new Vector2(boundThickness, ctx.canvas.height + boundThickness * 2), 0, undefined, undefined) right.anchored = true + right.mass = 0 + right.invMass = 0 + right.i = 0 + right.invI = 0 polygons.push(right) - model = new polyModule.Model([ - new polyModule.Box(new Vector2(0, 0), new Vector2(100, 50), 0), - new polyModule.Box(new Vector2(75, 0), new Vector2(50, 150), 0), - new polyModule.Box(new Vector2(-75, 0), new Vector2(50, 150), 0), - new polyModule.RegularPolygon(new Vector2(100, 0), new Vector2(100, 100), 30, 0, undefined, -20) - ], new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), 0) - console.log(model) - models.push(model) + // model = new polyModule.Model([ + // new polyModule.Box(new Vector2(0, 0), new Vector2(100, 50), 0), + // new polyModule.Box(new Vector2(75, 0), new Vector2(50, 150), 0), + // new polyModule.Box(new Vector2(-75, 0), new Vector2(50, 150), 0), + // new polyModule.RegularPolygon(new Vector2(100, 0), new Vector2(100, 100), 30, 0, undefined, -20) + // ], new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), 0) + // console.log(model) + // models.push(model) poly1 = new polyModule.Box(new Vector2(ctx.canvas.width / 2 , ctx.canvas.height / 2), new Vector2(300, 100), 0, undefined, -0) polygons.push(poly1) @@ -193,8 +219,8 @@ function startup() { // // undefined, // // 180 * Math.random(), // // ) - // ) - // } + ) + } window.requestAnimationFrame(loop); } From a2d16f8bc6f3a75597b567c86704d80481d7c134 Mon Sep 17 00:00:00 2001 From: BurntWaffleCake Date: Fri, 3 Nov 2023 10:00:00 -0500 Subject: [PATCH 3/4] color and oscillating gravity --- javascript/PhysicsEngine/main.js | 485 ++++++++++++++++++------------- 1 file changed, 278 insertions(+), 207 deletions(-) diff --git a/javascript/PhysicsEngine/main.js b/javascript/PhysicsEngine/main.js index f9d395b..39eb075 100644 --- a/javascript/PhysicsEngine/main.js +++ b/javascript/PhysicsEngine/main.js @@ -4,249 +4,320 @@ import * as polyModule from "./Polygon.js"; let src = document.getElementById("source"); let ctx = src.getContext("2d"); -let poly1 -let poly2 -let model +let poly1; +let poly2; +let model; -let bounds = [] -let polygons = [] -let models = [] +let bounds = []; +let polygons = []; +let models = []; function clearCanvas(ctx) { - ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); } function render(dt) { + for (let model of models) { + model.render(ctx); + } + for (let polygon of polygons) { + polygon.render(ctx); + } - for (let model of models) { - model.render(ctx) - } - - for (let polygon of polygons) { - polygon.render(ctx) - } - - let delta = poly2.pos.clone().add(poly2.toObjectSpace(poly1.pos)) - ctx.fillStyle = "rbg(0,0,255)" - ctx.fillRect(delta.x, delta.y, 10, 10) - + let delta = poly2.pos.clone().add(poly2.toObjectSpace(poly1.pos)); + ctx.fillStyle = "rbg(0,0,255)"; + ctx.fillRect(delta.x, delta.y, 10, 10); - // newPoly.render(ctx); + // newPoly.render(ctx); } function applyCollisionBounds(polygon) { - let width = ctx.canvas.width - let height = ctx.canvas.height - - if (polygon.pos.x + polygon.radius > width) { - polygon.pos.x = width - polygon.radius - polygon.vel.x = -polygon.vel.x * polygon.e - } - - if (polygon.pos.x - polygon.radius < 0) { - polygon.pos.x = polygon.radius - polygon.vel.x = -polygon.vel.x * polygon.e - } - - if (polygon.pos.y + polygon.radius > height) { - polygon.pos.y = height - polygon.radius - polygon.vel.y = -polygon.vel.y * polygon.e - } - - if (polygon.pos.y - polygon.radius < 0) { - polygon.pos.y = polygon.radius - polygon.vel.y = -polygon.vel.y * polygon.e - } + let width = ctx.canvas.width; + let height = ctx.canvas.height; + + if (polygon.pos.x + polygon.radius > width) { + polygon.pos.x = width - polygon.radius; + polygon.vel.x = -polygon.vel.x * polygon.e; + } + + if (polygon.pos.x - polygon.radius < 0) { + polygon.pos.x = polygon.radius; + polygon.vel.x = -polygon.vel.x * polygon.e; + } + + if (polygon.pos.y + polygon.radius > height) { + polygon.pos.y = height - polygon.radius; + polygon.vel.y = -polygon.vel.y * polygon.e; + } + + if (polygon.pos.y - polygon.radius < 0) { + polygon.pos.y = polygon.radius; + polygon.vel.y = -polygon.vel.y * polygon.e; + } } - -let gravity = new Vector2(1000, 0) -let substeps = 4 +let gravity = new Vector2(1000, 0); +let substeps = 4; function calculate(dt, t) { - if (paused) { return } - - gravity.set(1000 * Math.cos(t), 1000 * Math.sin(t)) - - poly2.rot += dt * (10 * Math.PI / 180) - - for (let model of models) { - for (let i = 0; i < substeps; i++) { - let dti = dt / substeps - model.vel.add(gravity.clone().scale(dti)) - model.tick(dti, t) - // if (!polygon.anchored) { - // // applyCollisionBounds(polygon) - // } - - for (let collPoly of polygons) { - let results = model.testPolygonCollision(collPoly, ctx) - - results.forEach(result => { - if (result == false) { return } - model.resolvePolygonCollision(result.polygon, result.mvt, result.normal, result.points) - }); - } - } - // console.log(model) + if (paused) { + return; + } + + gravity.set(1000 * Math.cos(t), 1000 * Math.sin(t)); + + poly2.rot += dt * ((10 * Math.PI) / 180); + + for (let model of models) { + for (let i = 0; i < substeps; i++) { + let dti = dt / substeps; + model.vel.add(gravity.clone().scale(dti)); + model.tick(dti, t); + // if (!polygon.anchored) { + // // applyCollisionBounds(polygon) + // }` + + for (let collPoly of polygons) { + let results = model.testPolygonCollision(collPoly, ctx); + + results.forEach((result) => { + if (result == false) { + return; + } + model.resolvePolygonCollision( + result.polygon, + result.mvt, + result.normal, + result.points + ); + }); + } } + // console.log(model) + } - for (let polygon of polygons) { - for (let i = 0; i < substeps; i++) { - let dti = dt / substeps - polygon.vel.add(gravity.clone().scale(dti)) + for (let polygon of polygons) { + for (let i = 0; i < substeps; i++) { + let dti = dt / substeps; + polygon.vel.add(gravity.clone().scale(dti)); - // model.vel.add(gravity.clone().scale(dti)) - polygon.tick(dti, t) + // model.vel.add(gravity.clone().scale(dti)) + polygon.tick(dti, t); - if (!polygon.anchored) { - // applyCollisionBounds(polygon) - } + if (!polygon.anchored) { + // applyCollisionBounds(polygon) + } - for (let collPoly of polygons) { - if (collPoly === polygon) { continue } - let result = polygon.testCollision(collPoly, ctx) - if (result == false) { continue } - polygon.resolveCollision(collPoly, result.mvt, result.normal, result.points) - } + for (let collPoly of polygons) { + if (collPoly === polygon) { + continue; } - + let result = polygon.testCollision(collPoly, ctx); + if (result == false) { + continue; + } + polygon.resolveCollision( + collPoly, + result.mvt, + result.normal, + result.points + ); + } } - - - + } } let time = 0; function loop(t) { - updateCanvasSize(); - clearCanvas(ctx); + updateCanvasSize(); + clearCanvas(ctx); - let dt = (t / 1000) - time; + let dt = t / 1000 - time; - if (mouse1down) { - let delta = new Vector2(prevMouseEvent.clientX, prevMouseEvent.clientY).subtract(poly1.pos) - // model.pos.set(prevMouseEvent.clientX, prevMouseEvent.clientY) - poly1.applyImpulse(delta.scale(poly1.mass / 2), poly1.pos) - } + if (mouse1down) { + let delta = new Vector2( + prevMouseEvent.clientX, + prevMouseEvent.clientY + ).subtract(poly1.pos); + // model.pos.set(prevMouseEvent.clientX, prevMouseEvent.clientY) + poly1.applyImpulse(delta.scale(poly1.mass / 2), poly1.pos); + } - calculate(dt, t / 1000); - render(dt); + calculate(dt, t / 1000); + render(dt); - time = (t / 1000); - window.requestAnimationFrame(loop); + time = t / 1000; + window.requestAnimationFrame(loop); } function updateCanvasSize() { - ctx.canvas.width = src.clientWidth; - ctx.canvas.height = src.clientHeight; + ctx.canvas.width = src.clientWidth; + ctx.canvas.height = src.clientHeight; } -let boundThickness = 1000 +let boundThickness = 1000; function startup() { - updateCanvasSize(); - - let top = new polyModule.Box(new Vector2(ctx.canvas.width / 2, -boundThickness / 2), new Vector2(ctx.canvas.width + boundThickness * 2, boundThickness), 0, undefined, undefined) - top.anchored = true - top.mass = 0 - top.invMass = 0 - top.i = 0 - top.invI = 0 - polygons.push(top) - - let bottom = new polyModule.Box(new Vector2(ctx.canvas.width / 2, ctx.canvas.height + boundThickness / 2), new Vector2(ctx.canvas.width + boundThickness * 2, boundThickness), 0, undefined, undefined) - bottom.anchored = true - bottom.mass = 0 - bottom.invMass = 0 - bottom.i = 0 - bottom.invI = 0 - polygons.push(bottom) - - let left = new polyModule.Box(new Vector2(-boundThickness / 2, ctx.canvas.height / 2), new Vector2(boundThickness, ctx.canvas.height + boundThickness * 2), 0, undefined, undefined) - left.anchored = true - left.mass = 0 - left.invMass = 0 - left.i = 0 - left.invI = 0 - polygons.push(left) - - let right = new polyModule.Box(new Vector2(ctx.canvas.width + boundThickness / 2, ctx.canvas.height / 2), new Vector2(boundThickness, ctx.canvas.height + boundThickness * 2), 0, undefined, undefined) - right.anchored = true - right.mass = 0 - right.invMass = 0 - right.i = 0 - right.invI = 0 - polygons.push(right) - - - // model = new polyModule.Model([ - // new polyModule.Box(new Vector2(0, 0), new Vector2(100, 50), 0), - // new polyModule.Box(new Vector2(75, 0), new Vector2(50, 150), 0), - // new polyModule.Box(new Vector2(-75, 0), new Vector2(50, 150), 0), - // new polyModule.RegularPolygon(new Vector2(100, 0), new Vector2(100, 100), 30, 0, undefined, -20) - // ], new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), 0) - // console.log(model) - // models.push(model) - - poly1 = new polyModule.Box(new Vector2(ctx.canvas.width / 2 , ctx.canvas.height / 2), new Vector2(300, 100), 0, undefined, -0) - polygons.push(poly1) - - // poly1 = new polyModule.RegularPolygon(new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), new Vector2(100, 100), 30, 0, undefined, -20) - // polygons.push(poly1) - - poly2 = new polyModule.Box(new Vector2(ctx.canvas.width / 2, ctx.canvas.height - 500), new Vector2(500, 100), 30, undefined, 0) - // polygons.push(poly2) - poly2.anchored = true - // poly2 = new polyModule.RegularPolygon(new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), new Vector2(100, 150), 50, 0, undefined, 10) - // poly2 = new polyModule.Wall(new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), 500, 0, new Vector2(0, 0), 0) - polygons.push(poly2) - - for (let i = 0; i < 25; i++) { - polygons.push( - new polyModule.RegularPolygon( - new Vector2(ctx.canvas.width * Math.random(), ctx.canvas.height * Math.random()), - new Vector2(25 + Math.random() * 100, 25 + Math.random() * 100), - 3 + Math.floor(Math.random() * 3), - 0, - undefined, - 180 * Math.random() - ) - - // // new polyModule.Box( - // // new Vector2(ctx.canvas.width * Math.random(), ctx.canvas.height * Math.random()), - // // new Vector2(25 + Math.random() * 100, 25 + Math.random() * 100), - // // 0, - // // undefined, - // // 180 * Math.random(), - // // ) - ) - } - - window.requestAnimationFrame(loop); + updateCanvasSize(); + + let top = new polyModule.Box( + new Vector2(ctx.canvas.width / 2, -boundThickness / 2), + new Vector2(ctx.canvas.width + boundThickness * 2, boundThickness), + 0, + undefined, + undefined + ); + top.anchored = true; + top.mass = 0; + top.invMass = 0; + top.i = 0; + top.invI = 0; + polygons.push(top); + + let bottom = new polyModule.Box( + new Vector2(ctx.canvas.width / 2, ctx.canvas.height + boundThickness / 2), + new Vector2(ctx.canvas.width + boundThickness * 2, boundThickness), + 0, + undefined, + undefined + ); + bottom.anchored = true; + bottom.mass = 0; + bottom.invMass = 0; + bottom.i = 0; + bottom.invI = 0; + polygons.push(bottom); + + let left = new polyModule.Box( + new Vector2(-boundThickness / 2, ctx.canvas.height / 2), + new Vector2(boundThickness, ctx.canvas.height + boundThickness * 2), + 0, + undefined, + undefined + ); + left.anchored = true; + left.mass = 0; + left.invMass = 0; + left.i = 0; + left.invI = 0; + polygons.push(left); + + let right = new polyModule.Box( + new Vector2(ctx.canvas.width + boundThickness / 2, ctx.canvas.height / 2), + new Vector2(boundThickness, ctx.canvas.height + boundThickness * 2), + 0, + undefined, + undefined + ); + right.anchored = true; + right.mass = 0; + right.invMass = 0; + right.i = 0; + right.invI = 0; + polygons.push(right); + + model = new polyModule.Model( + [ + new polyModule.Box(new Vector2(0, 0), new Vector2(100, 50), 0), + new polyModule.Box(new Vector2(75, 0), new Vector2(50, 150), 0), + new polyModule.Box(new Vector2(-75, 0), new Vector2(50, 150), 0), + new polyModule.RegularPolygon( + new Vector2(100, 0), + new Vector2(100, 100), + 30, + 0, + undefined, + -20 + ), + ], + new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), + 0 + ); + console.log(model); + models.push(model); + + poly1 = new polyModule.Box( + new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), + new Vector2(300, 100), + 0, + undefined, + -0 + ); + polygons.push(poly1); + + // poly1 = new polyModule.RegularPolygon(new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), new Vector2(100, 100), 30, 0, undefined, -20) + // polygons.push(poly1) + + poly2 = new polyModule.Box( + new Vector2(ctx.canvas.width / 2, ctx.canvas.height - 500), + new Vector2(500, 100), + 30, + undefined, + 0 + ); + // polygons.push(poly2) + poly2.anchored = true; + // poly2 = new polyModule.RegularPolygon(new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), new Vector2(100, 150), 50, 0, undefined, 10) + // poly2 = new polyModule.Wall(new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), 500, 0, new Vector2(0, 0), 0) + polygons.push(poly2); + + for (let i = 0; i < 25; i++) { + polygons.push( + new polyModule.RegularPolygon( + new Vector2( + ctx.canvas.width * Math.random(), + ctx.canvas.height * Math.random() + ), + new Vector2(25 + Math.random() * 100, 25 + Math.random() * 100), + 3 + Math.floor(Math.random() * 3), + 0, + undefined, + 180 * Math.random() + ) + + // // new polyModule.Box( + // // new Vector2(ctx.canvas.width * Math.random(), ctx.canvas.height * Math.random()), + // // new Vector2(25 + Math.random() * 100, 25 + Math.random() * 100), + // // 0, + // // undefined, + // // 180 * Math.random(), + // // ) + ); + } + + window.requestAnimationFrame(loop); } window.requestAnimationFrame(startup); -var prevMouseEvent = undefined -document.addEventListener('mousemove', (event) => { - prevMouseEvent = event -}) - -var mouse1down = false -var mouse2down = false -document.addEventListener('mousedown', (event) => { - if (event.buttons == 1) { mouse1down = true; return } - else if (event.buttons == 2) { mouse2down = true; return } - -}) - -document.addEventListener('mouseup', (event) => { - if (mouse1down) { mouse1down = false; return } - else if (mouse2down) { mouse2down = false; return } -}) - -let paused = false -document.addEventListener('keypress', (event) => { - if (event.key === " ") { - paused = !paused - } -}) +var prevMouseEvent = undefined; +document.addEventListener("mousemove", (event) => { + prevMouseEvent = event; +}); + +var mouse1down = false; +var mouse2down = false; +document.addEventListener("mousedown", (event) => { + if (event.buttons == 1) { + mouse1down = true; + return; + } else if (event.buttons == 2) { + mouse2down = true; + return; + } +}); + +document.addEventListener("mouseup", (event) => { + if (mouse1down) { + mouse1down = false; + return; + } else if (mouse2down) { + mouse2down = false; + return; + } +}); + +let paused = false; +document.addEventListener("keypress", (event) => { + if (event.key === " ") { + paused = !paused; + } +}); From 6fc5521f24cf54c062cc2fcbc3dd2fe276ef2012 Mon Sep 17 00:00:00 2001 From: BurntWaffleCake Date: Fri, 3 Nov 2023 10:14:11 -0500 Subject: [PATCH 4/4] circle optimization --- javascript/PhysicsEngine/Polygon.js | 1462 +++++++++++++++------------ javascript/PhysicsEngine/main.js | 11 +- 2 files changed, 817 insertions(+), 656 deletions(-) diff --git a/javascript/PhysicsEngine/Polygon.js b/javascript/PhysicsEngine/Polygon.js index d1549b9..143cb31 100644 --- a/javascript/PhysicsEngine/Polygon.js +++ b/javascript/PhysicsEngine/Polygon.js @@ -1,717 +1,877 @@ -import { Vector2 } from "./Vector2.js" +import { Vector2 } from "./Vector2.js"; -const debug = false +const debug = false; function minDisToLineSeg(a, b, e) { - let ab = b.clone().subtract(a) - let be = e.clone().subtract(b) - let ae = e.clone().subtract(a) - - let ab_be = ab.dot(be) - let ab_ae = ab.dot(ae) - - if (ab_be > 0) { - return (e.clone().subtract(b).magnitude()) - } else if (ab_ae < 0) { - return (e.clone().subtract(a).magnitude()) - } else { - let mod = Math.sqrt(ab.x * ab.x + ab.y * ab.y) - return Math.abs(ab.x * ae.y - ab.y * ae.x) / mod - } + let ab = b.clone().subtract(a); + let be = e.clone().subtract(b); + let ae = e.clone().subtract(a); + + let ab_be = ab.dot(be); + let ab_ae = ab.dot(ae); + + if (ab_be > 0) { + return e.clone().subtract(b).magnitude(); + } else if (ab_ae < 0) { + return e.clone().subtract(a).magnitude(); + } else { + let mod = Math.sqrt(ab.x * ab.x + ab.y * ab.y); + return Math.abs(ab.x * ae.y - ab.y * ae.x) / mod; + } } export class Face { - constructor(from, to) { - this.from = from.clone() - this.to = to.clone() - - let delta = to.clone().subtract(from).normal() - this.normal = new Vector2(-delta.y, delta.x) - } - - center() { - return this.to.clone().subtract(this.from).scale(.5).add(this.from); - } - - rotate(center, rad) { - this.from = center.clone().subtract(this.from).rotate(rad).add(center) - this.to = center.clone().subtract(this.to).rotate(rad).add(center) - return this - } - - clone() { - return new Face(this.from, this.to) - } - - render(ctx) { - ctx.beginPath(); - ctx.strokeStyle = "rgb(255, 255, 255)" - ctx.moveTo(this.from.x, this.from.y) - ctx.moveTo(this.to.x, this.to.y) - ctx.stroke(); - - ctx.beginPath(); - ctx.strokeStyle = "rgb(255,255,0)" - ctx.arc(this.from.x, this.from.y, 5, 0, 2 * Math.PI); - ctx.stroke(); - - ctx.beginPath(); - ctx.strokeStyle = "rgb(255,0,255)" - ctx.arc(this.to.x, this.to.y, 5, 0, 2 * Math.PI); - ctx.stroke(); - } + constructor(from, to) { + this.from = from.clone(); + this.to = to.clone(); + + let delta = to.clone().subtract(from).normal(); + this.normal = new Vector2(-delta.y, delta.x); + } + + center() { + return this.to.clone().subtract(this.from).scale(0.5).add(this.from); + } + + rotate(center, rad) { + this.from = center.clone().subtract(this.from).rotate(rad).add(center); + this.to = center.clone().subtract(this.to).rotate(rad).add(center); + return this; + } + + clone() { + return new Face(this.from, this.to); + } + + render(ctx) { + ctx.beginPath(); + ctx.strokeStyle = "rgb(255, 255, 255)"; + ctx.moveTo(this.from.x, this.from.y); + ctx.moveTo(this.to.x, this.to.y); + ctx.stroke(); + + ctx.beginPath(); + ctx.strokeStyle = "rgb(255,255,0)"; + ctx.arc(this.from.x, this.from.y, 5, 0, 2 * Math.PI); + ctx.stroke(); + + ctx.beginPath(); + ctx.strokeStyle = "rgb(255,0,255)"; + ctx.arc(this.to.x, this.to.y, 5, 0, 2 * Math.PI); + ctx.stroke(); + } } export class Model { - constructor(polygons, pos = new Vector2(0, 0), rot = 0, initVel = new Vector2(0, 0), initRotVel = 0) { - this.components = [] - polygons.forEach(element => { - this.components.push({ polygon: element, offset: element.pos.clone(), rotation: element.rot - rot }) - }); - - this.pos = pos - this.vel = initVel - this.rot = rot * Math.PI / 180 - this.rotVel = initRotVel * Math.PI / 180 - - this.e = 0 - this.df = .4 - this.sf = .6 - this.mTot = 0 - this.iTot = 0 - - polygons.forEach(element => { - this.mTot += element.mass - this.iTot += element.mass * element.pos.magnitude() ** 2 - }); - - this.invMTot = 1 / this.mTot - this.invITot = 1 / this.iTot + constructor( + polygons, + pos = new Vector2(0, 0), + rot = 0, + initVel = new Vector2(0, 0), + initRotVel = 0 + ) { + this.components = []; + polygons.forEach((element) => { + this.components.push({ + polygon: element, + offset: element.pos.clone(), + rotation: element.rot - rot, + }); + }); + + this.pos = pos; + this.vel = initVel; + this.rot = (rot * Math.PI) / 180; + this.rotVel = (initRotVel * Math.PI) / 180; + + this.e = 0; + this.df = 0.4; + this.sf = 0.6; + this.mTot = 0; + this.iTot = 0; + + polygons.forEach((element) => { + this.mTot += element.mass; + this.iTot += element.mass * element.pos.magnitude() ** 2; + }); + + this.invMTot = 1 / this.mTot; + this.invITot = 1 / this.iTot; + } + + render(ctx) { + this.components.forEach((element) => { + let rot = element.rotation; + let offset = element.offset; + let polygon = element.polygon; + + polygon.render(ctx); + }); + } + + tick(dt) { + if (this.anchored) { + this.vel.set(0, 0); + this.rotVel = 0; + return; + } else if (this.lockRot) { + this.rotVel = 0; + return; } - - render(ctx) { - this.components.forEach(element => { - let rot = element.rotation - let offset = element.offset - let polygon = element.polygon - - polygon.render(ctx) - }); + this.pos.add(this.vel.clone().scale(dt)); + this.rot += this.rotVel * dt; + + this.components.forEach((element) => { + let rot = element.rotation; + let offset = element.offset; + let polygon = element.polygon; + + polygon.rot = this.rot + rot; + polygon.pos.set( + this.pos.x + + offset.x * Math.cos(this.rot) - + offset.y * Math.sin(this.rot), + this.pos.y + + offset.x * Math.sin(this.rot) + + offset.y * Math.cos(this.rot) + ); + }); + } + + testModelCollision() {} + + testPolygonCollision(colPoly, ctx) { + let results = []; + this.components.forEach((element) => { + let rot = element.rotation; + let offset = element.offset; + let polygon = element.polygon; + + let result = polygon.testCollision(colPoly, ctx); + if (result == false) { + return; + } + results.push({ + polygon: colPoly, + mvt: result.mvt, + normal: result.normal, + points: result.points, + }); + // this.resolveCollision(polygon, result.mvt, result.normal, result.point) + }); + return results; + } + + applyImpulse(impulse, pos) { + let collArm = pos.clone().subtract(this.pos); + this.vel.add(impulse.clone().scale(this.invMTot)); + this.rotVel += this.invITot * collArm.cross(impulse); + } + + resolvePolygonCollision(polygon, mvt, normal, collisionPoints) { + if (this.anchored && polygon.anchored) { + return; + } else if (this.anchored) { + polygon.pos.add(normal.clone().scale(mvt / 2)); + } else if (polygon.anchored) { + this.pos.subtract(normal.clone().scale(mvt / 2)); + } else { + this.pos.subtract(normal.clone().scale(mvt / 2)); + polygon.pos.add(normal.clone().scale(mvt / 2)); } - tick(dt) { - if (this.anchored) { - this.vel.set(0, 0) - this.rotVel = 0 - return - } else if (this.lockRot) { - this.rotVel = 0 - return - } - this.pos.add(this.vel.clone().scale(dt)); - this.rot += this.rotVel * dt - - this.components.forEach(element => { - let rot = element.rotation - let offset = element.offset - let polygon = element.polygon + let impulses = []; + let collArm1s = []; + let collArm2s = []; + + collisionPoints.forEach((collPoint) => { + let collArm1 = collPoint.clone().subtract(this.pos); + let rotVel1 = new Vector2( + -this.rotVel * collArm1.y, + this.rotVel * collArm1.x + ); + let closVel1 = this.vel.clone().add(rotVel1); + + let collArm2 = collPoint.clone().subtract(polygon.pos); + let rotVel2 = new Vector2( + -polygon.rotVel * collArm2.y, + polygon.rotVel * collArm2.x + ); + let closVel2 = polygon.vel.clone().add(rotVel2); + + collArm1s.push(collArm1); + collArm2s.push(collArm2); + + let impAug1 = collArm1.cross(normal); + impAug1 = impAug1 * impAug1 * this.invITot; + let impAug2 = collArm2.cross(normal); + impAug2 = impAug2 * impAug2 * polygon.invI; + + let relVel = closVel1.clone().subtract(closVel2); + let sepVel = relVel.dot(normal); + let newSepVel = -sepVel * Math.min(this.e, polygon.e); + let vSepDiff = newSepVel - sepVel; + + let impulse = + vSepDiff / + (this.invMTot + polygon.invMass + impAug1 + impAug2) / + collisionPoints.length; + let impulseVec = normal.clone().scale(impulse); + + impulses.push(impulseVec); + + //Friction + let tangent = relVel + .clone() + .subtract(normal.clone().scale(relVel.dot(normal))); + if (tangent.magnitude() < 0.005) { + return; + } //tangent is near zero + tangent.normalize(); + + let frictionAug1 = collArm1.cross(tangent); + frictionAug1 = frictionAug1 * frictionAug1 * this.invITot; + let frictionAug2 = collArm2.cross(tangent); + frictionAug2 = frictionAug2 * frictionAug2 * polygon.invI; + + let impulseFriction = + relVel.dot(tangent) / + (frictionAug1 + frictionAug2 + this.invMTot + polygon.invMass) / + collisionPoints.length; + + let sf = (this.sf + polygon.sf) * 0.5; + let df = (this.df + polygon.df) * 0.5; + + let impFricVec; + if (impulseFriction <= impulse * sf) { + impFricVec = tangent.scale(-impulseFriction); + } else { + impFricVec = tangent.clone().scale(impulse * df); + } + + collArm1s.push(collArm1); + collArm2s.push(collArm2); + + impulses.push(impFricVec); + }); + + impulses.forEach((impulse, index) => { + let collArm1 = collArm1s[index]; + let collArm2 = collArm2s[index]; + + this.vel.add(impulse.clone().scale(this.invMTot)); + polygon.vel.subtract(impulse.clone().scale(polygon.invMass)); + + this.rotVel += this.invITot * collArm1.cross(impulse); + polygon.rotVel -= polygon.invI * collArm2.cross(impulse); + }); + } +} - polygon.rot = this.rot + rot - polygon.pos.set(this.pos.x + offset.x * Math.cos(this.rot) - offset.y * Math.sin(this.rot), this.pos.y + offset.x * Math.sin(this.rot) + offset.y * Math.cos(this.rot)) - }); +export class Polygon { + constructor( + points, + pos = new Vector2(0, 0), + rot = 0, + initVel = new Vector2(0, 0), + initRotVel = 0 + ) { + this.points = points; + + this.radius = this.points[this.points.length - 1].magnitude(); + this.hullSize = new Vector2(0, 0); + + this.sides = []; + for (let i = 0; i < points.length - 1; i++) { + this.sides.push(new Face(points[i], points[i + 1])); + + let point = this.points[this.points.length - 1 - i]; + let mag = point.magnitude(); + if (mag > this.radius) { + this.radius = mag; + } } - - testModelCollision() { - + this.sides.push(new Face(points[points.length - 1], points[0])); + + this.pos = pos; + this.vel = initVel; + this.rot = (rot * Math.PI) / 180; + this.rotVel = (initRotVel * Math.PI) / 180; + + this.e = 0.6; + this.df = 0.4; + this.sf = 0.6; + + this.lockRot = false; + this.anchored = false; + + this.mass = this.getArea(); + this.invMass = 1 / this.mass; + this.i = (1 / 2) * this.mass * this.radius * this.radius; + this.invI = 1 / this.i; + + this.fillColor = + "rgb(" + + String(Math.floor(Math.random() * 255)) + + "," + + String(Math.floor(Math.random() * 255)) + + "," + + String(Math.floor(Math.random() * 255)) + + ")"; + // this.fillColor = "rgb(255, 255, 255)" + this.strokeColor = "rgb(255, 255, 255)"; + } + + boundsCollide(polygon) { + return ( + (polygon.pos.x - this.pos.x) ** 2 + (polygon.pos.y - this.pos.y) ** 2 <= + (this.radius + polygon.radius) ** 2 + ); + } + + getArea() { + let crossSum = 0; + for (let i = 0; i < this.points.length - 1; i++) { + let a = this.points[i]; + let b = this.points[i + 1]; + crossSum += a.cross(b); } - - testPolygonCollision(colPoly, ctx) { - let results = [] - this.components.forEach(element => { - let rot = element.rotation - let offset = element.offset - let polygon = element.polygon - - let result = polygon.testCollision(colPoly, ctx) - if (result == false) { return } - results.push({polygon: colPoly, mvt: result.mvt, normal: result.normal, points: result.points}) - // this.resolveCollision(polygon, result.mvt, result.normal, result.point) - }); - return results + crossSum += this.points[this.points.length - 1].cross(this.points[0]); + + return Math.abs(crossSum) / 2; + } + + getRotDegrees() { + return (this.rot / Math.PI) * 180; + } + + toObjectSpace(vector = Vector2.prototype) { + return vector.clone().subtract(this.pos).rotate(-this.rot); + } + + toWorldSpace(vector) { + return new Vector2( + this.pos.x + + vector.x * Math.cos(this.rot) - + vector.y * Math.sin(this.rot), + this.pos.y + vector.x * Math.sin(this.rot) + vector.y * Math.cos(this.rot) + ); + } + + getWorldCoordinates() { + var worldPoints = []; + for (let point of this.points) { + worldPoints.push(this.toWorldSpace(point)); } - - applyImpulse(impulse, pos) { - let collArm = pos.clone().subtract(this.pos) - this.vel.add(impulse.clone().scale(this.invMTot)) - this.rotVel += this.invITot * collArm.cross(impulse) + return worldPoints; + } + + getWorldFaces() { + let points = this.getWorldCoordinates(); + let sides = []; + for (let i = 0; i < points.length - 1; i++) { + sides.push(new Face(points[i], points[i + 1])); } + sides.push(new Face(points[points.length - 1], points[0])); + return sides; + } - resolvePolygonCollision(polygon, mvt, normal, collisionPoints) { - if (this.anchored && polygon.anchored) { - return - } else if (this.anchored) { - polygon.pos.add(normal.clone().scale(mvt / 2)) - } else if (polygon.anchored) { - this.pos.subtract(normal.clone().scale(mvt / 2)) - } else { - this.pos.subtract(normal.clone().scale(mvt / 2)) - polygon.pos.add(normal.clone().scale(mvt / 2)) - } - - let impulses = [] - let collArm1s = [] - let collArm2s = [] - - collisionPoints.forEach(collPoint => { - let collArm1 = collPoint.clone().subtract(this.pos) - let rotVel1 = new Vector2(-this.rotVel * collArm1.y, this.rotVel * collArm1.x) - let closVel1 = this.vel.clone().add(rotVel1) - - let collArm2 = collPoint.clone().subtract(polygon.pos) - let rotVel2 = new Vector2(-polygon.rotVel * collArm2.y, polygon.rotVel * collArm2.x) - let closVel2 = polygon.vel.clone().add(rotVel2) - - collArm1s.push(collArm1) - collArm2s.push(collArm2) - - let impAug1 = collArm1.cross(normal) - impAug1 = impAug1 * impAug1 * this.invITot - let impAug2 = collArm2.cross(normal) - impAug2 = impAug2 * impAug2 * polygon.invI - - let relVel = closVel1.clone().subtract(closVel2) - let sepVel = relVel.dot(normal) - let newSepVel = -sepVel * Math.min(this.e, polygon.e) - let vSepDiff = newSepVel - sepVel + applyImpulse(impulse, pos) { + let collArm = pos.clone().subtract(this.pos); + this.vel.add(impulse.clone().scale(this.invMass)); + this.rotVel += this.invI * collArm.cross(impulse); + } - let impulse = vSepDiff / (this.invMTot + polygon.invMass + impAug1 + impAug2) / collisionPoints.length - let impulseVec = normal.clone().scale(impulse) + render(ctx = CanvasRenderingContext2D) { + ctx.strokeStyle = this.strokeColor; + ctx.fillStyle = this.fillColor; - impulses.push(impulseVec) + let worldCoords = this.getWorldCoordinates(); - //Friction - let tangent = relVel.clone().subtract(normal.clone().scale(relVel.dot(normal))) - if (tangent.magnitude() < 0.005) { return } //tangent is near zero - tangent.normalize() + ctx.save(); - let frictionAug1 = collArm1.cross(tangent) - frictionAug1 = frictionAug1 * frictionAug1 * this.invITot - let frictionAug2 = collArm2.cross(tangent) - frictionAug2 = frictionAug2 * frictionAug2 * polygon.invI + ctx.beginPath(); - let impulseFriction = relVel.dot(tangent) / (frictionAug1 + frictionAug2 + this.invMTot + polygon.invMass) / collisionPoints.length - - let sf = (this.sf + polygon.sf) * .5 - let df = (this.df + polygon.df) * .5 - - let impFricVec - if (impulseFriction <= impulse * sf) { - impFricVec = tangent.scale(-impulseFriction) - } else { - impFricVec = tangent.clone().scale(impulse * df) - } - - collArm1s.push(collArm1) - collArm2s.push(collArm2) - - impulses.push(impFricVec) - }); - - impulses.forEach((impulse, index) => { - let collArm1 = collArm1s[index] - let collArm2 = collArm2s[index] - - this.vel.add(impulse.clone().scale(this.invMTot)) - polygon.vel.subtract(impulse.clone().scale(polygon.invMass)) - - this.rotVel += this.invITot * collArm1.cross(impulse) - polygon.rotVel -= polygon.invI * collArm2.cross(impulse) - }); + ctx.fillStyle = this.fillColor; + ctx.strokeStyle = this.strokeColor; + //draw polygon + ctx.moveTo(worldCoords[0].x, worldCoords[0].y); + for (let point of worldCoords) { + ctx.lineTo(point.x, point.y); + } + ctx.lineTo(worldCoords[0].x, worldCoords[0].y); + ctx.fill(); + ctx.stroke(); + + //draw foward + ctx.beginPath(); + ctx.strokeStyle = "rgb(255, 0, 0)"; + ctx.moveTo(this.pos.x, this.pos.y); + ctx.lineTo( + this.pos.x + 50 * Math.cos(this.rot), + this.pos.y + 50 * Math.sin(this.rot) + ); + ctx.stroke(); + + ctx.beginPath(); + ctx.strokeStyle = "rgb(0, 255, 0)"; + ctx.moveTo(this.pos.x, this.pos.y); + ctx.lineTo( + this.pos.x + 50 * Math.cos(this.rot - Math.PI / 2), + this.pos.y + 50 * Math.sin(this.rot - Math.PI / 2) + ); + ctx.stroke(); + + if (debug) { + ctx.beginPath(); + ctx.strokeStyle = "rgb(0, 255, 255)"; + //draw normals + for (let face of this.sides) { + let worldPoint = this.toWorldSpace(face.center()); + let worldNormal = this.toWorldSpace(face.normal); + + ctx.moveTo(worldPoint.x, worldPoint.y); + ctx.lineTo( + worldPoint.x + (worldNormal.x - this.pos.x) * 25, + worldPoint.y + (worldNormal.y - this.pos.y) * 25 + ); + ctx.fillRect( + worldPoint.x + (worldNormal.x - this.pos.x) * 25, + worldPoint.y + (worldNormal.y - this.pos.y) * 25, + 3, + 3 + ); + } + ctx.stroke(); + + ctx.strokeStyle = "rgb(0, 255, 0)"; + ctx.beginPath(); + ctx.arc(this.pos.x, this.pos.y, this.radius, 0, 2 * Math.PI); + ctx.stroke(); } + ctx.restore(); + } -} - -export class Polygon { - constructor(points, pos = new Vector2(0, 0), rot = 0, initVel = new Vector2(0, 0), initRotVel = 0) { - this.points = points; - - this.radius = this.points[this.points.length - 1].magnitude() - this.hullSize = new Vector2(0, 0) - - this.sides = []; - for (let i = 0; i < points.length - 1; i++) { - this.sides.push(new Face(points[i], points[i + 1])) - - let point = this.points[this.points.length - 1 - i] - let mag = point.magnitude() - if (mag > this.radius) { - this.radius = mag - } - } - this.sides.push(new Face(points[points.length - 1], points[0])) - - this.pos = pos - this.vel = initVel - this.rot = rot * Math.PI / 180; - this.rotVel = initRotVel * Math.PI / 180; - - this.e = 0.6 - this.df = .4 - this.sf = .6 - - this.lockRot = false - this.anchored = false - - this.mass = this.getArea() - this.invMass = 1 / this.mass - this.i = 1 / 2 * this.mass * this.radius * this.radius - this.invI = 1 / this.i + projectToAxis(axis) { + let points = this.getWorldCoordinates(); - this.fillColor = "rgb("+ String(Math.floor(Math.random() * 255)) +","+ String(Math.floor(Math.random() * 255)) +","+ String(Math.floor(Math.random() * 255)) +")" - // this.fillColor = "rgb(255, 255, 255)" - this.strokeColor = "rgb(255, 255, 255)" - } + let max = points[0]; + let maxMag = max.projectToAxis(axis); + // let maxMag = max.dot(axis) - getArea() { - let crossSum = 0; - for (let i = 0; i < this.points.length - 1; i++) { - let a = this.points[i]; - let b = this.points[i + 1]; - crossSum += a.cross(b) - } - crossSum += this.points[this.points.length - 1].cross(this.points[0]) + let min = points[0]; + let minMag = maxMag; - return Math.abs(crossSum) / 2 - } + for (let i = 1; i < points.length; i++) { + let point = points[i]; + let mag = point.projectToAxis(axis); + // let mag = point.dot(point) - getRotDegrees() { - return this.rot / Math.PI * 180; + if (mag < minMag) { + min = point; + minMag = mag; + } else if (mag > maxMag) { + max = point; + maxMag = mag; + } } + return { minPoint: min, minMag: minMag, maxPoint: max, maxMag: maxMag }; + } - toObjectSpace(vector = Vector2.prototype) { - return vector.clone().subtract(this.pos).rotate(-this.rot) - } + segmentOverlaps(min1, max1, min2, max2) { + let overlap = Math.max(0, Math.min(max1, max2) - Math.max(min1, min2)); - toWorldSpace(vector) { - return new Vector2( - this.pos.x + vector.x * Math.cos(this.rot) - vector.y * Math.sin(this.rot), - this.pos.y + vector.x * Math.sin(this.rot) + vector.y * Math.cos(this.rot), - ) + if (overlap > 0) { + return overlap; } - - getWorldCoordinates() { - var worldPoints = []; - for (let point of this.points) { - worldPoints.push(this.toWorldSpace(point)); + return false; + } + + findCollidingPoint(polygon, ctx) { + let collPoints = []; + let minDis = Number.MAX_SAFE_INTEGER; + + for (let point of polygon.points) { + for (let face of this.sides) { + let worldPoint = polygon.toWorldSpace(point); + + let dis = minDisToLineSeg( + this.toWorldSpace(face.from), + this.toWorldSpace(face.to), + worldPoint + ); + + if (dis < minDis - 0.25) { + minDis = dis; + collPoints = [worldPoint]; + } else if (dis < minDis + 0.25) { + collPoints.push(worldPoint); } - return worldPoints; + } } - getWorldFaces() { - let points = this.getWorldCoordinates() - let sides = [] - for (let i = 0; i < points.length - 1; i++) { - sides.push(new Face(points[i], points[i + 1])) + for (let point of this.points) { + for (let face of polygon.sides) { + let worldPoint = this.toWorldSpace(point); + let dis = minDisToLineSeg( + polygon.toWorldSpace(face.from), + polygon.toWorldSpace(face.to), + worldPoint + ); + + if (dis < minDis - 0.25) { + minDis = dis; + collPoints = [worldPoint]; + } else if (dis < minDis + 0.25) { + collPoints.push(worldPoint); } - sides.push(new Face(points[points.length - 1], points[0])) - return sides + } } - applyImpulse(impulse, pos) { - let collArm = pos.clone().subtract(this.pos) - this.vel.add(impulse.clone().scale(this.invMass)) - this.rotVel += this.invI * collArm.cross(impulse) + if (debug) { + collPoints.forEach((point) => { + ctx.fillStyle = "rgb(255,0,0)"; + ctx.fillRect(point.x, point.y, 10, 10); + }); } - render(ctx = CanvasRenderingContext2D) { - ctx.strokeStyle = this.strokeColor; - ctx.fillStyle = this.fillColor; + return collPoints; + } - let worldCoords = this.getWorldCoordinates(); - - ctx.save(); - - ctx.beginPath(); - - ctx.fillStyle = this.fillColor - ctx.strokeStyle = this.strokeColor - - //draw polygon - ctx.moveTo(worldCoords[0].x, worldCoords[0].y); - for (let point of worldCoords) { - ctx.lineTo(point.x, point.y); - } - ctx.lineTo(worldCoords[0].x, worldCoords[0].y); - ctx.fill() - ctx.stroke() - - //draw foward - ctx.beginPath() - ctx.strokeStyle = "rgb(255, 0, 0)" - ctx.moveTo(this.pos.x, this.pos.y); - ctx.lineTo(this.pos.x + 50 * Math.cos(this.rot), this.pos.y + 50 * Math.sin(this.rot)) - ctx.stroke(); - - ctx.beginPath() - ctx.strokeStyle = "rgb(0, 255, 0)" - ctx.moveTo(this.pos.x, this.pos.y); - ctx.lineTo(this.pos.x + 50 * Math.cos(this.rot - Math.PI / 2), this.pos.y + 50 * Math.sin(this.rot - Math.PI / 2)) - ctx.stroke(); - - if (debug) { - - - ctx.beginPath(); - ctx.strokeStyle = "rgb(0, 255, 255)" - //draw normals - for (let face of this.sides) { - let worldPoint = this.toWorldSpace(face.center()) - let worldNormal = this.toWorldSpace(face.normal) - - ctx.moveTo(worldPoint.x, worldPoint.y) - ctx.lineTo(worldPoint.x + (worldNormal.x - this.pos.x) * 25, worldPoint.y + (worldNormal.y - this.pos.y) * 25); - ctx.fillRect(worldPoint.x + (worldNormal.x - this.pos.x) * 25, worldPoint.y + (worldNormal.y - this.pos.y) * 25, 3, 3) - } - ctx.stroke() - - ctx.strokeStyle = "rgb(0, 255, 0)" - ctx.beginPath(); - ctx.arc(this.pos.x, this.pos.y, this.radius, 0, 2 * Math.PI); - ctx.stroke(); - } - - ctx.restore(); - } - - projectToAxis(axis) { - let points = this.getWorldCoordinates() - - let max = points[0] - let maxMag = max.projectToAxis(axis) - // let maxMag = max.dot(axis) - - let min = points[0] - let minMag = maxMag - - for (let i = 1; i < points.length; i++) { - let point = points[i] - let mag = point.projectToAxis(axis) - // let mag = point.dot(point) - - if (mag < minMag) { - min = point - minMag = mag - } else if (mag > maxMag) { - max = point - maxMag = mag - } - } - return { minPoint: min, minMag: minMag, maxPoint: max, maxMag: maxMag } + resolveCollision(polygon, mvt, normal, collisionPoints) { + if (this.anchored && polygon.anchored) { + // return + } else if (this.anchored) { + polygon.pos.add(normal.clone().scale(mvt / 2)); + } else if (polygon.anchored) { + this.pos.subtract(normal.clone().scale(mvt / 2)); + } else { + this.pos.subtract(normal.clone().scale(mvt / 2)); + polygon.pos.add(normal.clone().scale(mvt / 2)); } - segmentOverlaps(min1, max1, min2, max2) { - let overlap = Math.max(0, Math.min(max1, max2) - Math.max(min1, min2)) - - if (overlap > 0) { - return overlap - } - return false + if (polygon.mass <= 0 && this.mass <= 0) { + return; } - - findCollidingPoint(polygon, ctx) { - let collPoints = [] - let minDis = Number.MAX_SAFE_INTEGER - - for (let point of polygon.points) { - for (let face of this.sides) { - let worldPoint = polygon.toWorldSpace(point) - - let dis = minDisToLineSeg(this.toWorldSpace(face.from), this.toWorldSpace(face.to), worldPoint) - - if (dis < minDis - 0.25) { - minDis = dis; - collPoints = [worldPoint] - } else if (dis < minDis + 0.25) { - collPoints.push(worldPoint) - } - } - } - - for (let point of this.points) { - for (let face of polygon.sides) { - let worldPoint = this.toWorldSpace(point) - let dis = minDisToLineSeg(polygon.toWorldSpace(face.from), polygon.toWorldSpace(face.to), worldPoint) - - if (dis < minDis - 0.25) { - minDis = dis; - collPoints = [worldPoint] - } else if (dis < minDis + 0.25) { - collPoints.push(worldPoint) - } - } - } - - if (debug) { - collPoints.forEach(point => { - ctx.fillStyle = "rgb(255,0,0)" - ctx.fillRect(point.x, point.y, 10, 10) - }); - } - - return collPoints + let impulses = []; + let impulseList = []; + let frictionImpulses = []; + let collArm1s = []; + let collArm2s = []; + + collisionPoints.forEach((collPoint, index) => { + let collArm1 = collPoint.clone().subtract(this.pos); + let collArm2 = collPoint.clone().subtract(polygon.pos); + + collArm1s.push(collArm1); + collArm2s.push(collArm2); + + let rotVel1 = new Vector2( + -this.rotVel * collArm1.y, + this.rotVel * collArm1.x + ); + let rotVel2 = new Vector2( + -polygon.rotVel * collArm2.y, + polygon.rotVel * collArm2.x + ); + + let closVel1 = this.vel.clone().add(rotVel1); + let closVel2 = polygon.vel.clone().add(rotVel2); + + let impAug1 = collArm1.cross(normal); + impAug1 = impAug1 * impAug1 * this.invI; + let impAug2 = collArm2.cross(normal); + impAug2 = impAug2 * impAug2 * polygon.invI; + + let relVel = closVel1.clone().subtract(closVel2); + let sepVel = relVel.dot(normal); + let newSepVel = -sepVel * Math.min(this.e, polygon.e); + let vSepDiff = newSepVel - sepVel; + + let impulse = + vSepDiff / + (this.invMass + polygon.invMass + impAug1 + impAug2) / + collisionPoints.length; + let impulseVec = normal.clone().scale(impulse); + + impulseList.push(impulse); + impulses.push(impulseVec); + }); + + impulses.forEach((impulse, index) => { + let collArm1 = collArm1s[index]; + let collArm2 = collArm2s[index]; + + this.vel.add(impulse.clone().scale(this.invMass)); + polygon.vel.subtract(impulse.clone().scale(polygon.invMass)); + + this.rotVel += this.invI * collArm1.cross(impulse); + polygon.rotVel -= polygon.invI * collArm2.cross(impulse); + }); + + collisionPoints.forEach((collPoint, index) => { + let collArm1 = collPoint.clone().subtract(this.pos); + let collArm2 = collPoint.clone().subtract(polygon.pos); + + collArm1s[index] = collArm1; + collArm2s[index] = collArm2; + + let rotVel1 = new Vector2( + -this.rotVel * collArm1.y, + this.rotVel * collArm1.x + ); + let rotVel2 = new Vector2( + -polygon.rotVel * collArm2.y, + polygon.rotVel * collArm2.x + ); + + let closVel1 = this.vel.clone().add(rotVel1); + let closVel2 = polygon.vel.clone().add(rotVel2); + + let relVel = closVel1.clone().subtract(closVel2); + let tangent = relVel + .clone() + .subtract(normal.clone().scale(relVel.dot(normal))); + if (tangent.magnitude() < 0.005) { + return; + } //tangent is near zero + tangent.normalize(); + + let frictionAug1 = collArm1.cross(tangent); + frictionAug1 = frictionAug1 * frictionAug1 * this.invI; + let frictionAug2 = collArm2.cross(tangent); + frictionAug2 = frictionAug2 * frictionAug2 * polygon.invI; + + let impulseFriction = + relVel.dot(tangent) / + (frictionAug1 + frictionAug2 + this.invMass + polygon.invMass) / + collisionPoints.length; + + let sf = (this.sf + polygon.sf) * 0.5; + let df = (this.df + polygon.df) * 0.5; + + let impFricVec; + let impulse = impulseList[index]; + if (impulseFriction <= impulse * sf) { + impFricVec = tangent.scale(-impulseFriction); + } else { + impFricVec = tangent.clone().scale(impulse * df); + } + + frictionImpulses.push(impFricVec); + }); + + frictionImpulses.forEach((impulse, index) => { + let collArm1 = collArm1s[index]; + let collArm2 = collArm2s[index]; + + this.vel.add(impulse.clone().scale(this.invMass)); + polygon.vel.subtract(impulse.clone().scale(polygon.invMass)); + + this.rotVel += this.invI * collArm1.cross(impulse); + polygon.rotVel -= polygon.invI * collArm2.cross(impulse); + }); + } + + testCollision(polygon, ctx) { + let minOverlap = Number.MAX_VALUE; + let normal; + + for (let face of this.sides) { + let axis = this.toWorldSpace(face.normal.clone()).subtract(this.pos); + + let selfProj = this.projectToAxis(axis); + let compProj = polygon.projectToAxis(axis); + + let overlap = this.segmentOverlaps( + selfProj.minMag, + selfProj.maxMag, + compProj.minMag, + compProj.maxMag + ); + if (overlap == false) { + return false; + } else if (overlap < minOverlap) { + minOverlap = overlap; + normal = axis; + } + // debug render // + let selfMinAxis = axis + .clone() + .scale(selfProj.minMag / 5) + .add(this.pos); + let selfMaxAxis = axis + .clone() + .scale(selfProj.maxMag / 5) + .add(this.pos); + let compMinAxis = axis + .clone() + .scale(compProj.minMag / 5) + .add(this.pos); + let compMaxAxis = axis + .clone() + .scale(compProj.maxMag / 5) + .add(this.pos); + + if (debug) { + ctx.fillStyle = overlap ? "rgb(0,255,255)" : "rgb(0,0,255)"; + ctx.fillRect(selfMinAxis.x - 2.5, selfMinAxis.y - 2.5, 5, 5); + ctx.fillRect(selfMaxAxis.x - 2.5, selfMaxAxis.y - 2.5, 5, 5); + ctx.fillStyle = overlap ? "rgb(255, 0 ,255)" : "rgb(255,0,0)"; + ctx.fillRect(compMinAxis.x - 2.5, compMinAxis.y - 2.5, 5, 5); + ctx.fillRect(compMaxAxis.x - 2.5, compMaxAxis.y - 2.5, 5, 5); + } } - resolveCollision(polygon, mvt, normal, collisionPoints) { - if (this.anchored && polygon.anchored) { - // return - } else if (this.anchored) { - polygon.pos.add(normal.clone().scale(mvt / 2)) - } else if (polygon.anchored) { - this.pos.subtract(normal.clone().scale(mvt / 2)) - } else { - this.pos.subtract(normal.clone().scale(mvt / 2)) - polygon.pos.add(normal.clone().scale(mvt / 2)) - } - - if (polygon.mass <= 0 && this.mass <= 0) { - return - } - - let impulses = [] - let impulseList = [] - let frictionImpulses = [] - let collArm1s = [] - let collArm2s = [] - - collisionPoints.forEach((collPoint, index) => { - let collArm1 = collPoint.clone().subtract(this.pos) - let collArm2 = collPoint.clone().subtract(polygon.pos) - - collArm1s.push(collArm1) - collArm2s.push(collArm2) - - let rotVel1 = new Vector2(-this.rotVel * collArm1.y, this.rotVel * collArm1.x) - let rotVel2 = new Vector2(-polygon.rotVel * collArm2.y, polygon.rotVel * collArm2.x) - - let closVel1 = this.vel.clone().add(rotVel1) - let closVel2 = polygon.vel.clone().add(rotVel2) - - let impAug1 = collArm1.cross(normal) - impAug1 = impAug1 * impAug1 * this.invI - let impAug2 = collArm2.cross(normal) - impAug2 = impAug2 * impAug2 * polygon.invI - - let relVel = closVel1.clone().subtract(closVel2) - let sepVel = relVel.dot(normal) - let newSepVel = -sepVel * Math.min(this.e, polygon.e) - let vSepDiff = newSepVel - sepVel - - let impulse = vSepDiff / (this.invMass + polygon.invMass + impAug1 + impAug2) / collisionPoints.length - let impulseVec = normal.clone().scale(impulse) - - impulseList.push(impulse) - impulses.push(impulseVec) - }) - - impulses.forEach((impulse, index) => { - let collArm1 = collArm1s[index] - let collArm2 = collArm2s[index] - - this.vel.add(impulse.clone().scale(this.invMass)) - polygon.vel.subtract(impulse.clone().scale(polygon.invMass)) - - this.rotVel += this.invI * collArm1.cross(impulse) - polygon.rotVel -= polygon.invI * collArm2.cross(impulse) - }); - - collisionPoints.forEach((collPoint, index) => { - let collArm1 = collPoint.clone().subtract(this.pos) - let collArm2 = collPoint.clone().subtract(polygon.pos) - - collArm1s[index] = collArm1 - collArm2s[index] = collArm2 - - let rotVel1 = new Vector2(-this.rotVel * collArm1.y, this.rotVel * collArm1.x) - let rotVel2 = new Vector2(-polygon.rotVel * collArm2.y, polygon.rotVel * collArm2.x) - - let closVel1 = this.vel.clone().add(rotVel1) - let closVel2 = polygon.vel.clone().add(rotVel2) - - let relVel = closVel1.clone().subtract(closVel2) - let tangent = relVel.clone().subtract(normal.clone().scale(relVel.dot(normal))) - if (tangent.magnitude() < 0.005) { return } //tangent is near zero - tangent.normalize() - - let frictionAug1 = collArm1.cross(tangent) - frictionAug1 = frictionAug1 * frictionAug1 * this.invI - let frictionAug2 = collArm2.cross(tangent) - frictionAug2 = frictionAug2 * frictionAug2 * polygon.invI - - let impulseFriction = relVel.dot(tangent) / (frictionAug1 + frictionAug2 + this.invMass + polygon.invMass) / collisionPoints.length - - let sf = (this.sf + polygon.sf) * .5 - let df = (this.df + polygon.df) * .5 - - let impFricVec - let impulse = impulseList[index] - if (impulseFriction <= impulse * sf) { - impFricVec = tangent.scale(-impulseFriction) - } else { - impFricVec = tangent.clone().scale(impulse * df) - } - - frictionImpulses.push(impFricVec) - }); - - frictionImpulses.forEach((impulse, index) => { - let collArm1 = collArm1s[index] - let collArm2 = collArm2s[index] - - this.vel.add(impulse.clone().scale(this.invMass)) - polygon.vel.subtract(impulse.clone().scale(polygon.invMass)) - - this.rotVel += this.invI * collArm1.cross(impulse) - polygon.rotVel -= polygon.invI * collArm2.cross(impulse) - }); - + for (let face of polygon.sides) { + let axis = polygon + .toWorldSpace(face.normal.clone()) + .subtract(polygon.pos); + + let selfProj = this.projectToAxis(axis); + let compProj = polygon.projectToAxis(axis); + + let overlap = this.segmentOverlaps( + selfProj.minMag, + selfProj.maxMag, + compProj.minMag, + compProj.maxMag + ); + if (overlap == false) { + return false; + } else if (overlap < minOverlap) { + minOverlap = overlap; + normal = axis; + } + let selfMinAxis = axis + .clone() + .scale(selfProj.minMag / 5) + .add(polygon.pos); + let selfMaxAxis = axis + .clone() + .scale(selfProj.maxMag / 5) + .add(polygon.pos); + let compMinAxis = axis + .clone() + .scale(compProj.minMag / 5) + .add(polygon.pos); + let compMaxAxis = axis + .clone() + .scale(compProj.maxMag / 5) + .add(polygon.pos); + + if (debug) { + ctx.fillStyle = overlap ? "rgb(0,255,255)" : "rgb(0,0,255)"; + ctx.fillRect(selfMinAxis.x - 2.5, selfMinAxis.y - 2.5, 5, 5); + ctx.fillRect(selfMaxAxis.x - 2.5, selfMaxAxis.y - 2.5, 5, 5); + ctx.fillStyle = overlap ? "rgb(255, 0 ,255)" : "rgb(255,0,0)"; + ctx.fillRect(compMinAxis.x - 2.5, compMinAxis.y - 2.5, 5, 5); + ctx.fillRect(compMaxAxis.x - 2.5, compMaxAxis.y - 2.5, 5, 5); + } } - testCollision(polygon, ctx) { - let minOverlap = Number.MAX_VALUE - let normal - - for (let face of this.sides) { - let axis = this.toWorldSpace(face.normal.clone()).subtract(this.pos) - - let selfProj = this.projectToAxis(axis) - let compProj = polygon.projectToAxis(axis) - - let overlap = this.segmentOverlaps(selfProj.minMag, selfProj.maxMag, compProj.minMag, compProj.maxMag) - if (overlap == false) { - return false - } else if (overlap < minOverlap) { - minOverlap = overlap - normal = axis - } - // debug render // - let selfMinAxis = axis.clone().scale(selfProj.minMag / 5).add(this.pos) - let selfMaxAxis = axis.clone().scale(selfProj.maxMag / 5).add(this.pos) - let compMinAxis = axis.clone().scale(compProj.minMag / 5).add(this.pos) - let compMaxAxis = axis.clone().scale(compProj.maxMag / 5).add(this.pos) - - if (debug) { - ctx.fillStyle = (overlap) ? "rgb(0,255,255)" : "rgb(0,0,255)" - ctx.fillRect(selfMinAxis.x - 2.5, selfMinAxis.y - 2.5, 5, 5) - ctx.fillRect(selfMaxAxis.x - 2.5, selfMaxAxis.y - 2.5, 5, 5) - ctx.fillStyle = (overlap) ? "rgb(255, 0 ,255)" : "rgb(255,0,0)" - ctx.fillRect(compMinAxis.x - 2.5, compMinAxis.y - 2.5, 5, 5) - ctx.fillRect(compMaxAxis.x - 2.5, compMaxAxis.y - 2.5, 5, 5) - } - } - - for (let face of polygon.sides) { - let axis = polygon.toWorldSpace(face.normal.clone()).subtract(polygon.pos) - - let selfProj = this.projectToAxis(axis) - let compProj = polygon.projectToAxis(axis) - - let overlap = this.segmentOverlaps(selfProj.minMag, selfProj.maxMag, compProj.minMag, compProj.maxMag) - if (overlap == false) { - return false - } else if (overlap < minOverlap) { - minOverlap = overlap - normal = axis - } - let selfMinAxis = axis.clone().scale(selfProj.minMag / 5).add(polygon.pos) - let selfMaxAxis = axis.clone().scale(selfProj.maxMag / 5).add(polygon.pos) - let compMinAxis = axis.clone().scale(compProj.minMag / 5).add(polygon.pos) - let compMaxAxis = axis.clone().scale(compProj.maxMag / 5).add(polygon.pos) - - if (debug) { - ctx.fillStyle = (overlap) ? "rgb(0,255,255)" : "rgb(0,0,255)" - ctx.fillRect(selfMinAxis.x - 2.5, selfMinAxis.y - 2.5, 5, 5) - ctx.fillRect(selfMaxAxis.x - 2.5, selfMaxAxis.y - 2.5, 5, 5) - ctx.fillStyle = (overlap) ? "rgb(255, 0 ,255)" : "rgb(255,0,0)" - ctx.fillRect(compMinAxis.x - 2.5, compMinAxis.y - 2.5, 5, 5) - ctx.fillRect(compMaxAxis.x - 2.5, compMaxAxis.y - 2.5, 5, 5) - } - } - - ctx.strokeStyle = "rgb(255,0,0)" - ctx.beginPath() - ctx.moveTo(this.pos.x, this.pos.y) - ctx.lineTo(this.pos.x + normal.x * minOverlap, this.pos.y + normal.y * minOverlap) - ctx.stroke() - - let delta = polygon.pos.clone().subtract(this.pos).normalize() - if (delta.dot(normal) < 0) { - normal.scale(-1) - } - - let collisionPoints = this.findCollidingPoint(polygon, ctx) - return { mvt: minOverlap, normal: normal, points: collisionPoints } + ctx.strokeStyle = "rgb(255,0,0)"; + ctx.beginPath(); + ctx.moveTo(this.pos.x, this.pos.y); + ctx.lineTo( + this.pos.x + normal.x * minOverlap, + this.pos.y + normal.y * minOverlap + ); + ctx.stroke(); + + let delta = polygon.pos.clone().subtract(this.pos).normalize(); + if (delta.dot(normal) < 0) { + normal.scale(-1); } - tick(dt, t) { - if (this.anchored) { - this.vel.set(0, 0) - this.rotVel = 0 - return - } else if (this.lockRot) { - this.rotVel = 0 - return - } - this.pos.add(this.vel.clone().scale(dt)); - this.rot += this.rotVel * dt + let collisionPoints = this.findCollidingPoint(polygon, ctx); + return { mvt: minOverlap, normal: normal, points: collisionPoints }; + } + + tick(dt, t) { + if (this.anchored) { + this.vel.set(0, 0); + this.rotVel = 0; + return; + } else if (this.lockRot) { + this.rotVel = 0; + return; } + this.pos.add(this.vel.clone().scale(dt)); + this.rot += this.rotVel * dt; + } } export class Box extends Polygon { - constructor(pos = new Vector2(0, 0), size = new Vector2(50, 50), rot = 0, initVel = new Vector2(0, 0), initRotVel = 0) { - let xHalf = size.x / 2 - let yHalf = size.y / 2 - - super( - [ - new Vector2(-xHalf, yHalf), - new Vector2(xHalf, yHalf), - new Vector2(xHalf, -yHalf), - new Vector2(-xHalf, -yHalf) - ], - pos, - rot, - initVel, - initRotVel - ) - - this.size = size - this.topLeft = this.points[0] - this.i = 1 / 12 * this.mass * (this.size.x * this.size.x + this.size.y * this.size.y) - this.invI = 1 / this.i - } + constructor( + pos = new Vector2(0, 0), + size = new Vector2(50, 50), + rot = 0, + initVel = new Vector2(0, 0), + initRotVel = 0 + ) { + let xHalf = size.x / 2; + let yHalf = size.y / 2; + + super( + [ + new Vector2(-xHalf, yHalf), + new Vector2(xHalf, yHalf), + new Vector2(xHalf, -yHalf), + new Vector2(-xHalf, -yHalf), + ], + pos, + rot, + initVel, + initRotVel + ); + + this.size = size; + this.topLeft = this.points[0]; + this.i = + (1 / 12) * + this.mass * + (this.size.x * this.size.x + this.size.y * this.size.y); + this.invI = 1 / this.i; + } } export class RegularPolygon extends Polygon { - constructor(pos = new Vector2(0, 0), size = new Vector2(25, 25), resolution = 6, rot = 0, initVel = new Vector2(0, 0), initRotVel = 0) { - let points = [] - - let rad = 2 * Math.PI / resolution - 0 - for (let i = 0; i < resolution; i++) { - points.push(new Vector2(size.x * Math.cos(-rad * i), size.y * Math.sin(-rad * i))) - } + constructor( + pos = new Vector2(0, 0), + size = new Vector2(25, 25), + resolution = 6, + rot = 0, + initVel = new Vector2(0, 0), + initRotVel = 0 + ) { + let points = []; + + let rad = (2 * Math.PI) / resolution - 0; + for (let i = 0; i < resolution; i++) { + points.push( + new Vector2(size.x * Math.cos(-rad * i), size.y * Math.sin(-rad * i)) + ); + } - super(points, pos, rot, initVel, initRotVel) + super(points, pos, rot, initVel, initRotVel); - this.size = size - } + this.size = size; + } } export class Wall extends Polygon { - constructor(pos = new Vector2(0, 0), length = 10, rot = 0, initVel = new Vector2(0, 0), initRotVel = 0) { - let points = [new Vector2(length / 2, 0), new Vector2(-length / 2, 0)] - super(points, pos, rot, initVel, initRotVel) - } -} \ No newline at end of file + constructor( + pos = new Vector2(0, 0), + length = 10, + rot = 0, + initVel = new Vector2(0, 0), + initRotVel = 0 + ) { + let points = [new Vector2(length / 2, 0), new Vector2(-length / 2, 0)]; + super(points, pos, rot, initVel, initRotVel); + } +} diff --git a/javascript/PhysicsEngine/main.js b/javascript/PhysicsEngine/main.js index 39eb075..0045d39 100644 --- a/javascript/PhysicsEngine/main.js +++ b/javascript/PhysicsEngine/main.js @@ -104,14 +104,15 @@ function calculate(dt, t) { // model.vel.add(gravity.clone().scale(dti)) polygon.tick(dti, t); - if (!polygon.anchored) { - // applyCollisionBounds(polygon) - } - for (let collPoly of polygons) { if (collPoly === polygon) { continue; } + + if (!polygon.boundsCollide(collPoly)) { + continue; + } + let result = polygon.testCollision(collPoly, ctx); if (result == false) { continue; @@ -260,7 +261,7 @@ function startup() { // poly2 = new polyModule.Wall(new Vector2(ctx.canvas.width / 2, ctx.canvas.height / 2), 500, 0, new Vector2(0, 0), 0) polygons.push(poly2); - for (let i = 0; i < 25; i++) { + for (let i = 0; i < 35; i++) { polygons.push( new polyModule.RegularPolygon( new Vector2(