package net.minecraft.client.renderer.entity; import com.google.common.collect.ImmutableList.Builder; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; import java.util.ArrayList; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.culling.Frustum; import net.minecraft.client.renderer.entity.state.EntityRenderState; import net.minecraft.client.renderer.entity.state.HitboxRenderState; import net.minecraft.client.renderer.entity.state.HitboxesRenderState; import net.minecraft.client.server.IntegratedServer; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityAttachment; import net.minecraft.world.entity.Leashable; import net.minecraft.world.entity.vehicle.AbstractMinecart; import net.minecraft.world.entity.vehicle.NewMinecartBehavior; import net.minecraft.world.level.LightLayer; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; import org.joml.Matrix4f; @Environment(EnvType.CLIENT) public abstract class EntityRenderer { protected static final float NAMETAG_SCALE = 0.025F; public static final int LEASH_RENDER_STEPS = 24; public static final float LEASH_WIDTH = 0.05F; protected final EntityRenderDispatcher entityRenderDispatcher; private final Font font; protected float shadowRadius; protected float shadowStrength = 1.0F; private final S reusedState = this.createRenderState(); protected EntityRenderer(EntityRendererProvider.Context context) { this.entityRenderDispatcher = context.getEntityRenderDispatcher(); this.font = context.getFont(); } public final int getPackedLightCoords(T entity, float partialTicks) { BlockPos blockPos = BlockPos.containing(entity.getLightProbePosition(partialTicks)); return LightTexture.pack(this.getBlockLightLevel(entity, blockPos), this.getSkyLightLevel(entity, blockPos)); } protected int getSkyLightLevel(T entity, BlockPos pos) { return entity.level().getBrightness(LightLayer.SKY, pos); } protected int getBlockLightLevel(T entity, BlockPos pos) { return entity.isOnFire() ? 15 : entity.level().getBrightness(LightLayer.BLOCK, pos); } public boolean shouldRender(T livingEntity, Frustum camera, double camX, double camY, double camZ) { if (!livingEntity.shouldRender(camX, camY, camZ)) { return false; } else if (!this.affectedByCulling(livingEntity)) { return true; } else { AABB aABB = this.getBoundingBoxForCulling(livingEntity).inflate(0.5); if (aABB.hasNaN() || aABB.getSize() == 0.0) { aABB = new AABB( livingEntity.getX() - 2.0, livingEntity.getY() - 2.0, livingEntity.getZ() - 2.0, livingEntity.getX() + 2.0, livingEntity.getY() + 2.0, livingEntity.getZ() + 2.0 ); } if (camera.isVisible(aABB)) { return true; } else { if (livingEntity instanceof Leashable leashable) { Entity entity = leashable.getLeashHolder(); if (entity != null) { AABB aABB2 = this.entityRenderDispatcher.getRenderer(entity).getBoundingBoxForCulling(entity); return camera.isVisible(aABB2) || camera.isVisible(aABB.minmax(aABB2)); } } return false; } } } protected AABB getBoundingBoxForCulling(T minecraft) { return minecraft.getBoundingBox(); } protected boolean affectedByCulling(T display) { return true; } public Vec3 getRenderOffset(S renderState) { return renderState.passengerOffset != null ? renderState.passengerOffset : Vec3.ZERO; } public void render(S renderState, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) { if (renderState.leashStates != null) { for (EntityRenderState.LeashState leashState : renderState.leashStates) { renderLeash(poseStack, bufferSource, leashState); } } if (renderState.nameTag != null) { this.renderNameTag(renderState, renderState.nameTag, poseStack, bufferSource, packedLight); } } private static void renderLeash(PoseStack poseStack, MultiBufferSource buffer, EntityRenderState.LeashState leashState) { float f = (float)(leashState.end.x - leashState.start.x); float g = (float)(leashState.end.y - leashState.start.y); float h = (float)(leashState.end.z - leashState.start.z); float i = Mth.invSqrt(f * f + h * h) * 0.05F / 2.0F; float j = h * i; float k = f * i; poseStack.pushPose(); poseStack.translate(leashState.offset); VertexConsumer vertexConsumer = buffer.getBuffer(RenderType.leash()); Matrix4f matrix4f = poseStack.last().pose(); for (int l = 0; l <= 24; l++) { addVertexPair(vertexConsumer, matrix4f, f, g, h, 0.05F, 0.05F, j, k, l, false, leashState); } for (int l = 24; l >= 0; l--) { addVertexPair(vertexConsumer, matrix4f, f, g, h, 0.05F, 0.0F, j, k, l, true, leashState); } poseStack.popPose(); } private static void addVertexPair( VertexConsumer consumer, Matrix4f pose, float startX, float startY, float startZ, float yOffset, float dy, float dx, float dz, int index, boolean reverse, EntityRenderState.LeashState leashState ) { float f = index / 24.0F; int i = (int)Mth.lerp(f, (float)leashState.startBlockLight, (float)leashState.endBlockLight); int j = (int)Mth.lerp(f, (float)leashState.startSkyLight, (float)leashState.endSkyLight); int k = LightTexture.pack(i, j); float g = index % 2 == (reverse ? 1 : 0) ? 0.7F : 1.0F; float h = 0.5F * g; float l = 0.4F * g; float m = 0.3F * g; float n = startX * f; float o; if (leashState.slack) { o = startY > 0.0F ? startY * f * f : startY - startY * (1.0F - f) * (1.0F - f); } else { o = startY * f; } float p = startZ * f; consumer.addVertex(pose, n - dx, o + dy, p + dz).setColor(h, l, m, 1.0F).setLight(k); consumer.addVertex(pose, n + dx, o + yOffset - dy, p - dz).setColor(h, l, m, 1.0F).setLight(k); } protected boolean shouldShowName(T entity, double distanceToCameraSq) { return entity.shouldShowName() || entity.hasCustomName() && entity == this.entityRenderDispatcher.crosshairPickEntity; } /** * Returns the font renderer from the set render manager */ public Font getFont() { return this.font; } protected void renderNameTag(S renderState, Component displayName, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) { Vec3 vec3 = renderState.nameTagAttachment; if (vec3 != null) { boolean bl = !renderState.isDiscrete; int i = "deadmau5".equals(displayName.getString()) ? -10 : 0; poseStack.pushPose(); poseStack.translate(vec3.x, vec3.y + 0.5, vec3.z); poseStack.mulPose(this.entityRenderDispatcher.cameraOrientation()); poseStack.scale(0.025F, -0.025F, 0.025F); Matrix4f matrix4f = poseStack.last().pose(); Font font = this.getFont(); float f = -font.width(displayName) / 2.0F; int j = (int)(Minecraft.getInstance().options.getBackgroundOpacity(0.25F) * 255.0F) << 24; font.drawInBatch( displayName, f, (float)i, -2130706433, false, matrix4f, bufferSource, bl ? Font.DisplayMode.SEE_THROUGH : Font.DisplayMode.NORMAL, j, packedLight ); if (bl) { font.drawInBatch( displayName, f, (float)i, -1, false, matrix4f, bufferSource, Font.DisplayMode.NORMAL, 0, LightTexture.lightCoordsWithEmission(packedLight, 2) ); } poseStack.popPose(); } } @Nullable protected Component getNameTag(T entity) { return entity.getDisplayName(); } protected float getShadowRadius(S renderState) { return this.shadowRadius; } protected float getShadowStrength(S renderState) { return this.shadowStrength; } public abstract S createRenderState(); public final S createRenderState(T entity, float partialTick) { S entityRenderState = this.reusedState; this.extractRenderState(entity, entityRenderState, partialTick); return entityRenderState; } public void extractRenderState(T entity, S reusedState, float partialTick) { reusedState.entityType = entity.getType(); reusedState.x = Mth.lerp((double)partialTick, entity.xOld, entity.getX()); reusedState.y = Mth.lerp((double)partialTick, entity.yOld, entity.getY()); reusedState.z = Mth.lerp((double)partialTick, entity.zOld, entity.getZ()); reusedState.isInvisible = entity.isInvisible(); reusedState.ageInTicks = entity.tickCount + partialTick; reusedState.boundingBoxWidth = entity.getBbWidth(); reusedState.boundingBoxHeight = entity.getBbHeight(); reusedState.eyeHeight = entity.getEyeHeight(); if (entity.isPassenger() && entity.getVehicle() instanceof AbstractMinecart abstractMinecart && abstractMinecart.getBehavior() instanceof NewMinecartBehavior newMinecartBehavior && newMinecartBehavior.cartHasPosRotLerp()) { double d = Mth.lerp((double)partialTick, abstractMinecart.xOld, abstractMinecart.getX()); double e = Mth.lerp((double)partialTick, abstractMinecart.yOld, abstractMinecart.getY()); double f = Mth.lerp((double)partialTick, abstractMinecart.zOld, abstractMinecart.getZ()); reusedState.passengerOffset = newMinecartBehavior.getCartLerpPosition(partialTick).subtract(new Vec3(d, e, f)); } else { reusedState.passengerOffset = null; } reusedState.distanceToCameraSq = this.entityRenderDispatcher.distanceToSqr(entity); boolean bl = reusedState.distanceToCameraSq < 4096.0 && this.shouldShowName(entity, reusedState.distanceToCameraSq); if (bl) { reusedState.nameTag = this.getNameTag(entity); reusedState.nameTagAttachment = entity.getAttachments().getNullable(EntityAttachment.NAME_TAG, 0, entity.getYRot(partialTick)); } else { reusedState.nameTag = null; } label77: { reusedState.isDiscrete = entity.isDiscrete(); if (entity instanceof Leashable leashable) { Entity g = leashable.getLeashHolder(); if (g instanceof Entity) { float gx = entity.getPreciseBodyRotation(partialTick) * (float) (Math.PI / 180.0); Vec3 vec3 = leashable.getLeashOffset(partialTick); BlockPos blockPos = BlockPos.containing(entity.getEyePosition(partialTick)); BlockPos blockPos2 = BlockPos.containing(g.getEyePosition(partialTick)); int i = this.getBlockLightLevel(entity, blockPos); int j = this.entityRenderDispatcher.getRenderer(g).getBlockLightLevel(g, blockPos2); int k = entity.level().getBrightness(LightLayer.SKY, blockPos); int l = entity.level().getBrightness(LightLayer.SKY, blockPos2); boolean bl2 = g.supportQuadLeashAsHolder() && leashable.supportQuadLeash(); int m = bl2 ? 4 : 1; if (reusedState.leashStates == null || reusedState.leashStates.size() != m) { reusedState.leashStates = new ArrayList(m); for (int n = 0; n < m; n++) { reusedState.leashStates.add(new EntityRenderState.LeashState()); } } if (bl2) { float h = g.getPreciseBodyRotation(partialTick) * (float) (Math.PI / 180.0); Vec3 vec32 = g.getPosition(partialTick); Vec3[] vec3s = leashable.getQuadLeashOffsets(); Vec3[] vec3s2 = g.getQuadLeashHolderOffsets(); int o = 0; while (true) { if (o >= m) { break label77; } EntityRenderState.LeashState leashState = (EntityRenderState.LeashState)reusedState.leashStates.get(o); leashState.offset = vec3s[o].yRot(-gx); leashState.start = entity.getPosition(partialTick).add(leashState.offset); leashState.end = vec32.add(vec3s2[o].yRot(-h)); leashState.startBlockLight = i; leashState.endBlockLight = j; leashState.startSkyLight = k; leashState.endSkyLight = l; leashState.slack = false; o++; } } else { Vec3 vec33 = vec3.yRot(-gx); EntityRenderState.LeashState leashState2 = (EntityRenderState.LeashState)reusedState.leashStates.getFirst(); leashState2.offset = vec33; leashState2.start = entity.getPosition(partialTick).add(vec33); leashState2.end = g.getRopeHoldPosition(partialTick); leashState2.startBlockLight = i; leashState2.endBlockLight = j; leashState2.startSkyLight = k; leashState2.endSkyLight = l; break label77; } } } reusedState.leashStates = null; } reusedState.displayFireAnimation = entity.displayFireAnimation(); Minecraft minecraft = Minecraft.getInstance(); if (minecraft.getEntityRenderDispatcher().shouldRenderHitBoxes() && !reusedState.isInvisible && !minecraft.showOnlyReducedInfo()) { this.extractHitboxes(entity, reusedState, partialTick); } else { reusedState.hitboxesRenderState = null; reusedState.serverHitboxesRenderState = null; } } private void extractHitboxes(T entity, S reusedState, float partialTick) { reusedState.hitboxesRenderState = this.extractHitboxes(entity, partialTick, false); reusedState.serverHitboxesRenderState = null; } private HitboxesRenderState extractHitboxes(T entity, float partialTick, boolean green) { Builder builder = new Builder<>(); AABB aABB = entity.getBoundingBox(); HitboxRenderState hitboxRenderState; if (green) { hitboxRenderState = new HitboxRenderState( aABB.minX - entity.getX(), aABB.minY - entity.getY(), aABB.minZ - entity.getZ(), aABB.maxX - entity.getX(), aABB.maxY - entity.getY(), aABB.maxZ - entity.getZ(), 0.0F, 1.0F, 0.0F ); } else { hitboxRenderState = new HitboxRenderState( aABB.minX - entity.getX(), aABB.minY - entity.getY(), aABB.minZ - entity.getZ(), aABB.maxX - entity.getX(), aABB.maxY - entity.getY(), aABB.maxZ - entity.getZ(), 1.0F, 1.0F, 1.0F ); } builder.add(hitboxRenderState); Entity entity2 = entity.getVehicle(); if (entity2 != null) { float f = Math.min(entity2.getBbWidth(), entity.getBbWidth()) / 2.0F; float g = 0.0625F; Vec3 vec3 = entity2.getPassengerRidingPosition(entity).subtract(entity.position()); HitboxRenderState hitboxRenderState2 = new HitboxRenderState(vec3.x - f, vec3.y, vec3.z - f, vec3.x + f, vec3.y + 0.0625, vec3.z + f, 1.0F, 1.0F, 0.0F); builder.add(hitboxRenderState2); } this.extractAdditionalHitboxes(entity, builder, partialTick); Vec3 vec32 = entity.getViewVector(partialTick); return new HitboxesRenderState(vec32.x, vec32.y, vec32.z, builder.build()); } protected void extractAdditionalHitboxes(T entity, Builder hitboxes, float partialTick) { } @Nullable private static Entity getServerSideEntity(Entity entity) { IntegratedServer integratedServer = Minecraft.getInstance().getSingleplayerServer(); if (integratedServer != null) { ServerLevel serverLevel = integratedServer.getLevel(entity.level().dimension()); if (serverLevel != null) { return serverLevel.getEntity(entity.getId()); } } return null; } }