Merge pull request 'feat(flight): enhance flight model with transonic drag and vortex lift features' (#1) from transsonic_stuff into master
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
commit
1e659816b8
12 files changed
+177
-26
No files matched your search
+1
-1
@@ -12,7 +12,7 @@ mapping_version=2023.09.03-1.20.1
|
|||||||
mod_id=improved_aerodynamics
|
mod_id=improved_aerodynamics
|
||||||
mod_name=VS: Improved Aerodynamics
|
mod_name=VS: Improved Aerodynamics
|
||||||
mod_license=MIT
|
mod_license=MIT
|
||||||
mod_version=0.2.0
|
mod_version=0.3.0
|
||||||
mod_group_id=dev.lumareth.improvedaerodynamics
|
mod_group_id=dev.lumareth.improvedaerodynamics
|
||||||
mod_authors=Lumareth
|
mod_authors=Lumareth
|
||||||
mod_description=Valkyrien Skies addon that keeps terrain streaming ahead of fast ships and smooths out Warium flight physics, with an optional flight model with real stalls and auto trim.
|
mod_description=Valkyrien Skies addon that keeps terrain streaming ahead of fast ships and smooths out Warium flight physics, with an optional flight model with real stalls and auto trim.
|
||||||
@@ -92,6 +92,37 @@ public final class Config {
|
|||||||
"with lift, so this only costs speed. Wings use Warium's 1.0, which also stands in for fuselage drag")
|
"with lift, so this only costs speed. Wings use Warium's 1.0, which also stands in for fuselage drag")
|
||||||
.defineInRange("stabilizerDrag", 0.2D, 0.0D, 2.0D);
|
.defineInRange("stabilizerDrag", 0.2D, 0.0D, 2.0D);
|
||||||
|
|
||||||
|
public static final ForgeConfigSpec.BooleanValue TRANSONIC_DRAG = BUILDER
|
||||||
|
.comment("Improved flight model: zero-lift drag rises steeply around the speed of sound, so craft can no longer",
|
||||||
|
"cruise supersonic without a lot of thrust or a clean shape. Induced and stall drag are not affected")
|
||||||
|
.define("transonicDrag", true);
|
||||||
|
|
||||||
|
public static final ForgeConfigSpec.DoubleValue SPEED_OF_SOUND = BUILDER
|
||||||
|
.comment("Improved flight model: speed of sound in blocks per second, Mach 1 for the transonic drag")
|
||||||
|
.defineInRange("speedOfSound", 100.0D, 10.0D, 1000.0D);
|
||||||
|
|
||||||
|
public static final ForgeConfigSpec.DoubleValue TRANSONIC_PEAK_DRAG = BUILDER
|
||||||
|
.comment("Improved flight model: multiplier for zero-lift drag at its peak, at Mach 1.05")
|
||||||
|
.defineInRange("transonicPeakDrag", 4.0D, 1.0D, 20.0D);
|
||||||
|
|
||||||
|
public static final ForgeConfigSpec.DoubleValue SUPERSONIC_DRAG = BUILDER
|
||||||
|
.comment("Improved flight model: multiplier for zero-lift drag once fully supersonic, from Mach 1.3")
|
||||||
|
.defineInRange("supersonicDrag", 2.0D, 1.0D, 20.0D);
|
||||||
|
|
||||||
|
public static final ForgeConfigSpec.BooleanValue VORTEX_LIFT = BUILDER
|
||||||
|
.comment("Improved flight model: wings with a low aspect ratio (delta wings) stall later and get extra lift at",
|
||||||
|
"high angles of attack from leading-edge vortices. Wings with an aspect ratio of 3 or more are not affected")
|
||||||
|
.define("vortexLift", true);
|
||||||
|
|
||||||
|
public static final ForgeConfigSpec.DoubleValue VORTEX_STALL_BONUS = BUILDER
|
||||||
|
.comment("Improved flight model: extra stall angle of the lowest aspect ratio wings (1 or less) as a fraction of",
|
||||||
|
"the normal stall angle, 1.0 doubles it, for example from 16 to 32 degrees")
|
||||||
|
.defineInRange("vortexStallBonus", 1.0D, 0.0D, 3.0D);
|
||||||
|
|
||||||
|
public static final ForgeConfigSpec.DoubleValue VORTEX_LIFT_STRENGTH = BUILDER
|
||||||
|
.comment("Improved flight model: multiplier for the extra lift the vortices make at high angles of attack")
|
||||||
|
.defineInRange("vortexLiftStrength", 1.0D, 0.0D, 3.0D);
|
||||||
|
|
||||||
public static final ForgeConfigSpec.DoubleValue TRIM_AUTHORITY = BUILDER
|
public static final ForgeConfigSpec.DoubleValue TRIM_AUTHORITY = BUILDER
|
||||||
.comment("Auto trim: largest pitch input (fraction of full deflection) the trim may hold")
|
.comment("Auto trim: largest pitch input (fraction of full deflection) the trim may hold")
|
||||||
.defineInRange("trimAuthority", 0.5D, 0.0D, 1.0D);
|
.defineInRange("trimAuthority", 0.5D, 0.0D, 1.0D);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.mojang.brigadier.tree.LiteralCommandNode;
|
|||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.commands.Commands;
|
import net.minecraft.commands.Commands;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraftforge.common.ForgeConfigSpec;
|
import net.minecraftforge.common.ForgeConfigSpec;
|
||||||
import net.minecraftforge.event.RegisterCommandsEvent;
|
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
@@ -17,6 +18,7 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* /improvedaerodynamics (or /aero) to read and change the most important server config values in game.
|
* /improvedaerodynamics (or /aero) to read and change the most important server config values in game.
|
||||||
* {@code /aero} lists them, {@code /aero <option>} shows one, {@code /aero <option> <value>} sets and saves it.
|
* {@code /aero} lists them, {@code /aero <option>} shows one, {@code /aero <option> <value>} sets and saves it.
|
||||||
|
* Needs operator rights, except for the owner of a singleplayer world, who may use them without cheats.
|
||||||
* All of them are read live, so changes apply immediately.
|
* All of them are read live, so changes apply immediately.
|
||||||
*/
|
*/
|
||||||
public final class ConfigCommands {
|
public final class ConfigCommands {
|
||||||
@@ -32,6 +34,13 @@ public final class ConfigCommands {
|
|||||||
new Option<>("lift_scale", Config.LIFT_SCALE, DoubleArgumentType.doubleArg(), Double.class),
|
new Option<>("lift_scale", Config.LIFT_SCALE, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
new Option<>("stall_angle_scale", Config.STALL_ANGLE_SCALE, DoubleArgumentType.doubleArg(), Double.class),
|
new Option<>("stall_angle_scale", Config.STALL_ANGLE_SCALE, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
new Option<>("stabilizer_drag", Config.STABILIZER_DRAG, DoubleArgumentType.doubleArg(), Double.class),
|
new Option<>("stabilizer_drag", Config.STABILIZER_DRAG, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
|
new Option<>("transonic_drag", Config.TRANSONIC_DRAG, BoolArgumentType.bool(), Boolean.class),
|
||||||
|
new Option<>("speed_of_sound", Config.SPEED_OF_SOUND, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
|
new Option<>("transonic_peak_drag", Config.TRANSONIC_PEAK_DRAG, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
|
new Option<>("supersonic_drag", Config.SUPERSONIC_DRAG, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
|
new Option<>("vortex_lift", Config.VORTEX_LIFT, BoolArgumentType.bool(), Boolean.class),
|
||||||
|
new Option<>("vortex_stall_bonus", Config.VORTEX_STALL_BONUS, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
|
new Option<>("vortex_lift_strength", Config.VORTEX_LIFT_STRENGTH, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
new Option<>("trim_authority", Config.TRIM_AUTHORITY, DoubleArgumentType.doubleArg(), Double.class),
|
new Option<>("trim_authority", Config.TRIM_AUTHORITY, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
new Option<>("trim_min_speed", Config.TRIM_MIN_SPEED, DoubleArgumentType.doubleArg(), Double.class),
|
new Option<>("trim_min_speed", Config.TRIM_MIN_SPEED, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
new Option<>("air_placeholder", Config.ENABLE_AIR_PLACEHOLDER, BoolArgumentType.bool(), Boolean.class),
|
new Option<>("air_placeholder", Config.ENABLE_AIR_PLACEHOLDER, BoolArgumentType.bool(), Boolean.class),
|
||||||
@@ -40,17 +49,26 @@ public final class ConfigCommands {
|
|||||||
private ConfigCommands() {
|
private ConfigCommands() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Operators, and the owner of a singleplayer world even when cheats are off. */
|
||||||
|
private static boolean mayUse(CommandSourceStack source) {
|
||||||
|
if (source.hasPermission(2)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return source.getEntity() instanceof ServerPlayer player
|
||||||
|
&& source.getServer().isSingleplayerOwner(player.getGameProfile());
|
||||||
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onRegisterCommands(RegisterCommandsEvent event) {
|
public static void onRegisterCommands(RegisterCommandsEvent event) {
|
||||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal("improvedaerodynamics")
|
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal("improvedaerodynamics")
|
||||||
.requires(source -> source.hasPermission(2))
|
.requires(ConfigCommands::mayUse)
|
||||||
.executes(context -> list(context.getSource()));
|
.executes(context -> list(context.getSource()));
|
||||||
for (Option<?> option : OPTIONS) {
|
for (Option<?> option : OPTIONS) {
|
||||||
root.then(optionNode(option));
|
root.then(optionNode(option));
|
||||||
}
|
}
|
||||||
LiteralCommandNode<CommandSourceStack> node = event.getDispatcher().register(root);
|
LiteralCommandNode<CommandSourceStack> node = event.getDispatcher().register(root);
|
||||||
event.getDispatcher().register(Commands.literal("aero")
|
event.getDispatcher().register(Commands.literal("aero")
|
||||||
.requires(source -> source.hasPermission(2))
|
.requires(ConfigCommands::mayUse)
|
||||||
.executes(context -> list(context.getSource()))
|
.executes(context -> list(context.getSource()))
|
||||||
.redirect(node));
|
.redirect(node));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,10 +205,7 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
|||||||
private void evaluate(Frame frame, Vector3dc centerOfMass, double[] v, double[] w, boolean improved, boolean commit, Wrench out) {
|
private void evaluate(Frame frame, Vector3dc centerOfMass, double[] v, double[] w, boolean improved, boolean commit, Wrench out) {
|
||||||
out.clear();
|
out.clear();
|
||||||
double weight = clock.weight();
|
double weight = clock.weight();
|
||||||
double liftConstant = ImprovedAero.LIFT_CONSTANT;
|
ImprovedAero.Tuning tuning = improved ? ImprovedAero.Tuning.read() : null;
|
||||||
double stallAngleScale = Config.STALL_ANGLE_SCALE.get();
|
|
||||||
double liftScale = Config.LIFT_SCALE.get();
|
|
||||||
double thinDrag = Config.STABILIZER_DRAG.get();
|
|
||||||
double[] r = new double[3];
|
double[] r = new double[3];
|
||||||
if (commit) {
|
if (commit) {
|
||||||
wingStalled = false;
|
wingStalled = false;
|
||||||
@@ -227,7 +224,7 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
|||||||
double input = s.type().controllable ? input(s, node, state, 0.0) : 0.0;
|
double input = s.type().controllable ? input(s, node, state, 0.0) : 0.0;
|
||||||
|
|
||||||
if (improved) {
|
if (improved) {
|
||||||
boolean nowStalled = ImprovedAero.evaluate(s, v, w, r, input, weight, liftConstant, liftScale, stallAngleScale, thinDrag, stalled[i], out);
|
boolean nowStalled = ImprovedAero.evaluate(s, v, w, r, input, weight, tuning, stalled[i], out);
|
||||||
if (commit) {
|
if (commit) {
|
||||||
stalled[i] = nowStalled;
|
stalled[i] = nowStalled;
|
||||||
wingStalled |= nowStalled && s.type() == SurfaceType.WING;
|
wingStalled |= nowStalled && s.type() == SurfaceType.WING;
|
||||||
@@ -242,7 +239,7 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
|||||||
probe.clear();
|
probe.clear();
|
||||||
double base = 0.0;
|
double base = 0.0;
|
||||||
if (improved) {
|
if (improved) {
|
||||||
ImprovedAero.evaluate(s, v, w, r, input, weight, liftConstant, liftScale, stallAngleScale, thinDrag, stalled[i], probe);
|
ImprovedAero.evaluate(s, v, w, r, input, weight, tuning, stalled[i], probe);
|
||||||
} else {
|
} else {
|
||||||
WariumAero.evaluate(s, v, w, r, input, weight, probe);
|
WariumAero.evaluate(s, v, w, r, input, weight, probe);
|
||||||
}
|
}
|
||||||
@@ -250,7 +247,7 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
|||||||
probe.clear();
|
probe.clear();
|
||||||
double nudged = input(s, node, state, delta);
|
double nudged = input(s, node, state, delta);
|
||||||
if (improved) {
|
if (improved) {
|
||||||
ImprovedAero.evaluate(s, v, w, r, nudged, weight, liftConstant, liftScale, stallAngleScale, thinDrag, stalled[i], probe);
|
ImprovedAero.evaluate(s, v, w, r, nudged, weight, tuning, stalled[i], probe);
|
||||||
} else {
|
} else {
|
||||||
WariumAero.evaluate(s, v, w, r, nudged, weight, probe);
|
WariumAero.evaluate(s, v, w, r, nudged, weight, probe);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,16 +13,17 @@ package dev.lumareth.improvedAerodynamics.flight;
|
|||||||
* @param node BlockPos.asLong of the Vehicle Control Node the surface listens to, {@link #NO_NODE} if none
|
* @param node BlockPos.asLong of the Vehicle Control Node the surface listens to, {@link #NO_NODE} if none
|
||||||
* @param brakeInput air brakes only: Warium's input value, they ignore smoothing and trim
|
* @param brakeInput air brakes only: Warium's input value, they ignore smoothing and trim
|
||||||
* @param aspectRatio span squared over area of the wing the block belongs to, 0 if not known yet
|
* @param aspectRatio span squared over area of the wing the block belongs to, 0 if not known yet
|
||||||
|
* @param wingGroup whether the wing the block belongs to contains WING blocks, not only control surfaces or stabilizers
|
||||||
*/
|
*/
|
||||||
public record AeroSurface(long key, SurfaceType type, boolean slab, double x, double y, double z, int facingX, int facingZ,
|
public record AeroSurface(long key, SurfaceType type, boolean slab, double x, double y, double z, int facingX, int facingZ,
|
||||||
double pressure, double fluidFactor, double speedMultiplier,
|
double pressure, double fluidFactor, double speedMultiplier,
|
||||||
long node, ControlAxis axis1, double multiplier1, ControlAxis axis2, double multiplier2,
|
long node, ControlAxis axis1, double multiplier1, ControlAxis axis2, double multiplier2,
|
||||||
double brakeInput, double aspectRatio) {
|
double brakeInput, double aspectRatio, boolean wingGroup) {
|
||||||
public static final long NO_NODE = Long.MIN_VALUE;
|
public static final long NO_NODE = Long.MIN_VALUE;
|
||||||
|
|
||||||
AeroSurface withAspectRatio(double aspectRatio) {
|
AeroSurface withWingShape(double aspectRatio, boolean wingGroup) {
|
||||||
return new AeroSurface(key, type, slab, x, y, z, facingX, facingZ, pressure, fluidFactor, speedMultiplier,
|
return new AeroSurface(key, type, slab, x, y, z, facingX, facingZ, pressure, fluidFactor, speedMultiplier,
|
||||||
node, axis1, multiplier1, axis2, multiplier2, brakeInput, aspectRatio);
|
node, axis1, multiplier1, axis2, multiplier2, brakeInput, aspectRatio, wingGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Warium's "Direction.getClockWise(Axis.Y)" of the facing, the span direction of a horizontal surface. */
|
/** Warium's "Direction.getClockWise(Axis.Y)" of the facing, the span direction of a horizontal surface. */
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package dev.lumareth.improvedAerodynamics.flight;
|
package dev.lumareth.improvedAerodynamics.flight;
|
||||||
|
|
||||||
|
import dev.lumareth.improvedAerodynamics.Config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flight model based on each surface's angle of attack, used instead of {@link WariumAero} when enabled.
|
* Flight model based on each surface's angle of attack, used instead of {@link WariumAero} when enabled.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -41,6 +43,18 @@ final class ImprovedAero {
|
|||||||
*/
|
*/
|
||||||
static final double LIFT_CONSTANT = 160.0 / (REFERENCE_SPEED * LIFT_SLOPE * REFERENCE_ANGLE);
|
static final double LIFT_CONSTANT = 160.0 / (REFERENCE_SPEED * LIFT_SLOPE * REFERENCE_ANGLE);
|
||||||
|
|
||||||
|
/** The config values the model reads, taken once per evaluation. */
|
||||||
|
record Tuning(double liftScale, double stallAngleScale, double thinDrag,
|
||||||
|
boolean transonic, double speedOfSound, double transonicPeakDrag, double supersonicDrag,
|
||||||
|
boolean vortex, double vortexStallBonus, double vortexLiftStrength) {
|
||||||
|
static Tuning read() {
|
||||||
|
return new Tuning(Config.LIFT_SCALE.get(), Config.STALL_ANGLE_SCALE.get(), Config.STABILIZER_DRAG.get(),
|
||||||
|
Config.TRANSONIC_DRAG.get(), Config.SPEED_OF_SOUND.get(), Config.TRANSONIC_PEAK_DRAG.get(),
|
||||||
|
Config.SUPERSONIC_DRAG.get(),
|
||||||
|
Config.VORTEX_LIFT.get(), Config.VORTEX_STALL_BONUS.get(), Config.VORTEX_LIFT_STRENGTH.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the surface's forces to out.
|
* Adds the surface's forces to out.
|
||||||
*
|
*
|
||||||
@@ -49,12 +63,10 @@ final class ImprovedAero {
|
|||||||
* @param r block center relative to the center of mass, ship axes
|
* @param r block center relative to the center of mass, ship axes
|
||||||
* @param input control input of the surface, -1 to 1
|
* @param input control input of the surface, -1 to 1
|
||||||
* @param stalled whether the surface was stalled in the previous substep
|
* @param stalled whether the surface was stalled in the previous substep
|
||||||
* @param liftScale multiplier for the lift only, not the drag
|
|
||||||
* @param thinDrag parasitic drag coefficient of stabilizers and vertical wings
|
|
||||||
* @return whether the surface is stalled now
|
* @return whether the surface is stalled now
|
||||||
*/
|
*/
|
||||||
static boolean evaluate(AeroSurface s, double[] v, double[] w, double[] r, double input, double weight,
|
static boolean evaluate(AeroSurface s, double[] v, double[] w, double[] r, double input, double weight,
|
||||||
double liftConstant, double liftScale, double stallAngleScale, double thinDrag, boolean stalled, Wrench out) {
|
Tuning tuning, boolean stalled, Wrench out) {
|
||||||
// Airflow at the block: ship velocity plus w x r
|
// Airflow at the block: ship velocity plus w x r
|
||||||
double vx = v[0] + w[1] * r[2] - w[2] * r[1];
|
double vx = v[0] + w[1] * r[2] - w[2] * r[1];
|
||||||
double vy = v[1] + w[2] * r[0] - w[0] * r[2];
|
double vy = v[1] + w[2] * r[0] - w[0] * r[2];
|
||||||
@@ -71,8 +83,13 @@ final class ImprovedAero {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warium's own per-block drag, lower for thin surfaces since they stabilize with lift, not drag
|
// Warium's own per-block drag, lower for thin surfaces since they stabilize with lift, not drag. Shock waves
|
||||||
double parasitic = -localSpeed * (s.type().thin ? thinDrag : s.type().parasiticDrag) * p / m;
|
// multiply it near the speed of sound; this is the only zero-lift drag a craft has, induced and stall drag
|
||||||
|
// below are not affected
|
||||||
|
double shockDrag = tuning.transonic()
|
||||||
|
? TransonicDrag.multiplier(localSpeed / tuning.speedOfSound(), tuning.transonicPeakDrag(), tuning.supersonicDrag())
|
||||||
|
: 1.0;
|
||||||
|
double parasitic = -localSpeed * (s.type().thin ? tuning.thinDrag() : s.type().parasiticDrag) * shockDrag * p / m;
|
||||||
out.addForce(r, vx * parasitic, vy * parasitic, vz * parasitic);
|
out.addForce(r, vx * parasitic, vy * parasitic, vz * parasitic);
|
||||||
|
|
||||||
// Chord c along the facing, normal n up for horizontal surfaces and to the side for vertical ones
|
// Chord c along the facing, normal n up for horizontal surfaces and to the side for vertical ones
|
||||||
@@ -99,7 +116,13 @@ final class ImprovedAero {
|
|||||||
}
|
}
|
||||||
alpha = Math.max(-Math.PI / 2.0, Math.min(Math.PI / 2.0, alpha));
|
alpha = Math.max(-Math.PI / 2.0, Math.min(Math.PI / 2.0, alpha));
|
||||||
|
|
||||||
double critical = Math.toRadians(s.slab() ? SurfaceType.SLAB_STALL_ANGLE : s.type().stallAngle) * stallAngleScale;
|
// Low aspect ratio wings keep their flow attached longer thanks to leading-edge vortices. Control surfaces
|
||||||
|
// in a wing's group stall with the wing instead of before it
|
||||||
|
double vortexFactor = tuning.vortex() && (s.type() == SurfaceType.WING || s.wingGroup() && s.type().controllable)
|
||||||
|
? VortexLift.factor(s.aspectRatio()) : 0.0;
|
||||||
|
double baseStall = s.slab() ? SurfaceType.SLAB_STALL_ANGLE
|
||||||
|
: s.wingGroup() && s.type().controllable ? SurfaceType.WING.stallAngle : s.type().stallAngle;
|
||||||
|
double critical = Math.toRadians(baseStall) * (1.0 + tuning.vortexStallBonus() * vortexFactor) * tuning.stallAngleScale();
|
||||||
double absAlpha = Math.abs(alpha);
|
double absAlpha = Math.abs(alpha);
|
||||||
// Flips only where both blends agree, so the hysteresis never makes the force jump
|
// Flips only where both blends agree, so the hysteresis never makes the force jump
|
||||||
if (!stalled && absAlpha > critical + STALL_BLEND) {
|
if (!stalled && absAlpha > critical + STALL_BLEND) {
|
||||||
@@ -115,11 +138,14 @@ final class ImprovedAero {
|
|||||||
double slope = aspectRatio > 0.0 ? 2.0 * Math.PI / (1.0 + 2.0 / aspectRatio) : LIFT_SLOPE;
|
double slope = aspectRatio > 0.0 ? 2.0 * Math.PI / (1.0 + 2.0 / aspectRatio) : LIFT_SLOPE;
|
||||||
double inducedDrag = aspectRatio > 0.0 ? 1.0 / (Math.PI * EFFICIENCY * aspectRatio) : INDUCED_DRAG;
|
double inducedDrag = aspectRatio > 0.0 ? 1.0 / (Math.PI * EFFICIENCY * aspectRatio) : INDUCED_DRAG;
|
||||||
double attachedLift = slope * alpha;
|
double attachedLift = slope * alpha;
|
||||||
|
if (s.type() == SurfaceType.WING) {
|
||||||
|
attachedLift += VortexLift.liftCoefficient(alpha, vortexFactor, tuning.vortexLiftStrength());
|
||||||
|
}
|
||||||
double lift = attached * attachedLift + (1.0 - attached) * SEPARATED_LIFT * Math.sin(2.0 * alpha);
|
double lift = attached * attachedLift + (1.0 - attached) * SEPARATED_LIFT * Math.sin(2.0 * alpha);
|
||||||
double drag = attached * inducedDrag * attachedLift * attachedLift
|
double drag = attached * inducedDrag * attachedLift * attachedLift
|
||||||
+ (1.0 - attached) * SEPARATED_DRAG * Math.sin(alpha) * Math.sin(alpha);
|
+ (1.0 - attached) * SEPARATED_DRAG * Math.sin(alpha) * Math.sin(alpha);
|
||||||
|
|
||||||
double q = liftConstant * p * fd * planeSpeedSq;
|
double q = LIFT_CONSTANT * p * fd * planeSpeedSq;
|
||||||
// Lift is perpendicular to the airflow in the chord/normal plane, on the side of n
|
// Lift is perpendicular to the airflow in the chord/normal plane, on the side of n
|
||||||
double side = u < 0.0 ? -1.0 : 1.0;
|
double side = u < 0.0 ? -1.0 : 1.0;
|
||||||
double lx = (Math.abs(u) * nx - side * vn * cx) / planeSpeed;
|
double lx = (Math.abs(u) * nx - side * vn * cx) / planeSpeed;
|
||||||
@@ -129,13 +155,13 @@ final class ImprovedAero {
|
|||||||
double dx = -(u * cx + vn * nx) / planeSpeed;
|
double dx = -(u * cx + vn * nx) / planeSpeed;
|
||||||
double dy = -(vn * ny) / planeSpeed;
|
double dy = -(vn * ny) / planeSpeed;
|
||||||
double dz = -(u * cz + vn * nz) / planeSpeed;
|
double dz = -(u * cz + vn * nz) / planeSpeed;
|
||||||
double liftForce = q * lift * liftScale;
|
double liftForce = q * lift * tuning.liftScale();
|
||||||
double dragForce = q * drag / m;
|
double dragForce = q * drag / m;
|
||||||
out.addForce(r, lx * liftForce + dx * dragForce, ly * liftForce + dy * dragForce, lz * liftForce + dz * dragForce);
|
out.addForce(r, lx * liftForce + dx * dragForce, ly * liftForce + dy * dragForce, lz * liftForce + dz * dragForce);
|
||||||
return stalled;
|
return stalled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double smoothstep(double edge0, double edge1, double x) {
|
static double smoothstep(double edge0, double edge1, double x) {
|
||||||
double t = Math.max(0.0, Math.min(1.0, (x - edge0) / (edge1 - edge0)));
|
double t = Math.max(0.0, Math.min(1.0, (x - edge0) / (edge1 - edge0)));
|
||||||
return t * t * (3.0 - 2.0 * t);
|
return t * t * (3.0 - 2.0 * t);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package dev.lumareth.improvedAerodynamics.flight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rise of zero-lift drag around the speed of sound: shock waves build up from Mach 0.8, drag peaks at Mach 1.05 and
|
||||||
|
* settles to a lower, still high, level once the flow is fully supersonic from Mach 1.3.
|
||||||
|
*/
|
||||||
|
final class TransonicDrag {
|
||||||
|
private static final double DRAG_RISE_START = 0.80;
|
||||||
|
private static final double PEAK = 1.05;
|
||||||
|
private static final double SUPERSONIC_START = 1.30;
|
||||||
|
|
||||||
|
private TransonicDrag() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mach airspeed divided by the speed of sound
|
||||||
|
* @param peakDrag multiplier at the peak
|
||||||
|
* @param supersonicDrag multiplier once fully supersonic
|
||||||
|
* @return factor for parasitic drag, 1 in subsonic flight
|
||||||
|
*/
|
||||||
|
static double multiplier(double mach, double peakDrag, double supersonicDrag) {
|
||||||
|
if (mach <= DRAG_RISE_START) {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
if (mach < PEAK) {
|
||||||
|
return 1.0 + (peakDrag - 1.0) * ImprovedAero.smoothstep(DRAG_RISE_START, PEAK, mach);
|
||||||
|
}
|
||||||
|
if (mach < SUPERSONIC_START) {
|
||||||
|
return peakDrag + (supersonicDrag - peakDrag) * ImprovedAero.smoothstep(PEAK, SUPERSONIC_START, mach);
|
||||||
|
}
|
||||||
|
return supersonicDrag;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package dev.lumareth.improvedAerodynamics.flight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leading-edge vortices of low aspect ratio (delta) wings: the sharp edge rolls the flow up into a vortex that keeps
|
||||||
|
* the wing lifting far beyond the angle where a straight wing stalls.
|
||||||
|
*/
|
||||||
|
final class VortexLift {
|
||||||
|
// Polhamus' leading-edge suction analogy constant, about pi for a sharp edge
|
||||||
|
private static final double VORTEX_CONSTANT = Math.PI;
|
||||||
|
private static final double FULL_VORTEX_ASPECT_RATIO = 1.0;
|
||||||
|
private static final double NO_VORTEX_ASPECT_RATIO = 3.0;
|
||||||
|
|
||||||
|
private VortexLift() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** How much of the vortex effect a wing gets: 1 at aspect ratio 1 or less, 0 at 3 or more, 0 if its shape is unknown. */
|
||||||
|
static double factor(double aspectRatio) {
|
||||||
|
if (aspectRatio <= 0.0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return 1.0 - ImprovedAero.smoothstep(FULL_VORTEX_ASPECT_RATIO, NO_VORTEX_ASPECT_RATIO, aspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lift coefficient added by the vortex, odd in the angle of attack like the normal lift. */
|
||||||
|
static double liftCoefficient(double alpha, double factor, double strength) {
|
||||||
|
double sin = Math.sin(alpha);
|
||||||
|
return strength * factor * VORTEX_CONSTANT * sin * Math.abs(sin) * Math.cos(alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,7 +97,7 @@ public final class WariumCapture {
|
|||||||
return new AeroSurface(pos.asLong(), type, slab, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
|
return new AeroSurface(pos.asLong(), type, slab, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
|
||||||
facing.getStepX(), facing.getStepZ(), pressure, fluidFactor(level), speedMultiplier,
|
facing.getStepX(), facing.getStepZ(), pressure, fluidFactor(level), speedMultiplier,
|
||||||
node, ControlAxis.of(key), ControlAxis.multiplier(key), ControlAxis.of(key2), ControlAxis.multiplier(key2),
|
node, ControlAxis.of(key), ControlAxis.multiplier(key), ControlAxis.of(key2), ControlAxis.multiplier(key2),
|
||||||
brakeInput, 0.0);
|
brakeInput, 0.0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Registers a node's inputs once per tick, and saves its trim to it now and then so it survives reloading. */
|
/** Registers a node's inputs once per tick, and saves its trim to it now and then so it survives reloading. */
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ final class WingShape {
|
|||||||
int[] count = new int[n];
|
int[] count = new int[n];
|
||||||
int[] minSpan = new int[n];
|
int[] minSpan = new int[n];
|
||||||
int[] maxSpan = new int[n];
|
int[] maxSpan = new int[n];
|
||||||
|
boolean[] hasWing = new boolean[n];
|
||||||
java.util.Arrays.fill(minSpan, Integer.MAX_VALUE);
|
java.util.Arrays.fill(minSpan, Integer.MAX_VALUE);
|
||||||
java.util.Arrays.fill(maxSpan, Integer.MIN_VALUE);
|
java.util.Arrays.fill(maxSpan, Integer.MIN_VALUE);
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
@@ -70,6 +71,7 @@ final class WingShape {
|
|||||||
int root = find(parent, i);
|
int root = find(parent, i);
|
||||||
int span = all[i].type().vertical ? cell[i * 3 + 1] : cell[i * 3] * Math.abs(all[i].rightX()) + cell[i * 3 + 2] * Math.abs(all[i].rightZ());
|
int span = all[i].type().vertical ? cell[i * 3 + 1] : cell[i * 3] * Math.abs(all[i].rightX()) + cell[i * 3 + 2] * Math.abs(all[i].rightZ());
|
||||||
count[root]++;
|
count[root]++;
|
||||||
|
hasWing[root] |= all[i].type() == SurfaceType.WING;
|
||||||
minSpan[root] = Math.min(minSpan[root], span);
|
minSpan[root] = Math.min(minSpan[root], span);
|
||||||
maxSpan[root] = Math.max(maxSpan[root], span);
|
maxSpan[root] = Math.max(maxSpan[root], span);
|
||||||
}
|
}
|
||||||
@@ -80,7 +82,7 @@ final class WingShape {
|
|||||||
int root = find(parent, i);
|
int root = find(parent, i);
|
||||||
double span = maxSpan[root] - minSpan[root] + 1;
|
double span = maxSpan[root] - minSpan[root] + 1;
|
||||||
double aspectRatio = span * span / count[root];
|
double aspectRatio = span * span / count[root];
|
||||||
all[i] = all[i].withAspectRatio(Math.max(MIN_ASPECT_RATIO, Math.min(MAX_ASPECT_RATIO, aspectRatio)));
|
all[i] = all[i].withWingShape(Math.max(MIN_ASPECT_RATIO, Math.min(MAX_ASPECT_RATIO, aspectRatio)), hasWing[root]);
|
||||||
}
|
}
|
||||||
return all;
|
return all;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,12 @@
|
|||||||
"message.improved_aerodynamics.auto_trim.off": "Automatische Trimmung aus",
|
"message.improved_aerodynamics.auto_trim.off": "Automatische Trimmung aus",
|
||||||
"message.improved_aerodynamics.auto_trim.unavailable": "Automatische Trimmung an, sie braucht aber physicsRateAerodynamics in der Serverkonfiguration",
|
"message.improved_aerodynamics.auto_trim.unavailable": "Automatische Trimmung an, sie braucht aber physicsRateAerodynamics in der Serverkonfiguration",
|
||||||
"message.improved_aerodynamics.auto_trim.no_seat": "Setz dich in einen Warium-Steuersitz, um die automatische Trimmung zu nutzen",
|
"message.improved_aerodynamics.auto_trim.no_seat": "Setz dich in einen Warium-Steuersitz, um die automatische Trimmung zu nutzen",
|
||||||
"message.improved_aerodynamics.auto_trim.no_node": "Dieser Sitz ist mit keinem Vehicle Control Node verbunden"
|
"message.improved_aerodynamics.auto_trim.no_node": "Dieser Sitz ist mit keinem Vehicle Control Node verbunden",
|
||||||
|
"config.improved_aerodynamics.transonicDrag": "Transsonischer Widerstand: Der Nullauftriebswiderstand steigt um die Schallgeschwindigkeit stark an (verbessertes Flugmodell)",
|
||||||
|
"config.improved_aerodynamics.speedOfSound": "Schallgeschwindigkeit in Blöcken pro Sekunde, Mach 1 für den transsonischen Widerstand",
|
||||||
|
"config.improved_aerodynamics.transonicPeakDrag": "Multiplikator für den Nullauftriebswiderstand an seinem Höchstwert bei Mach 1,05",
|
||||||
|
"config.improved_aerodynamics.supersonicDrag": "Multiplikator für den Nullauftriebswiderstand bei vollem Überschall, ab Mach 1,3",
|
||||||
|
"config.improved_aerodynamics.vortexLift": "Wirbelauftrieb: Flügel mit kleiner Streckung (Deltaflügel) überziehen später und liefern bei großen Anstellwinkeln mehr Auftrieb (verbessertes Flugmodell)",
|
||||||
|
"config.improved_aerodynamics.vortexStallBonus": "Zusätzlicher Überziehwinkel der Flügel mit der kleinsten Streckung, als Anteil des normalen Überziehwinkels",
|
||||||
|
"config.improved_aerodynamics.vortexLiftStrength": "Multiplikator für den zusätzlichen Auftrieb der Vorderkantenwirbel"
|
||||||
}
|
}
|
||||||
@@ -5,5 +5,12 @@
|
|||||||
"message.improved_aerodynamics.auto_trim.off": "Auto trim off",
|
"message.improved_aerodynamics.auto_trim.off": "Auto trim off",
|
||||||
"message.improved_aerodynamics.auto_trim.unavailable": "Auto trim on, but it needs physicsRateAerodynamics enabled in the server config",
|
"message.improved_aerodynamics.auto_trim.unavailable": "Auto trim on, but it needs physicsRateAerodynamics enabled in the server config",
|
||||||
"message.improved_aerodynamics.auto_trim.no_seat": "Sit in a Warium control seat to use auto trim",
|
"message.improved_aerodynamics.auto_trim.no_seat": "Sit in a Warium control seat to use auto trim",
|
||||||
"message.improved_aerodynamics.auto_trim.no_node": "This seat is not linked to a vehicle control node"
|
"message.improved_aerodynamics.auto_trim.no_node": "This seat is not linked to a vehicle control node",
|
||||||
|
"config.improved_aerodynamics.transonicDrag": "Transonic drag: zero-lift drag rises steeply around the speed of sound (improved flight model)",
|
||||||
|
"config.improved_aerodynamics.speedOfSound": "Speed of sound in blocks per second, Mach 1 for the transonic drag",
|
||||||
|
"config.improved_aerodynamics.transonicPeakDrag": "Zero-lift drag multiplier at its peak, Mach 1.05",
|
||||||
|
"config.improved_aerodynamics.supersonicDrag": "Zero-lift drag multiplier once fully supersonic, from Mach 1.3",
|
||||||
|
"config.improved_aerodynamics.vortexLift": "Vortex lift: low aspect ratio (delta) wings stall later and lift more at high angles of attack (improved flight model)",
|
||||||
|
"config.improved_aerodynamics.vortexStallBonus": "Extra stall angle of the lowest aspect ratio wings, as a fraction of the normal stall angle",
|
||||||
|
"config.improved_aerodynamics.vortexLiftStrength": "Multiplier for the extra lift the leading-edge vortices make"
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user