feat: started adding ability to set and draw racetracks

This commit is contained in:
Davide Passoni 2025-01-24 17:33:27 +01:00
parent 96415fd087
commit c2ea746d48
9 changed files with 154 additions and 20 deletions

View File

@ -51,6 +51,9 @@ namespace DataIndex {
shotsScatter,
shotsIntensity,
health,
racetrackLength,
racetrackAnchor,
racetrackBearing,
lastIndex,
endOfData = 255
};

View File

@ -109,6 +109,9 @@ public:
virtual void setShotsScatter(unsigned char newValue) { updateValue(shotsScatter, newValue, DataIndex::shotsScatter); }
virtual void setShotsIntensity(unsigned char newValue) { updateValue(shotsIntensity, newValue, DataIndex::shotsIntensity); }
virtual void setHealth(unsigned char newValue) { updateValue(health, newValue, DataIndex::health); }
virtual void setRacetrackLength(double newValue) { updateValue(racetrackLength, newValue, DataIndex::racetrackLength); }
virtual void setRacetrackAnchor(Coords newValue) { updateValue(racetrackAnchor, newValue, DataIndex::racetrackAnchor); }
virtual void setRacetrackBearing(double newValue) { updateValue(racetrackBearing, newValue, DataIndex::racetrackBearing); }
/********** Getters **********/
virtual string getCategory() { return category; };
@ -157,6 +160,9 @@ public:
virtual unsigned char getShotsScatter() { return shotsScatter; }
virtual unsigned char getShotsIntensity() { return shotsIntensity; }
virtual unsigned char getHealth() { return health; }
virtual double getRacetrackLength() { return racetrackLength; }
virtual Coords getRacetrackAnchor() { return racetrackAnchor; }
virtual double getRacetrackBearing() { return racetrackBearing; }
protected:
unsigned int ID;
@ -190,6 +196,9 @@ protected:
double desiredAltitude = 1;
bool desiredAltitudeType = 0; /* ASL */
unsigned int leaderID = NULL;
double racetrackLength = NULL;
Coords racetrackAnchor = Coords(NULL);
double racetrackBearing = NULL;
Offset formationOffset = Offset(NULL);
unsigned int targetID = NULL;
Coords targetPosition = Coords(NULL);

View File

@ -154,6 +154,11 @@ void AirUnit::AIloop()
{
srand(static_cast<unsigned int>(time(NULL)) + ID);
if (state != State::IDLE) {
setRacetrackAnchor(Coords(NULL));
setRacetrackBearing(NULL);
}
/* State machine */
switch (state) {
case State::IDLE: {
@ -166,23 +171,27 @@ void AirUnit::AIloop()
if (!getHasTask())
{
if (racetrackAnchor == Coords(NULL)) setRacetrackAnchor(position);
if (racetrackBearing == NULL) setRacetrackBearing(heading);
std::ostringstream taskSS;
if (isActiveTanker) {
taskSS << "{ [1] = { id = 'Tanker' }, [2] = { id = 'Orbit', pattern = 'Race-Track', altitude = " <<
desiredAltitude << ", speed = " << desiredSpeed << ", altitudeType = '" <<
desiredAltitude << ", lat = " << racetrackAnchor.lat << ", lng = " << racetrackAnchor.lng << ", speed = " << desiredSpeed << ", altitudeType = '" <<
(desiredAltitudeType ? "AGL" : "ASL") << "', speedType = '" << (desiredSpeedType ? "GS" : "CAS") << "', heading = " <<
heading << ", length = " << (50000 * 1.852) << " }}";
racetrackBearing << ", length = " << (racetrackLength != NULL ? racetrackLength : (50000 * 1.852)) << " }}";
}
else if (isActiveAWACS) {
taskSS << "{ [1] = { id = 'AWACS' }, [2] = { id = 'Orbit', pattern = 'Circle', altitude = " <<
desiredAltitude << ", speed = " << desiredSpeed << ", altitudeType = '" <<
(desiredAltitudeType ? "AGL" : "ASL") << "', speedType = '" << (desiredSpeedType ? "GS" : "CAS") << "' }}";
taskSS << "{ [1] = { id = 'AWACS' }, [2] = { id = 'Orbit', pattern = 'Race-Track', altitude = " <<
desiredAltitude << ", lat = " << racetrackAnchor.lat << ", lng = " << racetrackAnchor.lng << ", speed = " << desiredSpeed << ", altitudeType = '" <<
(desiredAltitudeType ? "AGL" : "ASL") << "', speedType = '" << (desiredSpeedType ? "GS" : "CAS") << "', heading = " <<
racetrackBearing << ", length = " << (racetrackLength != NULL ? racetrackLength : (desiredSpeed * 30)) << " }}";
}
else {
taskSS << "{ id = 'Orbit', pattern = 'Race-Track', altitude = " <<
desiredAltitude << ", speed = " << desiredSpeed << ", altitudeType = '" <<
desiredAltitude << ", lat = " << racetrackAnchor.lat << ", lng = " << racetrackAnchor.lng << ", speed = " << desiredSpeed << ", altitudeType = '" <<
(desiredAltitudeType ? "AGL" : "ASL") << "', speedType = '" << (desiredSpeedType ? "GS" : "CAS") << "', heading = " <<
heading << ", length = " << desiredSpeed * 30 << " }";
racetrackBearing << ", length = " << (racetrackLength != NULL ? racetrackLength: (desiredSpeed * 30)) << " }";
}
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);

View File

@ -353,6 +353,16 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
unit->setDesiredAltitudeType(to_string(value[L"altitudeType"]));
log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") altitude type: " + to_string(value[L"altitudeType"]), true);
}
}/************************/
else if (key.compare("setRacetrackLength") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setRacetrackLength(value[L"racetrackLength"].as_double());
log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") racetrack length: " + to_string(value[L"racetrackLength"].as_double()), true);
}
}
/************************/
else if (key.compare("cloneUnits") == 0)

