portal guns second pass

make more apis public for boogaloo
This commit is contained in:
craisinlord committed 2026-10-03 14:33:44 -05:00
1 parent c7cb48edfb
commit be9b18c4f5
25 files changed
+730 -197

No files matched your search

@@ -8,6 +8,10 @@ import java.util.Deque;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.function.Predicate;
import net.minecraft.core.Holder;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.effect.MobEffect;
@@ -17,6 +21,9 @@ import net.minecraft.world.entity.LivingEntity;
public final class ContractionAfterimages {
public static final int MAX_SAMPLES = 10;
public static final int SAMPLE_LIFETIME_TICKS = 13;
public static final int DEFAULT_COLOR = 0xFFD35A;
private static final List<Predicate<LivingEntity>> ACTIVATORS = new CopyOnWriteArrayList<>();
private static final List<Function<LivingEntity, Integer>> COLOR_PROVIDERS = new CopyOnWriteArrayList<>();
private static final Map<LivingEntity, Deque<Sample>> HISTORY = new WeakHashMap<>();
@@ -51,13 +58,45 @@ public final class ContractionAfterimages {
return HISTORY.get(entity);
}
public static void addActivator(Predicate<LivingEntity> activator) {
ACTIVATORS.add(activator);
}
public static void addColorProvider(Function<LivingEntity, Integer> provider) {
COLOR_PROVIDERS.add(provider);
}
public static int colorFor(Entity entity) {
if (!(entity instanceof LivingEntity living)) {
return DEFAULT_COLOR;
}
for (Function<LivingEntity, Integer> provider : COLOR_PROVIDERS) {
Integer color = provider.apply(living);
if (color != null) {
return color & 0xFFFFFF;
}
}
return DEFAULT_COLOR;
}
public static boolean isActive(LivingEntity living) {
Holder<MobEffect> holder = RoyalEffectHooks.contractedHolder();
boolean contracted = holder != null && living.hasEffect(holder);
boolean tuned = living instanceof net.minecraft.world.entity.player.Player player
&& TemporalTunerItem.isAvailable(player)
&& TimeDilationApi.getRate(living) > 1.0D;
return !living.isInvisible() && (contracted || tuned);
if (living.isInvisible()) {
return false;
}
if (contracted || tuned) {
return true;
}
for (Predicate<LivingEntity> activator : ACTIVATORS) {
if (activator.test(living)) {
return true;
}
}
return false;
}
public static float fade(Sample sample, int now) {
@@ -10,12 +10,14 @@ import java.util.UUID;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.RenderBuffers;
import net.minecraft.client.renderer.chunk.RenderRegionCache;
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
public final class PortalGunPortalRendererPool {
private static final int MAX_RENDERERS = 8;
private static final long PORTAL_SECTION_BUDGET_NANOS = 4_166_666L;
private static final RenderBuffers PORTAL_RENDER_BUFFERS = new RenderBuffers(2);
private static final LinkedHashMap<UUID, PortalRenderer> RENDERERS = new LinkedHashMap<>(8, 0.75F, true);
private static final Set<LevelRenderer> PROXY_RENDERERS = java.util.Collections.newSetFromMap(new IdentityHashMap<>());
private static final ThreadLocal<Long> SECTION_BUDGET_DEADLINE = ThreadLocal.withInitial(() -> 0L);
@@ -37,7 +39,7 @@ public final class PortalGunPortalRendererPool {
minecraft,
minecraft.getEntityRenderDispatcher(),
minecraft.getBlockEntityRenderDispatcher(),
minecraft.renderBuffers()
PORTAL_RENDER_BUFFERS
);
PROXY_RENDERERS.add(renderer);
renderer.setLevel(minecraft.level);
@@ -56,13 +58,11 @@ public final class PortalGunPortalRendererPool {
return PROXY_RENDERERS.contains(renderer);
}
public static void beginFrame() {
SECTION_BUDGET_DEADLINE.set(System.nanoTime() + PORTAL_SECTION_BUDGET_NANOS);
SCHEDULED_SECTION_REBUILDS.set(0);
SKIP_SET_NOT_DIRTY.set(false);
public static net.minecraft.client.renderer.MultiBufferSource.BufferSource bufferSource() {
return PORTAL_RENDER_BUFFERS.bufferSource();
}
public static void beginSceneBudget() {
public static void beginFrame() {
SECTION_BUDGET_DEADLINE.set(System.nanoTime() + PORTAL_SECTION_BUDGET_NANOS);
SCHEDULED_SECTION_REBUILDS.set(0);
SKIP_SET_NOT_DIRTY.set(false);
@@ -35,6 +35,9 @@ public final class PortalGunPortalViewAreaManager {
}
public static Scope enter(Minecraft minecraft, LevelRenderer renderer, UUID destinationId, Vec3 cameraPos, net.minecraft.client.Camera portalCamera) {
if (SodiumCompat.isLoaded()) {
return new SodiumScope(SodiumCompat.beginPortalView());
}
LevelRendererPortalViewAreaAccessor accessor = (LevelRendererPortalViewAreaAccessor) renderer;
int viewDistance = minecraft.options.getEffectiveRenderDistance();
if (ownerLevel != minecraft.level || ownerViewDistance != viewDistance) {
@@ -42,7 +45,6 @@ public final class PortalGunPortalViewAreaManager {
ownerLevel = minecraft.level;
ownerViewDistance = viewDistance;
}
Scope sodiumScope = SodiumCompat.isLoaded() ? new SodiumScope(SodiumCompat.beginPortalView(portalCamera)) : null;
ViewKey viewKey = new ViewKey(renderer, destinationId);
ViewState state = VIEW_AREAS.get(viewKey);
if (state == null) {
@@ -80,7 +82,7 @@ public final class PortalGunPortalViewAreaManager {
accessor.antarchy$setLastCameraSectionX(sectionX);
accessor.antarchy$setLastCameraSectionY(sectionY);
accessor.antarchy$setLastCameraSectionZ(sectionZ);
return sodiumScope == null ? scope : new CombinedScope(scope, sodiumScope);
return scope;
}
public static boolean routeSectionCompiled(SectionRenderDispatcher.RenderSection section) {
@@ -231,25 +233,6 @@ public final class PortalGunPortalViewAreaManager {
void close();
}
private static final class CombinedScope implements Scope {
private final Scope viewAreaScope;
private final Scope sodiumScope;
private CombinedScope(Scope viewAreaScope, Scope sodiumScope) {
this.viewAreaScope = viewAreaScope;
this.sodiumScope = sodiumScope;
}
@Override
public void close() {
try {
viewAreaScope.close();
} finally {
sodiumScope.close();
}
}
}
private static final class SodiumScope implements Scope {
private final SodiumCompat.TerrainScope terrainScope;
@@ -22,6 +22,7 @@ import com.mojang.blaze3d.vertex.MeshData;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.Tesselator;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.blaze3d.vertex.VertexSorting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -43,7 +44,9 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix4f;
import org.joml.Matrix4fStack;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.joml.Quaternionf;
public final class PortalGunPortalViewRenderer {
@@ -58,7 +61,6 @@ public final class PortalGunPortalViewRenderer {
private static final ResourceLocation BLUE_OVERLAY = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/vfx/portal_gun_portal_blue.png");
private static final ResourceLocation ORANGE_OVERLAY = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/vfx/portal_gun_portal_orange.png");
private static final TextureTarget[] PORTAL_TARGETS = new TextureTarget[MAX_RECURSION_DEPTH + 1];
private static TextureTarget depthBackup;
private static ShaderInstance portalViewShader;
private static boolean renderingPortalView;
private static boolean loggedRendererActivation;
@@ -87,6 +89,7 @@ public final class PortalGunPortalViewRenderer {
public static void releaseTargets() {
PortalSceneRenderTrace.reset();
PortalGunPortalVisibilityQueries.clear();
for (int i = 0; i < PORTAL_TARGETS.length; i++) {
TextureTarget target = PORTAL_TARGETS[i];
if (target != null) {
@@ -96,10 +99,6 @@ public final class PortalGunPortalViewRenderer {
}
}
java.util.Arrays.fill(TARGET_ALLOCATION_FAILURES, null);
if (depthBackup != null) {
depthBackup.destroyBuffers();
depthBackup = null;
}
}
public static void render(Camera camera, Matrix4f poseMatrix, Matrix4f projectionMatrix, DeltaTracker tickCounter) {
@@ -125,7 +124,7 @@ public final class PortalGunPortalViewRenderer {
Vec3 cameraPos = camera.getPosition();
Vec3 rootLook = new Vec3(camera.getLookVector()).normalize();
Vec3 rootUp = new Vec3(camera.getUpVector()).normalize();
Matrix4f rootViewMatrix = new Matrix4f().rotation(PortalGunTransformUtil.orientationQuaternion(rootLook, rootUp).conjugate(new Quaternionf()));
Matrix4f rootViewMatrix = new Matrix4f(poseMatrix);
List<PortalGunPortalEntity> portals = collectVisiblePortals(minecraft, cameraPos);
if (portals.isEmpty()) {
return;
@@ -135,10 +134,6 @@ public final class PortalGunPortalViewRenderer {
loggedMissingPortalViewShader = true;
Antarchy.LOGGER.error("Portal gun destination view shader is unavailable; skipping destination render");
}
minecraft.getMainRenderTarget().bindWrite(true);
for (PortalGunPortalEntity portal : portals) {
drawPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal);
}
return;
}
Vec3 effectiveCameraPos = cameraPos;
@@ -168,16 +163,8 @@ public final class PortalGunPortalViewRenderer {
return linked != null && linked.isAlive();
})
.sorted((left, right) -> Integer.compare(rootScreenClips.get(right).area(), rootScreenClips.get(left).area()))
.limit(MAX_PORTAL_VIEWS_PER_FRAME)
.toList();
Set<PortalGunPortalEntity> selectedPortalSet = Collections.newSetFromMap(new IdentityHashMap<>());
selectedPortalSet.addAll(selectedPortals);
List<PortalGunPortalEntity> renderOrder = new ArrayList<>(portals.size());
renderOrder.addAll(selectedPortals);
for (PortalGunPortalEntity portal : portals) {
if (!selectedPortalSet.contains(portal)) {
renderOrder.add(portal);
}
}
long linkedPortalCount = renderablePortals.stream()
.filter(portal -> portal.getLinkedPortal() != null && portal.getLinkedPortal().isAlive())
.count();
@@ -210,47 +197,43 @@ public final class PortalGunPortalViewRenderer {
);
}
if (selectedPortals.isEmpty()) {
minecraft.getMainRenderTarget().bindWrite(true);
for (PortalGunPortalEntity portal : portals) {
drawPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal);
}
return;
}
RenderContext rootContext = new RenderContext(effectiveCameraPos, rootLook, rootUp, rootViewMatrix);
PortalViewBudget viewBudget = new PortalViewBudget(MAX_PORTAL_VIEWS_PER_FRAME);
boolean directStencil = PortalStencilTarget.supportsDirect(minecraft.getMainRenderTarget());
PortalViewBudget fallbackBudget = directStencil ? null : new PortalViewBudget(MAX_PORTAL_VIEWS_PER_FRAME);
renderingPortalView = true;
boolean projectionBackedUp = false;
try {
RenderSystem.backupProjectionMatrix();
projectionBackedUp = true;
for (PortalGunPortalEntity portal : renderOrder) {
if (!selectedPortalSet.contains(portal)) {
minecraft.getMainRenderTarget().bindWrite(true);
drawPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal);
continue;
}
for (PortalGunPortalEntity portal : selectedPortals) {
PortalGunPortalEntity linkedPortal = portal.getLinkedPortal();
if (linkedPortal == null || !linkedPortal.isAlive()) {
minecraft.getMainRenderTarget().bindWrite(true);
drawPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal);
continue;
}
boolean rendered;
try {
rendered = renderPortalSurfaceDirect(minecraft, cameraEntity, tickCounter, rootContext, rootProjectionMatrix, portal, linkedPortal, rootScreenClips.get(portal));
if (directStencil) {
rendered = renderPortalSurfaceDirect(minecraft, cameraEntity, tickCounter, rootContext, rootProjectionMatrix, portal, linkedPortal, rootScreenClips.get(portal));
} else {
int textureId = renderPortalSurface(minecraft, cameraEntity, tickCounter, fallbackBudget, rootContext,
rootProjectionMatrix, portal, linkedPortal, 0, rootScreenClips.get(portal));
minecraft.gameRenderer.resetProjectionMatrix(rootProjectionMatrix);
minecraft.getMainRenderTarget().bindWrite(true);
rendered = textureId >= 0 && drawPortalQuad(cameraPos, rootViewMatrix, rootProjectionMatrix,
minecraft.getMainRenderTarget().width, minecraft.getMainRenderTarget().height,
portal, partialTick, textureId, rootScreenClips.get(portal));
}
} catch (RuntimeException exception) {
logRenderFailure(minecraft, exception);
RenderSystem.restoreProjectionMatrix();
projectionBackedUp = false;
RenderSystem.backupProjectionMatrix();
projectionBackedUp = true;
minecraft.getMainRenderTarget().bindWrite(true);
drawPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal);
continue;
}
if (!rendered) {
minecraft.getMainRenderTarget().bindWrite(true);
drawPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal);
continue;
}
RenderSystem.restoreProjectionMatrix();
@@ -258,14 +241,8 @@ public final class PortalGunPortalViewRenderer {
RenderSystem.backupProjectionMatrix();
projectionBackedUp = true;
minecraft.getMainRenderTarget().bindWrite(true);
ScreenClip clip = rootScreenClips.get(portal);
String portalPairKey = portal.getUUID() + "->" + linkedPortal.getUUID();
boolean composited;
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "direct-framebuffer", "target=" + targetWidth + "x" + targetHeight)) {
composited = true;
drawAnimatedPortalOverlay(minecraft, cameraPos, rootViewMatrix, portal);
}
if (composited && loggedPortalViewComposites.add(portalPairKey)) {
if (loggedPortalViewComposites.add(portalPairKey)) {
Antarchy.LOGGER.info("Portal gun destination view rendered directly source={} destination={} target={}x{}", portal.getId(), linkedPortal.getId(), targetWidth, targetHeight);
}
}
@@ -386,14 +363,10 @@ public final class PortalGunPortalViewRenderer {
}
int width = target.width;
int height = target.height;
if (depthBackup == null) {
depthBackup = new TextureTarget(width, height, true, Minecraft.ON_OSX);
} else if (depthBackup.width != width || depthBackup.height != height) {
depthBackup.resize(width, height, Minecraft.ON_OSX);
}
RenderContext transformedContext = transformContext(currentContext, sourcePortal, destinationPortal);
float partialTick = tickCounter.getGameTimeDeltaPartialTick(true);
Matrix4f clippedProjection = applyPortalClipPlane(baseProjectionMatrix, transformedContext, destinationPortal);
Matrix4f apertureCullProjection = cropProjectionToScreenClip(clippedProjection, clip, width, height);
PortalCamera portalCamera = new PortalCamera();
portalCamera.setup(minecraft.level, cameraEntity, partialTick, transformedContext.cameraPos(), transformedContext.look(), transformedContext.up());
String portalPairKey = sourcePortal.getUUID() + "->" + destinationPortal.getUUID();
@@ -407,6 +380,8 @@ public final class PortalGunPortalViewRenderer {
RenderTarget previousTransparencyTarget = transparencyTarget == null ? null : transparencyTarget.antarchy$getScreenTarget();
PortalStencilTarget.Scope stencilScope = null;
boolean contextPushed = false;
boolean renderDestination = PortalGunPortalVisibilityQueries.shouldRender(sourcePortal.getUUID(), destinationPortal.getUUID());
PortalSceneRenderTrace.event(portalPairKey, "destination-render-decision", "render=" + renderDestination + " clip=" + clip.width() + "x" + clip.height());
try {
if (entityEffectTarget != null) {
entityEffectTarget.antarchy$setScreenTarget(target);
@@ -417,22 +392,33 @@ public final class PortalGunPortalViewRenderer {
minecraft.gameRenderer.resetProjectionMatrix(clippedProjection);
int maskTexture = minecraft.getTextureManager().getTexture(BLUE_OVERLAY).getId();
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "direct-stencil-mask", "clip=" + clip.width() + "x" + clip.height())) {
stencilScope = PortalStencilTarget.beginDirect(target, depthBackup,
() -> drawPortalQuad(currentContext.cameraPos(), currentContext.viewMatrix(), baseProjectionMatrix, width, height, sourcePortal, partialTick, maskTexture, clip));
stencilScope = PortalStencilTarget.beginDirect(target, () -> {
boolean queryStarted = PortalGunPortalVisibilityQueries.begin(sourcePortal.getUUID(), destinationPortal.getUUID());
try {
drawPortalQuad(currentContext.cameraPos(), currentContext.viewMatrix(), baseProjectionMatrix, width, height, sourcePortal, partialTick, maskTexture, clip);
} finally {
PortalGunPortalVisibilityQueries.end(sourcePortal.getUUID(), destinationPortal.getUUID(), queryStarted);
}
},
() -> clearDepthInPortalStencil(minecraft),
() -> restorePortalApertureDepth(minecraft, currentContext.cameraPos(), currentContext.viewMatrix(), baseProjectionMatrix, sourcePortal, partialTick));
}
if (!renderDestination) {
return false;
}
RenderSystem.enableScissor(clip.x(), clip.y(), clip.width(), clip.height());
PortalGunPortalRenderState.pushPortalView(new PortalGunPortalRenderState.PortalRenderContext(
sourcePortal.getWorldPortalShape(), destinationPortal.getWorldPortalShape(), transformedContext.cameraPos(),
transformedContext.look(), transformedContext.up(), transformedContext.viewMatrix(), clippedProjection,
0, true, destinationPortal.getUUID()));
0, false, destinationPortal.getUUID()));
contextPushed = true;
proxyRenderer.prepareCullFrustum(portalCamera.getPosition(), transformedContext.viewMatrix(), clippedProjection);
proxyRenderer.prepareCullFrustum(portalCamera.getPosition(), transformedContext.viewMatrix(), apertureCullProjection);
PortalGunPortalViewAreaManager.Scope areaScope = PortalGunPortalViewAreaManager.enter(
minecraft, proxyRenderer, destinationPortal.getUUID(), transformedContext.cameraPos(), portalCamera);
try {
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "direct-manual-scene", "target=" + width + "x" + height)) {
PortalGunProxySceneRenderer.render(minecraft, proxyRenderer, portalCamera, partialTick,
transformedContext.viewMatrix(), clippedProjection, portalPairKey);
transformedContext.viewMatrix(), clippedProjection, apertureCullProjection, portalPairKey);
}
} finally {
areaScope.close();
@@ -448,7 +434,7 @@ public final class PortalGunPortalViewRenderer {
stencilScope.close();
}
if (contextPushed) {
PortalGunPortalRenderState.popPortalView(true);
PortalGunPortalRenderState.popPortalView(false);
}
} finally {
if (transparencyTarget != null) {
@@ -495,6 +481,7 @@ public final class PortalGunPortalViewRenderer {
return -1;
}
Matrix4f clippedProjectionMatrix = applyPortalClipPlane(baseProjectionMatrix, transformedContext, destinationPortal);
Matrix4f apertureCullProjection = cropProjectionToScreenClip(clippedProjectionMatrix, clip, target.width, target.height);
boolean renderAll = true;
PortalCamera portalCamera = new PortalCamera();
portalCamera.setup(minecraft.level, cameraEntity, tickCounter.getGameTimeDeltaPartialTick(true), transformedContext.cameraPos(), transformedContext.look(), transformedContext.up());
@@ -550,14 +537,14 @@ public final class PortalGunPortalViewRenderer {
PortalGunPortalRenderState.pushPortalView(portalRenderContext);
portalRenderStatePushed = true;
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "prepare-portal-view", "camera=" + transformedContext.cameraPos())) {
proxyRenderer.prepareCullFrustum(portalCamera.getPosition(), transformedContext.viewMatrix(), clippedProjectionMatrix);
proxyRenderer.prepareCullFrustum(portalCamera.getPosition(), transformedContext.viewMatrix(), apertureCullProjection);
}
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(portalPairKey, "activate-view-area", "destination=" + destinationPortal.getUUID())) {
PortalGunPortalViewAreaManager.Scope scope = PortalGunPortalViewAreaManager.enter(minecraft, proxyRenderer, destinationPortal.getUUID(), transformedContext.cameraPos(), portalCamera);
ignored.close();
try {
try (PortalSceneRenderTrace.Phase sceneTrace = PortalSceneRenderTrace.begin(portalPairKey, "manual-scene-subpass", "target=" + target.width + "x" + target.height)) {
PortalGunProxySceneRenderer.render(minecraft, proxyRenderer, portalCamera, partialTick, transformedContext.viewMatrix(), clippedProjectionMatrix, portalPairKey);
PortalGunProxySceneRenderer.render(minecraft, proxyRenderer, portalCamera, partialTick, transformedContext.viewMatrix(), clippedProjectionMatrix, apertureCullProjection, portalPairKey);
}
} finally {
try (PortalSceneRenderTrace.Phase releaseTrace = PortalSceneRenderTrace.begin(portalPairKey, "release-view-area", "destination=" + destinationPortal.getUUID())) {
@@ -749,6 +736,20 @@ public final class PortalGunPortalViewRenderer {
return new ScreenClip(scissorX, scissorY, scissorWidth, scissorHeight);
}
private static Matrix4f cropProjectionToScreenClip(Matrix4f projectionMatrix, ScreenClip clip, int width, int height) {
float left = (float) (clip.x() * 2.0D / width - 1.0D);
float right = (float) ((clip.x() + clip.width()) * 2.0D / width - 1.0D);
float bottom = (float) (clip.y() * 2.0D / height - 1.0D);
float top = (float) ((clip.y() + clip.height()) * 2.0D / height - 1.0D);
float widthNdc = Math.max(1.0E-5F, right - left);
float heightNdc = Math.max(1.0E-5F, top - bottom);
float scaleX = 2.0F / widthNdc;
float scaleY = 2.0F / heightNdc;
float offsetX = -(right + left) / widthNdc;
float offsetY = -(top + bottom) / heightNdc;
return new Matrix4f().translate(offsetX, offsetY, 0.0F).scale(scaleX, scaleY, 1.0F).mul(projectionMatrix);
}
private static List<Vector4f> clipPolygon(List<Vector4f> vertices, boolean nearPlane) {
if (vertices.isEmpty()) {
return vertices;
@@ -793,18 +794,9 @@ public final class PortalGunPortalViewRenderer {
if (clip == null || width <= 0 || height <= 0) {
return false;
}
PortalGunWorldPortalShape shape = portal.getWorldPortalShape().scaleAperture(portal.getPortalVisualScale(partialTick));
PortalGunWorldPortalShape shape = portal.getWorldPortalShape().scaleAperture(portal.getPortalVisualScale(partialTick) * PORTAL_APERTURE_SCALE);
Matrix4f combined = new Matrix4f(projectionMatrix).mul(viewMatrix);
double apertureInset = (1.0D - PORTAL_APERTURE_SCALE) * 0.5D;
double apertureStart = apertureInset;
double apertureEnd = 1.0D - apertureInset;
List<Vector4f> polygon = new ArrayList<>(4);
polygon.add(projectPortalVertex(combined, cameraPos, shape, apertureStart, apertureStart));
polygon.add(projectPortalVertex(combined, cameraPos, shape, apertureEnd, apertureStart));
polygon.add(projectPortalVertex(combined, cameraPos, shape, apertureEnd, apertureEnd));
polygon.add(projectPortalVertex(combined, cameraPos, shape, apertureStart, apertureEnd));
polygon = clipPolygon(polygon, false);
polygon = clipPolygon(polygon, true);
List<Vector4f> polygon = projectPortalPolygon(combined, cameraPos, shape);
if (polygon.size() < 3) {
return false;
}
@@ -818,29 +810,133 @@ public final class PortalGunPortalViewRenderer {
if (mesh == null) {
return false;
}
Matrix4fStack modelViewStack = RenderSystem.getModelViewStack();
Matrix4f previousProjection = new Matrix4f(RenderSystem.getProjectionMatrix());
VertexSorting previousSorting = RenderSystem.getVertexSorting();
modelViewStack.pushMatrix();
try {
RenderSystem.disableBlend();
RenderSystem.depthMask(false);
RenderSystem.enableDepthTest();
RenderSystem.disableCull();
modelViewStack.identity();
RenderSystem.applyModelViewMatrix();
RenderSystem.setProjectionMatrix(new Matrix4f(), previousSorting);
portalViewShader.setSampler("Sampler0", textureId);
RenderSystem.setShader(() -> portalViewShader);
RenderSystem.setShaderTexture(0, textureId);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
BufferUploader.drawWithShader(mesh);
} finally {
RenderSystem.setProjectionMatrix(previousProjection, previousSorting);
modelViewStack.popMatrix();
RenderSystem.applyModelViewMatrix();
restorePortalDrawState();
}
return true;
}
private static Vector4f projectPortalVertex(Matrix4f combined, Vec3 cameraPos, PortalGunWorldPortalShape shape, double u, double v) {
Vec3 worldPosition = shape.center()
.add(shape.right().scale((u * 2.0D - 1.0D) * shape.halfWidth()))
.add(shape.up().scale((v * 2.0D - 1.0D) * shape.halfHeight()))
.add(shape.normal().scale(SURFACE_OFFSET));
Vec3 relative = worldPosition.subtract(cameraPos);
return new Vector4f((float) relative.x, (float) relative.y, (float) relative.z, 1.0F).mul(combined);
private static void clearDepthInPortalStencil(Minecraft minecraft) {
int previousDepthFunction = GL11.glGetInteger(GL11.GL_DEPTH_FUNC);
RenderSystem.enableDepthTest();
RenderSystem.depthMask(true);
RenderSystem.colorMask(false, false, false, false);
GL11.glDepthFunc(GL11.GL_ALWAYS);
BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.TRIANGLES, DefaultVertexFormat.POSITION);
buffer.addVertex(-1.0F, -1.0F, 1.0F);
buffer.addVertex(3.0F, -1.0F, 1.0F);
buffer.addVertex(-1.0F, 3.0F, 1.0F);
MeshData mesh = buffer.build();
try {
drawClipSpaceMesh(minecraft, mesh);
} finally {
GL11.glDepthFunc(previousDepthFunction);
RenderSystem.colorMask(true, true, true, true);
RenderSystem.depthMask(true);
RenderSystem.enableDepthTest();
}
}
private static void restorePortalApertureDepth(Minecraft minecraft, Vec3 cameraPos, Matrix4f viewMatrix, Matrix4f projectionMatrix,
PortalGunPortalEntity portal, float partialTick) {
PortalGunWorldPortalShape shape = portal.getWorldPortalShape().scaleAperture(portal.getPortalVisualScale(partialTick) * PORTAL_APERTURE_SCALE);
Matrix4f combined = new Matrix4f(projectionMatrix).mul(viewMatrix);
List<Vector4f> polygon = projectPortalPolygon(combined, cameraPos, shape);
if (polygon.size() < 3) {
return;
}
BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.TRIANGLES, DefaultVertexFormat.POSITION);
for (int index = 1; index + 1 < polygon.size(); index++) {
addDepthVertex(buffer, polygon.get(0));
addDepthVertex(buffer, polygon.get(index));
addDepthVertex(buffer, polygon.get(index + 1));
}
MeshData mesh = buffer.build();
int previousDepthFunction = GL11.glGetInteger(GL11.GL_DEPTH_FUNC);
RenderSystem.enableDepthTest();
RenderSystem.depthMask(true);
RenderSystem.colorMask(false, false, false, false);
GL11.glDepthFunc(GL11.GL_ALWAYS);
try {
drawClipSpaceMesh(minecraft, mesh);
} finally {
GL11.glDepthFunc(previousDepthFunction);
RenderSystem.colorMask(true, true, true, true);
RenderSystem.depthMask(true);
RenderSystem.enableDepthTest();
}
}
private static void addDepthVertex(BufferBuilder buffer, Vector4f clipPosition) {
float inverseW = 1.0F / clipPosition.w();
buffer.addVertex(clipPosition.x() * inverseW, clipPosition.y() * inverseW, clipPosition.z() * inverseW);
}
private static void drawClipSpaceMesh(Minecraft minecraft, MeshData mesh) {
if (mesh == null) {
return;
}
Matrix4fStack modelViewStack = RenderSystem.getModelViewStack();
boolean cullEnabled = GL11.glIsEnabled(GL11.GL_CULL_FACE);
boolean blendEnabled = GL11.glIsEnabled(GL11.GL_BLEND);
Matrix4f previousProjection = new Matrix4f(RenderSystem.getProjectionMatrix());
VertexSorting previousSorting = RenderSystem.getVertexSorting();
modelViewStack.pushMatrix();
try {
RenderSystem.disableCull();
RenderSystem.disableBlend();
modelViewStack.identity();
RenderSystem.applyModelViewMatrix();
RenderSystem.setProjectionMatrix(new Matrix4f(), previousSorting);
RenderSystem.setShader(GameRenderer::getPositionShader);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
BufferUploader.drawWithShader(mesh);
} finally {
RenderSystem.setProjectionMatrix(previousProjection, previousSorting);
modelViewStack.popMatrix();
RenderSystem.applyModelViewMatrix();
if (cullEnabled) {
RenderSystem.enableCull();
} else {
RenderSystem.disableCull();
}
if (blendEnabled) {
RenderSystem.enableBlend();
} else {
RenderSystem.disableBlend();
}
}
}
private static List<Vector4f> projectPortalPolygon(Matrix4f combined, Vec3 cameraPos, PortalGunWorldPortalShape shape) {
Vec3[] corners = shape.getCorners(SURFACE_OFFSET);
List<Vector4f> polygon = new ArrayList<>(corners.length);
for (Vec3 corner : corners) {
Vec3 relative = corner.subtract(cameraPos);
polygon.add(new Vector4f((float) relative.x, (float) relative.y, (float) relative.z, 1.0F).mul(combined));
}
polygon = clipPolygon(polygon, false);
return clipPolygon(polygon, true);
}
private static void addPortalViewVertex(BufferBuilder buffer, Vector4f clipPosition) {
@@ -852,10 +948,10 @@ public final class PortalGunPortalViewRenderer {
}
private static void renderPortalTransitionEntities(Minecraft minecraft, Vec3 cameraPos, float partialTick, PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal) {
if (minecraft.level == null || minecraft.getEntityRenderDispatcher() == null || minecraft.renderBuffers() == null) {
if (minecraft.level == null || minecraft.getEntityRenderDispatcher() == null) {
return;
}
MultiBufferSource.BufferSource bufferSource = minecraft.renderBuffers().bufferSource();
MultiBufferSource.BufferSource bufferSource = PortalGunPortalRendererPool.bufferSource();
PoseStack poseStack = new PoseStack();
AABB transitionBounds = sourcePortal.getWorldPortalShape().getBoundsForCulling();
List<Entity> renderEntities = minecraft.level.getEntities(
@@ -0,0 +1,93 @@
package com.craisinlord.antarchy.content.client.renderer;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import org.lwjgl.opengl.GL15;
public final class PortalGunPortalVisibilityQueries {
private static final int MAX_ENTRIES = 128;
private static final LinkedHashMap<Pair, QueryState> QUERIES = new LinkedHashMap<>(16, 0.75F, true);
private PortalGunPortalVisibilityQueries() {
}
public static boolean shouldRender(UUID source, UUID destination) {
QueryState state = QUERIES.get(new Pair(source, destination));
if (state == null) {
return true;
}
if (state.inFlight && GL15.glGetQueryObjecti(state.query, GL15.GL_QUERY_RESULT_AVAILABLE) != 0) {
state.visible = GL15.glGetQueryObjecti(state.query, GL15.GL_QUERY_RESULT) != 0;
state.hasResult = true;
state.inFlight = false;
PortalSceneRenderTrace.event(source + "->" + destination, "visibility-query-result", "visible=" + state.visible);
}
return !state.hasResult || state.visible;
}
public static boolean begin(UUID source, UUID destination) {
Pair pair = new Pair(source, destination);
QueryState state = QUERIES.computeIfAbsent(pair, key -> new QueryState(GL15.glGenQueries()));
if (state.inFlight || state.active) {
trim();
return false;
}
state.active = true;
try {
GL15.glBeginQuery(GL15.GL_SAMPLES_PASSED, state.query);
} catch (RuntimeException | Error exception) {
state.active = false;
throw exception;
}
trim();
return true;
}
public static void end(UUID source, UUID destination, boolean started) {
if (!started) {
return;
}
QueryState state = QUERIES.get(new Pair(source, destination));
if (state == null || !state.active) {
return;
}
GL15.glEndQuery(GL15.GL_SAMPLES_PASSED);
state.active = false;
state.inFlight = true;
}
public static void clear() {
for (QueryState state : QUERIES.values()) {
GL15.glDeleteQueries(state.query);
}
QUERIES.clear();
}
private static void trim() {
Iterator<Map.Entry<Pair, QueryState>> iterator = QUERIES.entrySet().iterator();
while (QUERIES.size() > MAX_ENTRIES && iterator.hasNext()) {
QueryState state = iterator.next().getValue();
if (!state.active) {
GL15.glDeleteQueries(state.query);
iterator.remove();
}
}
}
private record Pair(UUID source, UUID destination) {
}
private static final class QueryState {
private final int query;
private boolean active;
private boolean inFlight;
private boolean hasResult;
private boolean visible = true;
private QueryState(int query) {
this.query = query;
}
}
}
@@ -4,6 +4,7 @@ import com.craisinlord.antarchy.mixins.client.LevelRendererPortalSceneInvoker;
import com.craisinlord.antarchy.mixins.client.LevelRendererPortalViewAreaAccessor;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.platform.Lighting;
import com.mojang.blaze3d.shaders.FogShape;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Camera;
import net.minecraft.client.CloudStatus;
@@ -13,7 +14,6 @@ import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.particle.ParticleRenderType;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
@@ -30,12 +30,21 @@ public final class PortalGunProxySceneRenderer {
private PortalGunProxySceneRenderer() {
}
public static void render(Minecraft minecraft, LevelRenderer renderer, Camera camera, float partialTick, Matrix4f modelViewMatrix, Matrix4f projectionMatrix, String tracePair) {
public static void render(Minecraft minecraft, LevelRenderer renderer, Camera camera, float partialTick, Matrix4f modelViewMatrix, Matrix4f projectionMatrix, Matrix4f cullProjectionMatrix, String tracePair) {
Matrix4fStack outerModelViewStack = RenderSystem.getModelViewStack();
float previousFogStart = RenderSystem.getShaderFogStart();
float previousFogEnd = RenderSystem.getShaderFogEnd();
float[] previousFogColor = RenderSystem.getShaderFogColor().clone();
FogShape previousFogShape = RenderSystem.getShaderFogShape();
outerModelViewStack.pushMatrix();
outerModelViewStack.identity();
RenderSystem.applyModelViewMatrix();
try {
Vec3 cameraPosition = camera.getPosition();
double cameraX = cameraPosition.x;
double cameraY = cameraPosition.y;
double cameraZ = cameraPosition.z;
Frustum frustum = new Frustum(modelViewMatrix, projectionMatrix);
Frustum frustum = new Frustum(modelViewMatrix, cullProjectionMatrix);
frustum.prepare(cameraX, cameraY, cameraZ);
LevelRendererPortalSceneInvoker scene = (LevelRendererPortalSceneInvoker) renderer;
float renderDistance = minecraft.gameRenderer.getRenderDistance();
@@ -55,29 +64,33 @@ public final class PortalGunProxySceneRenderer {
() -> FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_SKY, renderDistance, foggy, partialTick));
}
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "frustum-and-visibility", "renderDistance=" + rendererAccessDistance(renderer))) {
boolean sodium = SodiumCompat.isLoaded();
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "frustum-and-visibility", "renderDistance=" + (sodium ? minecraft.options.getEffectiveRenderDistance() : rendererAccessDistance(renderer)))) {
FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_TERRAIN, Math.max(renderDistance, 32.0F), foggy, partialTick);
scene.antarchy$setupPortalScene(camera, frustum, false, minecraft.player != null && minecraft.player.isSpectator());
}
LevelRendererPortalViewAreaAccessor viewAreaAccessor = (LevelRendererPortalViewAreaAccessor) renderer;
ObjectArrayList<SectionRenderDispatcher.RenderSection> fullVisibleSections = viewAreaAccessor.antarchy$getVisibleSections();
ObjectArrayList<SectionRenderDispatcher.RenderSection> budgetedVisibleSections = new ObjectArrayList<>();
PortalGunPortalRendererPool.beginSceneBudget();
int scannedVisibleSections = 0;
while (scannedVisibleSections < fullVisibleSections.size() && PortalGunPortalRendererPool.remainingSectionBudgetNanos() > 0L) {
budgetedVisibleSections.add(fullVisibleSections.get(scannedVisibleSections++));
}
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "compile-visible-sections", "visibleTotal=" + fullVisibleSections.size() + " visibleScanned=" + scannedVisibleSections + " rebuildBudgetNanos=" + PortalGunPortalRendererPool.remainingSectionBudgetNanos())) {
viewAreaAccessor.antarchy$setVisibleSections(budgetedVisibleSections);
try {
scene.antarchy$compilePortalSections(camera);
} finally {
viewAreaAccessor.antarchy$setVisibleSections(fullVisibleSections);
LevelRendererPortalViewAreaAccessor viewAreaAccessor = sodium ? null : (LevelRendererPortalViewAreaAccessor) renderer;
int visibleSectionCount = 0;
if (viewAreaAccessor != null) {
ObjectArrayList<SectionRenderDispatcher.RenderSection> fullVisibleSections = viewAreaAccessor.antarchy$getVisibleSections();
ObjectArrayList<SectionRenderDispatcher.RenderSection> budgetedVisibleSections = new ObjectArrayList<>();
int scannedVisibleSections = 0;
while (scannedVisibleSections < fullVisibleSections.size() && PortalGunPortalRendererPool.remainingSectionBudgetNanos() > 0L) {
budgetedVisibleSections.add(fullVisibleSections.get(scannedVisibleSections++));
}
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "compile-visible-sections", "visibleTotal=" + fullVisibleSections.size() + " visibleScanned=" + scannedVisibleSections + " rebuildBudgetNanos=" + PortalGunPortalRendererPool.remainingSectionBudgetNanos())) {
viewAreaAccessor.antarchy$setVisibleSections(budgetedVisibleSections);
try {
scene.antarchy$compilePortalSections(camera);
} finally {
viewAreaAccessor.antarchy$setVisibleSections(fullVisibleSections);
}
}
visibleSectionCount = fullVisibleSections.size();
}
PortalSceneRenderTrace.event(tracePair, "visible-sections-ready", "count=" + viewAreaAccessor.antarchy$getVisibleSections().size() + " scheduledRebuilds=" + PortalGunPortalRendererPool.scheduledSectionRebuilds());
PortalSceneRenderTrace.event(tracePair, "visible-sections-ready", "renderer=" + (sodium ? "sodium" : "vanilla") + " count=" + visibleSectionCount + " scheduledRebuilds=" + PortalGunPortalRendererPool.scheduledSectionRebuilds());
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "opaque-terrain-layers", "sections=" + viewAreaAccessor.antarchy$getVisibleSections().size())) {
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "opaque-terrain-layers", "sections=" + visibleSectionCount)) {
RenderSystem.enableDepthTest();
RenderSystem.depthMask(true);
scene.antarchy$renderPortalSectionLayer(RenderType.solid(), cameraX, cameraY, cameraZ, modelViewMatrix, projectionMatrix);
@@ -98,7 +111,7 @@ public final class PortalGunProxySceneRenderer {
RenderSystem.applyModelViewMatrix();
}
PoseStack poseStack = new PoseStack();
MultiBufferSource.BufferSource buffers = minecraft.renderBuffers().bufferSource();
MultiBufferSource.BufferSource buffers = PortalGunPortalRendererPool.bufferSource();
try {
int entityCount = 0;
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "entities", "dispatch-start")) {
@@ -122,32 +135,40 @@ public final class PortalGunProxySceneRenderer {
PortalSceneRenderTrace.event(tracePair, "entities-dispatched", "count=" + entityCount);
int blockEntityCount = 0;
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "block-entities", "visibleSections=" + viewAreaAccessor.antarchy$getVisibleSections().size())) {
for (var section : viewAreaAccessor.antarchy$getVisibleSections()) {
for (BlockEntity blockEntity : section.getCompiled().getRenderableBlockEntities()) {
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "block-entities", "visibleSections=" + visibleSectionCount)) {
if (sodium) {
int[] count = {0};
SodiumCompat.forEachVisibleBlockEntity(blockEntity -> {
renderBlockEntity(minecraft, blockEntity, cameraX, cameraY, cameraZ, partialTick, poseStack, buffers);
blockEntityCount++;
}
}
synchronized (viewAreaAccessor.antarchy$getGlobalBlockEntities()) {
for (BlockEntity blockEntity : viewAreaAccessor.antarchy$getGlobalBlockEntities()) {
if (frustum.isVisible(new AABB(blockEntity.getBlockPos()).inflate(1.0D))) {
count[0]++;
});
blockEntityCount = count[0];
} else {
for (var section : viewAreaAccessor.antarchy$getVisibleSections()) {
for (BlockEntity blockEntity : section.getCompiled().getRenderableBlockEntities()) {
renderBlockEntity(minecraft, blockEntity, cameraX, cameraY, cameraZ, partialTick, poseStack, buffers);
blockEntityCount++;
}
}
synchronized (viewAreaAccessor.antarchy$getGlobalBlockEntities()) {
for (BlockEntity blockEntity : viewAreaAccessor.antarchy$getGlobalBlockEntities()) {
if (frustum.isVisible(new AABB(blockEntity.getBlockPos()).inflate(1.0D))) {
renderBlockEntity(minecraft, blockEntity, cameraX, cameraY, cameraZ, partialTick, poseStack, buffers);
blockEntityCount++;
}
}
}
}
}
buffers.endBatch();
PortalSceneRenderTrace.event(tracePair, "block-entities-dispatched", "count=" + blockEntityCount);
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "translucent-and-particles", "rendering")) {
minecraft.particleEngine.render(minecraft.gameRenderer.lightTexture(), camera, partialTick, frustum, type -> !type.isTranslucent());
scene.antarchy$renderPortalSectionLayer(RenderType.translucent(), cameraX, cameraY, cameraZ, modelViewMatrix, projectionMatrix);
buffers.endBatch(RenderType.lines());
buffers.endBatch();
scene.antarchy$renderPortalSectionLayer(RenderType.tripwire(), cameraX, cameraY, cameraZ, modelViewMatrix, projectionMatrix);
minecraft.particleEngine.render(minecraft.gameRenderer.lightTexture(), camera, partialTick, frustum, ParticleRenderType::isTranslucent);
minecraft.particleEngine.render(minecraft.gameRenderer.lightTexture(), camera, partialTick);
}
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "clouds", minecraft.options.getCloudsType().toString())) {
@@ -173,6 +194,14 @@ public final class PortalGunProxySceneRenderer {
RenderSystem.disableBlend();
}
}
} finally {
outerModelViewStack.popMatrix();
RenderSystem.applyModelViewMatrix();
RenderSystem.setShaderFogStart(previousFogStart);
RenderSystem.setShaderFogEnd(previousFogEnd);
RenderSystem.setShaderFogColor(previousFogColor[0], previousFogColor[1], previousFogColor[2], previousFogColor[3]);
RenderSystem.setShaderFogShape(previousFogShape);
}
}
private static int rendererAccessDistance(LevelRenderer renderer) {
@@ -186,8 +215,11 @@ public final class PortalGunProxySceneRenderer {
}
BlockPos pos = blockEntity.getBlockPos();
poseStack.pushPose();
poseStack.translate(pos.getX() - cameraX, pos.getY() - cameraY, pos.getZ() - cameraZ);
minecraft.getBlockEntityRenderDispatcher().render(blockEntity, partialTick, poseStack, buffers);
poseStack.popPose();
try {
poseStack.translate(pos.getX() - cameraX, pos.getY() - cameraY, pos.getZ() - cameraZ);
minecraft.getBlockEntityRenderDispatcher().render(blockEntity, partialTick, poseStack, buffers);
} finally {
poseStack.popPose();
}
}
}
@@ -5,7 +5,9 @@ import java.util.HashSet;
import java.util.Set;
public final class PortalSceneRenderTrace {
private static final String DEBUG_PROPERTY = "antarchy.portalGun.debugRender";
private static final Set<String> LOGGED_PHASES = new HashSet<>();
private static final Phase NOOP_PHASE = new Phase(0L, "", "", false, 0L);
private static long generation;
private static String armedKey = "";
@@ -27,6 +29,9 @@ public final class PortalSceneRenderTrace {
}
public static Phase begin(String pair, String phase, String detail) {
if (!Boolean.getBoolean(DEBUG_PROPERTY)) {
return NOOP_PHASE;
}
long currentGeneration;
boolean shouldLog;
synchronized (PortalSceneRenderTrace.class) {
@@ -40,6 +45,9 @@ public final class PortalSceneRenderTrace {
}
public static void event(String pair, String event, String detail) {
if (!Boolean.getBoolean(DEBUG_PROPERTY)) {
return;
}
long currentGeneration;
boolean shouldLog;
synchronized (PortalSceneRenderTrace.class) {
@@ -1,5 +1,6 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.Antarchy;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.systems.RenderSystem;
import java.util.Collections;
@@ -9,16 +10,25 @@ import java.util.Objects;
import java.util.Set;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import com.mojang.blaze3d.pipeline.TextureTarget;
public final class PortalStencilTarget {
private static final Map<RenderTarget, Attachment> ATTACHMENTS = Collections.synchronizedMap(new IdentityHashMap<>());
private static final Map<RenderTarget, Attachment> MAIN_STENCIL_ATTACHMENTS = Collections.synchronizedMap(new IdentityHashMap<>());
private static final Map<RenderTarget, Integer> UNSUPPORTED_MAIN_TARGETS = Collections.synchronizedMap(new IdentityHashMap<>());
private static final Set<RenderTarget> DIRECT_TARGETS = Collections.newSetFromMap(new IdentityHashMap<>());
private PortalStencilTarget() {
}
public static void prepareMainTarget(RenderTarget target) {
target.bindWrite(true);
if (stencilBits() > 0) {
UNSUPPORTED_MAIN_TARGETS.remove(target);
return;
}
if (Objects.equals(UNSUPPORTED_MAIN_TARGETS.get(target), target.frameBufferId)) {
return;
}
try {
java.lang.reflect.Method enabled = target.getClass().getMethod("isStencilEnabled");
if (!Boolean.TRUE.equals(enabled.invoke(target))) {
@@ -27,14 +37,26 @@ public final class PortalStencilTarget {
} catch (ReflectiveOperationException ignored) {
}
target.bindWrite(true);
if (stencilBits() <= 0) {
try {
ensureMainStencilAttachment(target);
} catch (IllegalStateException exception) {
UNSUPPORTED_MAIN_TARGETS.put(target, target.frameBufferId);
Antarchy.LOGGER.warn("Portal gun direct stencil is unavailable; using offscreen portal rendering", exception);
}
}
}
public static Scope beginDirect(RenderTarget target, TextureTarget depthBackup, Runnable drawApertureMask) {
public static boolean supportsDirect(RenderTarget target) {
target.bindWrite(true);
if (GL11.glGetInteger(GL11.GL_STENCIL_BITS) <= 0) {
return stencilBits() > 0;
}
public static Scope beginDirect(RenderTarget target, Runnable drawApertureMask, Runnable clearApertureDepth, Runnable restoreApertureDepth) {
target.bindWrite(true);
if (stencilBits() <= 0) {
throw new IllegalStateException("Main render target has no stencil attachment");
}
depthBackup.copyDepthFrom(target);
DIRECT_TARGETS.add(target);
target.bindWrite(true);
RenderSystem.disableScissor();
@@ -45,14 +67,32 @@ public final class PortalStencilTarget {
RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
RenderSystem.colorMask(false, false, false, false);
RenderSystem.depthMask(false);
RenderSystem.disableDepthTest();
RenderSystem.enableDepthTest();
GL11.glEnable(GL11.GL_STENCIL_TEST);
try {
Objects.requireNonNull(drawApertureMask).run();
RenderSystem.colorMask(false, false, false, false);
RenderSystem.depthMask(true);
RenderSystem.stencilMask(0x00);
RenderSystem.stencilFunc(GL11.GL_EQUAL, 1, 0xFF);
RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
Objects.requireNonNull(clearApertureDepth).run();
} catch (RuntimeException | Error exception) {
resetState();
target.copyDepthFrom(depthBackup);
DIRECT_TARGETS.remove(target);
try {
target.bindWrite(true);
GL11.glEnable(GL11.GL_STENCIL_TEST);
RenderSystem.stencilMask(0x00);
RenderSystem.stencilFunc(GL11.GL_EQUAL, 1, 0xFF);
RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
RenderSystem.colorMask(false, false, false, false);
RenderSystem.depthMask(true);
restoreApertureDepth.run();
} catch (RuntimeException | Error restoreFailure) {
exception.addSuppressed(restoreFailure);
} finally {
DIRECT_TARGETS.remove(target);
resetState();
}
throw exception;
}
RenderSystem.colorMask(true, true, true, true);
@@ -60,9 +100,8 @@ public final class PortalStencilTarget {
RenderSystem.stencilMask(0x00);
RenderSystem.stencilFunc(GL11.GL_EQUAL, 1, 0xFF);
RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
RenderSystem.enableDepthTest();
return new Scope(target, depthBackup);
return new Scope(target, restoreApertureDepth);
}
public static Scope begin(RenderTarget target, Runnable drawApertureMask) {
@@ -107,10 +146,15 @@ public final class PortalStencilTarget {
public static void release(RenderTarget target) {
DIRECT_TARGETS.remove(target);
UNSUPPORTED_MAIN_TARGETS.remove(target);
Attachment attachment = ATTACHMENTS.remove(target);
if (attachment != null) {
GL30.glDeleteRenderbuffers(attachment.renderbuffer());
}
Attachment mainAttachment = MAIN_STENCIL_ATTACHMENTS.remove(target);
if (mainAttachment != null) {
GL30.glDeleteRenderbuffers(mainAttachment.renderbuffer());
}
}
public static void clear() {
@@ -118,9 +162,47 @@ public final class PortalStencilTarget {
GL30.glDeleteRenderbuffers(attachment.renderbuffer());
}
ATTACHMENTS.clear();
for (Attachment attachment : MAIN_STENCIL_ATTACHMENTS.values()) {
GL30.glDeleteRenderbuffers(attachment.renderbuffer());
}
MAIN_STENCIL_ATTACHMENTS.clear();
UNSUPPORTED_MAIN_TARGETS.clear();
DIRECT_TARGETS.clear();
}
private static void ensureMainStencilAttachment(RenderTarget target) {
Attachment attachment = MAIN_STENCIL_ATTACHMENTS.get(target);
if (attachment != null
&& attachment.framebuffer() == target.frameBufferId
&& attachment.width() == target.width
&& attachment.height() == target.height) {
throw new IllegalStateException("Main render target stencil attachment has no stencil bits");
}
if (attachment != null) {
GL30.glDeleteRenderbuffers(attachment.renderbuffer());
MAIN_STENCIL_ATTACHMENTS.remove(target);
}
int previousRenderbuffer = GL11.glGetInteger(GL30.GL_RENDERBUFFER_BINDING);
int renderbuffer = GL30.glGenRenderbuffers();
try {
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, renderbuffer);
GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_STENCIL_INDEX8, target.width, target.height);
GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, renderbuffer);
int status = GL30.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER);
if (status != GL30.GL_FRAMEBUFFER_COMPLETE) {
GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, 0);
throw new IllegalStateException("Main render target is incomplete with stencil attachment: 0x" + Integer.toHexString(status));
}
MAIN_STENCIL_ATTACHMENTS.put(target, new Attachment(target.frameBufferId, renderbuffer, target.width, target.height));
PortalSceneRenderTrace.event("main-target", "stencil-attached", "framebuffer=" + target.frameBufferId + " size=" + target.width + "x" + target.height);
} catch (RuntimeException | Error exception) {
GL30.glDeleteRenderbuffers(renderbuffer);
throw exception;
} finally {
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, previousRenderbuffer);
}
}
private static void ensureAttachment(RenderTarget target) {
Attachment attachment = ATTACHMENTS.get(target);
if (attachment != null
@@ -162,21 +244,28 @@ public final class PortalStencilTarget {
RenderSystem.enableDepthTest();
}
private static int stencilBits() {
int type = GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER, GL30.GL_STENCIL_ATTACHMENT,
GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE);
return type == GL11.GL_NONE ? 0 : GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER,
GL30.GL_STENCIL_ATTACHMENT, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE);
}
private record Attachment(int framebuffer, int renderbuffer, int width, int height) {
}
public static final class Scope implements AutoCloseable {
private boolean closed;
private final RenderTarget target;
private final TextureTarget depthBackup;
private final Runnable restoreApertureDepth;
private Scope(RenderTarget target) {
this(target, null);
}
private Scope(RenderTarget target, TextureTarget depthBackup) {
private Scope(RenderTarget target, Runnable restoreApertureDepth) {
this.target = Objects.requireNonNull(target);
this.depthBackup = depthBackup;
this.restoreApertureDepth = restoreApertureDepth;
}
@Override
@@ -185,12 +274,23 @@ public final class PortalStencilTarget {
return;
}
closed = true;
if (this.depthBackup != null) {
this.target.copyDepthFrom(this.depthBackup);
this.target.bindWrite(true);
DIRECT_TARGETS.remove(this.target);
try {
if (this.restoreApertureDepth != null) {
this.target.bindWrite(true);
GL11.glEnable(GL11.GL_STENCIL_TEST);
RenderSystem.stencilMask(0x00);
RenderSystem.stencilFunc(GL11.GL_EQUAL, 1, 0xFF);
RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
RenderSystem.colorMask(false, false, false, false);
RenderSystem.depthMask(true);
this.restoreApertureDepth.run();
}
} finally {
if (this.restoreApertureDepth != null) {
DIRECT_TARGETS.remove(this.target);
}
resetState();
}
resetState();
}
}
}
@@ -5,7 +5,9 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.function.Consumer;
import net.minecraft.client.Camera;
import net.minecraft.world.level.block.entity.BlockEntity;
public final class SodiumCompat {
private static final String SODIUM_RENDERER_CLASS = "net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer";
@@ -29,13 +31,10 @@ public final class SodiumCompat {
RENDER_STATE.get().activeSetup = new TerrainSetup(camera, viewport, spectator, updateChunks);
}
public static TerrainScope beginPortalView(Camera portalCamera) {
public static TerrainScope beginPortalView() {
RenderState state = RENDER_STATE.get();
TerrainSetup previousSetup = state.activeSetup;
state.previousSetups.push(new PreviousSetup(previousSetup));
if (previousSetup != null) {
restoreTerrainSetup(previousSetup.withCamera(portalCamera), state);
}
return () -> {
PreviousSetup previous = state.previousSetups.pop();
if (previous.setup() == null) {
@@ -50,6 +49,20 @@ public final class SodiumCompat {
RENDER_STATE.remove();
}
public static void forEachVisibleBlockEntity(Consumer<BlockEntity> consumer) {
try {
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
Class<?> rendererClass = Class.forName(SODIUM_RENDERER_CLASS, false,
contextLoader == null ? SodiumCompat.class.getClassLoader() : contextLoader);
Object renderer = rendererClass.getMethod("instanceNullable").invoke(null);
if (renderer != null) {
rendererClass.getMethod("iterateVisibleBlockEntities", Consumer.class).invoke(renderer, consumer);
}
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | LinkageError exception) {
logRestoreFailure("Could not iterate Sodium block entities for a portal view", exception);
}
}
private static void restoreTerrainSetup(TerrainSetup setup, RenderState state) {
try {
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
@@ -77,7 +90,14 @@ public final class SodiumCompat {
state.activeSetup = setup;
return;
}
terrainSetup.invoke(renderer, setup.camera(), setup.viewport(), setup.spectator(), setup.updateChunks());
Class<?> deviceClass = Class.forName("net.caffeinemc.mods.sodium.client.gl.device.RenderDevice", false,
contextLoader == null ? SodiumCompat.class.getClassLoader() : contextLoader);
deviceClass.getMethod("enterManagedCode").invoke(null);
try {
terrainSetup.invoke(renderer, setup.camera(), setup.viewport(), setup.spectator(), setup.updateChunks());
} finally {
deviceClass.getMethod("exitManagedCode").invoke(null);
}
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | LinkageError exception) {
logRestoreFailure("Could not restore Sodium terrain after a portal view", exception);
state.activeSetup = setup;
@@ -110,8 +130,5 @@ public final class SodiumCompat {
}
private record TerrainSetup(Camera camera, Object viewport, boolean spectator, boolean updateChunks) {
private TerrainSetup withCamera(Camera camera) {
return new TerrainSetup(camera, this.viewport, this.spectator, this.updateChunks);
}
}
}
@@ -17,7 +17,6 @@ import org.joml.Matrix4f;
import org.joml.Vector3f;
public final class TemporalContractionAfterimageLayer<T extends LivingEntity, M extends EntityModel<T>> extends RenderLayer<T, M> {
private static final int RGB = 0xFFD35A;
private static final float BASE_ALPHA = 0.42F;
private static final int SAMPLE_STRIDE = 2;
@@ -64,7 +63,7 @@ public final class TemporalContractionAfterimageLayer<T extends LivingEntity, M
Vector3f local = inverse.transformDirection(new Vector3f((float) dx, (float) dy, (float) dz));
int alpha = (int) (BASE_ALPHA * fade * 255.0F) & 0xFF;
int color = alpha << 24 | RGB;
int color = alpha << 24 | ContractionAfterimages.colorFor(entity);
model.setupAnim(entity, sample.limbPos, sample.limbSpeed, sample.ageInTicks,
sample.headYaw - sample.bodyYaw, sample.xRot);
@@ -18,7 +18,6 @@ import software.bernie.geckolib.renderer.GeoRenderer;
import software.bernie.geckolib.renderer.layer.GeoRenderLayer;
public final class TemporalContractionGeoLayer<T extends GeoAnimatable> extends GeoRenderLayer<T> {
private static final int RGB = 0xFFD35A;
private static final float BASE_ALPHA = 0.42F;
private static final int SAMPLE_STRIDE = 2;
@@ -66,7 +65,7 @@ public final class TemporalContractionGeoLayer<T extends GeoAnimatable> extends
Vector3f local = inverse.transformDirection(new Vector3f((float) dx, (float) dy, (float) dz));
int alpha = (int) (BASE_ALPHA * fade * 255.0F) & 0xFF;
int color = alpha << 24 | RGB;
int color = alpha << 24 | ContractionAfterimages.colorFor(entity);
poseStack.pushPose();
poseStack.translate(local.x, local.y, local.z);
@@ -79,6 +79,7 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity {
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final int OPEN_TICKS = 5;
public static final int TELEPORT_COOLDOWN_TICKS = 3;
private static final String DEBUG_TRAVERSAL_PROPERTY = "antarchy.portalGun.debugTraversal";
private static final double PORTAL_INSIDE_SHIFT = 0.05D;
private static final Set<String> LOGGED_CROSSING_PATHS = new HashSet<>();
private static final double HALF_DEPTH = 0.35D;
@@ -358,22 +359,26 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity {
Vec3 currentProbe = this.teleportProbePosition(entity, currentBox);
Vec3 normal = this.getNormalVec().normalize();
if (entity instanceof Player && Math.abs(normal.y) < 0.5D) {
double offset = Math.min(0.05D, Math.abs(this.position().subtract(entity.position()).dot(normal)));
Vec3 shift = normal.scale(offset);
if (this.getScanRange().move(shift).contains(currentProbe)
|| !this.getPortalInsides().move(shift).contains(currentProbe)) {
PortalGunWorldPortalShape shape = this.getWorldPortalShape();
double previousDepth = shape.localCoords(previousProbe).depth();
double currentDepth = shape.localCoords(currentProbe).depth();
double halfBodyDepth = entity.getBbWidth() * 0.5D;
if (previousDepth <= 0.0D || currentDepth >= previousDepth - 1.0E-6D
|| currentDepth - halfBodyDepth > 0.02D || currentDepth < -shape.halfDepth()) {
return null;
}
PortalGunWorldPortalShape shape = this.getWorldPortalShape();
AABB flatPlane = shape.getFlatPlane();
boolean aligned;
if (Math.abs(normal.y) > 0.5D) {
aligned = currentBox.minX >= flatPlane.minX && currentBox.maxX <= flatPlane.maxX
&& currentBox.minZ >= flatPlane.minZ && currentBox.maxZ <= flatPlane.maxZ;
} else {
aligned = currentBox.minY >= flatPlane.minY && currentBox.maxY <= flatPlane.maxY;
Vec3 center = currentBox.getCenter();
PortalGunWorldPortalShape.PortalLocalCoords localCenter = shape.localCoords(center);
double halfX = (currentBox.maxX - currentBox.minX) * 0.5D;
double halfY = (currentBox.maxY - currentBox.minY) * 0.5D;
double halfZ = (currentBox.maxZ - currentBox.minZ) * 0.5D;
double widthExtent = halfX * Math.abs(shape.right().x) + halfY * Math.abs(shape.right().y) + halfZ * Math.abs(shape.right().z);
double heightExtent = halfX * Math.abs(shape.up().x) + halfY * Math.abs(shape.up().y) + halfZ * Math.abs(shape.up().z);
if (Math.abs(localCenter.horizontal()) + widthExtent > shape.halfWidth() + 1.0E-3D
|| Math.abs(localCenter.vertical()) + heightExtent > shape.halfHeight() + 1.0E-3D) {
return null;
}
return aligned ? new PortalCrossing(currentProbe, offset) : null;
return new PortalCrossing(currentProbe, Math.max(0.05D, currentDepth + 0.05D));
}
return this.crossesPortal(previousProbe, currentProbe) ? new PortalCrossing(currentProbe, 0.0D) : null;
}
@@ -416,6 +421,10 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity {
}
PortalCrossing crossing = portal.resolveCrossing(entity, previousBox, currentBox);
if (crossing != null) {
if (entity instanceof Player && Boolean.getBoolean(DEBUG_TRAVERSAL_PROPERTY)) {
Antarchy.LOGGER.info("Portal gun traversal accepted path=movement portal={} player={} probe={} offset={} linked={}",
portal.getUUID(), entity.getUUID(), crossing.probe(), crossing.normalOffset(), linked.getUUID());
}
logCrossingPath(entity, "movement");
portal.teleportEntity(entity, linked, crossing.probe(), resolvedMovement, crossing.normalOffset());
return;
@@ -506,6 +515,10 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity {
}
PortalGunPortalEntity linked = this.findLinkedPortal(serverLevel);
if (linked == null || !linked.isAlive()) {
if (Boolean.getBoolean(DEBUG_TRAVERSAL_PROPERTY) && this.ageTicks % 40 == 0) {
Antarchy.LOGGER.info("Portal gun traversal unavailable portal={} side={} linked={} loaded={}",
this.getUUID(), this.getPortalSide(), this.linkedPortalId, linked != null);
}
if (this.ageTicks % 100 == 0) {
Antarchy.LOGGER.debug("Portal gun portal has no loaded linked portal portal={} owner={} side={} linked={}", this.getUUID(), this.ownerId, this.getPortalSide(), this.linkedPortalId);
}
@@ -560,8 +573,20 @@ public class PortalGunPortalEntity extends Entity implements GeoEntity {
AABB previousBox = currentBox.move(entity.xo - entity.getX(), entity.yo - entity.getY(), entity.zo - entity.getZ());
PortalCrossing crossing = this.resolveCrossing(entity, previousBox, currentBox);
if (crossing == null) {
if (entity instanceof Player && Boolean.getBoolean(DEBUG_TRAVERSAL_PROPERTY) && this.ageTicks % 20 == 0) {
Vec3 probe = this.teleportProbePosition(entity, currentBox);
double depth = this.getWorldPortalShape().localCoords(probe).depth();
if (depth < 1.5D) {
Antarchy.LOGGER.info("Portal gun traversal waiting portal={} player={} depth={} position={} linked={}",
this.getUUID(), entity.getUUID(), depth, entity.position(), linked.getUUID());
}
}
continue;
}
if (entity instanceof Player && Boolean.getBoolean(DEBUG_TRAVERSAL_PROPERTY)) {
Antarchy.LOGGER.info("Portal gun traversal accepted portal={} player={} probe={} offset={} linked={}",
this.getUUID(), entity.getUUID(), crossing.probe(), crossing.normalOffset(), linked.getUUID());
}
logCrossingPath(entity, "portal_tick");
Vec3 resolvedMovement = entity.position().subtract(entity.xo, entity.yo, entity.zo);
this.teleportEntity(entity, linked, crossing.probe(), resolvedMovement, crossing.normalOffset());
@@ -5,6 +5,8 @@ import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Predicate;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
@@ -17,6 +19,7 @@ public final class TimeDilationApi {
};
private static final Map<UUID, Double> SYNCED_CLIENT_RATES = new ConcurrentHashMap<>();
private static final ThreadLocal<Integer> ROTATION_BYPASS_DEPTH = ThreadLocal.withInitial(() -> 0);
private static final List<Predicate<Entity>> HORIZONTAL_ONLY_MOVEMENT = new CopyOnWriteArrayList<>();
private TimeDilationApi() {
}
@@ -98,7 +101,7 @@ public final class TimeDilationApi {
}
public static void applySyncedRate(UUID entityUuid, double rate) {
double clampedRate = TimeDilationMath.clampRate(rate);
double clampedRate = TimeDilationMath.clampRate(rate, TimeDilationMath.ABSOLUTE_MAX_RATE);
SYNCED_CLIENT_RATES.put(entityUuid, clampedRate);
}
@@ -122,7 +125,75 @@ public final class TimeDilationApi {
}
public static void syncEntityRate(Entity entity, double rate) {
syncDispatcher.accept(entity, TimeDilationMath.clampRate(rate));
syncDispatcher.accept(entity, TimeDilationMath.clampRate(rate, TimeDilationMath.ABSOLUTE_MAX_RATE));
}
public static void addHorizontalOnlyMovement(Predicate<Entity> predicate) {
HORIZONTAL_ONLY_MOVEMENT.add(predicate);
}
public static boolean isHorizontalOnlyMovement(Entity entity) {
for (Predicate<Entity> predicate : HORIZONTAL_ONLY_MOVEMENT) {
if (predicate.test(entity)) {
return true;
}
}
return false;
}
public static double getRateCeiling(Entity entity) {
if (entity instanceof TimeDilationEntityAccess access) {
return access.antarchy$getTimeDilationCeiling();
}
return TimeDilationMath.MAX_RATE;
}
public static void setRateCeiling(Entity entity, double ceiling) {
if (entity instanceof TimeDilationEntityAccess access) {
access.antarchy$setTimeDilationCeiling(ceiling);
TimeDilationManager.trackPotentiallyAffected(entity);
}
}
public static void clearRateCeiling(Entity entity) {
setRateCeiling(entity, TimeDilationMath.MAX_RATE);
}
public static void setPersonalRate(Entity entity, String sourceId, double rate) {
if (entity instanceof TimeDilationEntityAccess access) {
double clampedRate = TimeDilationMath.clampRate(rate, TimeDilationMath.ABSOLUTE_MAX_RATE);
if (Math.abs(clampedRate - TimeDilationMath.NORMAL_RATE) < 0.001D) {
access.antarchy$getPersonalTimeDilationRates().remove(sourceId);
} else {
access.antarchy$getPersonalTimeDilationRates().put(sourceId, clampedRate);
}
TimeDilationManager.trackPotentiallyAffected(entity);
}
}
public static void clearPersonalRate(Entity entity, String sourceId) {
if (entity instanceof TimeDilationEntityAccess access
&& access.antarchy$getPersonalTimeDilationRates().remove(sourceId) != null) {
TimeDilationManager.trackPotentiallyAffected(entity);
}
}
public static double getPersonalRate(Entity entity, String sourceId) {
if (entity instanceof TimeDilationEntityAccess access) {
return access.antarchy$getPersonalTimeDilationRates().getOrDefault(sourceId, TimeDilationMath.NORMAL_RATE);
}
return TimeDilationMath.NORMAL_RATE;
}
public static boolean hasPersonalRates(Entity entity) {
return entity instanceof TimeDilationEntityAccess access && !access.antarchy$getPersonalTimeDilationRates().isEmpty();
}
public static TimeDilationFieldEntity createAttachedField(ServerLevel level, Entity owner, double radius, double rate, int durationTicks) {
TimeDilationFieldEntity field = TimeDilationFieldEntity.create(level, owner.position(), radius, rate, durationTicks, owner.getUUID(), true);
field.attachTo(owner);
level.addFreshEntity(field);
return field;
}
public static void syncFields(ServerPlayer player, List<TimeDilationFieldSnapshot> fields) {
@@ -1,6 +1,14 @@
package com.craisinlord.antarchy.content.time;
import java.util.Map;
public interface TimeDilationEntityAccess {
double antarchy$getTimeDilationCeiling();
void antarchy$setTimeDilationCeiling(double ceiling);
Map<String, Double> antarchy$getPersonalTimeDilationRates();
double antarchy$getTimeDilationRate();
void antarchy$setTimeDilationRate(double rate);
@@ -77,6 +77,7 @@ public final class TimeDilationManager {
&& Math.abs(access.antarchy$getTimeDilationRate() - TimeDilationMath.NORMAL_RATE) < 0.001D
&& Math.abs(access.antarchy$getInheritedTimeDilationRate() - TimeDilationMath.NORMAL_RATE) < 0.001D
&& Math.abs(TimeDilationApi.getVehicleRate(entity) - TimeDilationMath.NORMAL_RATE) < 0.001D
&& !TimeDilationApi.hasPersonalRates(entity)
&& !hasTuner && !hasEffect) {
return;
}
@@ -215,8 +216,16 @@ public final class TimeDilationManager {
} else {
fastRate = Math.max(fastRate, inheritedRate);
}
double targetRate = slowRate < TimeDilationMath.NORMAL_RATE ? slowRate : fastRate;
double rate = TimeDilationMath.transitionRate(previousRate, targetRate);
for (double personalRate : access.antarchy$getPersonalTimeDilationRates().values()) {
if (personalRate < TimeDilationMath.NORMAL_RATE) {
slowRate = Math.min(slowRate, personalRate);
} else {
fastRate = Math.max(fastRate, personalRate);
}
}
double ceiling = access.antarchy$getTimeDilationCeiling();
double targetRate = TimeDilationMath.clampRate(slowRate < TimeDilationMath.NORMAL_RATE ? slowRate : fastRate, ceiling);
double rate = TimeDilationMath.transitionRate(previousRate, targetRate, ceiling);
if (Math.abs(targetRate - TimeDilationMath.NORMAL_RATE) < 0.001D
&& Math.abs(rate - TimeDilationMath.NORMAL_RATE) < 0.001D) {
TimeDilationApi.clearRate(entity);
@@ -4,6 +4,7 @@ public final class TimeDilationMath {
public static final double MIN_RATE = 0.05D;
public static final double NORMAL_RATE = 1.0D;
public static final double MAX_RATE = 3.0D;
public static final double ABSOLUTE_MAX_RATE = 20.0D;
public static final int FIELD_TRANSITION_TICKS = 20;
public static final int EFFECT_TRANSITION_TICKS = 20;
private static final double GAUSSIAN_EDGE_EXPONENT = -4.5D;
@@ -18,12 +19,31 @@ public final class TimeDilationMath {
return Math.max(MIN_RATE, Math.min(MAX_RATE, rate));
}
public static double clampCeiling(double ceiling) {
if (Double.isNaN(ceiling) || Double.isInfinite(ceiling)) {
return MAX_RATE;
}
return Math.max(NORMAL_RATE, Math.min(ABSOLUTE_MAX_RATE, ceiling));
}
public static double clampRate(double rate, double ceiling) {
if (Double.isNaN(rate) || Double.isInfinite(rate)) {
return NORMAL_RATE;
}
return Math.max(MIN_RATE, Math.min(clampCeiling(ceiling), rate));
}
public static double transitionRate(double currentRate, double targetRate) {
double current = clampRate(currentRate);
double target = clampRate(targetRate);
double maximumStep = (MAX_RATE - MIN_RATE) / EFFECT_TRANSITION_TICKS;
return transitionRate(currentRate, targetRate, MAX_RATE);
}
public static double transitionRate(double currentRate, double targetRate, double ceiling) {
double maximum = clampCeiling(ceiling);
double current = clampRate(currentRate, maximum);
double target = clampRate(targetRate, maximum);
double maximumStep = (maximum - MIN_RATE) / EFFECT_TRANSITION_TICKS;
double delta = Math.max(-maximumStep, Math.min(maximumStep, target - current));
return clampRate(current + delta);
return clampRate(current + delta, maximum);
}
public static double gaussianFalloff(double distance, double radius) {
@@ -4,6 +4,7 @@ import com.craisinlord.antarchy.content.client.PortalGunPortalRenderState;
import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewAreaManager;
import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalRendererPool;
import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewRenderer;
import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalVisibilityQueries;
import com.craisinlord.antarchy.content.client.renderer.PortalStencilTarget;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
@@ -17,7 +18,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public abstract class LevelRendererPortalLifecycleMixin {
@Inject(method = "renderLevel", at = @At("HEAD"))
private void antarchy$preparePortalStencil(CallbackInfo ci) {
if (!PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this)) {
if (PortalGunPortalViewRenderer.isEnabled() && !PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this)) {
PortalStencilTarget.prepareMainTarget(Minecraft.getInstance().getMainRenderTarget());
}
}
@@ -29,6 +30,7 @@ public abstract class LevelRendererPortalLifecycleMixin {
}
PortalGunPortalRendererPool.clear();
PortalGunPortalViewAreaManager.clear();
PortalGunPortalVisibilityQueries.clear();
}
@Inject(method = "close", at = @At("HEAD"))
@@ -39,6 +39,9 @@ public abstract class EntityMoveTimeDilationMixin {
if (Math.abs(rate - TimeDilationMath.NORMAL_RATE) < 0.001D) {
return movement;
}
if (rate > TimeDilationMath.NORMAL_RATE && TimeDilationApi.isHorizontalOnlyMovement(entity)) {
return movement.multiply(rate, 1.0D, rate);
}
return movement.scale(rate);
}
}
@@ -20,6 +20,25 @@ public abstract class EntityTimeDilationStateMixin implements TimeDilationEntity
private boolean antarchy$inTimeDilationMove;
@Unique
private boolean antarchy$applyingExternalImpulse;
@Unique
private double antarchy$timeDilationCeiling = TimeDilationMath.MAX_RATE;
@Unique
private final Map<String, Double> antarchy$personalTimeDilationRates = new HashMap<>();
@Override
public double antarchy$getTimeDilationCeiling() {
return this.antarchy$timeDilationCeiling;
}
@Override
public void antarchy$setTimeDilationCeiling(double ceiling) {
this.antarchy$timeDilationCeiling = TimeDilationMath.clampCeiling(ceiling);
}
@Override
public Map<String, Double> antarchy$getPersonalTimeDilationRates() {
return this.antarchy$personalTimeDilationRates;
}
@Override
public double antarchy$getTimeDilationRate() {
@@ -29,7 +48,7 @@ public abstract class EntityTimeDilationStateMixin implements TimeDilationEntity
@Override
public void antarchy$setTimeDilationRate(double rate) {
double previousRate = this.antarchy$timeDilationRate;
this.antarchy$timeDilationRate = TimeDilationMath.clampRate(rate);
this.antarchy$timeDilationRate = TimeDilationMath.clampRate(rate, TimeDilationMath.ABSOLUTE_MAX_RATE);
if (previousRate != TimeDilationMath.NORMAL_RATE
&& this.antarchy$timeDilationRate == TimeDilationMath.NORMAL_RATE) {
this.antarchy$timeDilationTimerProgress.clear();
@@ -53,7 +72,7 @@ public abstract class EntityTimeDilationStateMixin implements TimeDilationEntity
@Override
public int antarchy$consumeTimeDilationTicks(String timerKey, double rate) {
double clampedRate = TimeDilationMath.clampRate(rate);
double clampedRate = TimeDilationMath.clampRate(rate, TimeDilationMath.ABSOLUTE_MAX_RATE);
if (Math.abs(clampedRate - TimeDilationMath.NORMAL_RATE) < 0.001D) {
return 1;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

@@ -0,0 +1,5 @@
{
"villager": {
"hat": "none"
}
}
@@ -633,8 +633,8 @@ public final class AntarchyFabricClientBootstrap {
BloodCrystalKatanaTrailHandler.render(context);
ScorpionWhipTetherRenderHandler.render(context);
WormHookTetherRenderHandler.render(context);
if (context.matrixStack() != null && com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewRenderer.isEnabled()) {
com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewRenderer.render(context.camera(), context.matrixStack().last().pose(), context.projectionMatrix(), context.tickCounter());
if (com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewRenderer.isEnabled()) {
com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewRenderer.render(context.camera(), context.positionMatrix(), context.projectionMatrix(), context.tickCounter());
}
});
}
@@ -17,7 +17,7 @@ public final class PortalGunPortalViewRenderHandler {
if (!PortalGunPortalViewRenderer.isEnabled()) {
return;
}
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_BLOCK_ENTITIES) {
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_LEVEL) {
return;
}
PortalGunPortalViewRenderer.render(event.getCamera(), event.getModelViewMatrix(), event.getProjectionMatrix(), event.getPartialTick());