火力规则:装备匹配规则实现,目标规则实现,阵位规则、航迹规则【初版】
This commit is contained in:
@@ -45,6 +45,8 @@ public class FireRuleExecuteTargetItemVO {
|
||||
|
||||
private String cruiseRouteId;
|
||||
|
||||
private String moveRouteId;
|
||||
|
||||
private List<FireRuleCruiseRouteOffsetItemVO> cruiseRouteOffset;
|
||||
|
||||
private String fireType;
|
||||
|
||||
@@ -1,42 +1,45 @@
|
||||
package com.solution.rule.domain.ultimately.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 火力规则输出根文档(与 {@code 火力规则输出.json} 字段一一对应)。
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class FireRuleOutputVO {
|
||||
|
||||
/**
|
||||
* 来源场景文件名
|
||||
*/
|
||||
private String sourceFile;
|
||||
|
||||
/**
|
||||
* 火力规则任务列表(可含防区等输出字段)
|
||||
*/
|
||||
private List<FireRuleTaskInputVO> fireRuleInputs;
|
||||
|
||||
/**
|
||||
* 场景任务节点列表
|
||||
*/
|
||||
@JsonProperty("Tasks")
|
||||
private List<FireRuleSceneTaskNodeVO> tasks;
|
||||
@JsonProperty("TrackParam")
|
||||
private FireRuleTrackParamVO trackParam;
|
||||
|
||||
/**
|
||||
* 编组列表
|
||||
*/
|
||||
@JsonProperty("Groups")
|
||||
private List<FireRuleSceneGroupNodeVO> groups;
|
||||
@JsonIgnore
|
||||
public List<FireRuleSceneGroupNodeVO> getGroups() {
|
||||
return trackParam == null ? null : trackParam.getGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* 红方装备列表
|
||||
*/
|
||||
private List<FireRuleRedWeaponEquipmentVO> redWeapons;
|
||||
@JsonIgnore
|
||||
public void setGroups(List<FireRuleSceneGroupNodeVO> groups) {
|
||||
if (trackParam == null) {
|
||||
trackParam = new FireRuleTrackParamVO();
|
||||
}
|
||||
trackParam.setGroups(groups);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<FireRuleRedWeaponEquipmentVO> getRedWeapons() {
|
||||
return trackParam == null ? null : trackParam.getRedWeapons();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setRedWeapons(List<FireRuleRedWeaponEquipmentVO> redWeapons) {
|
||||
if (trackParam == null) {
|
||||
trackParam = new FireRuleTrackParamVO();
|
||||
}
|
||||
trackParam.setRedWeapons(redWeapons);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@ import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 火力规则输出中 redWeapons 数组的单项(红方装备/平台)
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class FireRuleRedWeaponEquipmentVO {
|
||||
@@ -23,15 +20,10 @@ public class FireRuleRedWeaponEquipmentVO {
|
||||
private String platformType;
|
||||
|
||||
private Boolean isStrikeTarget;
|
||||
|
||||
private Boolean isReconTarget;
|
||||
|
||||
private Boolean isInterferenceTarget;
|
||||
|
||||
private Boolean isDefendImportantPlace;
|
||||
//命中率
|
||||
private Double successTargetRad;
|
||||
|
||||
private String groupType;
|
||||
|
||||
@JsonProperty("EquipmentID")
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.solution.rule.domain.ultimately.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class FireRuleRouteTrackPointVO {
|
||||
private String index;
|
||||
private String longitude;
|
||||
private String latitude;
|
||||
private String height;
|
||||
private String speed;
|
||||
private String psia;
|
||||
private Integer time;
|
||||
private String active;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.solution.rule.domain.ultimately.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class FireRuleTrackParamVO {
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
private Map<String, FireRuleTrackRouteVO> routeMap = new LinkedHashMap<>();
|
||||
|
||||
@JsonProperty("Groups")
|
||||
private List<FireRuleSceneGroupNodeVO> groups;
|
||||
|
||||
@JsonProperty("redWeapons")
|
||||
private List<FireRuleRedWeaponEquipmentVO> redWeapons;
|
||||
|
||||
@JsonAnySetter
|
||||
public void putRoute(String key, JsonNode value) {
|
||||
if ("Groups".equals(key) || "redWeapons".equals(key) || value == null) {
|
||||
return;
|
||||
}
|
||||
routeMap.put(key, MAPPER.convertValue(value, FireRuleTrackRouteVO.class));
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, FireRuleTrackRouteVO> getRouteMap() {
|
||||
return routeMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.solution.rule.domain.ultimately.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class FireRuleTrackRouteVO {
|
||||
private String name;
|
||||
@JsonProperty("StartTime")
|
||||
private Integer startTime;
|
||||
@JsonProperty("EndTime")
|
||||
private Integer endTime;
|
||||
@JsonProperty("TrackType")
|
||||
private String trackType;
|
||||
@JsonProperty("HeightType")
|
||||
private String heightType;
|
||||
private String seaType;
|
||||
@JsonProperty("TrackPoints")
|
||||
private List<FireRuleRouteTrackPointVO> trackPoints;
|
||||
@JsonProperty("Color")
|
||||
private String color;
|
||||
@JsonProperty("PointCount")
|
||||
private Integer pointCount;
|
||||
}
|
||||
@@ -18,8 +18,12 @@ import com.solution.rule.domain.ultimately.vo.FireRuleRedSubComponentsVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleRedWeaponEquipmentVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleRedPlatformSlotVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleRedWeaponSlotVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleOutputVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleRouteTrackPointVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleSceneTaskNodeVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleSceneTaskPayloadVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleTrackParamVO;
|
||||
import com.solution.rule.domain.ultimately.vo.FireRuleTrackRouteVO;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -170,6 +174,399 @@ public final class FireRuleRedWeaponOutputFillHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 航迹规则:按蓝方 trackPoints 生成 {@link FireRuleTrackParamVO#routeMap} 条目(JSON 顶层 key=航迹 id),
|
||||
* 并将同一 id 写入对应红方任务 {@code execute[0].targetList[*].moveRouteId}。多蓝方任务循环时 merge,不覆盖已有 id。
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static void fillTrackParamAndBindMoveRoute(FireRuleOutputVO out, FireRuleTaskInputDTO blueTask, Map params) {
|
||||
if (out == null || blueTask == null || out.getTasks() == null || out.getTasks().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ensureTrackParam(out);
|
||||
List<FireRuleSceneTaskNodeVO> tasks = out.getTasks();
|
||||
List<FireRuleRedWeaponEquipmentVO> reds = out.getRedWeapons();
|
||||
if (reds == null) {
|
||||
reds = Collections.emptyList();
|
||||
}
|
||||
List<Coord> warPoly = toWarZonePolygon(blueTask.getWarZoneLocation());
|
||||
Coord blueAnchor = computeBlueAnchor(blueTask);
|
||||
Coord clampAnchor = resolveClampAnchor(blueAnchor, warPoly);
|
||||
boolean zoneClamp = Boolean.parseBoolean(String.valueOf(params.getOrDefault("enableTrackWarZoneClamp", true)));
|
||||
|
||||
String dirMode = str(params, "trackPointDirectionMode", "head2next");
|
||||
double fallbackBrg = readDouble(params, "trackFallbackBearingDeg", readDouble(params, "fallbackBearingDeg", 0d));
|
||||
double mainBearing = computeTrackBearingDeg(blueTask, dirMode, fallbackBrg);
|
||||
|
||||
String algo = str(params, "trackRouteAlgorithm", "followBlue");
|
||||
String nameSuffix = str(params, "trackRouteNameSuffix", "航迹");
|
||||
String groundType = str(params, "trackGroundTrackType", "routeLineGround");
|
||||
String blueIdSeg = sanitizeRouteIdSegment(nz(blueTask.getId()));
|
||||
|
||||
for (int i = 0; i < tasks.size(); i++) {
|
||||
FireRuleSceneTaskNodeVO node = tasks.get(i);
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
FireRuleRedWeaponEquipmentVO red = i < reds.size() ? reds.get(i) : null;
|
||||
List<TrackNode> nodes = extractSortedBlueTrackNodes(blueTask);
|
||||
if (nodes.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
List<TrackNode> transformed = applyTrackRouteAlgorithm(nodes, algo, params, mainBearing, i);
|
||||
int extraMax = readInt(params, "trackExtraNodesMax", 0);
|
||||
if (extraMax > 0) {
|
||||
transformed = injectExtraTrackNodes(transformed, extraMax);
|
||||
}
|
||||
if (zoneClamp && warPoly.size() >= 3) {
|
||||
transformed = clampTrackNodesToWarZone(transformed, warPoly, clampAnchor);
|
||||
}
|
||||
boolean air = resolveAirTrack(blueTask, red, params);
|
||||
String routeName = nz(node.getDrawName()) + nameSuffix;
|
||||
String routeId = buildUniqueRouteId(out.getTrackParam().getRouteMap(), blueIdSeg, sanitizeRouteIdSegment(nz(node.getId())), i);
|
||||
|
||||
FireRuleTrackRouteVO route = buildTrackRouteVo(transformed, routeName, air, groundType);
|
||||
out.getTrackParam().getRouteMap().put(routeId, route);
|
||||
bindMoveRouteIdToFirstExecute(node, routeId);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureTrackParam(FireRuleOutputVO out) {
|
||||
if (out.getTrackParam() == null) {
|
||||
out.setTrackParam(new FireRuleTrackParamVO());
|
||||
}
|
||||
}
|
||||
|
||||
private static String buildUniqueRouteId(Map<String, FireRuleTrackRouteVO> existing, String blueSeg, String nodeSeg, int index) {
|
||||
String base = "route_" + blueSeg + "_" + nodeSeg + "_" + index;
|
||||
if (existing == null || !existing.containsKey(base)) {
|
||||
return base;
|
||||
}
|
||||
int k = 1;
|
||||
while (existing.containsKey(base + "_" + k)) {
|
||||
k++;
|
||||
}
|
||||
return base + "_" + k;
|
||||
}
|
||||
|
||||
private static void bindMoveRouteIdToFirstExecute(FireRuleSceneTaskNodeVO node, String routeId) {
|
||||
if (node == null || isBlank(routeId)) {
|
||||
return;
|
||||
}
|
||||
FireRuleSceneTaskPayloadVO payload = node.getTask();
|
||||
if (payload == null || payload.getExecute() == null || payload.getExecute().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
FireRuleExecuteBlockVO block = payload.getExecute().get(0);
|
||||
if (block == null || block.getTargetList() == null) {
|
||||
return;
|
||||
}
|
||||
for (FireRuleExecuteTargetItemVO item : block.getTargetList()) {
|
||||
if (item != null) {
|
||||
item.setMoveRouteId(routeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static FireRuleTrackRouteVO buildTrackRouteVo(List<TrackNode> nodes, String name, boolean air, String groundType) {
|
||||
FireRuleTrackRouteVO vo = new FireRuleTrackRouteVO();
|
||||
vo.setName(name);
|
||||
vo.setStartTime(null);
|
||||
vo.setEndTime(null);
|
||||
vo.setTrackType(air ? "routeLineAir" : groundType);
|
||||
vo.setHeightType("msl");
|
||||
vo.setSeaType("seaLevel");
|
||||
List<FireRuleRouteTrackPointVO> pts = new ArrayList<>(nodes.size());
|
||||
for (int j = 0; j < nodes.size(); j++) {
|
||||
TrackNode tn = nodes.get(j);
|
||||
FireRuleRouteTrackPointVO p = new FireRuleRouteTrackPointVO();
|
||||
p.setIndex(String.valueOf(j + 1));
|
||||
p.setLongitude(formatCoordNumber(tn.coord.lon));
|
||||
p.setLatitude(formatCoordNumber(tn.coord.lat));
|
||||
p.setHeight(tn.coord.height != null ? formatCoordNumber(tn.coord.height) : "0");
|
||||
p.setSpeed(tn.speed != null ? formatCoordNumber(tn.speed) : null);
|
||||
pts.add(p);
|
||||
}
|
||||
vo.setTrackPoints(pts);
|
||||
vo.setPointCount(pts.size());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private static String formatCoordNumber(double v) {
|
||||
return String.valueOf(v);
|
||||
}
|
||||
|
||||
private static boolean resolveAirTrack(FireRuleTaskInputDTO blue, FireRuleRedWeaponEquipmentVO red, Map params) {
|
||||
String dt = blue != null ? nz(blue.getDataType()) : "";
|
||||
if (csvContainsAny(str(params, "trackAirDataTypeCsv", ""), dt, false)) {
|
||||
return true;
|
||||
}
|
||||
String blob = nz(blue != null ? blue.getDrawName() : "");
|
||||
if (red != null) {
|
||||
blob += " " + nz(red.getName()) + " " + nz(red.getPlatformType());
|
||||
}
|
||||
return csvContainsAny(str(params, "trackAirKeywordsCsv", ""), blob, false);
|
||||
}
|
||||
|
||||
private static boolean csvContainsAny(String csv, String text, boolean tokenAsEquals) {
|
||||
if (isBlank(csv) || text == null) {
|
||||
return false;
|
||||
}
|
||||
String t = text.toLowerCase();
|
||||
for (String part : csv.split(",")) {
|
||||
String k = part.trim();
|
||||
if (k.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (tokenAsEquals) {
|
||||
if (t.equalsIgnoreCase(k)) {
|
||||
return true;
|
||||
}
|
||||
} else if (text.contains(k) || t.contains(k.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String sanitizeRouteIdSegment(String raw) {
|
||||
if (raw == null || raw.isEmpty()) {
|
||||
return "x";
|
||||
}
|
||||
String s = raw.replaceAll("[^a-zA-Z0-9_-]", "_");
|
||||
return s.isEmpty() ? "x" : s;
|
||||
}
|
||||
|
||||
private static class TrackNode {
|
||||
Coord coord;
|
||||
Double speed;
|
||||
TrackNode(Coord coord, Double speed) {
|
||||
this.coord = coord;
|
||||
this.speed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<TrackNode> extractSortedBlueTrackNodes(FireRuleTaskInputDTO blueTask) {
|
||||
List<TrackNode> list = new ArrayList<>();
|
||||
if (blueTask == null || blueTask.getTrackPoints() == null) {
|
||||
return list;
|
||||
}
|
||||
List<FireRuleTrackPointDTO> pts = new ArrayList<>(blueTask.getTrackPoints());
|
||||
pts.sort((a, b) -> {
|
||||
int ia = a != null && a.getIndex() != null ? a.getIndex() : 0;
|
||||
int ib = b != null && b.getIndex() != null ? b.getIndex() : 0;
|
||||
return Integer.compare(ia, ib);
|
||||
});
|
||||
for (FireRuleTrackPointDTO p : pts) {
|
||||
if (p == null || p.getLongitude() == null || p.getLatitude() == null) {
|
||||
continue;
|
||||
}
|
||||
list.add(new TrackNode(new Coord(p.getLongitude(), p.getLatitude(), p.getHeight()), p.getSpeed()));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static List<TrackNode> applyTrackRouteAlgorithm(List<TrackNode> nodes, String algo, Map params, double mainBearing, int taskIndex) {
|
||||
if (nodes == null || nodes.isEmpty()) {
|
||||
return nodes;
|
||||
}
|
||||
String a = algo == null ? "followBlue" : algo.trim();
|
||||
if ("shortestPath".equalsIgnoreCase(a)) {
|
||||
return applyShortestPath(nodes, params);
|
||||
}
|
||||
if ("flank".equalsIgnoreCase(a)) {
|
||||
return applyFlank(nodes, params, taskIndex);
|
||||
}
|
||||
if ("jam".equalsIgnoreCase(a)) {
|
||||
return applyJam(nodes, params, mainBearing);
|
||||
}
|
||||
return copyTrackNodes(nodes);
|
||||
}
|
||||
|
||||
private static List<TrackNode> copyTrackNodes(List<TrackNode> nodes) {
|
||||
List<TrackNode> out = new ArrayList<>(nodes.size());
|
||||
for (TrackNode n : nodes) {
|
||||
out.add(new TrackNode(new Coord(n.coord.lon, n.coord.lat, n.coord.height), n.speed));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static List<TrackNode> applyShortestPath(List<TrackNode> nodes, Map params) {
|
||||
int segN = readInt(params, "trackShortPathSegments", 3);
|
||||
if (segN < 1) {
|
||||
segN = 1;
|
||||
}
|
||||
List<TrackNode> out = new ArrayList<>();
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
if (i == 0) {
|
||||
out.add(copyNode(nodes.get(i)));
|
||||
continue;
|
||||
}
|
||||
Coord a = nodes.get(i - 1).coord;
|
||||
Coord b = nodes.get(i).coord;
|
||||
Double spPrev = nodes.get(i - 1).speed;
|
||||
Double spNext = nodes.get(i).speed;
|
||||
for (int s = 1; s < segN; s++) {
|
||||
double t = (double) s / (double) segN;
|
||||
double lon = a.lon + (b.lon - a.lon) * t;
|
||||
double lat = a.lat + (b.lat - a.lat) * t;
|
||||
Double h = null;
|
||||
if (a.height != null && b.height != null) {
|
||||
h = a.height + (b.height - a.height) * t;
|
||||
} else if (b.height != null) {
|
||||
h = b.height;
|
||||
} else {
|
||||
h = a.height;
|
||||
}
|
||||
Double spd = null;
|
||||
if (spPrev != null && spNext != null) {
|
||||
spd = spPrev + (spNext - spPrev) * t;
|
||||
} else if (spNext != null) {
|
||||
spd = spNext;
|
||||
} else {
|
||||
spd = spPrev;
|
||||
}
|
||||
out.add(new TrackNode(new Coord(lon, lat, h), spd));
|
||||
}
|
||||
out.add(copyNode(nodes.get(i)));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static TrackNode copyNode(TrackNode n) {
|
||||
return new TrackNode(new Coord(n.coord.lon, n.coord.lat, n.coord.height), n.speed);
|
||||
}
|
||||
|
||||
private static List<TrackNode> applyFlank(List<TrackNode> nodes, Map params, int taskIndex) {
|
||||
double offsetM = readDouble(params, "trackFlankOffsetMeters", 800d);
|
||||
String sideMode = str(params, "trackFlankSideMode", "alternate");
|
||||
List<TrackNode> out = new ArrayList<>(nodes.size());
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
Coord c = nodes.get(i).coord;
|
||||
double brg;
|
||||
if (i < nodes.size() - 1) {
|
||||
Coord nxt = nodes.get(i + 1).coord;
|
||||
brg = bearingDeg(c.lon, c.lat, nxt.lon, nxt.lat);
|
||||
} else if (i > 0) {
|
||||
Coord prev = nodes.get(i - 1).coord;
|
||||
brg = bearingDeg(prev.lon, prev.lat, c.lon, c.lat);
|
||||
} else {
|
||||
brg = 0d;
|
||||
}
|
||||
int sign = flankSign(sideMode, i, taskIndex);
|
||||
Coord moved = moveByMeters(c, sign * offsetM, brg + 90d);
|
||||
out.add(new TrackNode(moved, nodes.get(i).speed));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static int flankSign(String mode, int pointIndex, int taskIndex) {
|
||||
if ("left".equalsIgnoreCase(mode)) {
|
||||
return 1;
|
||||
}
|
||||
if ("right".equalsIgnoreCase(mode)) {
|
||||
return -1;
|
||||
}
|
||||
return (pointIndex + taskIndex) % 2 == 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
private static List<TrackNode> applyJam(List<TrackNode> nodes, Map params, double mainBearing) {
|
||||
double wobble = readDouble(params, "trackJamWobbleMeters", 400d);
|
||||
double periods = readDouble(params, "trackJamSegments", 4d);
|
||||
if (periods < 0.5d) {
|
||||
periods = 0.5d;
|
||||
}
|
||||
int n = nodes.size();
|
||||
List<TrackNode> out = new ArrayList<>(n);
|
||||
for (int i = 0; i < n; i++) {
|
||||
double frac = n <= 1 ? 0d : (double) i / (double) (n - 1);
|
||||
double lateral = wobble * Math.sin(2d * Math.PI * periods * frac);
|
||||
Coord c = moveByMeters(nodes.get(i).coord, lateral, mainBearing + 90d);
|
||||
out.add(new TrackNode(c, nodes.get(i).speed));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static List<TrackNode> injectExtraTrackNodes(List<TrackNode> nodes, int extraMax) {
|
||||
if (nodes == null || nodes.size() < 2 || extraMax <= 0) {
|
||||
return nodes;
|
||||
}
|
||||
int segs = nodes.size() - 1;
|
||||
List<Integer> perSeg = new ArrayList<>(Collections.nCopies(segs, 0));
|
||||
int base = extraMax / segs;
|
||||
int rem = extraMax % segs;
|
||||
for (int s = 0; s < segs; s++) {
|
||||
perSeg.set(s, base + (s < rem ? 1 : 0));
|
||||
}
|
||||
List<TrackNode> out = new ArrayList<>();
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
out.add(copyNode(nodes.get(i)));
|
||||
if (i >= nodes.size() - 1) {
|
||||
break;
|
||||
}
|
||||
int ins = perSeg.get(i);
|
||||
Coord a = nodes.get(i).coord;
|
||||
Coord b = nodes.get(i + 1).coord;
|
||||
Double sa = nodes.get(i).speed;
|
||||
Double sb = nodes.get(i + 1).speed;
|
||||
for (int k = 1; k <= ins; k++) {
|
||||
double t = (double) k / (double) (ins + 1);
|
||||
double lon = a.lon + (b.lon - a.lon) * t;
|
||||
double lat = a.lat + (b.lat - a.lat) * t;
|
||||
Double h = null;
|
||||
if (a.height != null && b.height != null) {
|
||||
h = a.height + (b.height - a.height) * t;
|
||||
} else if (b.height != null) {
|
||||
h = b.height;
|
||||
} else {
|
||||
h = a.height;
|
||||
}
|
||||
Double spd = null;
|
||||
if (sa != null && sb != null) {
|
||||
spd = sa + (sb - sa) * t;
|
||||
} else if (sb != null) {
|
||||
spd = sb;
|
||||
} else {
|
||||
spd = sa;
|
||||
}
|
||||
out.add(new TrackNode(new Coord(lon, lat, h), spd));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static List<TrackNode> clampTrackNodesToWarZone(List<TrackNode> nodes, List<Coord> poly, Coord anchor) {
|
||||
List<TrackNode> out = new ArrayList<>(nodes.size());
|
||||
for (TrackNode n : nodes) {
|
||||
Coord c = n.coord;
|
||||
if (!isPointInPolygon(c, poly)) {
|
||||
c = projectInsidePolygon(c, anchor, poly);
|
||||
}
|
||||
out.add(new TrackNode(c, n.speed));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Coord resolveClampAnchor(Coord blueAnchor, List<Coord> poly) {
|
||||
if (poly.size() >= 3) {
|
||||
if (blueAnchor != null && isPointInPolygon(blueAnchor, poly)) {
|
||||
return blueAnchor;
|
||||
}
|
||||
return polygonCentroid(poly);
|
||||
}
|
||||
return blueAnchor != null ? blueAnchor : new Coord(0d, 0d, null);
|
||||
}
|
||||
|
||||
private static Coord polygonCentroid(List<Coord> poly) {
|
||||
double sx = 0, sy = 0;
|
||||
for (Coord c : poly) {
|
||||
sx += c.lon;
|
||||
sy += c.lat;
|
||||
}
|
||||
int n = poly.size();
|
||||
return new Coord(sx / n, sy / n, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 目标分配:为每个红方 Tasks 节点写入 task.execute[0].targetList[*].targetId。\n
|
||||
* targetId 来源:当前蓝方任务 {@link FireRuleTaskInputDTO#getTaskWeapons()} 下每条武器的 equipmentId。\n
|
||||
|
||||
@@ -225,6 +225,26 @@ public final class RuleFunction {
|
||||
FireRuleRedWeaponOutputFillHelper.fillPlatformPositions(out.getRedWeapons(), fact.getTask(), p);
|
||||
}
|
||||
|
||||
/**
|
||||
* 航迹规则:根据蓝方 trackPoints 与作战区生成 TrackParam 航迹,并绑定 execute.targetList.moveRouteId。
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static void trackRoute(DroolsFact fact, Map globalParams) {
|
||||
if (fact == null || fact.getTask() == null || fact.getFireRuleOutputVO() == null) {
|
||||
return;
|
||||
}
|
||||
Map<String, Object> p = castParams(globalParams);
|
||||
boolean enabled = Boolean.parseBoolean(String.valueOf(p.getOrDefault("trackRuleEnabled", true)));
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
FireRuleOutputVO out = fact.getFireRuleOutputVO();
|
||||
if (out.getTasks() == null || out.getTasks().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
FireRuleRedWeaponOutputFillHelper.fillTrackParamAndBindMoveRoute(out, fact.getTask(), p);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Object> castParams(Map raw) {
|
||||
return raw == null ? new java.util.HashMap<>() : (Map<String, Object>) raw;
|
||||
|
||||
Reference in New Issue
Block a user