View File

@ -295,6 +295,9 @@ void Unit::getData(stringstream& ss, unsigned long long time)
case DataIndex::shotsScatter: appendNumeric(ss, datumIndex, shotsScatter); break;
case DataIndex::shotsIntensity: appendNumeric(ss, datumIndex, shotsIntensity); break;
case DataIndex::health: appendNumeric(ss, datumIndex, health); break;
case DataIndex::racetrackLength: appendNumeric(ss, datumIndex, racetrackLength); break;
case DataIndex::racetrackAnchor: appendNumeric(ss, datumIndex, racetrackAnchor); break;
case DataIndex::racetrackBearing: appendNumeric(ss, datumIndex, racetrackBearing); break;
}
}
}

View File

@ -484,6 +484,9 @@ export enum DataIndexes {
shotsScatter,
shotsIntensity,
health,
racetrackLength,
racetrackAnchor,
racetrackBearing,
endOfData = 255,
}

View File

@ -248,6 +248,9 @@ export interface UnitData {
shotsScatter: number;
shotsIntensity: number;
health: number;
racetrackLength: number;
racetrackAnchor: LatLng;
racetrackBearing: number;
}
export interface LoadoutItemBlueprint {

View File

@ -555,8 +555,8 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
</div>
<OlLabelToggle
toggled={selectedUnitsData.desiredAltitudeType === undefined ? undefined : selectedUnitsData.desiredAltitudeType === "AGL"}
leftLabel={"AGL"}
rightLabel={"ASL"}
leftLabel={"ASL"}
rightLabel={"AGL"}
onClick={() => {
selectedUnits.forEach((unit) => {
unit.setAltitudeType(selectedUnitsData.desiredAltitudeType === "ASL" ? "AGL" : "ASL");
@ -617,8 +617,8 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
{!(everyUnitIsGround || everyUnitIsNavy) && (
<OlLabelToggle
toggled={selectedUnitsData.desiredSpeedType === undefined ? undefined : selectedUnitsData.desiredSpeedType === "GS"}
leftLabel={"GS"}
rightLabel={"CAS"}
leftLabel={"CAS"}
rightLabel={"GS"}
onClick={() => {
selectedUnits.forEach((unit) => {
unit.setSpeedType(selectedUnitsData.desiredSpeedType === "CAS" ? "GS" : "CAS");

View File

@ -1,4 +1,4 @@
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point, LeafletMouseEvent, DomEvent, DomUtil } from "leaflet";
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point, LeafletMouseEvent, DomEvent, DomUtil, Circle } from "leaflet";
import { getApp } from "../olympusapp";
import {
enumToCoalition,
@ -17,6 +17,8 @@ import {
zeroAppend,
computeBearingRangeString,
adjustBrightness,
bearingAndDistanceToLatLng,
mToNm,
} from "../other/utils";
import { CustomMarker } from "../map/markers/custommarker";
import { SVGInjector } from "@tanem/svg-injector";
@ -145,6 +147,9 @@ export abstract class Unit extends CustomMarker {
#shotsScatter: number = 2;
#shotsIntensity: number = 2;
#health: number = 100;
#racetrackLength: number = 0;
#racetrackAnchor: LatLng = new LatLng(0, 0);
#racetrackBearing: number = 0;
/* Other members used to draw the unit, mostly ancillary stuff like targets, ranges and so on */
#blueprint: UnitBlueprint | null = null;
@ -166,6 +171,9 @@ export abstract class Unit extends CustomMarker {
#detectionMethods: number[] = [];
#trailPositions: LatLng[] = [];
#trailPolylines: Polyline[] = [];
#racetrackPolylines: Polyline[] = [new Polyline([]), new Polyline([])];
#racetrackArcs: Polyline[] = [new Polyline([]), new Polyline([])];
#anchorMarkers: Marker[];
/* Inputs timers */
#debounceTimeout: number | null = null;
@ -312,6 +320,15 @@ export abstract class Unit extends CustomMarker {
getHealth() {
return this.#health;
}
getRaceTrackLength() {
return this.#racetrackLength;
}
getRaceTrackAnchor() {
return this.#racetrackAnchor;
}
getRaceTrackBearing() {
return this.#racetrackBearing;
}
static getConstructor(type: string) {
if (type === "GroundUnit") return GroundUnit;
@ -326,7 +343,7 @@ export abstract class Unit extends CustomMarker {
this.ID = ID;
this.#pathPolyline = new Polyline([], {
color: colors.GRAY,
color: colors.STEEL_BLUE,
weight: 3,
opacity: 0.5,
smoothFactor: 1,
@ -588,6 +605,15 @@ export abstract class Unit extends CustomMarker {
this.#health = dataExtractor.extractUInt8();
updateMarker = true;
break;
case DataIndexes.racetrackLength:
this.#racetrackLength = dataExtractor.extractFloat64();
break;
case DataIndexes.racetrackAnchor:
this.#racetrackAnchor = dataExtractor.extractLatLng();
break;
case DataIndexes.racetrackBearing:
this.#racetrackBearing = dataExtractor.extractFloat64();
break;
}
}
@ -691,6 +717,9 @@ export abstract class Unit extends CustomMarker {
shotsScatter: this.#shotsScatter,
shotsIntensity: this.#shotsIntensity,
health: this.#health,
racetrackLength: this.#racetrackLength,
racetrackAnchor: this.#racetrackAnchor,
racetrackBearing: this.#racetrackBearing,
};
}
@ -719,6 +748,7 @@ export abstract class Unit extends CustomMarker {
this.#clearContacts();
this.#clearPath();
this.#clearTargetPosition();
this.#clearRacetrack();
}
/* When the group leader is selected, if grouping is active, all the other group members are also selected */
@ -861,6 +891,7 @@ export abstract class Unit extends CustomMarker {
/* Leaflet does not like it when you change coordinates when the map is zooming */
if (!getApp().getMap().isZooming()) {
this.#drawPath();
this.#drawRacetrack();
this.#drawContacts();
this.#drawTarget();
}
@ -893,7 +924,7 @@ export abstract class Unit extends CustomMarker {
el.classList.add("unit");
el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`);
el.setAttribute("data-coalition", this.#coalition);
var iconOptions = this.getIconOptions();
/* Generate and append elements depending on active options */
@ -1569,12 +1600,14 @@ export abstract class Unit extends CustomMarker {
} else if (element.querySelector(".unit-braa")) (<HTMLElement>element.querySelector(".unit-braa")).innerText = ``;
/* Set operate as */
element.querySelector(".unit")?.setAttribute(
"data-operate-as",
this.getState() === UnitState.MISS_ON_PURPOSE || this.getState() === UnitState.SCENIC_AAA || this.getState() === UnitState.SIMULATE_FIRE_FIGHT
? this.#operateAs
: "neutral"
);
element
.querySelector(".unit")
?.setAttribute(
"data-operate-as",
this.getState() === UnitState.MISS_ON_PURPOSE || this.getState() === UnitState.SCENIC_AAA || this.getState() === UnitState.SIMULATE_FIRE_FIGHT
? this.#operateAs
: "neutral"
);
}
/* Set vertical offset for altitude stacking */
@ -1677,6 +1710,67 @@ export abstract class Unit extends CustomMarker {
}
}
#drawRacetrack() {
let groundspeed = this.#speed;
let racetrackLength = this.#racetrackLength;
if (racetrackLength === 0) {
if (this.getIsActiveTanker())
racetrackLength = nmToM(50);
else
racetrackLength = this.#desiredSpeed * 30;
}
const radius = Math.pow(groundspeed, 2) / 9.81 / Math.tan(deg2rad(22.5));
const point1 = this.#racetrackAnchor;
const point2 = bearingAndDistanceToLatLng(point1.lat, point1.lng, this.#racetrackBearing, racetrackLength);
const point3 = bearingAndDistanceToLatLng(point2.lat, point2.lng, this.#racetrackBearing - deg2rad(90), radius * 2);
const point4 = bearingAndDistanceToLatLng(point1.lat, point1.lng, this.#racetrackBearing - deg2rad(90), radius * 2);
const center1 = bearingAndDistanceToLatLng(point2.lat, point2.lng, this.#racetrackBearing - deg2rad(90), radius);
const center2 = bearingAndDistanceToLatLng(point1.lat, point1.lng, this.#racetrackBearing - deg2rad(90), radius);
if (!getApp().getMap().hasLayer(this.#racetrackPolylines[0])) {
this.#racetrackPolylines[0] = new Polyline([point1, point2]);
this.#racetrackPolylines[0].addTo(getApp().getMap());
} else {
this.#racetrackPolylines[0].setLatLngs([point1, point2]);
}
if (!getApp().getMap().hasLayer(this.#racetrackPolylines[1])) {
this.#racetrackPolylines[1] = new Polyline([point3, point4]);
this.#racetrackPolylines[1].addTo(getApp().getMap());
} else {
this.#racetrackPolylines[1].setLatLngs([point3, point4]);
}
const arc1Points: LatLng[] = [];
const arc2Points: LatLng[] = [];
for (let theta = 0; theta <= 180; theta += 5) {
arc1Points.push(bearingAndDistanceToLatLng(center1.lat, center1.lng, this.#racetrackBearing + deg2rad(theta - 90), radius));
arc2Points.push(bearingAndDistanceToLatLng(center2.lat, center2.lng, this.#racetrackBearing + deg2rad(theta + 90), radius));
}
if (!getApp().getMap().hasLayer(this.#racetrackArcs[0])) {
this.#racetrackArcs[0] = new Polyline(arc1Points);
this.#racetrackArcs[0].addTo(getApp().getMap());
} else {
this.#racetrackArcs[0].setLatLngs(arc1Points);
}
if (!getApp().getMap().hasLayer(this.#racetrackArcs[1])) {
this.#racetrackArcs[1] = new Polyline(arc2Points);
this.#racetrackArcs[1].addTo(getApp().getMap());
} else {
this.#racetrackArcs[1].setLatLngs(arc2Points);
}
}
#clearRacetrack() {
this.#racetrackPolylines.forEach((polyline) => getApp().getMap().removeLayer(polyline));
this.#racetrackArcs.forEach((arc) => getApp().getMap().removeLayer(arc));
}
#drawContacts() {
this.#clearContacts();
if (getApp().getMap().getOptions().showUnitContacts) {