2017-02-09 00:00:35 +00:00
|
|
|
module TS.SpaceTac {
|
2015-04-21 20:14:17 +00:00
|
|
|
// Find the nearest intersection between a line and a circle
|
|
|
|
// Circle is supposed to be centered at (0,0)
|
|
|
|
// Nearest intersection to (x1,y1) is returned
|
2017-05-29 18:12:57 +00:00
|
|
|
function intersectLineCircle(x1: number, y1: number, x2: number, y2: number, r: number): [number, number] | null {
|
|
|
|
let a = y2 - y1;
|
|
|
|
let b = -(x2 - x1);
|
|
|
|
let c = -(a * x1 + b * y1);
|
|
|
|
let x0 = -a * c / (a * a + b * b), y0 = -b * c / (a * a + b * b);
|
|
|
|
let EPS = 10e-8;
|
|
|
|
if (c * c > r * r * (a * a + b * b) + EPS) {
|
|
|
|
return null;
|
|
|
|
} else if (Math.abs(c * c - r * r * (a * a + b * b)) < EPS) {
|
|
|
|
return [x0, y0];
|
|
|
|
} else {
|
|
|
|
let d = r * r - c * c / (a * a + b * b);
|
|
|
|
let mult = Math.sqrt(d / (a * a + b * b));
|
|
|
|
let ax, ay, bx, by;
|
|
|
|
ax = x0 + b * mult;
|
|
|
|
bx = x0 - b * mult;
|
|
|
|
ay = y0 - a * mult;
|
|
|
|
by = y0 + a * mult;
|
2015-04-21 20:14:17 +00:00
|
|
|
|
2017-05-29 18:12:57 +00:00
|
|
|
let candidates: [number, number][] = [
|
|
|
|
[x0 + b * mult, y0 - a * mult],
|
|
|
|
[x0 - b * mult, y0 + a * mult]
|
|
|
|
]
|
|
|
|
return minBy(candidates, ([x, y]) => Math.sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1)));
|
|
|
|
}
|
2015-04-21 20:14:17 +00:00
|
|
|
}
|
|
|
|
|
2014-12-31 00:00:00 +00:00
|
|
|
// Target for a capability
|
|
|
|
// This could be a location in space, or a ship
|
2017-02-07 18:54:53 +00:00
|
|
|
export class Target {
|
2014-12-31 00:00:00 +00:00
|
|
|
// Coordinates of the target
|
|
|
|
x: number;
|
|
|
|
y: number;
|
|
|
|
|
|
|
|
// If the target is a ship, this attribute will be set
|
2017-02-13 19:31:45 +00:00
|
|
|
ship: Ship | null;
|
2014-12-31 00:00:00 +00:00
|
|
|
|
|
|
|
// Standard constructor
|
2017-02-13 19:31:45 +00:00
|
|
|
constructor(x: number, y: number, ship: Ship | null = null) {
|
2014-12-31 00:00:00 +00:00
|
|
|
this.x = x;
|
|
|
|
this.y = y;
|
|
|
|
this.ship = ship;
|
|
|
|
}
|
|
|
|
|
2017-05-09 23:20:05 +00:00
|
|
|
jasmineToString() {
|
|
|
|
if (this.ship) {
|
|
|
|
return this.ship.jasmineToString();
|
|
|
|
} else {
|
|
|
|
return `(${this.x},${this.y})`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-31 00:00:00 +00:00
|
|
|
// Constructor to target a single ship
|
|
|
|
static newFromShip(ship: Ship): Target {
|
|
|
|
return new Target(ship.arena_x, ship.arena_y, ship);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Constructor to target a location in space
|
|
|
|
static newFromLocation(x: number, y: number): Target {
|
|
|
|
return new Target(x, y, null);
|
|
|
|
}
|
2015-01-28 00:00:00 +00:00
|
|
|
|
2015-01-29 00:00:00 +00:00
|
|
|
// Get distance to another target
|
2017-05-09 23:20:05 +00:00
|
|
|
getDistanceTo(other: { x: number, y: number }): number {
|
2015-01-29 00:00:00 +00:00
|
|
|
var dx = other.x - this.x;
|
|
|
|
var dy = other.y - this.y;
|
|
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
|
|
}
|
|
|
|
|
2015-03-11 00:00:00 +00:00
|
|
|
// Get the normalized angle, in radians, to another target
|
2017-05-09 23:20:05 +00:00
|
|
|
getAngleTo(other: { x: number, y: number }): number {
|
2015-03-11 00:00:00 +00:00
|
|
|
var dx = other.x - this.x;
|
|
|
|
var dy = other.y - this.y;
|
|
|
|
return Math.atan2(dy, dx);
|
|
|
|
}
|
|
|
|
|
2015-01-28 00:00:00 +00:00
|
|
|
// Check if a target is in range from a specific point
|
|
|
|
isInRange(x: number, y: number, radius: number): boolean {
|
|
|
|
var dx = this.x - x;
|
|
|
|
var dy = this.y - y;
|
|
|
|
var length = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
return (length <= radius);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Constraint a target, to be in a given range from a specific point
|
|
|
|
// May return the original target if it's already in radius
|
|
|
|
constraintInRange(x: number, y: number, radius: number): Target {
|
|
|
|
var dx = this.x - x;
|
|
|
|
var dy = this.y - y;
|
|
|
|
var length = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
if (length <= radius) {
|
|
|
|
return this;
|
|
|
|
} else {
|
|
|
|
var factor = radius / length;
|
|
|
|
return Target.newFromLocation(x + dx * factor, y + dy * factor);
|
|
|
|
}
|
|
|
|
}
|
2015-04-21 20:14:17 +00:00
|
|
|
|
|
|
|
// Force a target to stay out of a given circle
|
|
|
|
// If the target is in the circle, it will be moved to the nearest intersection between targetting line
|
|
|
|
// and the circle
|
|
|
|
// May return the original target if it's already out of the circle
|
|
|
|
moveOutOfCircle(circlex: number, circley: number, radius: number, sourcex: number, sourcey: number): Target {
|
|
|
|
var dx = this.x - circlex;
|
|
|
|
var dy = this.y - circley;
|
|
|
|
var length = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
if (length >= radius) {
|
|
|
|
// Already out of circle
|
|
|
|
return this;
|
|
|
|
} else {
|
|
|
|
// Find nearest intersection with circle
|
2017-05-29 18:12:57 +00:00
|
|
|
var res = intersectLineCircle(sourcex - circlex, sourcey - circley, dx, dy, radius);
|
|
|
|
if (res) {
|
|
|
|
return Target.newFromLocation(res[0] + circlex, res[1] + circley);
|
|
|
|
} else {
|
|
|
|
return this;
|
|
|
|
}
|
2015-04-21 20:14:17 +00:00
|
|
|
}
|
|
|
|
}
|
2014-12-31 00:00:00 +00:00
|
|
|
}
|
2015-01-07 00:00:00 +00:00
|
|
|
}
|