沿垂线的反射向量

Reflecting Vector along Perpendicular

在 SO 社区的大力帮助下,我创建了一个程序,其中一个球位于具有特定位置和速度的三角形中。使用参数方程,我确定了球将击中三角形的位置。然后我找到了球击中的三角形边的垂直线,这样我就可以将球从墙上反射出来,绘制它的“弹跳”轨迹。我的目标是我可以绘制出球在 n 次迭代中如何从墙上反弹。但是我遇到了一个问题。

即使我使用 p5.js A.reflect(B) 方法沿垂线反射球,它也会产生一些非常奇怪的结果。 我尝试了很多东西(即修改 getPerp() 函数、单元测试等),但都无济于事。任何帮助将不胜感激。 MWE 低于 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/p5@1.4.1/lib/p5.min.js"></script>

你混淆了向量和点。一个点有 2 个坐标,从坐标系的原点开始测量。一个向量表示一个方向,减去2个点就可以得到。因此,入射向量为:

inVec = createVector(hitPoint.x - P0.x, hitPoint.y - P0.y);

向量(Vx,Vy)的法向量(垂直向量)为:

N = (-Vy, Vx)

因此,由 2 个点 (Ax, Ay) 和 (Bx, By) 给出的直线的法向量(垂直)为:

N = (Ay - By, Bx - Ax)

reflect()函数通过给定的曲面法向量反映曲面上的入射向量。函数的参数是法向量:

refVec = inVec.reflect(n);

line 在两点之间画一条线。 refVec 不是一个点而是一个向量。因此,您需要绘制从 hitPointhitPoint + refVec 的直线,而不是从 hitPointrefVec.

的形式

总计:

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);

Demo