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
@@ -0,0 +1,34 @@
|
||||
package dev.lumareth.improvedAerodynamics.client.aim;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import dev.lumareth.improvedAerodynamics.ImprovedAerodynamics;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
|
||||
import net.minecraftforge.client.settings.KeyConflictContext;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
/** Keys of cursor aim. The free look key is not shift: in a seat, shift gets the player out of it. */
|
||||
public final class AimKeys {
|
||||
public static final KeyMapping TOGGLE = new KeyMapping("key.improved_aerodynamics.toggle_cursor_aim",
|
||||
KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_K, "key.categories.improved_aerodynamics");
|
||||
public static final KeyMapping FREE_LOOK = new KeyMapping("key.improved_aerodynamics.cursor_aim_free_look",
|
||||
KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_LEFT_ALT, "key.categories.improved_aerodynamics");
|
||||
|
||||
private AimKeys() {
|
||||
}
|
||||
|
||||
@Mod.EventBusSubscriber(modid = ImprovedAerodynamics.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||
public static final class Registration {
|
||||
private Registration() {
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRegisterKeyMappings(RegisterKeyMappingsEvent event) {
|
||||
event.register(TOGGLE);
|
||||
event.register(FREE_LOOK);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dev.lumareth.improvedAerodynamics.client.aim;
|
||||
|
||||
import net.mcreator.valkyrienwarium.entity.SeatEntityEntity;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
|
||||
/** Everything that touches Warium VS classes, so nothing else loads them when Warium is missing. */
|
||||
final class AimSeat {
|
||||
private AimSeat() {
|
||||
}
|
||||
|
||||
static Entity seatOf(Entity rider) {
|
||||
Entity root = rider.getRootVehicle();
|
||||
return root instanceof SeatEntityEntity ? root : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
package dev.lumareth.improvedAerodynamics.client.aim;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.BufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.BufferUploader;
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.Tesselator;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import dev.lumareth.improvedAerodynamics.ImprovedAerodynamics;
|
||||
import dev.lumareth.improvedAerodynamics.client.pipper.GunPipperRenderer;
|
||||
import dev.lumareth.improvedAerodynamics.flight.AimBasis;
|
||||
import dev.lumareth.improvedAerodynamics.network.AimPacket;
|
||||
import dev.lumareth.improvedAerodynamics.network.Network;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.CameraType;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.InputEvent;
|
||||
import net.minecraftforge.client.event.RenderLevelStageEvent;
|
||||
import net.minecraftforge.client.event.ViewportEvent;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaterniondc;
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector3f;
|
||||
import org.valkyrienskies.core.api.ships.ClientShip;
|
||||
import org.valkyrienskies.core.api.ships.Ship;
|
||||
import org.valkyrienskies.mod.common.VSGameUtilsKt;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Cursor aim, the client side. While it is on:
|
||||
* <ul>
|
||||
* <li>the mouse moves a cursor instead of the camera. The cursor is a direction relative to the gun reticle (the
|
||||
* craft's boresight), at most {@code maxCursorAngleDegrees} away from it, and springs back to it when the mouse
|
||||
* rests. It is sent to the server every tick, where a virtual pilot flies the craft by it;</li>
|
||||
* <li>the camera is a chase view, looking along the reticle, or, with look ahead, towards the cursor;</li>
|
||||
* <li>holding the free look key moves the camera instead of the cursor, it eases back when released;</li>
|
||||
* <li>the mouse wheel changes how far the camera is behind the craft.</li>
|
||||
* </ul>
|
||||
* The camera is driven through the rotation of the player, which for a rider of a Valkyrien Skies ship is relative to
|
||||
* the ship, so the camera follows the ship's attitude like Valkyrien Skies does for any mounted player. Mouse
|
||||
* movement shows up as a change of that rotation, which is taken as the movement of the cursor and put back.
|
||||
*/
|
||||
@Mod.EventBusSubscriber(modid = ImprovedAerodynamics.MOD_ID, value = Dist.CLIENT)
|
||||
public final class CursorAimClient {
|
||||
private static final int CURSOR_COLOR = 0xFFFFE040;
|
||||
private static final int RETICLE_COLOR = 0xFFFFFFFF;
|
||||
private static final float MARKER_DISTANCE = 2.0F;
|
||||
private static final double LOOK_AHEAD_RATE = 6.0;
|
||||
private static final double FREE_LOOK_RETURN_RATE = 10.0;
|
||||
|
||||
private static boolean active;
|
||||
// Cursor as components along the craft's forward (gun reticle), right and up axes
|
||||
private static double cf = 1.0;
|
||||
private static double cr;
|
||||
private static double cu;
|
||||
// Camera direction in ship space and the free look offset on top of it, degrees
|
||||
private static double[] cameraDirection;
|
||||
private static double offsetYaw;
|
||||
private static double offsetPitch;
|
||||
// Rotation the player was given last frame, to see what the mouse did since
|
||||
private static boolean haveLast;
|
||||
private static float lastYaw;
|
||||
private static float lastPitch;
|
||||
private static long lastNanos;
|
||||
private static double zoom;
|
||||
private static CameraType previousCamera;
|
||||
private static Method cameraMove;
|
||||
|
||||
private CursorAimClient() {
|
||||
}
|
||||
|
||||
// ---- Toggling and the packet to the server ----
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) {
|
||||
return;
|
||||
}
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
LocalPlayer player = minecraft.player;
|
||||
while (AimKeys.TOGGLE.consumeClick()) {
|
||||
if (active) {
|
||||
deactivate(minecraft);
|
||||
} else if (player != null) {
|
||||
activate(minecraft, player);
|
||||
}
|
||||
}
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
if (player == null || !seated(player)) {
|
||||
deactivate(minecraft);
|
||||
return;
|
||||
}
|
||||
send(minecraft, true);
|
||||
}
|
||||
|
||||
private static boolean seated(LocalPlayer player) {
|
||||
return ModList.get().isLoaded("valkyrien_warium") && AimSeat.seatOf(player) != null;
|
||||
}
|
||||
|
||||
private static boolean serverHasMod(Minecraft minecraft) {
|
||||
return minecraft.getConnection() != null && Network.CHANNEL.isRemotePresent(minecraft.getConnection().getConnection());
|
||||
}
|
||||
|
||||
private static void activate(Minecraft minecraft, LocalPlayer player) {
|
||||
if (!seated(player)) {
|
||||
player.displayClientMessage(Component.translatable("message.improved_aerodynamics.cursor_aim.no_seat"), true);
|
||||
return;
|
||||
}
|
||||
if (!serverHasMod(minecraft)) {
|
||||
player.displayClientMessage(Component.translatable("message.improved_aerodynamics.cursor_aim.no_server"), true);
|
||||
return;
|
||||
}
|
||||
active = true;
|
||||
cf = 1.0;
|
||||
cr = 0.0;
|
||||
cu = 0.0;
|
||||
cameraDirection = null;
|
||||
offsetYaw = 0.0;
|
||||
offsetPitch = 0.0;
|
||||
haveLast = false;
|
||||
lastNanos = System.nanoTime();
|
||||
previousCamera = minecraft.options.getCameraType();
|
||||
minecraft.options.setCameraType(CameraType.THIRD_PERSON_BACK);
|
||||
player.displayClientMessage(Component.translatable("message.improved_aerodynamics.cursor_aim.on"), true);
|
||||
}
|
||||
|
||||
private static void deactivate(Minecraft minecraft) {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
active = false;
|
||||
send(minecraft, false);
|
||||
if (previousCamera != null) {
|
||||
minecraft.options.setCameraType(previousCamera);
|
||||
previousCamera = null;
|
||||
}
|
||||
if (minecraft.player != null) {
|
||||
minecraft.player.displayClientMessage(Component.translatable("message.improved_aerodynamics.cursor_aim.off"), true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void send(Minecraft minecraft, boolean on) {
|
||||
if (serverHasMod(minecraft)) {
|
||||
Network.CHANNEL.sendToServer(new AimPacket(on, (float) cf, (float) cr, (float) cu));
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Mouse and camera, once per frame before the world is rendered ----
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderTick(TickEvent.RenderTickEvent event) {
|
||||
if (!active || event.phase != TickEvent.Phase.START) {
|
||||
return;
|
||||
}
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
LocalPlayer player = minecraft.player;
|
||||
ClientLevel level = minecraft.level;
|
||||
if (player == null || level == null || !seated(player)) {
|
||||
return;
|
||||
}
|
||||
Entity seat = AimSeat.seatOf(player);
|
||||
AimBasis basis = AimBasis.of(level, seat.blockPosition());
|
||||
if (basis == null) {
|
||||
return;
|
||||
}
|
||||
if (minecraft.options.getCameraType() != CameraType.THIRD_PERSON_BACK) {
|
||||
minecraft.options.setCameraType(CameraType.THIRD_PERSON_BACK);
|
||||
}
|
||||
|
||||
long now = System.nanoTime();
|
||||
double dt = Mth.clamp((now - lastNanos) / 1.0E9, 0.0, 0.1);
|
||||
lastNanos = now;
|
||||
|
||||
// What the mouse did: the player's rotation moved away from what it was set to
|
||||
double deltaYaw = 0.0;
|
||||
double deltaPitch = 0.0;
|
||||
if (haveLast && minecraft.screen == null) {
|
||||
deltaYaw = Mth.wrapDegrees(player.getYRot() - lastYaw);
|
||||
deltaPitch = player.getXRot() - lastPitch;
|
||||
if (Math.abs(deltaYaw) > 90.0 || Math.abs(deltaPitch) > 90.0) {
|
||||
// Not the mouse, something reset the rotation
|
||||
deltaYaw = 0.0;
|
||||
deltaPitch = 0.0;
|
||||
}
|
||||
}
|
||||
double sensitivity = CursorAimClientConfig.MOUSE_SENSITIVITY.get();
|
||||
boolean freeLook = minecraft.screen == null && AimKeys.FREE_LOOK.isDown();
|
||||
|
||||
double spring = CursorAimClientConfig.SPRING_BACK_SECONDS.get();
|
||||
if (spring > 0.0) {
|
||||
shrinkCursor(Math.exp(-dt / spring));
|
||||
}
|
||||
if (freeLook) {
|
||||
offsetYaw += deltaYaw * sensitivity;
|
||||
offsetPitch = Mth.clamp(offsetPitch - deltaPitch * sensitivity, -89.0, 89.0);
|
||||
} else {
|
||||
moveCursor(Math.toRadians(deltaYaw * sensitivity), Math.toRadians(-deltaPitch * sensitivity));
|
||||
double decay = Math.exp(-dt * FREE_LOOK_RETURN_RATE);
|
||||
offsetYaw *= decay;
|
||||
offsetPitch *= decay;
|
||||
}
|
||||
|
||||
// Camera: along the reticle, or turned towards the cursor by the look ahead setting
|
||||
double lookAhead = CursorAimClientConfig.LOOK_AHEAD.get();
|
||||
double[] forward = basis.forward();
|
||||
if (cameraDirection == null || lookAhead <= 0.0) {
|
||||
cameraDirection = forward.clone();
|
||||
} else {
|
||||
double[] target = basis.toShip(cf, cr, cu);
|
||||
cameraDirection = turnTowards(cameraDirection, target, 1.0 - Math.exp(-lookAhead * LOOK_AHEAD_RATE * dt));
|
||||
}
|
||||
double[] direction = rotate(cameraDirection, basis.up(), -Math.toRadians(offsetYaw));
|
||||
direction = rotate(direction, rotate(basis.right(), basis.up(), -Math.toRadians(offsetYaw)), Math.toRadians(offsetPitch));
|
||||
|
||||
// Valkyrien Skies reads the view of a mounted player in ship space: yaw 0 looks along +Z
|
||||
float yaw = (float) Math.toDegrees(Math.atan2(-direction[0], direction[2]));
|
||||
float pitch = (float) Math.toDegrees(Math.atan2(-direction[1], Math.hypot(direction[0], direction[2])));
|
||||
player.setYRot(yaw);
|
||||
player.setXRot(pitch);
|
||||
player.yRotO = yaw;
|
||||
player.xRotO = pitch;
|
||||
player.yHeadRot = yaw;
|
||||
player.yHeadRotO = yaw;
|
||||
lastYaw = yaw;
|
||||
lastPitch = pitch;
|
||||
haveLast = true;
|
||||
}
|
||||
|
||||
/** Moves the cursor like a look direction: yaw towards the right, pitch up, radians. */
|
||||
private static void moveCursor(double yaw, double pitch) {
|
||||
double nf = cf * Math.cos(yaw) - cr * Math.sin(yaw);
|
||||
double nr = cf * Math.sin(yaw) + cr * Math.cos(yaw);
|
||||
cf = nf;
|
||||
cr = nr;
|
||||
nf = cf * Math.cos(pitch) - cu * Math.sin(pitch);
|
||||
double nu = cf * Math.sin(pitch) + cu * Math.cos(pitch);
|
||||
cf = nf;
|
||||
cu = nu;
|
||||
normalizeCursor();
|
||||
limitCursor();
|
||||
}
|
||||
|
||||
/** Brings the cursor back towards the reticle along the shortest way, keeping only this fraction of its angle. */
|
||||
private static void shrinkCursor(double keep) {
|
||||
double angle = Math.acos(Mth.clamp(cf, -1.0, 1.0));
|
||||
double lateral = Math.sqrt(cr * cr + cu * cu);
|
||||
if (angle < 1.0E-6 || lateral < 1.0E-9) {
|
||||
cf = 1.0;
|
||||
cr = 0.0;
|
||||
cu = 0.0;
|
||||
return;
|
||||
}
|
||||
double newAngle = angle * keep;
|
||||
double scale = Math.sin(newAngle) / lateral;
|
||||
cf = Math.cos(newAngle);
|
||||
cr *= scale;
|
||||
cu *= scale;
|
||||
}
|
||||
|
||||
private static void normalizeCursor() {
|
||||
double length = Math.sqrt(cf * cf + cr * cr + cu * cu);
|
||||
if (!(length > 1.0E-9)) {
|
||||
cf = 1.0;
|
||||
cr = 0.0;
|
||||
cu = 0.0;
|
||||
return;
|
||||
}
|
||||
cf /= length;
|
||||
cr /= length;
|
||||
cu /= length;
|
||||
}
|
||||
|
||||
private static void limitCursor() {
|
||||
double limit = Math.toRadians(CursorAimClientConfig.MAX_CURSOR_ANGLE_DEGREES.get());
|
||||
double angle = Math.acos(Mth.clamp(cf, -1.0, 1.0));
|
||||
if (angle <= limit) {
|
||||
return;
|
||||
}
|
||||
double lateral = Math.sqrt(cr * cr + cu * cu);
|
||||
if (lateral < 1.0E-9) {
|
||||
return;
|
||||
}
|
||||
double scale = Math.sin(limit) / lateral;
|
||||
cf = Math.cos(limit);
|
||||
cr *= scale;
|
||||
cu *= scale;
|
||||
}
|
||||
|
||||
// ---- Mouse wheel and camera distance ----
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onScroll(InputEvent.MouseScrollingEvent event) {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
if (!active || minecraft.screen != null) {
|
||||
return;
|
||||
}
|
||||
zoom = Mth.clamp(zoom - event.getScrollDelta() * CursorAimClientConfig.ZOOM_STEP.get(), -3.0, CursorAimClientConfig.ZOOM_MAX.get());
|
||||
event.setCanceled(true);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onCameraAngles(ViewportEvent.ComputeCameraAngles event) {
|
||||
if (!active || Math.abs(zoom) < 1.0E-3) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (cameraMove == null) {
|
||||
// Camera.move(double, double, double), moves along the camera's own axes
|
||||
cameraMove = ObfuscationReflectionHelper.findMethod(Camera.class, "m_90568_", double.class, double.class, double.class);
|
||||
}
|
||||
cameraMove.invoke(event.getCamera(), -zoom, 0.0, 0.0);
|
||||
} catch (ReflectiveOperationException | RuntimeException exception) {
|
||||
ImprovedAerodynamics.LOGGER.warn("Cursor aim camera zoom is not available", exception);
|
||||
zoom = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Drawing the cursor ----
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderLevel(RenderLevelStageEvent event) {
|
||||
if (!active || event.getStage() != RenderLevelStageEvent.Stage.AFTER_PARTICLES) {
|
||||
return;
|
||||
}
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
ClientLevel level = minecraft.level;
|
||||
LocalPlayer player = minecraft.player;
|
||||
if (level == null || player == null || !seated(player)) {
|
||||
return;
|
||||
}
|
||||
Entity seat = AimSeat.seatOf(player);
|
||||
AimBasis basis = AimBasis.of(level, seat.blockPosition());
|
||||
Ship ship = VSGameUtilsKt.getLoadedShipManagingPos(level, BlockPos.containing(seat.getX(), seat.getY(), seat.getZ()));
|
||||
if (basis == null || ship == null) {
|
||||
return;
|
||||
}
|
||||
Quaterniondc rotation = ship instanceof ClientShip clientShip
|
||||
? clientShip.getRenderTransform().getShipToWorldRotation() : ship.getTransform().getShipToWorldRotation();
|
||||
double[] cursorShip = basis.toShip(cf, cr, cu);
|
||||
Vector3d cursorWorld = rotation.transform(new Vector3d(cursorShip[0], cursorShip[1], cursorShip[2]));
|
||||
double[] forward = basis.forward();
|
||||
Vector3d reticleWorld = rotation.transform(new Vector3d(forward[0], forward[1], forward[2]));
|
||||
|
||||
Camera camera = event.getCamera();
|
||||
Matrix4f matrix = event.getPoseStack().last().pose();
|
||||
BufferBuilder builder = Tesselator.getInstance().getBuilder();
|
||||
RenderSystem.disableDepthTest();
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.defaultBlendFunc();
|
||||
RenderSystem.disableCull();
|
||||
RenderSystem.setShader(GameRenderer::getPositionColorShader);
|
||||
builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
|
||||
|
||||
float size = (float) (CursorAimClientConfig.MARKER_SIZE.get() * MARKER_DISTANCE);
|
||||
if (CursorAimClientConfig.SHOW_RETICLE.get()) {
|
||||
plus(builder, matrix, camera, reticleWorld, size * 0.35F, RETICLE_COLOR);
|
||||
}
|
||||
ring(builder, matrix, camera, cursorWorld, size, CURSOR_COLOR);
|
||||
BufferUploader.drawWithShader(builder.end());
|
||||
|
||||
RenderSystem.enableCull();
|
||||
RenderSystem.enableDepthTest();
|
||||
RenderSystem.disableBlend();
|
||||
}
|
||||
|
||||
private static Vector3f screenPoint(Camera camera, Vector3d direction) {
|
||||
Vector3f center = new Vector3f((float) direction.x, (float) direction.y, (float) direction.z);
|
||||
if (center.lengthSquared() < 1.0E-9F) {
|
||||
return null;
|
||||
}
|
||||
return center.normalize().mul(MARKER_DISTANCE);
|
||||
}
|
||||
|
||||
private static void ring(BufferBuilder builder, Matrix4f matrix, Camera camera, Vector3d direction, float radius, int color) {
|
||||
Vector3f center = screenPoint(camera, direction);
|
||||
if (center == null) {
|
||||
return;
|
||||
}
|
||||
Vector3f left = new Vector3f(camera.getLeftVector());
|
||||
Vector3f up = new Vector3f(camera.getUpVector());
|
||||
float thickness = radius * 0.12F;
|
||||
GunPipperRenderer.ring(builder, matrix, center, left, up, radius, thickness, color);
|
||||
GunPipperRenderer.quad(builder, matrix, center, left, up, thickness * 1.4F, thickness * 1.4F, color);
|
||||
}
|
||||
|
||||
private static void plus(BufferBuilder builder, Matrix4f matrix, Camera camera, Vector3d direction, float arm, int color) {
|
||||
Vector3f center = screenPoint(camera, direction);
|
||||
if (center == null) {
|
||||
return;
|
||||
}
|
||||
Vector3f left = new Vector3f(camera.getLeftVector());
|
||||
Vector3f up = new Vector3f(camera.getUpVector());
|
||||
float thickness = arm * 0.14F;
|
||||
GunPipperRenderer.quad(builder, matrix, center, left, up, arm, thickness, color);
|
||||
GunPipperRenderer.quad(builder, matrix, center, left, up, thickness, arm, color);
|
||||
}
|
||||
|
||||
// ---- Vector helpers on arrays of three ----
|
||||
|
||||
/** Rotates v about the unit axis by an angle in radians (Rodrigues). */
|
||||
private static double[] rotate(double[] v, double[] axis, double angle) {
|
||||
double c = Math.cos(angle);
|
||||
double s = Math.sin(angle);
|
||||
double dot = v[0] * axis[0] + v[1] * axis[1] + v[2] * axis[2];
|
||||
double cx = axis[1] * v[2] - axis[2] * v[1];
|
||||
double cy = axis[2] * v[0] - axis[0] * v[2];
|
||||
double cz = axis[0] * v[1] - axis[1] * v[0];
|
||||
return new double[]{
|
||||
v[0] * c + cx * s + axis[0] * dot * (1.0 - c),
|
||||
v[1] * c + cy * s + axis[1] * dot * (1.0 - c),
|
||||
v[2] * c + cz * s + axis[2] * dot * (1.0 - c)};
|
||||
}
|
||||
|
||||
/** Turns unit vector a towards unit vector b by a fraction of the angle between them. */
|
||||
private static double[] turnTowards(double[] a, double[] b, double fraction) {
|
||||
double dot = Mth.clamp(a[0] * b[0] + a[1] * b[1] + a[2] * b[2], -1.0, 1.0);
|
||||
double cx = a[1] * b[2] - a[2] * b[1];
|
||||
double cy = a[2] * b[0] - a[0] * b[2];
|
||||
double cz = a[0] * b[1] - a[1] * b[0];
|
||||
double sin = Math.sqrt(cx * cx + cy * cy + cz * cz);
|
||||
if (sin < 1.0E-9) {
|
||||
return dot > 0.0 ? b.clone() : a.clone();
|
||||
}
|
||||
double[] axis = {cx / sin, cy / sin, cz / sin};
|
||||
return rotate(a, axis, Math.acos(dot) * fraction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package dev.lumareth.improvedAerodynamics.client.aim;
|
||||
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
|
||||
/** Client side settings of cursor aim. Lives in improved_aerodynamics-cursor-aim-client.toml. */
|
||||
public final class CursorAimClientConfig {
|
||||
public static final ForgeConfigSpec SPEC;
|
||||
|
||||
public static final ForgeConfigSpec.DoubleValue LOOK_AHEAD;
|
||||
public static final ForgeConfigSpec.DoubleValue MOUSE_SENSITIVITY;
|
||||
public static final ForgeConfigSpec.DoubleValue SPRING_BACK_SECONDS;
|
||||
public static final ForgeConfigSpec.DoubleValue MAX_CURSOR_ANGLE_DEGREES;
|
||||
public static final ForgeConfigSpec.DoubleValue ZOOM_STEP;
|
||||
public static final ForgeConfigSpec.DoubleValue ZOOM_MAX;
|
||||
public static final ForgeConfigSpec.DoubleValue MARKER_SIZE;
|
||||
public static final ForgeConfigSpec.BooleanValue SHOW_RETICLE;
|
||||
|
||||
static {
|
||||
ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder();
|
||||
builder.comment("Cursor aim, client side").push("cursor_aim");
|
||||
LOOK_AHEAD = builder.comment("How strongly the camera looks into the direction of the turn. 0 never moves it, 1 (100%) turns it quickly",
|
||||
"to look where the cursor points. In between it follows more slowly")
|
||||
.defineInRange("lookAhead", 0.0, 0.0, 1.0);
|
||||
MOUSE_SENSITIVITY = builder.comment("Multiplier for how far the mouse moves the cursor (or the camera while the free look key is held)")
|
||||
.defineInRange("mouseSensitivity", 1.0, 0.05, 10.0);
|
||||
SPRING_BACK_SECONDS = builder.comment("The cursor returns to the gun reticle with this time constant in seconds when you stop moving the mouse.",
|
||||
"0 makes it stay where you left it")
|
||||
.defineInRange("springBackSeconds", 1.0, 0.0, 10.0);
|
||||
MAX_CURSOR_ANGLE_DEGREES = builder.comment("Furthest the cursor can go from the gun reticle, 135 is a 270 degree cone")
|
||||
.defineInRange("maxCursorAngleDegrees", 135.0, 10.0, 180.0);
|
||||
ZOOM_STEP = builder.comment("Blocks the camera moves per mouse wheel notch")
|
||||
.defineInRange("zoomStep", 2.0, 0.1, 20.0);
|
||||
ZOOM_MAX = builder.comment("Furthest the camera can be pulled back from its normal distance, in blocks")
|
||||
.defineInRange("zoomMax", 80.0, 0.0, 500.0);
|
||||
MARKER_SIZE = builder.comment("Radius of the cursor ring on screen")
|
||||
.defineInRange("markerSize", 0.04, 0.01, 0.3);
|
||||
SHOW_RETICLE = builder.comment("Draw a small cross where the gun reticle points, the zero of the cursor, so you can see it when the camera looks away")
|
||||
.define("showReticle", true);
|
||||
builder.pop();
|
||||
SPEC = builder.build();
|
||||
}
|
||||
|
||||
private CursorAimClientConfig() {
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -218,7 +218,7 @@ public final class GunPipperRenderer {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ring(BufferBuilder builder, Matrix4f matrix, Vector3f center, Vector3f left, Vector3f up, float radius, float thickness, int color) {
|
||||
public static void ring(BufferBuilder builder, Matrix4f matrix, Vector3f center, Vector3f left, Vector3f up, float radius, float thickness, int color) {
|
||||
final int segments = 32;
|
||||
float inner = radius - thickness;
|
||||
float outer = radius + thickness;
|
||||
@@ -232,7 +232,7 @@ public final class GunPipperRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
private static void quad(BufferBuilder builder, Matrix4f matrix, Vector3f center, Vector3f left, Vector3f up, float halfX, float halfY, int color) {
|
||||
public static void quad(BufferBuilder builder, Matrix4f matrix, Vector3f center, Vector3f left, Vector3f up, float halfX, float halfY, int color) {
|
||||
vertex(builder, matrix, center, left, up, -halfX, -halfY, color);
|
||||
vertex(builder, matrix, center, left, up, halfX, -halfY, color);
|
||||
vertex(builder, matrix, center, left, up, halfX, halfY, color);
|
||||
|
||||
Reference in new issue
Block a user