我该如何解决这个三角问题

时间:2019-07-27 14:31:06

标签: javascript collision-detection game-physics trigonometry

我正在创建游戏,并希望解决此问题以使我的游戏功能正常。

Asin(RX)+ Bcos(RX)+ CX + D = 0 查找最小X,该X等于0或更大,等于1或更小。 A,B,C和R是变量。

我可以使用反三角函数,例如acos,asin等。但是我想使计算尽可能准确。

我试图使用三角函数公式使其变得简单,但是我做不到。

更详细地, 有一个带有移动和碰撞的旋转盒子。

White lines indicate the trajectories of one of the box corners. Red line is a collision.

我想知道确切的时间,当拐角撞到碰撞时。


const Box = {
  Position: {
    x: 0,
    y: -300
  },

  Velocity: {
    x: 1500,
    y: 500
  },

  RotationSpeed: 19,
  DiagonalLength: 120
};

const Collision = {
  p1: {
    x: 50,
    y: -200
  },
  p2: {
    x: 1500,
    y: 300
  }
};

// **********************************************************
// Get a corner position of the BoX at provided time
// CornerID: 0-3 identify the edge
// time: Time 
// **********************************************************
getBoxCornerPosition(CornerID, time) {
  let addRadian = CornerID * Math.PI / 2;

  let posx =
    Box.Position.x + // Base position of the Box
    Box.Velocity.x * time + // Move distance of the Box
    Box.DiagonalLength * Math.cos(Box.RotationSpeed * time + addRadian); // Edge position

  let posy =
    Box.Position.y +
    Box.Velocity.y * time +
    Box.DiagonalLength * Math.sin(Box.RotationSpeed * time + addRadian);

  return [posx, posy];
}

我知道可能没有答案,也可能有多个答案。 我想知道边缘碰到碰撞的最短时间(如果有答案)。 谢谢。

2 个答案:

答案 0 :(得分:0)

如果我理解正确,您可以使这个正方形在平面中移动,以便其中心沿直线以恒定速度移动,而正方形本身以恒定的角速度围绕其中心旋转。然后,用以下等式描述红色角的运动(实际上是正方形上几乎所有的点):

x(t) = r0*cos(R*t) - r0*sin(R*t) + v1*t + x0
y(t) = r0*sin(R*t) + r0*cos(R*t) + v2*t + y0

红线由看起来像这样的方程式给出:

a*x + b*y = c

因此,正方形的红色角与红线发生碰撞的时间是在满足以下条件时(红色角位于红线上):

a*x(t) + b*y(t) = c

明确写成的样子:

a*(r0*cos(R*t) - r0*sin(R*t) + v1*t + x0) + b*(r0*sin(R*t) + r0*cos(R*t) + v2*t + y0) = c

,然后将类似的术语归为一类:

r0*(a + b)*cos(R*t) + r0*(b - a)*sin(R*t) + (a*v1 + b*v2)*t + (a*x0 + b*y0 - c) = 0

或缩写形式:

A*cos(R*t) + B*sin(R*t) + C*t + D = 0

其中A = r0*(a + b), B = r0*(b - a), C = a*v1 + b*v2, D = a*x0 + b*y0 - c 如果我们重命名参数t = X 我们确切地得到了您的方程式

A*cos(R*X) + B*sin(R*X) + C*X + D = 0

此方程式没有封闭形式的解决方案,您必须用数字方法求解。为此,您必须查看函数Y = f(X),其中

f(X) = A*cos(R*X) + B*sin(R*X) + C*X + D

此函数f(X)的几何含义是:f(t)是时间t上的红点与红线之间的定向距离,乘以一个常数(更确切地说是{{ 1}})。实际上,如果选择红线的系数1/sqrt(a^2 + b^2)使得a, b, c,则a^2 + b^2 = 1 是时间为{时,红点之间的定向距离{1}}和红线。

要解决此问题,首先我们按如下方式重写函数

f(t)

由于t存在一个角度A_B = sqrt(A^2 + B^2) f(X) = A_B*( (A/A_B)*cos(R*X) + (B/A_B)*sin(R*X) ) + C*X + D 使得

(A/A_B)^2 + (B/A_B)^2 = 1

所以我们可以设置

w

因此

cos(w) = A/A_B = A/sqrt(A^2 + B^2)

首先,您必须查看导数

w = arccos(A/A_B)

并找到所有零(如果有的话,也就是f(X) = A_B*( cos(w)*cos(R*X) + sin(w)*sin(R*X) ) + C*X + D = A_B*cos(R*X - w) + C*X + D 这样的所有f_prime(X) = - R*A_B*sin(R*X - w) + C )。

