Skip to content

Commit

Permalink
Optimize and add test to Vec2.signedAngle (#16183)
Browse files Browse the repository at this point in the history
  • Loading branch information
shrinktofit authored Sep 5, 2023
1 parent db10a26 commit 42ff683
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 5 deletions.
13 changes: 10 additions & 3 deletions cocos/core/math/vec2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -779,11 +779,18 @@ export class Vec2 extends ValueType {
* @zh 获取当前向量和指定向量之间的有符号弧度。<br/>
* 有符号弧度的取值范围为 (-PI, PI],当前向量可以通过逆时针旋转有符号角度与指定向量同向。<br/>
* @param other specified vector
* @return The signed angle between the current vector and the specified vector (in radians); if there is a zero vector in the current vector and the specified vector, 0 is returned.
* @return The signed angle between the current vector and the specified vector (in radians);
* if there is a zero vector in the current vector and the specified vector, 0 is returned.
*/
public signAngle (other: Vec2): number {
const angle = this.angle(other);
return this.cross(other) < 0 ? -angle : angle;
// θ = atan(tan(θ))
// = atan(sin(θ) / cos(θ))
// = atan2(sin(θ), cos(θ))
// = atan2(|a|·|b|·sin(θ), |a|·|b|·cos(θ))
// = atan2(cross(a, b), dot(a, b))
const cross = this.cross(other);
const dot = this.dot(other);
return Math.atan2(cross, dot);
}

/**
Expand Down
40 changes: 38 additions & 2 deletions tests/core/math/vec2.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { log } from '../../test.log';
import { Vec2 } from '../../../cocos/core/math/vec2';
import { v2, Vec2 } from '../../../cocos/core/math/vec2';
import { Vec3 } from '../../../cocos/core/math/vec3';
import { Mat3 } from '../../../cocos/core/math/mat3';
import { Mat4 } from '../../../cocos/core/math/mat4';
import { toRadian, toDegree } from '../../../exports/base';

describe('Test Vec2', () => {
test('normalize', () => {
Expand Down Expand Up @@ -71,4 +72,39 @@ describe('Test Vec2', () => {
log('rotate: ', v0);
expect(Vec2.equals(v0, expect0)).toBe(true);
});
});

test(`Signed angle`, () => {
const t = (v1: Readonly<Vec2>, v2: Readonly<Vec2>, expectedDeg: number) => {
expect(toDegree(v1.signAngle(v2))).toBeCloseTo(expectedDeg, 1e-6);
expect(toDegree(v2.signAngle(v1))).toBeCloseTo(-expectedDeg, 1e-6);
};

t(v2(0, 0), v2(0, 0), 0.0);
t(v2(0, 0), v2(3, -4), 0.0);

{
const v1 = polar(toRadian(10), 2);
const v1Angle = 10;
const v2 = (deg: number) => polar(toRadian(deg), 0.1);

t(v1, v2(72.3), 62.3);
t(v1, v2(v1Angle + 90), 90);
t(v1, v2(v1Angle + 140), 140);
t(v1, v2(v1Angle + 179.9999), 179.9999);

// Opposite vector results singularity.
const v2_180 = v2(v1Angle + 180);
expect(Math.abs(v1.signAngle(v2_180))).toBeCloseTo(Math.PI);
expect(Math.abs(v2_180.signAngle(v1))).toBeCloseTo(Math.PI);

t(v1, v2(v1Angle + 180.0001), 180.0001 - 360);
t(v1, v2(v1Angle + 225), 225 - 360);
t(v1, v2(v1Angle + 279.9999), 279.9999 - 360);
t(v1, v2(v1Angle + 280.0001), 280.0001 - 360);
}
});
});

function polar(angle: number, length: number) {
return v2(Math.cos(angle) * length, Math.sin(angle) * length);
}

0 comments on commit 42ff683

Please sign in to comment.