我正在创建游戏,并希望解决此问题以使我的游戏功能正常。
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];
}
我知道可能没有答案,也可能有多个答案。 我想知道边缘碰到碰撞的最短时间(如果有答案)。 谢谢。
答案 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);
}