Add Metalstorm-style cursor aim
Virtual stick cursor steered through the gun reticle, server-side virtual pilot using measured control responses, client cursor/camera handling with look-ahead, free look and scroll zoom. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YVREx3uo7nz1FEhzveWTUh
This commit is contained in:
17 files changed
+1037
-38
No files matched your search
@@ -27,6 +27,7 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
||||
private static final double INPUT_RATE_PER_TICK = 0.2;
|
||||
private static final double VELOCITY_EPSILON = 0.05;
|
||||
private static final double ANGULAR_EPSILON = 0.005;
|
||||
private static final ControlAxis[] MEASURED_AXES = {ControlAxis.PITCH, ControlAxis.YAW, ControlAxis.ROLL};
|
||||
|
||||
private final SubstepClock clock;
|
||||
|
||||
@@ -57,9 +58,22 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
||||
// Torque (ship axes) one unit of pitch input on this node's pitch surfaces makes, measured every substep
|
||||
final Vector3d pitchResponse = new Vector3d();
|
||||
final Vector3d pitchResponseBuilding = new Vector3d();
|
||||
// The same for yaw and roll, measured while cursor aim flies the node
|
||||
final Vector3d yawResponse = new Vector3d();
|
||||
final Vector3d yawResponseBuilding = new Vector3d();
|
||||
final Vector3d rollResponse = new Vector3d();
|
||||
final Vector3d rollResponseBuilding = new Vector3d();
|
||||
final AutoTrim.Targets trimTargets = new AutoTrim.Targets();
|
||||
volatile double savedTrim;
|
||||
|
||||
Vector3d responseBuilding(ControlAxis axis) {
|
||||
return switch (axis) {
|
||||
case YAW -> yawResponseBuilding;
|
||||
case ROLL -> rollResponseBuilding;
|
||||
default -> pitchResponseBuilding;
|
||||
};
|
||||
}
|
||||
|
||||
NodeState(NodeData data) {
|
||||
pitch = data.pitch();
|
||||
yaw = data.yaw();
|
||||
@@ -211,6 +225,8 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
||||
wingStalled = false;
|
||||
for (NodeState state : nodeStates.values()) {
|
||||
state.pitchResponseBuilding.zero();
|
||||
state.yawResponseBuilding.zero();
|
||||
state.rollResponseBuilding.zero();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +237,7 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
||||
r[2] = s.z() - centerOfMass.z();
|
||||
NodeData node = s.node() != AeroSurface.NO_NODE ? frame.nodes.get(s.node()) : null;
|
||||
NodeState state = node != null ? nodeStates.get(node.key()) : null;
|
||||
double input = s.type().controllable ? input(s, node, state, 0.0) : 0.0;
|
||||
double input = s.type().controllable ? input(s, node, state, ControlAxis.NONE, 0.0) : 0.0;
|
||||
|
||||
if (improved) {
|
||||
boolean nowStalled = ImprovedAero.evaluate(s, v, w, r, input, weight, tuning, stalled[i], out);
|
||||
@@ -233,53 +249,62 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
||||
WariumAero.evaluate(s, v, w, r, input, weight, out);
|
||||
}
|
||||
|
||||
// How much a little more pitch input on this node would change the torque, for auto trim
|
||||
if (commit && node != null && node.autoTrim() && s.type().controllable && s.listensToPitch()) {
|
||||
double delta = 0.05;
|
||||
probe.clear();
|
||||
double base = 0.0;
|
||||
if (improved) {
|
||||
ImprovedAero.evaluate(s, v, w, r, input, weight, tuning, stalled[i], probe);
|
||||
} else {
|
||||
WariumAero.evaluate(s, v, w, r, input, weight, probe);
|
||||
// How much a little more input on an axis would change the torque: pitch for auto trim, all three for
|
||||
// cursor aim
|
||||
if (commit && node != null && s.type().controllable) {
|
||||
boolean aimed = CursorAimRegistry.isActive(node.key());
|
||||
for (ControlAxis axis : MEASURED_AXES) {
|
||||
if (!s.listensTo(axis) || !(aimed || (axis == ControlAxis.PITCH && node.autoTrim()))) {
|
||||
continue;
|
||||
}
|
||||
double delta = 0.05;
|
||||
probe.clear();
|
||||
if (improved) {
|
||||
ImprovedAero.evaluate(s, v, w, r, input, weight, tuning, stalled[i], probe);
|
||||
} else {
|
||||
WariumAero.evaluate(s, v, w, r, input, weight, probe);
|
||||
}
|
||||
double[] before = {probe.totalTorque(0), probe.totalTorque(1), probe.totalTorque(2)};
|
||||
probe.clear();
|
||||
double nudged = input(s, node, state, axis, delta);
|
||||
if (improved) {
|
||||
ImprovedAero.evaluate(s, v, w, r, nudged, weight, tuning, stalled[i], probe);
|
||||
} else {
|
||||
WariumAero.evaluate(s, v, w, r, nudged, weight, probe);
|
||||
}
|
||||
state.responseBuilding(axis).add(
|
||||
(probe.totalTorque(0) - before[0]) / delta,
|
||||
(probe.totalTorque(1) - before[1]) / delta,
|
||||
(probe.totalTorque(2) - before[2]) / delta);
|
||||
}
|
||||
double[] before = {probe.totalTorque(0), probe.totalTorque(1), probe.totalTorque(2)};
|
||||
probe.clear();
|
||||
double nudged = input(s, node, state, delta);
|
||||
if (improved) {
|
||||
ImprovedAero.evaluate(s, v, w, r, nudged, weight, tuning, stalled[i], probe);
|
||||
} else {
|
||||
WariumAero.evaluate(s, v, w, r, nudged, weight, probe);
|
||||
}
|
||||
state.pitchResponseBuilding.add(
|
||||
(probe.totalTorque(0) - before[0]) / delta,
|
||||
(probe.totalTorque(1) - before[1]) / delta,
|
||||
(probe.totalTorque(2) - before[2]) / delta);
|
||||
}
|
||||
}
|
||||
|
||||
if (commit) {
|
||||
for (NodeState state : nodeStates.values()) {
|
||||
state.pitchResponse.set(state.pitchResponseBuilding);
|
||||
state.yawResponse.set(state.yawResponseBuilding);
|
||||
state.rollResponse.set(state.rollResponseBuilding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A control surface's input, combined from its two keys and clamped like Warium does. */
|
||||
private static double input(AeroSurface s, NodeData node, NodeState state, double extraPitch) {
|
||||
private static double input(AeroSurface s, NodeData node, NodeState state, ControlAxis nudged, double extra) {
|
||||
if (node == null || state == null) {
|
||||
return 0.0;
|
||||
}
|
||||
double value = axisValue(s.axis1(), node, state, extraPitch) * s.multiplier1()
|
||||
+ axisValue(s.axis2(), node, state, extraPitch) * s.multiplier2();
|
||||
double value = axisValue(s.axis1(), node, state, nudged, extra) * s.multiplier1()
|
||||
+ axisValue(s.axis2(), node, state, nudged, extra) * s.multiplier2();
|
||||
return Math.max(-1.0, Math.min(1.0, value));
|
||||
}
|
||||
|
||||
private static double axisValue(ControlAxis axis, NodeData node, NodeState state, double extraPitch) {
|
||||
private static double axisValue(ControlAxis axis, NodeData node, NodeState state, ControlAxis nudged, double extra) {
|
||||
double nudge = axis == nudged ? extra : 0.0;
|
||||
return switch (axis) {
|
||||
case PITCH -> state.pitch + state.trimOutput + extraPitch;
|
||||
case YAW -> state.yaw;
|
||||
case ROLL -> state.roll;
|
||||
case PITCH -> state.pitch + state.trimOutput + nudge;
|
||||
case YAW -> state.yaw + nudge;
|
||||
case ROLL -> state.roll + nudge;
|
||||
case THROTTLE -> node.throttle();
|
||||
case NONE -> 0.0;
|
||||
};
|
||||
@@ -293,10 +318,12 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
||||
if (state == null) {
|
||||
continue;
|
||||
}
|
||||
state.pitch = moveTowards(state.pitch, node.pitch(), step);
|
||||
state.yaw = moveTowards(state.yaw, node.yaw(), step);
|
||||
state.roll = moveTowards(state.roll, node.roll(), step);
|
||||
AutoTrim.update(state, node, worldVelocity, altitude, rotation, inertia, w, dt, improved && wingStalled);
|
||||
// Cursor aim, if the pilot has it on, supplies the inputs the pilot is not giving with the keys
|
||||
NodeData effective = CursorPilot.apply(state, node, rotation, inertia, w, worldVelocity.length(), wingStalled);
|
||||
state.pitch = moveTowards(state.pitch, effective.pitch(), step);
|
||||
state.yaw = moveTowards(state.yaw, effective.yaw(), step);
|
||||
state.roll = moveTowards(state.roll, effective.roll(), step);
|
||||
AutoTrim.update(state, effective, worldVelocity, altitude, rotation, inertia, w, dt, improved && wingStalled);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user