X

仅在f_prime(X) = 0时有效,否则不为零。 导数的零给出所有时间sin(R*X - w) = C / (R*A_B) X = ( arcsin(C / (R*A_B)) + w + 2*k*pi ) / R X = (pi - arcsin(C / (R*A_B)) + w + 2*k*pi) / R ,与红色角的白色轨迹的切线平行于红色线。如果有零,则零将-1 < C / (R*A_B) < 1变量的实线分成间隔。您必须找到t=X的间隔,其中X的间隔是第一次,函数[X1, X2]会更改符号,即必须找到f_prime(X1) = f_prime(X2) = 0的时间间隔。第一次。然后以f(X)为起点并运行牛顿方法,进行迭代

f(X1) * f(X2) < 0

直到X0 = (X1 + X2)/2为您选择的epsilon,类似于“ epsilon = 0.00001”。结果应为您要寻找的零。

但是,如果导数没有零,则可以选择说X = 0,然后再次使用牛顿法。我为牛顿方法的简单实现编写的Python代码片段看起来像这样

X = X - f(X)/f_prime(X)

这是个主意,但是您必须弄清楚细节,然后修改我写的公式,以确保没有错误。

答案 1 :(得分:0)

由于@Futurologist,我能够编写出可以正常工作的代码。
计算方法有所不同,但基本上我遵循了他建议的方法。

我添加了第一部分,检查是否未击中。
谢谢。

// Rotating and Moving box
let L = 180; // Box diagonal length
let M = {  // Box Move Speed
  x: 7,
  y: 3
}
let R = 0.30; // Box rotating speed
let P = { // Box position
  x: -700,
  y: -300
}

// Collision line: ax + by + c = 0
let a = 1;
let b = -2;
let c = 5;
// **********************************************************
// Get Box x, y according to given time
// This is for reference
// **********************************************************
getBoxPoint(time) {
  let Mx = M.x;
  let My = M.y;
  let Px = P.x;
  let Py = P.y;

  let x = L * Math.cos(R * time) + Mx * time + Px;
  let y = L * Math.sin(R * time) + My * time + Py;
  return [x, y];
}

// **********************************************************
// return greater than 0 hit time [] in ascending order
// return null when it never hit
// **********************************************************
GetHitTimes() {
  let result = checkParallel_HitRange();
  if (result === null) {
    console.log('Never Hit');
    return null;
  }
  if (result === undefined) {
    return calcHitTime(); // hit infinity times
  }

  let [minTime, maxTime] = result;

  if (maxTime < 0) {
    console.log('Never Hit in the future (hit in the past)');
    return null;
  }
  if (minTime < 0) {
    minTime = 0;
  }

  return calcHitTime(minTime, maxTime);
}

// **********************************************************
// Check if Box movement and Collision line is parallel
// Return:
// null: never hit
// undefined: hit positions are infinity
// [minTime, maxTime]: Range of potential collisions time
// **********************************************************
checkParallel_HitRange() {
  if (a / b === -M.y / M.x) { // is Parallel
    let distance = getColLineDistance(P.x, P.y);
    if (distance > L) { // Box and Collision are never hit
      return null;
    }
    // Collision points are infinity
    return undefined;
  }
  else {
    // Check range of potential collisions
    // x = Mx * time + Px
    // y = My * time + Py
    // D = Math.sqrt(a * a + b * b)
    // +-L = (a * x + b * y + c) / D
    // +-L = ( (aMx + bMy) * time + aPx + aPy + c ) / D
    // +-LD = (aMx + bMy) * time + aPx + aPy + c
    // (aMx + bMy) * time = +-LD - aPx - aPy - c
    // time = (+-LD - aPx - aPy -c) / (aMx + bMy)
    let D = Math.sqrt(a * a + b * b);
    let time1 = (L * D - c - a * P.x - b * P.y) / (a * M.x + b * M.y);
    let time2 = (-L * D - c - a * P.x - b * P.y) / (a * M.x + b * M.y);
    console.log('Potential collison times are ' + time1 + ' - ' + time2);
    if (time1 < time2) {
      return [time1, time2];
    }
    return [time2, time1];
  }
}

// **********************************************************
// Get distance between given point from Collision line
// **********************************************************
getColLineDistance(x, y) {
  return Math.abs(a * x + b * y + c) / Math.sqrt(a * a + b * b);
}


