With much help from the SO community, I've created a program where a ball is in a triangle with a certain position and velocity. Using parametric equations, I've determined where the ball will hit the triangle. I then found the perpendicular to the side of the triangle where the ball would hit, so that I could reflect the ball off the wall, to plot its "bounce" trajectory. My goal is that I can plot how the ball will bounce off the walls for n iterations. But I've come up against a problem.
Even though I'm using the p5.js A.reflect(B)
method to reflect the ball along the perpendicular, it's producing some truly strange results.
I've tried many things (i.e., modifying the getPerp()
function, unit testing, etc.), but to no avail. Any help would be greatly appreciated. MWE below and in Codepen.
let angle = 0;
let sides = [];
let vertices = [];
const len = 100;
function setup() {
createCanvas(windowWidth, windowHeight);
angleMode(DEGREES);
angleOne = createSlider(0, 89, 60);
angleOne.position(10, 10);
angleOne.style("width", "80px");
angleTwo = createSlider(0, 89, 60);
angleTwo.position(10, 30);
angleTwo.style("width", "80px");
rotateVel = createSlider(0, 360, 60);
rotateVel.position(10, 50);
rotateVel.style("width", "80px");
// Initial vertice & side setup (these don't change)
let v1 = createVector(width / 2 - len / 2, height / 2);
let v2 = createVector(width / 2 len / 2, height / 2);
sides[0] = new Side(v1.x, v1.y, v2.x, v2.y, "green");
vertices[0] = new Vertex(v1.x, v1.y);
vertices[1] = new Vertex(v2.x, v2.y);
}
function draw() {
background(255);
let angOne = angleOne.value();
let angTwo = angleTwo.value();
let rotVel = rotateVel.value();
fill(0);
strokeWeight(0);
textSize(15);
text(angOne, 100, 25);
text(angTwo, 100, 45);
text(rotVel, 100, 65);
let v2Offset = createVector(len * cos(-angOne), len * sin(-angOne));
let v3Offset = createVector(-len * cos(angTwo), -len * sin(angTwo));
vertices[2] = new Vertex(
vertices[0].a.x v2Offset.x,
vertices[0].a.y v2Offset.y
);
vertices[3] = new Vertex(
vertices[1].a.x v3Offset.x,
vertices[1].a.y v3Offset.y
);
// Update the sides
sides[1] = new Side(
vertices[0].a.x,
vertices[0].a.y,
vertices[2].a.x,
vertices[2].a.y
);
sides[3] = new Side(
vertices[1].a.x,
vertices[1].a.y,
vertices[3].a.x,
vertices[3].a.y
);
const m1 =
(vertices[2].a.y - vertices[0].a.y) / (vertices[2].a.x - vertices[0].a.x);
const m2 =
(vertices[3].a.y - vertices[1].a.y) / (vertices[3].a.x - vertices[1].a.x);
// Calculate the y-offset relative to vertices[0]
const b2 = (vertices[1].a.x - vertices[0].a.x) * -m2;
const xInt = b2 / (m1 - m2);
const yInt = xInt * m1;
// Note xInt and yInt are relative to vertices[0]
// draw all the things
// sides.forEach((s) => s.show());
// stroke(0, 255, 0);
// strokeWeight(20);
point(vertices[0].a.x xInt, vertices[0].a.y yInt);
vertices[4] = new Vertex(vertices[0].a.x xInt, vertices[0].a.y yInt);
sides[4] = new Side(
vertices[1].a.x,
vertices[1].a.y,
vertices[0].a.x xInt,
vertices[0].a.y yInt,
"blue"
);
sides[5] = new Side(
vertices[0].a.x,
vertices[0].a.y,
vertices[0].a.x xInt,
vertices[0].a.y yInt,
"purple"
);
scale(4); // so I can make the triangle actually *visible*
translate(-width / 3, -height / 3);
sides[0].show();
sides[4].show();
sides[5].show();
vertices[0].show();
vertices[1].show();
vertices[4].show();
strokeWeight(1);
stroke(255, 0, 0);
noFill();
arc(vertices[0].a.x, vertices[0].a.y, 40, 40, -1 * angleOne.value(), 0, PIE);
arc(
vertices[1].a.x,
vertices[1].a.y,
40,
40,
-180,
-(180 - angleTwo.value()),
PIE
);
stroke(0);
// stroke("purple"); // Change the color
strokeWeight(5); // Make the points 10 pixels in size
let P1x = vertices[0].a.x;
let P1y = vertices[0].a.y;
let P0 = createVector(P1x 60, P1y - 40);
push();
let V0 = createVector(10 * cos(rotVel), 10 * sin(-rotVel));
pop();
point(P0.x, P0.y);
// point(P0.x V0.x, P0.y V0.y);
strokeWeight(2);
line(P0.x, P0.y, P0.x V0.x, P0.y V0.y);
let hit = wallHit(P0, V0);
// print("hit[0] is", hit[0]);
hitPoint = createVector(P0.x V0.x * hit[0], P0.y V0.y * hit[0]);
let A = createVector(
vertices[4].a.x - vertices[1].a.x,
vertices[4].a.y - vertices[1].a.y
);
// let B = p5.Vector.sub(vertices[1].a, vertices[2].a);
// let C = p5.Vector.sub(vertices[2].a, vertices[4].a);
// findNormal(hitPoint, A);
setLineDash([5, 5]);
stroke(0);
// let perpA = getY(vertices[1].a.x - 100, hitPoint);
// let normalA = createVector(vertices[1].a.x - 100, perpA);
// let incVec = createVector(hitPoint.x, hitPoint.y);
// let refVec = normalA.reflect(incVec);
let IOne = createVector(0, 0);
let ITwo = createVector(0, 0);
let IThree = createVector(0, 0);
// line(hitPoint.x, hitPoint.y, normalA.x, normalA.y);
let AOne = createVector(P1x, P1y);
let ATwo = createVector(vertices[1].a.x, vertices[1].a.y 0.1);
let AThree = createVector(
(P1x vertices[1].a.x) / 2,
(P1y vertices[1].a.y) / 2
);
// getPerp(AOne, ATwo, hitPoint);
let BOne = createVector(P1x, P1y);
let BTwo = createVector(vertices[4].a.x, vertices[4].a.y 0.1);
let BThree = createVector(
(P1x vertices[4].a.x) / 2,
(P1y vertices[4].a.y) / 2
);
// getPerp(BOne, BTwo, hitPoint);
let COne = createVector(vertices[1].a.x, vertices[1].a.y);
let CTwo = createVector(vertices[4].a.x, vertices[4].a.y 0.1);
let CThree = createVector(
(vertices[1].a.x vertices[4].a.x) / 2,
(vertices[1].a.y vertices[4].a.y) / 2
);
// getPerp(COne, CTwo, hitPoint);
let incVec = createVector(hitPoint.x, hitPoint.y);
line(P0.x, P0.y, incVec.x, incVec.y);
let perp = getPerp(IOne, ITwo, IThree);
let testOne = createVector(vertices[0].a.x 20, vertices[0].a.y 20);
let testTwo = createVector(vertices[1].a.x 20, vertices[1].a.y 20);
let refVec = testOne.reflect(testTwo);
// line(testOne.x, testOne.y, testTwo.x, testTwo.y);
if (hit[1] == "t3") {
perp = getPerp(AOne, ATwo, hitPoint);
refVec = perp.reflect(incVec);
} else if (hit[1] == "t1") {
perp = getPerp(BOne, BTwo, hitPoint);
refVec = perp.reflect(incVec);
} else {
perp = getPerp(COne, CTwo, hitPoint);
refVec = perp.reflect(incVec);
}
stroke("black");
line(hitPoint.x, hitPoint.y, refVec.x, refVec.y);
setLineDash([0, 0]);
}
function setLineDash(list) {
drawingContext.setLineDash(list);
}
class Side {
constructor(x1, y1, x2, y2, col = "black") {
this.a = createVector(x1, y1);
this.b = createVector(x2, y2);
this.color = col;
}
show() {
stroke(this.color);
strokeWeight(4);
line(this.a.x, this.a.y, this.b.x, this.b.y);
}
}
class Vertex {
constructor(x1, y1) {
this.a = createVector(x1, y1);
}
show() {
stroke(255, 0, 0);
strokeWeight(10);
point(this.a.x, this.a.y);
}
}
function wallHit(pos, vel) {
let P1x = vertices[0].a.x;
let P1y = vertices[0].a.y;
let P2x = vertices[1].a.x;
let P2y = vertices[1].a.y;
let P3x = vertices[4].a.x;
let P3y = vertices[4].a.y;
let A1 = P3y - P1y;
let B1 = -(P3x - P1x);
let C1 = A1 * P1x B1 * P1y;
let A2 = -(P3y - P2y);
let B2 = P3x - P2x;
let C2 = A2 * P2x B2 * P2y;
let A3 = -(P2y - P1y);
let B3 = P2x - P1x;
let C3 = A3 * P2x B3 * P2y;
let t1 = (C1 - A1 * pos.x - B1 * pos.y) / (A1 * vel.x B1 * vel.y);
let t2 = (C2 - A2 * pos.x - B2 * pos.y) / (A2 * vel.x B2 * vel.y);
let t3 = (C3 - A3 * pos.x - B3 * pos.y) / (A3 * vel.x B3 * vel.y);
let times = [t1, t2, t3];
let posTimes = [];
for (let i = 0; i < times.length; i ) {
times[i] = round(times[i], 2);
}
// console.log("After rounding:", times);
for (let i = 0; i < times.length; i ) {
if (times[i] > 0) {
posTimes.push(times[i]);
}
}
let hitPoint = createVector(0, 1);
// console.log("posTimes:", posTimes);
trueTime = min(posTimes);
if (trueTime == round(t1, 2)) {
// print("Hit Purple");
return [t1, "t1"];
// hitPoint = createVector(P0.x V0.x * t1, P0.y V0.y * t1);
// line(P0.x, P0.y, hitPoint.x, hitPoint.y);
} else if (trueTime == round(t2, 2)) {
// print("Hit Blue");
return [t2, "t2"];
// hitPoint = createVector(P0.x V0.x * t2, P0.y V0.y * t2);
// line(P0.x, P0.y, hitPoint.x, hitPoint.y);
} else {
// print("Hit Green");
return [t3, "t3"];
// hitPoint = createVector(P0.x V0.x * t3, P0.y V0.y * t3);
// line(P0.x, P0.y, hitPoint.x, hitPoint.y);
}
}
function findNormal(pos, wall) {
let perpVector = createVector(-wall.y, wall.x);
strokeWeight(2);
stroke(0);
line(pos.x, pos.y, perpVector.x, perpVector.y);
// line(P0.x, P0.y, hitPoint.x, hitPoint.y);
}
function getY(x, HP) {
let perpY =
-((vertices[4].a.x - vertices[1].a.x) / (vertices[4].a.y - vertices[1].a.y)) *
x
(HP.y
((vertices[4].a.x - vertices[1].a.x) / (vertices[4].a.y - vertices[1].a.y)) *
HP.x);
return perpY;
}
function getPerp(p1, p2, p3) {
x1 = p1.x;
y1 = p1.y;
x2 = p2.x;
y2 = p2.y;
x3 = p3.x;
y3 = p3.y;
stroke(0);
strokeWeight(2);
line1 = line(x1, y1, x2, y2);
m1 = (y2 - y1) / (x2 - x1);
m2 = -1 / m1;
b2 = y3 - x3 * m2;
stroke(255, 0, 0);
line(x3, m2 * x3 b2, x1, m2 * x1 b2);
let final = createVector(m2 * x1 b2 - (m2 * x3 b2), x1 - x3);
return final;
}
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/p5.min.js"></script>
CodePudding user response:
You are confusing vectors and points. A point has 2 coordinates measured from the origin of the coordinate system. A vector represents a direction and can be given by subtracting 2 points. Therefore, the incident vector is:
inVec = createVector(hitPoint.x - P0.x, hitPoint.y - P0.y);
The normal vector (perpendicular vector) of a vector (Vx, Vy) is:
N = (-Vy, Vx)
Hence the normal vector (perpendicular) to a line given by 2 points (Ax, Ay) and (Bx, By) is:
N = (Ay - By, Bx - Ax)
The reflect()
function reflects an incidence vector on a surface by a given normal vector of the surface. The argument of the function is the normal vector:
refVec = inVec.reflect(n);
line
draws a line between 2 points. refVec
is not a point but a vector. Therefore you need to draw the line from hitPoint
to hitPoint refVec
instead of form hitPoint
to refVec
.
All together:
inVec = createVector(hitPoint.x - P0.x, hitPoint.y - P0.y);
if (hit[1] == "t3") {
perp = getPerp(AOne, ATwo, hitPoint);
let n = createVector(AOne.y - ATwo.y, ATwo.x - AOne.x);
refVec = inVec.reflect(n);
} else if (hit[1] == "t1") {
perp = getPerp(BOne, BTwo, hitPoint);
let n = createVector(BOne.y - BTwo.y, BTwo.x - BOne.x);
refVec = inVec.reflect(n);
} else {
perp = getPerp(COne, CTwo, hitPoint);
let n = createVector(COne.y - CTwo.y, CTwo.x - COne.x);
refVec = inVec.reflect(n);
}
stroke("black");
line(hitPoint.x, hitPoint.y, hitPoint.x refVec.x, hitPoint.y refVec.y);