Skip to content

Commit 432415f

Browse files
authored
Block Outline Fixes (#429)
This refactors the block outline mixins to wrap the vanilla rendering instead of replace it. This has the main benefit of allowing other mods using the neoforge and fabric block outline events to work as expected on sub-levels. This is also an initial step towards fixing #403
1 parent dce0e4d commit 432415f

7 files changed

Lines changed: 347 additions & 46 deletions

File tree

common/src/main/java/dev/ryanhcode/sable/mixin/block_decal_render/LevelRendererMixin.java

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,20 @@
22

33
import com.llamalad7.mixinextras.sugar.Local;
44
import com.mojang.blaze3d.vertex.PoseStack;
5-
import com.mojang.blaze3d.vertex.VertexConsumer;
65
import dev.ryanhcode.sable.Sable;
76
import dev.ryanhcode.sable.companion.math.Pose3dc;
87
import dev.ryanhcode.sable.sublevel.ClientSubLevel;
98
import net.minecraft.client.Camera;
109
import net.minecraft.client.DeltaTracker;
11-
import net.minecraft.client.Minecraft;
1210
import net.minecraft.client.multiplayer.ClientLevel;
1311
import net.minecraft.client.renderer.GameRenderer;
1412
import net.minecraft.client.renderer.LevelRenderer;
1513
import net.minecraft.client.renderer.LightTexture;
1614
import net.minecraft.core.BlockPos;
17-
import net.minecraft.world.entity.Entity;
18-
import net.minecraft.world.level.block.state.BlockState;
1915
import net.minecraft.world.phys.Vec3;
20-
import net.minecraft.world.phys.shapes.CollisionContext;
21-
import net.minecraft.world.phys.shapes.VoxelShape;
2216
import org.jetbrains.annotations.Nullable;
2317
import org.joml.Matrix4f;
2418
import org.joml.Quaternionf;
25-
import org.joml.Vector3d;
2619
import org.spongepowered.asm.mixin.Mixin;
2720
import org.spongepowered.asm.mixin.Shadow;
2821
import org.spongepowered.asm.mixin.Unique;
@@ -33,51 +26,18 @@
3326
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
3427

3528
/**
36-
* Changes the distance block damage is rendered from, transforms block damage rendering for sublevels, and renders block hover outlines for sublevels.
29+
* Changes the distance block damage is rendered from, and transforms block damage rendering for sublevels.
3730
*/
3831
@Mixin(LevelRenderer.class)
3932
public abstract class LevelRendererMixin {
33+
4034
// Storage vectors to avoid repeated allocation
41-
private final @Unique Vector3d sable$localTranslationStorage = new Vector3d();
42-
private final @Unique Vector3d sable$globalTranslationStorage = new Vector3d();
4335
private final @Unique Quaternionf sable$orientationStorage = new Quaternionf();
4436

4537
@Shadow
4638
@Nullable
4739
private ClientLevel level;
4840

49-
@Shadow
50-
protected static void renderShape(final PoseStack arg, final VertexConsumer arg2, final VoxelShape arg3, final double d, final double e, final double f, final float g, final float h, final float i, final float j) {
51-
}
52-
53-
@Inject(method = "renderHitOutline", at = @At("HEAD"), cancellable = true)
54-
private void sable$preRenderHitOutline(final PoseStack ps, final VertexConsumer pConsumer, final Entity pEntity, final double pCamX, final double pCamY, final double pCamZ, final BlockPos blockPos, final BlockState blockState, final CallbackInfo ci) {
55-
final ClientSubLevel subLevel = (ClientSubLevel) Sable.HELPER.getContaining(this.level, blockPos);
56-
57-
if (subLevel == null) {
58-
return;
59-
}
60-
61-
ps.pushPose();
62-
63-
final Pose3dc pose = subLevel.renderPose();
64-
65-
final Vec3 cameraPos = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition();
66-
67-
final Vector3d globalTranslation = pose.position().sub(cameraPos.x, cameraPos.y, cameraPos.z, this.sable$globalTranslationStorage);
68-
final Vector3d localTranslation = this.sable$localTranslationStorage.set(blockPos.getX(), blockPos.getY(), blockPos.getZ()).sub(pose.rotationPoint());
69-
70-
// apply transforms
71-
ps.translate(globalTranslation.x, globalTranslation.y, globalTranslation.z);
72-
ps.mulPose(this.sable$orientationStorage.set(pose.orientation()));
73-
ps.translate(localTranslation.x, localTranslation.y, localTranslation.z);
74-
75-
renderShape(ps, pConsumer, blockState.getShape(this.level, blockPos, CollisionContext.of(pEntity)), 0, 0, 0, 0.0F, 0.0F, 0.0F, 0.4F);
76-
77-
ps.popPose();
78-
ci.cancel();
79-
}
80-
8141
@Inject(method = "renderLevel", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;last()Lcom/mojang/blaze3d/vertex/PoseStack$Pose;", shift = At.Shift.BEFORE))
8242
private void sable$preRenderBlockDamage(final DeltaTracker deltaTracker, final boolean bl, final Camera camera, final GameRenderer gameRenderer, final LightTexture lightTexture, final Matrix4f matrix4f, final Matrix4f matrix4f2, final CallbackInfo ci, @Local(ordinal = 0) final PoseStack ps, @Local(ordinal = 0) final BlockPos pos) {
8343

common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_sublevel_collision/CameraMixin.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ public class CameraMixin {
3232

3333
@WrapOperation(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setPosition(DDD)V"))
3434
private void sable$setPosition(final Camera instance,
35-
final double d,
36-
final double e,
37-
final double f,
35+
final double x,
36+
final double y,
37+
final double z,
3838
final Operation<Void> original,
3939
@Local(argsOnly = true) final Entity entity,
4040
@Local(argsOnly = true) final float partialTicks) {
@@ -58,7 +58,7 @@ public class CameraMixin {
5858
return;
5959
}
6060

61-
original.call(instance, d, e, f);
61+
original.call(instance, x, y, z);
6262

6363
}
6464

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package dev.ryanhcode.sable.mixinhelpers.block_outline_render;
2+
3+
import dev.ryanhcode.sable.companion.math.Pose3dc;
4+
import net.minecraft.client.Camera;
5+
import net.minecraft.core.BlockPos;
6+
import net.minecraft.world.entity.Entity;
7+
import net.minecraft.world.level.material.FogType;
8+
import net.minecraft.world.phys.Vec3;
9+
import org.jetbrains.annotations.ApiStatus;
10+
import org.jetbrains.annotations.NotNull;
11+
import org.jetbrains.annotations.Nullable;
12+
import org.joml.Quaterniond;
13+
import org.joml.Quaternionf;
14+
import org.joml.Vector3f;
15+
16+
@ApiStatus.Internal
17+
public class SubLevelCamera extends Camera {
18+
19+
private Camera renderCamera;
20+
private final Quaterniond inverseOrientation = new Quaterniond();
21+
private final Quaternionf inverseOrientationf = new Quaternionf();
22+
private final Vector3f rotationYXZ = new Vector3f();
23+
24+
private final BlockPos.MutableBlockPos blockPosition = new BlockPos.MutableBlockPos();
25+
private Vec3 pos = Vec3.ZERO;
26+
27+
public void setCamera(final Camera renderCamera) {
28+
this.renderCamera = renderCamera;
29+
}
30+
31+
public void setPose(@Nullable final Pose3dc pose) {
32+
if (pose != null) {
33+
final Vec3 pos = pose.transformPositionInverse(this.renderCamera.getPosition());
34+
35+
final Quaternionf rotation = this.rotation();
36+
this.renderCamera.rotation().mul(this.inverseOrientationf.set(pose.orientation().invert(this.inverseOrientation)), rotation);
37+
38+
this.blockPosition.set(pos.x, pos.y, pos.z);
39+
this.pos = pos;
40+
41+
rotation.getEulerAnglesYXZ(this.rotationYXZ);
42+
43+
this.getLookVector().set(0.0F, 0.0F, -1.0F).rotate(rotation);
44+
this.getUpVector().set(0.0F, 1.0F, 0.0F).rotate(rotation);
45+
this.getLeftVector().set(-1.0F, 0.0F, 0.0F).rotate(rotation);
46+
} else {
47+
this.pos = this.renderCamera.getPosition();
48+
this.blockPosition.set(this.pos.x, this.pos.y, this.pos.z);
49+
this.rotationYXZ.set(this.renderCamera.getXRot(), this.renderCamera.getYRot(), 0);
50+
51+
final Quaternionf rotation = this.rotation();
52+
rotation.set(this.renderCamera.rotation());
53+
54+
this.getLookVector().set(0.0F, 0.0F, -1.0F).rotate(rotation);
55+
this.getUpVector().set(0.0F, 1.0F, 0.0F).rotate(rotation);
56+
this.getLeftVector().set(-1.0F, 0.0F, 0.0F).rotate(rotation);
57+
}
58+
}
59+
60+
public void clear() {
61+
this.renderCamera = null;
62+
this.pos = Vec3.ZERO;
63+
}
64+
65+
@Override
66+
public @NotNull Vec3 getPosition() {
67+
return this.pos;
68+
}
69+
70+
@Override
71+
public @NotNull BlockPos getBlockPosition() {
72+
return this.blockPosition;
73+
}
74+
75+
@Override
76+
public float getXRot() {
77+
return (float) (180.0 / Math.PI * -this.rotationYXZ.x);
78+
}
79+
80+
@Override
81+
public float getYRot() {
82+
return (float) (180.0 / Math.PI * -this.rotationYXZ.y + 180.0);
83+
}
84+
85+
@Override
86+
public @NotNull Entity getEntity() {
87+
return this.renderCamera.getEntity();
88+
}
89+
90+
@Override
91+
public boolean isInitialized() {
92+
return this.renderCamera.isInitialized();
93+
}
94+
95+
@Override
96+
public boolean isDetached() {
97+
return this.renderCamera.isDetached();
98+
}
99+
100+
@Override
101+
public @NotNull NearPlane getNearPlane() {
102+
return this.renderCamera.getNearPlane();
103+
}
104+
105+
@Override
106+
public @NotNull FogType getFluidInCamera() {
107+
return this.renderCamera.getFluidInCamera();
108+
}
109+
110+
@Override
111+
public void reset() {
112+
this.renderCamera.reset();
113+
}
114+
115+
@Override
116+
public float getPartialTickTime() {
117+
return this.renderCamera.getPartialTickTime();
118+
}
119+
120+
public Camera getRenderCamera() {
121+
return this.renderCamera;
122+
}
123+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package dev.ryanhcode.sable.fabric.mixin.block_outline_render;
2+
3+
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
4+
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
5+
import com.llamalad7.mixinextras.sugar.Local;
6+
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
7+
import com.mojang.blaze3d.vertex.PoseStack;
8+
import com.mojang.blaze3d.vertex.VertexConsumer;
9+
import dev.ryanhcode.sable.Sable;
10+
import dev.ryanhcode.sable.companion.math.Pose3dc;
11+
import dev.ryanhcode.sable.mixinhelpers.block_outline_render.SubLevelCamera;
12+
import dev.ryanhcode.sable.sublevel.ClientSubLevel;
13+
import net.minecraft.client.Camera;
14+
import net.minecraft.client.multiplayer.ClientLevel;
15+
import net.minecraft.client.renderer.LevelRenderer;
16+
import net.minecraft.core.BlockPos;
17+
import net.minecraft.world.entity.Entity;
18+
import net.minecraft.world.level.block.state.BlockState;
19+
import net.minecraft.world.phys.Vec3;
20+
import org.jetbrains.annotations.Nullable;
21+
import org.joml.Quaterniondc;
22+
import org.joml.Quaternionf;
23+
import org.joml.Vector3dc;
24+
import org.spongepowered.asm.mixin.Debug;
25+
import org.spongepowered.asm.mixin.Mixin;
26+
import org.spongepowered.asm.mixin.Shadow;
27+
import org.spongepowered.asm.mixin.Unique;
28+
import org.spongepowered.asm.mixin.injection.At;
29+
import org.spongepowered.asm.mixin.injection.Inject;
30+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
31+
32+
/**
33+
* Transforms block hover outlines for sublevels.
34+
*/
35+
@Debug(export = true)
36+
@Mixin(value = LevelRenderer.class, priority = 400)
37+
// Make sure this applies first so the camera can be modified
38+
public abstract class LevelRendererMixin {
39+
40+
// Storage vectors to avoid repeated allocation
41+
private final @Unique Quaternionf sable$orientationStorage = new Quaternionf();
42+
private final @Unique SubLevelCamera sable$sublevelCamera = new SubLevelCamera();
43+
44+
@Shadow
45+
@Nullable
46+
private ClientLevel level;
47+
48+
@Inject(method = "renderLevel", at = @At("HEAD"))
49+
public void modifyCamera(final CallbackInfo ci, @Local(argsOnly = true) final LocalRef<Camera> cameraRef) {
50+
this.sable$sublevelCamera.setCamera(cameraRef.get());
51+
this.sable$sublevelCamera.setPose(null);
52+
cameraRef.set(this.sable$sublevelCamera);
53+
}
54+
55+
@Inject(method = "renderLevel", at = @At("TAIL"))
56+
public void clearCamera(final CallbackInfo ci, @Local(argsOnly = true) final LocalRef<Camera> cameraRef) {
57+
// This is important to make sure events fired after this mixin still have access to the camera
58+
cameraRef.set(this.sable$sublevelCamera.getRenderCamera());
59+
this.sable$sublevelCamera.clear();
60+
}
61+
62+
@WrapOperation(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/LevelRenderer;renderHitOutline(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/world/entity/Entity;DDDLnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V"))
63+
private void sable$preRenderHitOutline(final LevelRenderer instance, final PoseStack poseStack, final VertexConsumer consumer, final Entity entity, final double camX, final double camY, final double camZ, final BlockPos pos, final BlockState state, final Operation<Void> original, @Local(argsOnly = true) final Camera camera) {
64+
final ClientSubLevel subLevel = (ClientSubLevel) Sable.HELPER.getContaining(this.level, pos);
65+
66+
if (subLevel == null) {
67+
original.call(instance, poseStack, consumer, entity, camX, camY, camZ, pos, state);
68+
return;
69+
}
70+
71+
poseStack.pushPose();
72+
73+
final Pose3dc pose = subLevel.renderPose();
74+
75+
this.sable$sublevelCamera.setPose(pose);
76+
final Vec3 cameraPosition = this.sable$sublevelCamera.getPosition();
77+
78+
final Vector3dc position = pose.position();
79+
final Vector3dc rotationPoint = pose.rotationPoint();
80+
final Quaterniondc orientation = pose.orientation();
81+
final Vector3dc scale = pose.scale();
82+
83+
poseStack.translate(
84+
(float) (position.x() - camX),
85+
(float) (position.y() - camY),
86+
(float) (position.z() - camZ)
87+
);
88+
poseStack.mulPose(this.sable$orientationStorage.set(orientation));
89+
poseStack.translate(
90+
(float) -(rotationPoint.x() - cameraPosition.x),
91+
(float) -(rotationPoint.y() - cameraPosition.y),
92+
(float) -(rotationPoint.z() - cameraPosition.z)
93+
);
94+
poseStack.scale((float) scale.x(), (float) scale.y(), (float) scale.z());
95+
96+
original.call(instance, poseStack, consumer, entity, cameraPosition.x, cameraPosition.y, cameraPosition.z, pos, state);
97+
98+
poseStack.popPose();
99+
100+
this.sable$sublevelCamera.setPose(null);
101+
}
102+
}

fabric/src/main/resources/sable-fabric.mixins.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"minVersion": "0.8",
66
"plugin": "dev.ryanhcode.sable.plugin.SableMixinPlugin",
77
"client": [
8+
"block_outline_render.LevelRendererMixin",
89
"camera_rotation.CameraMixin",
910
"compatibility.sodiumextras.EmbyToolsMixin",
1011
"dynamic_directional_shading.SectionCompilerMixin",

0 commit comments

Comments
 (0)