package dev.lumareth.improvedAerodynamics.flight; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import java.util.Collection; /** * Finds the aspect ratio (span squared over area) of every wing in a ship, which sets how well it makes lift and how * much drag that lift costs: long narrow wings are efficient, short stubby ones are not. *

* Neighbouring blocks of the same kind and facing form one wing. Horizontal wings also join across gaps of up to * {@link #MAX_GAP} blocks along the span, so the two halves of a wing either side of a fuselage count as one wing and * the span is the full wingspan. Air brakes are not wings and are left alone. */ final class WingShape { private static final int MAX_GAP = 6; private static final double MIN_ASPECT_RATIO = 0.5; private static final double MAX_ASPECT_RATIO = 12.0; private WingShape() { } static AeroSurface[] withAspectRatios(Collection surfaces) { AeroSurface[] all = surfaces.toArray(new AeroSurface[0]); int n = all.length; int[] cell = new int[n * 3]; Long2IntOpenHashMap index = new Long2IntOpenHashMap(n * 2); index.defaultReturnValue(-1); int[] parent = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; cell[i * 3] = (int) Math.floor(all[i].x()); cell[i * 3 + 1] = (int) Math.floor(all[i].y()); cell[i * 3 + 2] = (int) Math.floor(all[i].z()); if (all[i].type() != SurfaceType.AIR_BRAKE) { index.put(key(all[i], cell[i * 3], cell[i * 3 + 1], cell[i * 3 + 2]), i); } } for (int i = 0; i < n; i++) { AeroSurface s = all[i]; if (s.type() == SurfaceType.AIR_BRAKE) { continue; } int x = cell[i * 3]; int y = cell[i * 3 + 1]; int z = cell[i * 3 + 2]; // Direct neighbours for (int d = 0; d < 6; d++) { int axis = d >> 1; int step = (d & 1) == 0 ? 1 : -1; union(parent, i, index.get(key(s, x + (axis == 0 ? step : 0), y + (axis == 1 ? step : 0), z + (axis == 2 ? step : 0)))); } // Gaps along the span of horizontal wings, only in one direction since the other side finds this block if (!s.type().vertical) { for (int gap = 2; gap <= MAX_GAP + 1; gap++) { union(parent, i, index.get(key(s, x + s.rightX() * gap, y, z + s.rightZ() * gap))); } } } int[] count = new int[n]; int[] minSpan = 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(maxSpan, Integer.MIN_VALUE); for (int i = 0; i < n; i++) { if (all[i].type() == SurfaceType.AIR_BRAKE) { continue; } 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()); count[root]++; hasWing[root] |= all[i].type() == SurfaceType.WING; minSpan[root] = Math.min(minSpan[root], span); maxSpan[root] = Math.max(maxSpan[root], span); } for (int i = 0; i < n; i++) { if (all[i].type() == SurfaceType.AIR_BRAKE) { continue; } int root = find(parent, i); double span = maxSpan[root] - minSpan[root] + 1; double aspectRatio = span * span / count[root]; all[i] = all[i].withWingShape(Math.max(MIN_ASPECT_RATIO, Math.min(MAX_ASPECT_RATIO, aspectRatio)), hasWing[root]); } return all; } /** Position plus everything that must match for two blocks to be the same wing. */ private static long key(AeroSurface s, int x, int y, int z) { long kind = (s.type().vertical ? 2 : 0) | (s.facingX() != 0 ? 1 : 0); return net.minecraft.core.BlockPos.asLong(x, y, z) * 4 + kind; } private static int find(int[] parent, int i) { while (parent[i] != i) { parent[i] = parent[parent[i]]; i = parent[i]; } return i; } private static void union(int[] parent, int a, int b) { if (b < 0) { return; } parent[find(parent, a)] = find(parent, b); } }