Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/ATC-134 #209

Merged
merged 10 commits into from
Dec 19, 2016
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
- Adds gulp-cli and adds [tools readme](tools/README.md) link to gulp issues with Windows [#194](https://github.com/n8rzz/atc/issues/194)
- Changes `routeString` to `routeCode` to better fit what it is and also fixs trancpercy in to the `routeModel` [#188] (https://github.com/n8rzz/atc/issues/188)
-.toUpperCase() is now called on intilization and removed from the getter

- Prevents collision detection for aircraft that are outside of our airspace [#134](https://github.com/n8rzz/atc/issues/134)
- Originally reported under [#736](https://github.com/zlsa/atc/issues/736)



Expand Down
6 changes: 4 additions & 2 deletions src/assets/scripts/aircraft/AircraftConflict.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,16 @@ export default class AircraftConflict {
* Check for collision
*/
checkCollision() {
if (this.aircraft[0].wow() || this.aircraft[1].wow()) {
if (this.aircraft[0].isOnGround() || this.aircraft[1].isOnGround()) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I like this name a lot better. doesn't leave anything to the imagination about what its for.

return; // TEMPORARY FIX FOR CRASHES BTWN ARRIVALS AND TAXIIED A/C
}

// TODO: enumerate the magic numbers.
// Collide within 160 feet
const airport = window.airportController.airport_get();

if (((this.distance < 0.05) && (this.altitude < 160)) &&
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remember to add a line between constants and conditional blocks

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. 👍

(this.aircraft[0].isVisible() && this.aircraft[1].isVisible())
(this.aircraft[0].isInsideAirspace(airport) && this.aircraft[1].isInsideAirspace(airport))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for setting airport to a const and sending that in to this method.

) {
this.collided = true;
const isWarning = true;
Expand Down
2 changes: 1 addition & 1 deletion src/assets/scripts/aircraft/AircraftController.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export default class AircraftController {
remove = true;
}

if (aircraft.hit && aircraft.wow()) {
if (aircraft.hit && aircraft.isOnGround()) {
window.uiController.ui_log(`Lost radar contact with ${aircraft.getCallsign()}`);
speech_say([
{ type: 'callsign', content: aircraft },
Expand Down
86 changes: 48 additions & 38 deletions src/assets/scripts/aircraft/AircraftInstanceModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -1382,7 +1382,7 @@ export default class Aircraft {
* @param data
*/
runReroute(data) {
// capitalize everything
// TODO: capitalize everything?
data = data[0].toUpperCase();
let worked = true;
const route = this.fms.formatRoute(data);
Expand Down Expand Up @@ -1473,7 +1473,7 @@ export default class Aircraft {
return ['fail', 'inbound'];
}

if (!this.wow()) {
if (!this.isOnGround()) {
return ['fail', 'already airborne'];
}
if (this.mode === FLIGHT_MODES.APRON) {
Expand Down Expand Up @@ -1704,6 +1704,41 @@ export default class Aircraft {
return this.approachOffset <= 0.048;
}

/**
* Checks if the aircraft is inside the airspace of a specified airport
*
* @for AircraftInstanceModel
* @method isInsideAirspace
* @param {airport} airport the airport whose airspace we are checking
* @return {Boolean}
* @private
*/
isInsideAirspace(airport) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need to do this, but in cases like this where you only need a few properties from a massive object you could destructure them:

isInsideAirspace(airport) { }
// becomes
isInsideAirspace({ perimeter, ctr_radius, ctr_ceiling }) { }

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Good information; wouldn't have thought of that.

Didn't implement here... thought it looked more clean just as airport since we used three of its properties.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

totally cool. I just wanted to call that out in case you ever need it in the future 😉

let withinAirspaceLateralBoundaries = this.distance <= airport.ctr_radius;
const withinAirspaceAltitudeRange = this.altitude <= airport.ctr_ceiling;

if (!_isNil(airport.perimeter)) { // polygonal airspace boundary
withinAirspaceLateralBoundaries = point_in_area(this.position, airport.perimeter);
}

return withinAirspaceAltitudeRange && withinAirspaceLateralBoundaries;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes!!! 👍

one thing to remember and I don't think we've gone over this yet, so I'll let this go as is this time. When a method returns a boolean or a constant is a boolean, it should be prepended with is. so instead of

return isWithinAirspaceAltitudeRange && isWithinAirspaceLateralBoundaries;

this reads a little better and you can immediately tell that those two things are booleans.

}

/**
* Aircraft has "weight-on-wheels" (on the ground)
* @for AircraftInstanceModel
* @method isOnGround
*/
isOnGround() {
const error_allowance_ft = 5;
const airport = window.airportController.airport_get();
const runway = airport.getRunway(this.rwy_dep || this.rwy_arr);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this assuming that one of this.rwy_dep || this.rwy_arr will always be null (or undefined)? Why not just create a getter in the AirportModel for something like activeRunway or something?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get activeRunway() {
    return this.rwy_dep 
        ? this.rwy_dep 
        : this.rwy_arr; 
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the one instance where I am ok with ternaries, by the way. In a case like this I think it makes sense and is just as readable as the alternative

Copy link
Collaborator Author

@erikquinn erikquinn Dec 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that getter simply doesn't make sense though... it's not in relation to the airport, it has to do with each individual aircraft. Aircraft.rwy_dep is the runway they are assigned or have already used to take off. Aircraft.rwy_arr is the runway they are assigned or have already used to land. It's not the active arrival runway and active departure runway; it's what each aircraft used, and it's stored for reasons of retrieving the appropriate rwy sections within instrument procedures.

In the current way the game works, each aircraft will only have one of rwy_dep or rwy_arr, because we don't have departures come back and land. So this simpleton solution is one that works at this stage in time. It could be made a getter of the AircraftInstanceModel, but this is a very bizarre, hard to name, only-going-to-be-needed-here operation. We're essentially asking "Hey aircraft, what runway have/did/will you use(d) at the airport, regardless of what you used it for"? The only information we'd be likely to need in other cases is "What runway did/will you depart?" or "What runway did/will you use to land?"

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I understand. and you're right, the getter would be in the AircraftInstanceModel.

Essentially whats happening here, from what I understand, is 'give me the runway you used/or are going to use'. This is where the getter would be helpful because it can do that logic on its own. Thus, anything calling the getter simply says 'give me the runway info' and the getter figures out which one it should send back. and then everything is right in the world.

But, if this isn't used all over the place, it probably isn't really worth the effort.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added #225 so if we ever decide to fix it, we have an issue to refer to :-)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not like it's a lot of effort, but this is literally the only place it would be used. Adding it would increase readability, but shouldn't single-use methods be avoided if possible? If not, I could just add another method to the pile-- no biggie.

const nearRunwayAltitude = abs(this.altitude - runway.elevation) < error_allowance_ft;
const nearAirportAltitude = abs(this.altitude - airport.position.elevation) < error_allowance_ft;

return nearRunwayAltitude || nearAirportAltitude;
}

/**
* Aircraft is actively following an instrument approach and is elegible for reduced separation
*
Expand All @@ -1725,7 +1760,7 @@ export default class Aircraft {
*/
isStopped() {
// TODO: enumerate the magic number.
return this.wow() && this.speed < 5;
return this.isOnGround() && this.speed < 5;
}

/**
Expand Down Expand Up @@ -1980,7 +2015,7 @@ export default class Aircraft {
this.fms.setCurrent({ start_speed: this.fms.currentWaypoint.speed });
}

if (this.wow()) {
if (this.isOnGround()) {
this.target.altitude = runway.elevation;
this.target.speed = 0;
} else {
Expand Down Expand Up @@ -2136,7 +2171,7 @@ export default class Aircraft {
}

// If stalling, make like a meteorite and fall to the earth!
if (this.speed < this.model.speed.min && !this.wow()) {
if (this.speed < this.model.speed.min && !this.isOnGround()) {
this.target.altitude = Math.min(0, this.target.altitude);
}

Expand Down Expand Up @@ -2235,7 +2270,7 @@ export default class Aircraft {

// TURNING
// this.target.heading = radians_normalize(this.target.heading);
if (!this.wow() && this.heading !== this.target.heading) {
if (!this.isOnGround() && this.heading !== this.target.heading) {
// Perform standard turns 3 deg/s or 25 deg bank, whichever
// requires less bank angle.
// Formula based on http://aviation.stackexchange.com/a/8013
Expand Down Expand Up @@ -2290,7 +2325,7 @@ export default class Aircraft {
}
}

if (this.wow()) {
if (this.isOnGround()) {
this.trend = 0;
}

Expand All @@ -2300,7 +2335,7 @@ export default class Aircraft {
if (this.target.speed < this.speed - 0.01) {
difference = -this.model.rate.decelerate * window.gameController.game_delta() / 2;

if (this.wow()) {
if (this.isOnGround()) {
difference *= 3.5;
}
} else if (this.target.speed > this.speed + 0.01) {
Expand Down Expand Up @@ -2348,7 +2383,7 @@ export default class Aircraft {
const wind = window.airportController.airport_get().wind;
let vector;

if (this.wow()) {
if (this.isOnGround()) {
vector = vscale([sin(angle), cos(angle)], scaleSpeed);
} else {
let crab_angle = 0;
Expand Down Expand Up @@ -2387,22 +2422,10 @@ export default class Aircraft {
this.radial += tau();
}

// polygonal airspace boundary
if (window.airportController.airport_get().perimeter) {
let inside = point_in_area(this.position, window.airportController.airport_get().perimeter);
const isInsideAirspace = this.isInsideAirspace(window.airportController.airport_get());

// TODO: this logic is duplicated below. abstract to new method
if (inside !== this.inside_ctr) {
this.crossBoundary(inside);
}
} else {
// simple circular airspace boundary
let inside = this.distance <= window.airportController.airport_get().ctr_radius &&
this.altitude <= window.airportController.airport_get().ctr_ceiling;

if (inside !== this.inside_ctr) {
this.crossBoundary(inside);
}
if (isInsideAirspace !== this.inside_ctr) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remember to add a line between constants and conditional blocks

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. 👍

this.crossBoundary(isInsideAirspace);
}
}

Expand Down Expand Up @@ -2481,7 +2504,7 @@ export default class Aircraft {
});
}

if (this.terrain_ranges && !this.wow()) {
if (this.terrain_ranges && !this.isOnGround()) {
const terrain = prop.airport.current.terrain;
const prev_level = this.terrain_ranges[this.terrain_level];
const ele = Math.ceil(this.altitude, 1000);
Expand Down Expand Up @@ -2664,17 +2687,4 @@ export default class Aircraft {
removeConflict(other) {
delete this.conflicts[other.getCallsign()];
}

/**
* Aircraft has "weight-on-wheels" (on the ground)
* @for AircraftInstanceModel
* @method wow
*/
wow() {
const error_allowance = 5;
const apt = window.airportController.airport_get();
const rwy_elev = apt.getRunway(this.rwy_dep || this.rwy_arr).elevation;
const apt_elev = apt.position.elevation;
return this.altitude - (rwy_elev || apt_elev) < error_allowance;
}
}
2 changes: 1 addition & 1 deletion src/assets/scripts/aircraft/FlightManagementSystem/Leg.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export default class Leg {

// TODO: refactor/abstract this boolean logic
// Remove the placeholder leg (if present)
if (fms.my_aircraft.wow() &&
if (fms.my_aircraft.isOnGround() &&
fms.legs.length > 0 &&
fms.legs[0].route === airport.icao &&
pairs.length > 0
Expand Down