aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/utils/EntityOutlineRenderer.kt
diff options
context:
space:
mode:
authorhannibal2 <24389977+hannibal00212@users.noreply.github.com>2023-09-07 13:06:38 +0200
committerhannibal2 <24389977+hannibal00212@users.noreply.github.com>2023-09-07 13:06:38 +0200
commitd345adc3ecd390d9b722127a4013a6edb6c3dc76 (patch)
tree2aac88e988a2bf146745e7267ef8b2996b467f1d /src/main/java/at/hannibal2/skyhanni/utils/EntityOutlineRenderer.kt
parent6be7873d9475686fdc404212cebd45f22edbff87 (diff)
downloadskyhanni-d345adc3ecd390d9b722127a4013a6edb6c3dc76.tar.gz
skyhanni-d345adc3ecd390d9b722127a4013a6edb6c3dc76.tar.bz2
skyhanni-d345adc3ecd390d9b722127a4013a6edb6c3dc76.zip
removing the extra toggle "entity outlines", the logic will now be enabled if any feature that works with it is enabled
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/utils/EntityOutlineRenderer.kt')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/EntityOutlineRenderer.kt757
1 files changed, 382 insertions, 375 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/EntityOutlineRenderer.kt b/src/main/java/at/hannibal2/skyhanni/utils/EntityOutlineRenderer.kt
index 454df1454..e86d5bfda 100644
--- a/src/main/java/at/hannibal2/skyhanni/utils/EntityOutlineRenderer.kt
+++ b/src/main/java/at/hannibal2/skyhanni/utils/EntityOutlineRenderer.kt
@@ -1,376 +1,383 @@
-package at.hannibal2.skyhanni.utils
-
-import at.hannibal2.skyhanni.SkyHanniMod
-import at.hannibal2.skyhanni.events.LorenzTickEvent
-import at.hannibal2.skyhanni.events.RenderEntityOutlineEvent
-import at.hannibal2.skyhanni.mixins.transformers.CustomRenderGlobal
-import net.minecraft.client.Minecraft
-import net.minecraft.client.renderer.GlStateManager
-import net.minecraft.client.renderer.OpenGlHelper
-import net.minecraft.client.renderer.RenderHelper
-import net.minecraft.client.renderer.culling.ICamera
-import net.minecraft.client.shader.Framebuffer
-import net.minecraft.entity.Entity
-import net.minecraft.entity.EntityLivingBase
-import net.minecraft.util.BlockPos
-import net.minecraftforge.client.MinecraftForgeClient
-import net.minecraftforge.common.MinecraftForge
-import net.minecraftforge.fml.common.eventhandler.EventPriority
-import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
-import net.minecraftforge.fml.common.gameevent.TickEvent
-import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent
-import org.lwjgl.opengl.GL11
-import org.lwjgl.opengl.GL13
-import org.lwjgl.opengl.GL30
-import java.lang.reflect.InvocationTargetException
-import java.lang.reflect.Method
-
-/**
- * Class to handle all entity outlining, including xray and no-xray rendering
- * Features that include entity outlining should subscribe to the {@link RenderEntityOutlineEvent}.
- *
- * Credit to SkyblockAddons and Biscuit Development
- * https://github.com/BiscuitDevelopment/SkyblockAddons/blob/main/src/main/java/codes/biscuit/skyblockaddons/features/EntityOutlines/EntityOutlineRenderer.java
- *
- */
- object EntityOutlineRenderer {
- private val entityRenderCache: CachedInfo = CachedInfo(null, null, null)
- private var stopLookingForOptifine = false
- private var isFastRender: Method? = null
- private var isShaders: Method? = null
- private var isAntialiasing: Method? = null
- private var emptyLastTick = false
- private val swapBuffer by lazy { initSwapBuffer() }
- private val logger = LorenzLogger("entity_outline_renderer")
- private val config get() = SkyHanniMod.feature.misc
- private val mc get() = Minecraft.getMinecraft()
- private val BUF_FLOAT_4: java.nio.FloatBuffer = org.lwjgl.BufferUtils.createFloatBuffer(4)
-
- /**
- * @return a new framebuffer with the size of the main framebuffer
- */
- private fun initSwapBuffer(): Framebuffer {
- val main = mc.framebuffer
- val framebuffer = Framebuffer(main.framebufferTextureWidth, main.framebufferTextureHeight, true)
- framebuffer.setFramebufferFilter(GL11.GL_NEAREST)
- framebuffer.setFramebufferColor(0.0f, 0.0f, 0.0f, 0.0f)
- return framebuffer
- }
-
- private fun updateFramebufferSize() {
- val width = mc.displayWidth
- val height = mc.displayHeight
- if (swapBuffer.framebufferWidth != width || swapBuffer.framebufferHeight != height) {
- swapBuffer.createBindFramebuffer(width, height)
- }
- val rg = mc.renderGlobal as CustomRenderGlobal
- val outlineBuffer = rg.entityOutlineFramebuffer_skyhanni
- if (outlineBuffer.framebufferWidth != width || outlineBuffer.framebufferHeight != height) {
- outlineBuffer.createBindFramebuffer(width, height)
- rg.entityOutlineShader_skyhanni.createBindFramebuffers(width, height)
- }
- }
-
- /**
- * Renders xray and no-xray entity outlines.
- *
- * @param camera the current camera
- * @param partialTicks the progress to the next tick
- * @param x the camera x position
- * @param y the camera y position
- * @param z the camera z position
- */
- @JvmStatic
- fun renderEntityOutlines(camera: ICamera, partialTicks: Float, vector: LorenzVec): Boolean {
- val shouldRenderOutlines = shouldRenderEntityOutlines()
-
- if (!(shouldRenderOutlines && !isCacheEmpty() && MinecraftForgeClient.getRenderPass() == 0)) {
- return !shouldRenderOutlines
- }
-
- val renderGlobal = mc.renderGlobal as CustomRenderGlobal
- val renderManager = mc.renderManager
- mc.theWorld.theProfiler.endStartSection("entityOutlines")
- updateFramebufferSize()
-
- // Clear and bind the outline framebuffer
- renderGlobal.entityOutlineFramebuffer_skyhanni.framebufferClear()
- renderGlobal.entityOutlineFramebuffer_skyhanni.bindFramebuffer(false)
-
- // Vanilla options
- RenderHelper.disableStandardItemLighting()
- GlStateManager.disableFog()
- mc.renderManager.setRenderOutlines(true)
-
- // Enable outline mode
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_REPLACE);
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL13.GL_CONSTANT);
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_ALPHA, GL11.GL_TEXTURE);
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA);
-
- // Render x-ray outlines first, ignoring the depth buffer bit
- if (!isXrayCacheEmpty()) {
- // Xray is enabled by disabling depth testing
- GlStateManager.depthFunc(GL11.GL_ALWAYS)
-
- entityRenderCache.xrayCache?.forEach { (key, value) ->
- // Test if the entity should render, given the player's camera position
- if (!shouldRender(camera, key, vector)) return@forEach
-
- try {
- if (key !is EntityLivingBase) outlineColor(value)
- renderManager.renderEntityStatic(key, partialTicks, true)
- } catch (ignored: Exception) {
- }
- }
-
- // Reset depth function
- GlStateManager.depthFunc(GL11.GL_LEQUAL)
- }
-
- // Render no-xray outlines second, taking into consideration the depth bit
- if (!isNoXrayCacheEmpty()) {
- if (!isNoOutlineCacheEmpty()) {
- // Render other entities + terrain that may occlude an entity outline into a depth buffer
- swapBuffer.framebufferClear()
- copyBuffers(mc.framebuffer, swapBuffer, GL11.GL_DEPTH_BUFFER_BIT)
- swapBuffer.bindFramebuffer(false)
-
- // Copy terrain + other entities depth into outline frame buffer to now switch to no-xray outlines
- entityRenderCache.noOutlineCache?.forEach { entity ->
- // Test if the entity should render, given the player's instantaneous camera position
- if (!shouldRender(camera, entity, vector)) return@forEach
-
- try {
- renderManager.renderEntityStatic(entity, partialTicks, true)
- } catch (ignored: Exception) {
- }
- }
-
- // Copy the entire depth buffer of everything that might occlude outline to outline framebuffer
- copyBuffers(swapBuffer, renderGlobal.entityOutlineFramebuffer_skyhanni, GL11.GL_DEPTH_BUFFER_BIT)
- renderGlobal.entityOutlineFramebuffer_skyhanni.bindFramebuffer(false)
- } else {
- copyBuffers(mc.framebuffer, renderGlobal.entityOutlineFramebuffer_skyhanni, GL11.GL_DEPTH_BUFFER_BIT)
- }
-
- // Xray disabled by re-enabling traditional depth testing
- entityRenderCache.noXrayCache?.forEach { (key, value) ->
- // Test if the entity should render, given the player's instantaneous camera position
- if (!shouldRender(camera, key, vector)) return@forEach
-
- try {
- if (key !is EntityLivingBase) outlineColor(value)
- renderManager.renderEntityStatic(key, partialTicks, true)
- } catch (ignored: Exception) {
- }
- }
- }
-
- // Disable outline mode
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_ALPHA, GL11.GL_TEXTURE);
- GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA);
-
- // Vanilla options
- RenderHelper.enableStandardItemLighting()
- mc.renderManager.setRenderOutlines(false)
-
- // Load the outline shader
- GlStateManager.depthMask(false)
- renderGlobal.entityOutlineShader_skyhanni.loadShaderGroup(partialTicks)
- GlStateManager.depthMask(true)
-
- // Reset GL/framebuffers for next render layers
- GlStateManager.enableLighting()
- mc.framebuffer.bindFramebuffer(false)
- GlStateManager.enableFog()
- GlStateManager.enableBlend()
- GlStateManager.enableColorMaterial()
- GlStateManager.enableDepth()
- GlStateManager.enableAlpha()
-
- return !shouldRenderOutlines
- }
-
- @JvmStatic
- fun getCustomOutlineColor(entity: Entity?): Int? {
- if (entityRenderCache.xrayCache?.containsKey(entity) == true) {
- return entityRenderCache.xrayCache!![entity]
- }
- return if (entityRenderCache.noXrayCache?.containsKey(entity) == true) {
- entityRenderCache.noXrayCache!![entity]
- } else null
- }
-
- /**
- * Caches optifine settings and determines whether outlines should be rendered
- *
- * @return `true` iff outlines should be rendered
- */
- @JvmStatic
- fun shouldRenderEntityOutlines(): Boolean {
- val renderGlobal = mc.renderGlobal as CustomRenderGlobal
-
- // Vanilla Conditions
- if (renderGlobal.entityOutlineFramebuffer_skyhanni == null || renderGlobal.entityOutlineShader_skyhanni == null || mc.thePlayer == null) return false
-
- // Skyblock Conditions
- if (!LorenzUtils.inSkyBlock) {
- return false
- }
-
- // Main toggle for outlines features
- if (!config.enableEntityOutlines) {
- return false
- }
-
- // Optifine Conditions
- if (!stopLookingForOptifine && isFastRender == null) {
- try {
- val config = Class.forName("Config")
- try {
- isFastRender = config.getMethod("isFastRender")
- isShaders = config.getMethod("isShaders")
- isAntialiasing = config.getMethod("isAntialiasing")
- } catch (ex: Exception) {
- logger.log("Couldn't find Optifine methods for entity outlines.")
- stopLookingForOptifine = true
- }
- } catch (ex: Exception) {
- logger.log("Couldn't find Optifine for entity outlines.")
- stopLookingForOptifine = true
- }
- }
- var isFastRenderValue = false
- var isShadersValue = false
- var isAntialiasingValue = false
- if (isFastRender != null) {
- try {
- isFastRenderValue = isFastRender!!.invoke(null) as Boolean
- isShadersValue = isShaders!!.invoke(null) as Boolean
- isAntialiasingValue = isAntialiasing!!.invoke(null) as Boolean
- } catch (ex: IllegalAccessException) {
- logger.log("An error occurred while calling Optifine methods for entity outlines... $ex")
- } catch (ex: InvocationTargetException) {
- logger.log("An error occurred while calling Optifine methods for entity outlines... $ex")
- }
- }
- return !isFastRenderValue && !isShadersValue && !isAntialiasingValue
- }
-
- /**
- * Apply the same rendering standards as in [net.minecraft.client.renderer.RenderGlobal.renderEntities] lines 659 to 669
- *
- * @param camera the current camera
- * @param entity the entity to render
- * @param x the camera x position
- * @param y the camera y position
- * @param z the camera z position
- * @return whether the entity should be rendered
- */
- private fun shouldRender(camera: ICamera, entity: Entity, vector: LorenzVec): Boolean =
- // Only render the view entity when sleeping or in 3rd person mode
- if (entity === mc.renderViewEntity &&
- !(mc.renderViewEntity is EntityLivingBase && (mc.renderViewEntity as EntityLivingBase).isPlayerSleeping ||
- mc.gameSettings.thirdPersonView != 0)) {
- false
- } else mc.theWorld.isBlockLoaded(BlockPos(entity)) && (mc.renderManager.shouldRender(entity, camera, vector.x, vector.y, vector.z) || entity.riddenByEntity === mc.thePlayer)
- // Only render if renderManager would render and the world is loaded at the entity
-
- private fun outlineColor(color: Int) {
- BUF_FLOAT_4.put(0, (color shr 16 and 255).toFloat() / 255.0f)
- BUF_FLOAT_4.put(1, (color shr 8 and 255).toFloat() / 255.0f)
- BUF_FLOAT_4.put(2, (color and 255).toFloat() / 255.0f)
- BUF_FLOAT_4.put(3, 1f)
- GL11.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, BUF_FLOAT_4)
- }
-
- /**
- * Function that copies a portion of a framebuffer to another framebuffer.
- *
- *
- * Note that this requires GL3.0 to function properly
- *
- *
- * The major use of this function is to copy the depth-buffer portion of the world framebuffer to the entity outline framebuffer.
- * This enables us to perform no-xray outlining on entities, as we can use the world framebuffer's depth testing on the outline frame buffer
- *
- * @param frameToCopy the framebuffer from which we are copying data
- * @param frameToPaste the framebuffer onto which we are copying the data
- * @param buffersToCopy the bit mask indicating the sections to copy (see [GL11.GL_DEPTH_BUFFER_BIT], [GL11.GL_COLOR_BUFFER_BIT], [GL11.GL_STENCIL_BUFFER_BIT])
- */
- private fun copyBuffers(frameToCopy: Framebuffer?, frameToPaste: Framebuffer?, buffersToCopy: Int) {
- if (OpenGlHelper.isFramebufferEnabled()) {
- OpenGlHelper.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, frameToCopy!!.framebufferObject)
- OpenGlHelper.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, frameToPaste!!.framebufferObject)
- GL30.glBlitFramebuffer(0, 0, frameToCopy.framebufferWidth, frameToCopy.framebufferHeight,
- 0, 0, frameToPaste.framebufferWidth, frameToPaste.framebufferHeight,
- buffersToCopy, GL11.GL_NEAREST)
- }
- }
-
- fun isCacheEmpty() = isXrayCacheEmpty() && isNoXrayCacheEmpty()
-
- private fun isXrayCacheEmpty() = entityRenderCache.xrayCache?.isEmpty() ?: true
- private fun isNoXrayCacheEmpty() = entityRenderCache.noXrayCache?.isEmpty() ?: true
- private fun isNoOutlineCacheEmpty() = entityRenderCache.noOutlineCache?.isEmpty() ?: true
-
- /**
- * Updates the cache at the start of every minecraft tick to improve efficiency.
- * Identifies and caches all entities in the world that should be outlined.
- *
- *
- * Calls to [.shouldRender] are frustum based, rely on partialTicks,
- * and so can't be updated on a per-tick basis without losing information.
- *
- *
- * This works since entities are only updated once per tick, so the inclusion or exclusion of an entity
- * to be outlined can be cached each tick with no loss of data
- *
- * @param event the client tick event
- */
- @SubscribeEvent
- fun onTick(event: LorenzTickEvent) {
- if (event.phase == EventPriority.NORMAL) {
- val rg = mc.renderGlobal as CustomRenderGlobal
-
- if (mc.theWorld != null && shouldRenderEntityOutlines()) {
- // These events need to be called in this specific order for the xray to have priority over the no xray
- // Get all entities to render xray outlines
- val xrayOutlineEvent = RenderEntityOutlineEvent(RenderEntityOutlineEvent.Type.XRAY, null)
- MinecraftForge.EVENT_BUS.post(xrayOutlineEvent)
- // Get all entities to render no xray outlines, using pre-filtered entities (no need to test xray outlined entities)
- val noxrayOutlineEvent = RenderEntityOutlineEvent(RenderEntityOutlineEvent.Type.NO_XRAY, xrayOutlineEvent.entitiesToChooseFrom)
- MinecraftForge.EVENT_BUS.post(noxrayOutlineEvent)
- // Cache the entities for future use
- entityRenderCache.xrayCache = xrayOutlineEvent.entitiesToOutline
- entityRenderCache.noXrayCache = noxrayOutlineEvent.entitiesToOutline
- entityRenderCache.noOutlineCache = noxrayOutlineEvent.entitiesToChooseFrom
- emptyLastTick = if (isCacheEmpty()) {
- if (!emptyLastTick) {
- rg.entityOutlineFramebuffer_skyhanni.framebufferClear()
- }
- true
- } else {
- false
- }
- } else if (!emptyLastTick) {
- entityRenderCache.xrayCache = null
- entityRenderCache.noXrayCache = null
- entityRenderCache.noOutlineCache = null
- if (rg.entityOutlineFramebuffer_skyhanni != null) rg.entityOutlineFramebuffer_skyhanni.framebufferClear()
- emptyLastTick = true
- }
- }
- }
-
- private class CachedInfo(var xrayCache: HashMap<Entity, Int>?, var noXrayCache: HashMap<Entity, Int>?, var noOutlineCache: HashSet<Entity>?)
+package at.hannibal2.skyhanni.utils
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.events.LorenzTickEvent
+import at.hannibal2.skyhanni.events.RenderEntityOutlineEvent
+import at.hannibal2.skyhanni.mixins.transformers.CustomRenderGlobal
+import net.minecraft.client.Minecraft
+import net.minecraft.client.renderer.GlStateManager
+import net.minecraft.client.renderer.OpenGlHelper
+import net.minecraft.client.renderer.RenderHelper
+import net.minecraft.client.renderer.culling.ICamera
+import net.minecraft.client.shader.Framebuffer
+import net.minecraft.entity.Entity
+import net.minecraft.entity.EntityLivingBase
+import net.minecraft.util.BlockPos
+import net.minecraftforge.client.MinecraftForgeClient
+import net.minecraftforge.common.MinecraftForge
+import net.minecraftforge.fml.common.eventhandler.EventPriority
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import net.minecraftforge.fml.common.gameevent.TickEvent
+import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent
+import org.lwjgl.opengl.GL11
+import org.lwjgl.opengl.GL13
+import org.lwjgl.opengl.GL30
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+
+/**
+ * Class to handle all entity outlining, including xray and no-xray rendering
+ * Features that include entity outlining should subscribe to the {@link RenderEntityOutlineEvent}.
+ *
+ * Credit to SkyblockAddons and Biscuit Development
+ * https://github.com/BiscuitDevelopment/SkyblockAddons/blob/main/src/main/java/codes/biscuit/skyblockaddons/features/EntityOutlines/EntityOutlineRenderer.java
+ *
+ */
+ object EntityOutlineRenderer {
+ private val entityRenderCache: CachedInfo = CachedInfo(null, null, null)
+ private var stopLookingForOptifine = false
+ private var isFastRender: Method? = null
+ private var isShaders: Method? = null
+ private var isAntialiasing: Method? = null
+ private var emptyLastTick = false
+ private val swapBuffer by lazy { initSwapBuffer() }
+ private val logger = LorenzLogger("entity_outline_renderer")
+ private val config get() = SkyHanniMod.feature.misc
+ private val mc get() = Minecraft.getMinecraft()
+ private val BUF_FLOAT_4: java.nio.FloatBuffer = org.lwjgl.BufferUtils.createFloatBuffer(4)
+
+ /**
+ * @return a new framebuffer with the size of the main framebuffer
+ */
+ private fun initSwapBuffer(): Framebuffer {
+ val main = mc.framebuffer
+ val framebuffer = Framebuffer(main.framebufferTextureWidth, main.framebufferTextureHeight, true)
+ framebuffer.setFramebufferFilter(GL11.GL_NEAREST)
+ framebuffer.setFramebufferColor(0.0f, 0.0f, 0.0f, 0.0f)
+ return framebuffer
+ }
+
+ private fun updateFramebufferSize() {
+ val width = mc.displayWidth
+ val height = mc.displayHeight
+ if (swapBuffer.framebufferWidth != width || swapBuffer.framebufferHeight != height) {
+ swapBuffer.createBindFramebuffer(width, height)
+ }
+ val rg = mc.renderGlobal as CustomRenderGlobal
+ val outlineBuffer = rg.entityOutlineFramebuffer_skyhanni
+ if (outlineBuffer.framebufferWidth != width || outlineBuffer.framebufferHeight != height) {
+ outlineBuffer.createBindFramebuffer(width, height)
+ rg.entityOutlineShader_skyhanni.createBindFramebuffers(width, height)
+ }
+ }
+
+ /**
+ * Renders xray and no-xray entity outlines.
+ *
+ * @param camera the current camera
+ * @param partialTicks the progress to the next tick
+ * @param x the camera x position
+ * @param y the camera y position
+ * @param z the camera z position
+ */
+ @JvmStatic
+ fun renderEntityOutlines(camera: ICamera, partialTicks: Float, vector: LorenzVec): Boolean {
+ val shouldRenderOutlines = shouldRenderEntityOutlines()
+
+ if (!(shouldRenderOutlines && !isCacheEmpty() && MinecraftForgeClient.getRenderPass() == 0)) {
+ return !shouldRenderOutlines
+ }
+
+ val renderGlobal = mc.renderGlobal as CustomRenderGlobal
+ val renderManager = mc.renderManager
+ mc.theWorld.theProfiler.endStartSection("entityOutlines")
+ updateFramebufferSize()
+
+ // Clear and bind the outline framebuffer
+ renderGlobal.entityOutlineFramebuffer_skyhanni.framebufferClear()
+ renderGlobal.entityOutlineFramebuffer_skyhanni.bindFramebuffer(false)
+
+ // Vanilla options
+ RenderHelper.disableStandardItemLighting()
+ GlStateManager.disableFog()
+ mc.renderManager.setRenderOutlines(true)
+
+ // Enable outline mode
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_REPLACE);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL13.GL_CONSTANT);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_ALPHA, GL11.GL_TEXTURE);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA);
+
+ // Render x-ray outlines first, ignoring the depth buffer bit
+ if (!isXrayCacheEmpty()) {
+ // Xray is enabled by disabling depth testing
+ GlStateManager.depthFunc(GL11.GL_ALWAYS)
+
+ entityRenderCache.xrayCache?.forEach { (key, value) ->
+ // Test if the entity should render, given the player's camera position
+ if (!shouldRender(camera, key, vector)) return@forEach
+
+ try {
+ if (key !is EntityLivingBase) outlineColor(value)
+ renderManager.renderEntityStatic(key, partialTicks, true)
+ } catch (ignored: Exception) {
+ }
+ }
+
+ // Reset depth function
+ GlStateManager.depthFunc(GL11.GL_LEQUAL)
+ }
+
+ // Render no-xray outlines second, taking into consideration the depth bit
+ if (!isNoXrayCacheEmpty()) {
+ if (!isNoOutlineCacheEmpty()) {
+ // Render other entities + terrain that may occlude an entity outline into a depth buffer
+ swapBuffer.framebufferClear()
+ copyBuffers(mc.framebuffer, swapBuffer, GL11.GL_DEPTH_BUFFER_BIT)
+ swapBuffer.bindFramebuffer(false)
+
+ // Copy terrain + other entities depth into outline frame buffer to now switch to no-xray outlines
+ entityRenderCache.noOutlineCache?.forEach { entity ->
+ // Test if the entity should render, given the player's instantaneous camera position
+ if (!shouldRender(camera, entity, vector)) return@forEach
+
+ try {
+ renderManager.renderEntityStatic(entity, partialTicks, true)
+ } catch (ignored: Exception) {
+ }
+ }
+
+ // Copy the entire depth buffer of everything that might occlude outline to outline framebuffer
+ copyBuffers(swapBuffer, renderGlobal.entityOutlineFramebuffer_skyhanni, GL11.GL_DEPTH_BUFFER_BIT)
+ renderGlobal.entityOutlineFramebuffer_skyhanni.bindFramebuffer(false)
+ } else {
+ copyBuffers(mc.framebuffer, renderGlobal.entityOutlineFramebuffer_skyhanni, GL11.GL_DEPTH_BUFFER_BIT)
+ }
+
+ // Xray disabled by re-enabling traditional depth testing
+ entityRenderCache.noXrayCache?.forEach { (key, value) ->
+ // Test if the entity should render, given the player's instantaneous camera position
+ if (!shouldRender(camera, key, vector)) return@forEach
+
+ try {
+ if (key !is EntityLivingBase) outlineColor(value)
+ renderManager.renderEntityStatic(key, partialTicks, true)
+ } catch (ignored: Exception) {
+ }
+ }
+ }
+
+ // Disable outline mode
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_ALPHA, GL11.GL_TEXTURE);
+ GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA);
+
+ // Vanilla options
+ RenderHelper.enableStandardItemLighting()
+ mc.renderManager.setRenderOutlines(false)
+
+ // Load the outline shader
+ GlStateManager.depthMask(false)
+ renderGlobal.entityOutlineShader_skyhanni.loadShaderGroup(partialTicks)
+ GlStateManager.depthMask(true)
+
+ // Reset GL/framebuffers for next render layers
+ GlStateManager.enableLighting()
+ mc.framebuffer.bindFramebuffer(false)
+ GlStateManager.enableFog()
+ GlStateManager.enableBlend()
+ GlStateManager.enableColorMaterial()
+ GlStateManager.enableDepth()
+ GlStateManager.enableAlpha()
+
+ return !shouldRenderOutlines
+ }
+
+ @JvmStatic
+ fun getCustomOutlineColor(entity: Entity?): Int? {
+ if (entityRenderCache.xrayCache?.containsKey(entity) == true) {
+ return entityRenderCache.xrayCache!![entity]
+ }
+ return if (entityRenderCache.noXrayCache?.containsKey(entity) == true) {
+ entityRenderCache.noXrayCache!![entity]
+ } else null
+ }
+
+ /**
+ * Caches optifine settings and determines whether outlines should be rendered
+ *
+ * @return `true` iff outlines should be rendered
+ */
+ @JvmStatic
+ fun shouldRenderEntityOutlines(): Boolean {
+ val renderGlobal = mc.renderGlobal as CustomRenderGlobal
+
+ // Vanilla Conditions
+ if (renderGlobal.entityOutlineFramebuffer_skyhanni == null || renderGlobal.entityOutlineShader_skyhanni == null || mc.thePlayer == null) return false
+
+ // Skyblock Conditions
+ if (!LorenzUtils.inSkyBlock) {
+ return false
+ }
+
+ // Main toggle for outlines features
+ if (!isEnabled()) {
+ return false
+ }
+
+ // Optifine Conditions
+ if (!stopLookingForOptifine && isFastRender == null) {
+ try {
+ val config = Class.forName("Config")
+ try {
+ isFastRender = config.getMethod("isFastRender")
+ isShaders = config.getMethod("isShaders")
+ isAntialiasing = config.getMethod("isAntialiasing")
+ } catch (ex: Exception) {
+ logger.log("Couldn't find Optifine methods for entity outlines.")
+ stopLookingForOptifine = true
+ }
+ } catch (ex: Exception) {
+ logger.log("Couldn't find Optifine for entity outlines.")
+ stopLookingForOptifine = true
+ }
+ }
+ var isFastRenderValue = false
+ var isShadersValue = false
+ var isAntialiasingValue = false
+ if (isFastRender != null) {
+ try {
+ isFastRenderValue = isFastRender!!.invoke(null) as Boolean
+ isShadersValue = isShaders!!.invoke(null) as Boolean
+ isAntialiasingValue = isAntialiasing!!.invoke(null) as Boolean
+ } catch (ex: IllegalAccessException) {
+ logger.log("An error occurred while calling Optifine methods for entity outlines... $ex")
+ } catch (ex: InvocationTargetException) {
+ logger.log("An error occurred while calling Optifine methods for entity outlines... $ex")
+ }
+ }
+ return !isFastRenderValue && !isShadersValue && !isAntialiasingValue
+ }
+
+ // Add new features that need the entity outline logic here
+ private fun isEnabled(): Boolean {
+ if (SkyHanniMod.feature.fishing.rareSeaCreatureHighlight) return true
+
+ return false
+ }
+
+ /**
+ * Apply the same rendering standards as in [net.minecraft.client.renderer.RenderGlobal.renderEntities] lines 659 to 669
+ *
+ * @param camera the current camera
+ * @param entity the entity to render
+ * @param x the camera x position
+ * @param y the camera y position
+ * @param z the camera z position
+ * @return whether the entity should be rendered
+ */
+ private fun shouldRender(camera: ICamera, entity: Entity, vector: LorenzVec): Boolean =
+ // Only render the view entity when sleeping or in 3rd person mode
+ if (entity === mc.renderViewEntity &&
+ !(mc.renderViewEntity is EntityLivingBase && (mc.renderViewEntity as EntityLivingBase).isPlayerSleeping ||
+ mc.gameSettings.thirdPersonView != 0)) {
+ false
+ } else mc.theWorld.isBlockLoaded(BlockPos(entity)) && (mc.renderManager.shouldRender(entity, camera, vector.x, vector.y, vector.z) || entity.riddenByEntity === mc.thePlayer)
+ // Only render if renderManager would render and the world is loaded at the entity
+
+ private fun outlineColor(color: Int) {
+ BUF_FLOAT_4.put(0, (color shr 16 and 255).toFloat() / 255.0f)
+ BUF_FLOAT_4.put(1, (color shr 8 and 255).toFloat() / 255.0f)
+ BUF_FLOAT_4.put(2, (color and 255).toFloat() / 255.0f)
+ BUF_FLOAT_4.put(3, 1f)
+ GL11.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, BUF_FLOAT_4)
+ }
+
+ /**
+ * Function that copies a portion of a framebuffer to another framebuffer.
+ *
+ *
+ * Note that this requires GL3.0 to function properly
+ *
+ *
+ * The major use of this function is to copy the depth-buffer portion of the world framebuffer to the entity outline framebuffer.
+ * This enables us to perform no-xray outlining on entities, as we can use the world framebuffer's depth testing on the outline frame buffer
+ *
+ * @param frameToCopy the framebuffer from which we are copying data
+ * @param frameToPaste the framebuffer onto which we are copying the data
+ * @param buffersToCopy the bit mask indicating the sections to copy (see [GL11.GL_DEPTH_BUFFER_BIT], [GL11.GL_COLOR_BUFFER_BIT], [GL11.GL_STENCIL_BUFFER_BIT])
+ */
+ private fun copyBuffers(frameToCopy: Framebuffer?, frameToPaste: Framebuffer?, buffersToCopy: Int) {
+ if (OpenGlHelper.isFramebufferEnabled()) {
+ OpenGlHelper.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, frameToCopy!!.framebufferObject)
+ OpenGlHelper.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, frameToPaste!!.framebufferObject)
+ GL30.glBlitFramebuffer(0, 0, frameToCopy.framebufferWidth, frameToCopy.framebufferHeight,
+ 0, 0, frameToPaste.framebufferWidth, frameToPaste.framebufferHeight,
+ buffersToCopy, GL11.GL_NEAREST)
+ }
+ }
+
+ fun isCacheEmpty() = isXrayCacheEmpty() && isNoXrayCacheEmpty()
+
+ private fun isXrayCacheEmpty() = entityRenderCache.xrayCache?.isEmpty() ?: true
+ private fun isNoXrayCacheEmpty() = entityRenderCache.noXrayCache?.isEmpty() ?: true
+ private fun isNoOutlineCacheEmpty() = entityRenderCache.noOutlineCache?.isEmpty() ?: true
+
+ /**
+ * Updates the cache at the start of every minecraft tick to improve efficiency.
+ * Identifies and caches all entities in the world that should be outlined.
+ *
+ *
+ * Calls to [.shouldRender] are frustum based, rely on partialTicks,
+ * and so can't be updated on a per-tick basis without losing information.
+ *
+ *
+ * This works since entities are only updated once per tick, so the inclusion or exclusion of an entity
+ * to be outlined can be cached each tick with no loss of data
+ *
+ * @param event the client tick event
+ */
+ @SubscribeEvent
+ fun onTick(event: LorenzTickEvent) {
+ if (event.phase == EventPriority.NORMAL) {
+ val rg = mc.renderGlobal as CustomRenderGlobal
+
+ if (mc.theWorld != null && shouldRenderEntityOutlines()) {
+ // These events need to be called in this specific order for the xray to have priority over the no xray
+ // Get all entities to render xray outlines
+ val xrayOutlineEvent = RenderEntityOutlineEvent(RenderEntityOutlineEvent.Type.XRAY, null)
+ MinecraftForge.EVENT_BUS.post(xrayOutlineEvent)
+ // Get all entities to render no xray outlines, using pre-filtered entities (no need to test xray outlined entities)
+ val noxrayOutlineEvent = RenderEntityOutlineEvent(RenderEntityOutlineEvent.Type.NO_XRAY, xrayOutlineEvent.entitiesToChooseFrom)
+ MinecraftForge.EVENT_BUS.post(noxrayOutlineEvent)
+ // Cache the entities for future use
+ entityRenderCache.xrayCache = xrayOutlineEvent.entitiesToOutline
+ entityRenderCache.noXrayCache = noxrayOutlineEvent.entitiesToOutline
+ entityRenderCache.noOutlineCache = noxrayOutlineEvent.entitiesToChooseFrom
+ emptyLastTick = if (isCacheEmpty()) {
+ if (!emptyLastTick) {
+ rg.entityOutlineFramebuffer_skyhanni.framebufferClear()
+ }
+ true
+ } else {
+ false
+ }
+ } else if (!emptyLastTick) {
+ entityRenderCache.xrayCache = null
+ entityRenderCache.noXrayCache = null
+ entityRenderCache.noOutlineCache = null
+ if (rg.entityOutlineFramebuffer_skyhanni != null) rg.entityOutlineFramebuffer_skyhanni.framebufferClear()
+ emptyLastTick = true
+ }
+ }
+ }
+
+ private class CachedInfo(var xrayCache: HashMap<Entity, Int>?, var noXrayCache: HashMap<Entity, Int>?, var noOutlineCache: HashSet<Entity>?)
} \ No newline at end of file