// **********************************************************
// Calculate and return hit time array with in given min-max time
// **********************************************************
calcHitTime(minTime, maxTime) {
  console.log('Min / Max ' + minTime + ', ' + maxTime);
  let Mx = M.x;
  let My = M.y;
  let Px = P.x;
  let Py = P.y;

  // line: ax + by + c = 0
  // x = L * Math.cos(R * time) + Mx * time + Px;
  // y = L * Math.sin(R * time) + My * time + Py;


  // a (  L * cos(R * time) + Mx * time + Px )   +   b ( L * sin(R * time) + My * time + Py ) + c = 0
  // aL * cos(R * time) + aMx * time + aPx + bL * sin(R * time) + bMy * time + bPy + c = 0
  // aL * cos(R * time) + bL * sin(R * time) + aMx * time + bMy * time + aPx  + bPy + c = 0
  // bL * sin (R * time)   +   aL *cos(R * time)   +   (aMX + bMY) * time   +   Pxa + Pyb + c = 0;

  // time = x / R
  // bL * sin (x)   +   aL *cos(x)   +   (aMX + bMY) / R * x   +   Pxa + Pyb + c = 0;

  let A = b * L;
  let B = a * L;
  let C = (a * Mx + b * My) / R;
  let D = a * Px + b * Py + c;

  // Asin(x) + Bcos(x) + Cx + D = 0;
  // asinθ + bcosθ = √a²+b² sin (θ + α)

  // f(x) = E * sin(x + Alpha) + Cx + D  ...   Fx(x, E, Alpha, C, D)
  // f'(x) = E * cos(x + Alpha) + C  ...   F_Prime(x, E, Alpha, C)
  let E = Math.sqrt(A * A + B * B);
  let Alpha = Math.acos(A / E);

  // Find all x when F'(x) = 0
  // f'(x) = E * cos(x + Alpha) + C = 0
  // cos(x + Alpha) = -C / E
  // x + Alpha = acos( -C / E) 
  // x = acos(-C / E) - Alpha  ...  getZeroOf_F_Prime(Alpha, C, E)

  let ZeroTime = getZeroTimeOf_F_Prime(Alpha, C, E);

  // Set range of check
  let endK;
  if (minTime === undefined) { // means have parallel move and hit infinity times
    endK = 20; // Limiting number of check
  }
  else {
    let startAdjust = (minTime - ZeroTime) / (2 * Math.PI);
    startAdjust = Math.floor(startAdjust);
    ZeroTime += startAdjust * 2 * Math.PI;
    endK = (maxTime - ZeroTime) / (2 * Math.PI);
    endK = Math.ceil(endK) + 1;

    if (endK > 20) {
      endK = 20;  // Limiting number of check
    }
  }

  // Get distance values in the range
  let distance = [];
  let checkTime;
  for (let loop = 0; loop < endK; loop++) {
    checkTime = (ZeroTime + 2 * Math.PI * loop) * R;
    distance[loop] = Fx(checkTime, E, Alpha, C, D);
    console.log(checkTime / R + ' : ' + distance[loop]);
  };

  let epsilon = 0.00001;
  let answerTime = [];
  let answerNum = 0;
  for (let loop1 = 0; loop1 < endK - 1; loop1++) {
    if (distance[loop1] * distance[loop1 + 1] < -1) { // hit moment shoud be between here

      // Newton method iterating
      let time = (ZeroTime + 2 * Math.PI * (loop1 + 0.5)) * R;
      time = Newton(time, E, Alpha, C, D);
      let prevTime = time;

      let loop2;
      for (loop2 = 0; loop2 < 5; loop2++) {
        time = this.Newton(time, E, Alpha, C, D);
        console.log('    iterate: ' + time / R);
        if (Math.abs(prevTime - time) < epsilon) {
          break;
        }
        prevTime = time;
      };
      if (loop2 >= 5) { // Usually iteration should convergence less than 5 times
        console.warn('Something wrong!');
      }
      console.log('Answer: ' + time / R);
      answerTime[answerNum] = time / R;
      answerNum++;
    }
  }

  return answerTime;
}

// **********************************************************
// Return the moment when the distance increase or decrease becomes zero
// **********************************************************
getZeroTimeOf_F_Prime(Alpha, C, E) {
  return Math.acos(-C / E) - Alpha;
}

// **********************************************************
// Return the distance
// **********************************************************
Fx(x, E, Alpha, C, D) {
  return E * Math.sin(x + Alpha) + C * x + D;
}

// **********************************************************
// 
// **********************************************************
F_Prime(x, E, Alpha, C) {
  return E * Math.cos(x + Alpha) + C;
}

// **********************************************************
// Newton Method iterating function
// **********************************************************
Newton(x, E, Alpha, C, D) {
  return x - Fx(x, E, Alpha, C, D) / F_Prime(x, E, Alpha, C);
}