Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions common/src/main/java/com/xtracr/realcamera/RealCameraCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
import com.xtracr.realcamera.renderer.MultiVertexCatcher;
import com.xtracr.realcamera.renderer.RoutingSubmitCollector;
import com.xtracr.realcamera.renderer.state.VertexData;
import com.xtracr.realcamera.util.CameraRotationResolver;
import com.xtracr.realcamera.util.CameraTransform;
import com.xtracr.realcamera.util.LocUtil;
import com.xtracr.realcamera.util.MathUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.entity.EntityRenderDispatcher;
Expand All @@ -23,11 +23,15 @@
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3d;

public final class RealCameraCore {
private static final MultiVertexCatcher vertexCatcher = MultiVertexCatcher.create();
private static final CameraTransform smoothedCamera = new CameraTransform();
private static final CameraRotationResolver rotationResolver = new CameraRotationResolver();
private static BindResult lastResult = BindResult.EMPTY, newResult = BindResult.EMPTY;
private static Object trackedLevel;
private static Entity trackedEntity;
private static boolean active = false, rendering = false;
private static int failureFrames = 0;

Expand All @@ -47,12 +51,20 @@ public static void initialize(Minecraft client, boolean advanceGameTime) {
Entity entity = client.getCameraEntity();
active = advanceGameTime && ConfigFile.config().enabled && client.options.getCameraType().isFirstPerson() && entity != null && !DisableHelper.MAIN_FEATURE.disabled(entity);
rendering = ConfigFile.config().renderModel && !DisableHelper.RENDER_MODEL.disabled(entity);
if (!active || ConfigFile.config().isClassic) {
resetRotationTracking();
} else if (trackedLevel != client.level || trackedEntity != entity) {
rotationResolver.reset();
trackedLevel = client.level;
trackedEntity = entity;
}
}

public static void reset() {
smoothedCamera.setPosition(Vec3.ZERO);
smoothedCamera.setRotation(new Matrix3f());
failureFrames = 0;
resetRotationTracking();
}

public static Vec3 getRawPos(Vec3 cameraPos, Vec3 entityPos) {
Expand All @@ -62,9 +74,8 @@ public static Vec3 getRawPos(Vec3 cameraPos, Vec3 entityPos) {
}

public static Vec3 getEulerAngle(float pitch, float yaw, float roll) {
if (!currentTarget().bindConfig().bindRotation()) return new Vec3(pitch, yaw, roll);
double scale = Math.toDegrees(1);
return MathUtil.getEulerAngleYXZ(smoothedCamera.getRotation()).multiply(scale, -scale, scale);
Vector3d result = rotationResolver.resolve(smoothedCamera.getRotation(), pitch, yaw, roll, currentTarget().bindConfig());
return new Vec3(result.x, result.y, result.z);
}

public static void computeCamera(Minecraft client, float partialTicks) {
Expand Down Expand Up @@ -93,6 +104,7 @@ public static void computeCamera(Minecraft client, float partialTicks) {
}
if (!lastResult.available() || failureFrames > retentionFrames) {
lastResult = BindResult.EMPTY;
resetRotationTracking();
active = false;
return;
}
Expand Down Expand Up @@ -166,4 +178,10 @@ private static void computeBindResult(BuiltIterableBuffer builtBuffer) {
return;
}
}

private static void resetRotationTracking() {
rotationResolver.reset();
trackedLevel = null;
trackedEntity = null;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.xtracr.realcamera.config;

import com.google.gson.annotations.JsonAdapter;
import com.xtracr.realcamera.config.codec.BindConfigAdapter;

import java.util.List;
import java.util.function.Predicate;

Expand Down Expand Up @@ -50,6 +53,23 @@ public DisableConfig[] filteredDisableConfigs(Predicate<DisableConfig> filter) {
public record TargetConfig(float forwardU, float forwardV, float upwardU, float upwardV, float posU, float posV) {
}

public record BindConfig(boolean bindX, boolean bindY, boolean bindZ, boolean bindRotation) {
@JsonAdapter(BindConfigAdapter.class)
public record BindConfig(boolean bindX, boolean bindY, boolean bindZ, boolean bindPitch, boolean bindYaw, boolean bindRoll) {
public BindConfig(boolean bindX, boolean bindY, boolean bindZ, boolean bindRotation) {
this(bindX, bindY, bindZ, bindRotation, bindRotation, bindRotation);
}

/** Retains the legacy all-rotation accessor contract. */
public boolean bindRotation() {
return bindPitch && bindYaw && bindRoll;
}

public boolean bindAnyRotation() {
return bindPitch || bindYaw || bindRoll;
}

public boolean bindNoRotation() {
return !bindAnyRotation();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.xtracr.realcamera.config.codec;

import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.xtracr.realcamera.config.BindTarget.BindConfig;

import java.io.IOException;

public final class BindConfigAdapter extends TypeAdapter<BindConfig> {
@Override
public void write(JsonWriter out, BindConfig value) throws IOException {
out.beginObject();
out.name("bindX").value(value.bindX());
out.name("bindY").value(value.bindY());
out.name("bindZ").value(value.bindZ());
out.name("bindRotation").value(value.bindRotation());
out.name("bindPitch").value(value.bindPitch());
out.name("bindYaw").value(value.bindYaw());
out.name("bindRoll").value(value.bindRoll());
out.endObject();
}

@Override
public BindConfig read(JsonReader in) throws IOException {
boolean bindX = false, bindY = false, bindZ = false, legacyRotation = false;
Boolean bindPitch = null, bindYaw = null, bindRoll = null;

in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "bindX" -> bindX = in.nextBoolean();
case "bindY" -> bindY = in.nextBoolean();
case "bindZ" -> bindZ = in.nextBoolean();
case "bindRotation" -> legacyRotation = in.nextBoolean();
case "bindPitch" -> bindPitch = in.nextBoolean();
case "bindYaw" -> bindYaw = in.nextBoolean();
case "bindRoll" -> bindRoll = in.nextBoolean();
default -> in.skipValue();
}
}
in.endObject();

return new BindConfig(
bindX,
bindY,
bindZ,
bindPitch == null ? legacyRotation : bindPitch,
bindYaw == null ? legacyRotation : bindYaw,
bindRoll == null ? legacyRotation : bindRoll
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
import java.util.zip.InflaterInputStream;

public final class ConfigCodec {
static final short CURRENT_VERSION = 703;
static final short CURRENT_VERSION = 704;
static final Short2ReferenceMap<StreamCodec<ByteBuf, BindTarget>> CODECS = Short2ReferenceMap.ofEntries(
Short2ReferenceMap.entry(CURRENT_VERSION, ConfigCodec703.CODEC)
Short2ReferenceMap.entry((short) 703, ConfigCodec703.CODEC),
Short2ReferenceMap.entry(CURRENT_VERSION, ConfigCodec704.CODEC)
);

public static BindTarget readWithVersion(ByteBuf byteBuf) throws DecoderException, IllegalArgumentException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.xtracr.realcamera.config.codec;

import com.xtracr.realcamera.config.BindTarget;
import com.xtracr.realcamera.config.BindTarget.BindConfig;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;

final class ConfigCodec704 {
static final StreamCodec<ByteBuf, BindConfig> BIND_CONFIG_CODEC = StreamCodec.composite(
ByteBufCodecs.BYTE,
bindConfig -> {
byte bindFlags = 0;
if (bindConfig.bindX()) bindFlags |= 0x01;
if (bindConfig.bindY()) bindFlags |= 0x02;
if (bindConfig.bindZ()) bindFlags |= 0x04;
if (bindConfig.bindPitch()) bindFlags |= 0x08;
if (bindConfig.bindYaw()) bindFlags |= 0x10;
if (bindConfig.bindRoll()) bindFlags |= 0x20;
return bindFlags;
},
bindFlags -> new BindConfig(
(bindFlags & 0x01) != 0,
(bindFlags & 0x02) != 0,
(bindFlags & 0x04) != 0,
(bindFlags & 0x08) != 0,
(bindFlags & 0x10) != 0,
(bindFlags & 0x20) != 0
)
);

static final StreamCodec<ByteBuf, BindTarget> CODEC = StreamCodec.composite(
ByteBufCodecs.STRING_UTF8, BindTarget::name,
ByteBufCodecs.STRING_UTF8, BindTarget::textureId,
ByteBufCodecs.VAR_INT, BindTarget::priority,
ByteBufCodecs.FLOAT, BindTarget::disablingDepth,
ConfigCodec703.TARGET_CONFIG_CODEC, BindTarget::targetConfig,
BIND_CONFIG_CODEC, BindTarget::bindConfig,
ConfigCodec703.OFFSET_CONFIG_CODEC, BindTarget::offsets,
ConfigCodec703.DISABLE_CONFIGS_CODEC, BindTarget::disableConfigs,
BindTarget::new
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import net.minecraft.client.gui.layouts.FrameLayout;
import net.minecraft.client.gui.layouts.GridLayout;
import net.minecraft.client.gui.layouts.LayoutSettings;
import net.minecraft.client.gui.layouts.LinearLayout;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.screens.Screen;
Expand Down Expand Up @@ -84,7 +85,9 @@ public final class ModelViewScreen extends Screen {
private final CycleIconButton bindXButton = new CycleIconButton(16, 16, 1, 2);
private final CycleIconButton bindYButton = new CycleIconButton(16, 16, 0, 2);
private final CycleIconButton bindZButton = new CycleIconButton(16, 16, 1, 2);
private final CycleIconButton bindRotButton = new CycleIconButton(16, 16, 1, 2);
private final CycleIconButton bindPitchButton = new CycleIconButton(16, 16, 1, 2);
private final CycleIconButton bindYawButton = new CycleIconButton(16, 16, 1, 2);
private final CycleIconButton bindRollButton = new CycleIconButton(16, 16, 1, 2);
private final DoubleSlider entityPitchSlider = createSlider("pitch", wideWidgetWidth, -90.0, 90.0);
private final DoubleSlider entityYawSlider = createSlider("yaw", wideWidgetWidth, -60.0, 60.0);
private final NumberWidgetPair offsetXPair = new NumberWidgetPair(font, "offsetX", compactWidgetWidth, widgetHeight, ModConfig.MIN_OFFSET_F, ModConfig.MAX_OFFSET_F);
Expand All @@ -97,6 +100,7 @@ public final class ModelViewScreen extends Screen {
private final List<UVRectangleWidget> rectWidgets = new ArrayList<>();
private final Map<String, Set<String>> hiddenNameMap = new HashMap<>();
private final List<NumberWidgetPair> widgetPairs = List.of(offsetXPair, offsetYPair, offsetZPair, offsetPitchPair, offsetYawPair, offsetRollPair);
private final SimpleIconButton resetOffsetsButton = new SimpleIconButton(0, 0, _ -> widgetPairs.forEach(pair -> pair.setNumber(0)));
private final CycleButton<Integer> selectingButton = createCyclingButtonBuilder(ImmutableSortedMap.of(
0, LocUtil.MODEL_VIEW_WIDGET("forwardVector").withStyle(ChatFormatting.GREEN),
1, LocUtil.MODEL_VIEW_WIDGET("upwardVector").withStyle(ChatFormatting.RED),
Expand Down Expand Up @@ -133,6 +137,14 @@ public final class ModelViewScreen extends Screen {

public ModelViewScreen() {
super(LocUtil.MODEL_VIEW_TITLE());
configureBindButton(bindXButton, LocUtil.literal("X"), false);
configureBindButton(bindYButton, LocUtil.literal("Y"), false);
configureBindButton(bindZButton, LocUtil.literal("Z"), false);
configureBindButton(bindPitchButton, LocUtil.CONFIG_OPTION("pitch"), true);
configureBindButton(bindYawButton, LocUtil.CONFIG_OPTION("yaw"), true);
configureBindButton(bindRollButton, LocUtil.CONFIG_OPTION("roll"), true);
resetOffsetsButton.setMessage(LocUtil.MODEL_VIEW_WIDGET("resetOffsets"));
resetOffsetsButton.setTooltip(createTooltip("resetOffsets"));
}

@Override
Expand Down Expand Up @@ -200,16 +212,20 @@ private void initLeftWidgets() {
case PREVIEW -> {
rows.addChild(toggleSliderButton, 2);
LayoutSettings numericControlSettings = grid.newCellSettings().padding(-20, 2, 0, 0);
rows.addChild(bindXButton, smallSettings).setTooltip(createTooltip("bindButtons"));
rows.addChild(bindXButton, smallSettings);
rows.addChild(offsetXPair, numericControlSettings);
rows.addChild(bindYButton, smallSettings).setTooltip(createTooltip("bindButtons"));
rows.addChild(bindYButton, smallSettings);
rows.addChild(offsetYPair, numericControlSettings);
rows.addChild(bindZButton, smallSettings).setTooltip(createTooltip("bindButtons"));
rows.addChild(bindZButton, smallSettings);
rows.addChild(offsetZPair, numericControlSettings);
rows.addChild(bindRotButton, smallSettings).setTooltip(createTooltip("bindButtons"));
rows.addChild(bindPitchButton, smallSettings);
rows.addChild(offsetPitchPair, numericControlSettings);
rows.addChild(offsetYawPair, 2, grid.newCellSettings().padding(26, 2, 0, 0));
rows.addChild(new SimpleIconButton(0, 0, _ -> widgetPairs.forEach(pair -> pair.setNumber(0))), smallSettings);
rows.addChild(bindYawButton, smallSettings);
rows.addChild(offsetYawPair, numericControlSettings);
LinearLayout rollButtons = LinearLayout.horizontal().spacing(2);
rollButtons.addChild(bindRollButton);
rollButtons.addChild(resetOffsetsButton);
rows.addChild(rollButtons, smallSettings);
rows.addChild(offsetRollPair, numericControlSettings);
rows.addChild(scaleField, smallSettings).setTooltip(createTooltip("scale"));
rows.addChild(depthField, smallSettings).setTooltip(createTooltip("depth"));
Expand Down Expand Up @@ -658,7 +674,10 @@ private void loadBindTarget(BindTarget target) {
bindXButton.setValue(target.bindConfig().bindX() ? 0 : 1);
bindYButton.setValue(target.bindConfig().bindY() ? 0 : 1);
bindZButton.setValue(target.bindConfig().bindZ() ? 0 : 1);
bindRotButton.setValue(target.bindConfig().bindRotation() ? 0 : 1);
bindPitchButton.setValue(target.bindConfig().bindPitch() ? 0 : 1);
bindYawButton.setValue(target.bindConfig().bindYaw() ? 0 : 1);
bindRollButton.setValue(target.bindConfig().bindRoll() ? 0 : 1);
updateBindButtonMessages();
OffsetConfig offsets = target.offsets();
scaleField.setNumber(offsets.scale);
offsetXPair.setNumber(offsets.x);
Expand All @@ -680,7 +699,14 @@ private BindTarget genBindTarget() {
newDisableConfigs.set(i, currentDisableConfig);
}
TargetConfig targetConfig = new TargetConfig(forwardUField.getNumber(), forwardVField.getNumber(), upwardUField.getNumber(), upwardVField.getNumber(), posUField.getNumber(), posVField.getNumber());
BindConfig bindConfig = new BindConfig(bindXButton.getValue() == 0, bindYButton.getValue() == 0, bindZButton.getValue() == 0, bindRotButton.getValue() == 0);
BindConfig bindConfig = new BindConfig(
bindXButton.getValue() == 0,
bindYButton.getValue() == 0,
bindZButton.getValue() == 0,
bindPitchButton.getValue() == 0,
bindYawButton.getValue() == 0,
bindRollButton.getValue() == 0
);
OffsetConfig offsets = new OffsetConfig(scaleField.getNumber(), offsetXPair.getNumber(), offsetYPair.getNumber(), offsetZPair.getNumber(), offsetPitchPair.getNumber(), offsetYawPair.getNumber(), offsetRollPair.getNumber());
return new BindTarget(nameField.getValue(), textureIdField.getValue(), priorityField.getNumber(), depthField.getNumber(), targetConfig, bindConfig, offsets, newDisableConfigs);
}
Expand All @@ -701,6 +727,26 @@ private Tooltip createTooltip(String key, Object... args) {
return Tooltip.create(LocUtil.MODEL_VIEW_TOOLTIP(key, args));
}

private void configureBindButton(CycleIconButton button, Component axis, boolean rotation) {
button.setOnValueChange(_ -> updateBindButtonMessage(button, axis, rotation));
updateBindButtonMessage(button, axis, rotation);
}

private void updateBindButtonMessages() {
updateBindButtonMessage(bindXButton, LocUtil.literal("X"), false);
updateBindButtonMessage(bindYButton, LocUtil.literal("Y"), false);
updateBindButtonMessage(bindZButton, LocUtil.literal("Z"), false);
updateBindButtonMessage(bindPitchButton, LocUtil.CONFIG_OPTION("pitch"), true);
updateBindButtonMessage(bindYawButton, LocUtil.CONFIG_OPTION("yaw"), true);
updateBindButtonMessage(bindRollButton, LocUtil.CONFIG_OPTION("roll"), true);
}

private void updateBindButtonMessage(CycleIconButton button, Component axis, boolean rotation) {
Component state = LocUtil.MODEL_VIEW_WIDGET(button.getValue() == 0 ? "enabled" : "disabled");
button.setMessage(LocUtil.MODEL_VIEW_WIDGET(rotation ? "bindRotationAxis" : "bindPositionAxis", axis, state));
button.setTooltip(createTooltip(rotation ? "bindRotationButton" : "bindPositionButton", axis));
}

private UVRectangleWidget createRectWidget(UVRectangle rect) {
return new UVRectangleWidget(rect.uMin(), rect.vMin(), rect.uMax(), rect.vMax());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.xtracr.realcamera.util;

import com.xtracr.realcamera.config.BindTarget.BindConfig;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix3f;
import org.joml.Vector3d;

/** Resolves each camera Y-X-Z coordinate from the adjusted model candidate or vanilla camera. */
public final class CameraRotationResolver {
private final ContinuousEulerAngleTracker tracker = new ContinuousEulerAngleTracker();

public Vector3d resolve(
Matrix3f adjustedModelRotation,
double vanillaPitch,
double vanillaYaw,
double vanillaRoll,
BindConfig bindConfig
) {
Vector3d reference = new Vector3d(
Math.toRadians(vanillaPitch),
Math.toRadians(-vanillaYaw),
Math.toRadians(vanillaRoll)
);
Vector3d tracked = tracker.update(adjustedModelRotation, reference);

if (bindConfig.bindNoRotation()) {
return new Vector3d(vanillaPitch, vanillaYaw, vanillaRoll);
}
if (bindConfig.bindRotation()) {
double scale = Math.toDegrees(1);
Vec3 existing = MathUtil.getEulerAngleYXZ(adjustedModelRotation).multiply(scale, -scale, scale);
return new Vector3d(existing.x, existing.y, existing.z);
}

double modelPitch = Math.toDegrees(tracked.x);
double modelYaw = -Math.toDegrees(tracked.y);
double modelRoll = Math.toDegrees(tracked.z);
return new Vector3d(
bindConfig.bindPitch() ? modelPitch : vanillaPitch,
bindConfig.bindYaw() ? modelYaw : vanillaYaw,
bindConfig.bindRoll() ? modelRoll : vanillaRoll
);
}

public void reset() {
tracker.reset();
}
}
Loading