From d9cc03511a877a25b29fa8e16fb44b2bc9ea19ed Mon Sep 17 00:00:00 2001 From: octarine-noise Date: Sat, 21 Dec 2019 15:08:29 +0100 Subject: [PATCH] Cache round log neighborhood data for faster chunk re-rendering --- .../mods/betterfoliage/client/Client.kt | 2 + .../betterfoliage/client/chunk/Overlay.kt | 122 ++++++++++++ .../client/render/AbstractRenderColumn.kt | 188 ++++++++++++------ .../betterfoliage/client/render/RenderLog.kt | 17 +- src/main/kotlin/mods/octarinecore/Utils.kt | 12 +- .../render/AbstractBlockRenderingHandler.kt | 8 +- 6 files changed, 274 insertions(+), 75 deletions(-) create mode 100644 src/main/kotlin/mods/betterfoliage/client/chunk/Overlay.kt diff --git a/src/main/kotlin/mods/betterfoliage/client/Client.kt b/src/main/kotlin/mods/betterfoliage/client/Client.kt index c245f0b..2a9db5f 100644 --- a/src/main/kotlin/mods/betterfoliage/client/Client.kt +++ b/src/main/kotlin/mods/betterfoliage/client/Client.kt @@ -1,6 +1,7 @@ package mods.betterfoliage.client import mods.betterfoliage.BetterFoliageMod +import mods.betterfoliage.client.chunk.ChunkOverlayManager import mods.betterfoliage.client.gui.ConfigGuiFactory import mods.betterfoliage.client.integration.* import mods.betterfoliage.client.render.* @@ -66,6 +67,7 @@ object Client { // init singletons val singletons = listOf( + ChunkOverlayManager, LeafRegistry, GrassRegistry, LeafWindTracker, diff --git a/src/main/kotlin/mods/betterfoliage/client/chunk/Overlay.kt b/src/main/kotlin/mods/betterfoliage/client/chunk/Overlay.kt new file mode 100644 index 0000000..3ff6a17 --- /dev/null +++ b/src/main/kotlin/mods/betterfoliage/client/chunk/Overlay.kt @@ -0,0 +1,122 @@ +package mods.betterfoliage.client.chunk + +import mods.betterfoliage.client.Client +import net.minecraft.block.state.IBlockState +import net.minecraft.client.multiplayer.WorldClient +import net.minecraft.entity.Entity +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.util.SoundCategory +import net.minecraft.util.SoundEvent +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.ChunkPos +import net.minecraft.world.IBlockAccess +import net.minecraft.world.IWorldEventListener +import net.minecraft.world.World +import net.minecraft.world.chunk.EmptyChunk +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.event.world.ChunkEvent +import net.minecraftforge.event.world.WorldEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import org.apache.logging.log4j.Level + +/** + * Represents some form of arbitrary non-persistent data that can be calculated and cached for each block position + */ +interface ChunkOverlayLayer { + abstract fun calculate(world: IBlockAccess, pos: BlockPos): T + abstract fun onBlockUpdate(world: IBlockAccess, pos: BlockPos) +} + +/** + * Query, lazy calculation and lifecycle management of multiple layers of chunk overlay data. + */ +object ChunkOverlayManager : IBlockUpdateListener { + init { + Client.log(Level.INFO, "Initializing client overlay manager") + MinecraftForge.EVENT_BUS.register(this) + } + + val chunkData = mutableMapOf() + val layers = mutableListOf>() + + /** + * Get the overlay data for a given layer and position + * + * @param layer Overlay layer to query + * @param world World to use if calculation of overlay value is necessary + * @param pos Block position + */ + fun get(layer: ChunkOverlayLayer, world: IBlockAccess, pos: BlockPos): T? { + val data = chunkData[ChunkPos(pos)] ?: return null + data.get(layer, pos).let { value -> + if (value !== ChunkOverlayData.UNCALCULATED) return value + val newValue = layer.calculate(world, pos) + data.set(layer, pos, newValue) + return newValue + } + } + + /** + * Clear the overlay data for a given layer and position + * + * @param layer Overlay layer to clear + * @param pos Block position + */ + fun clear(layer: ChunkOverlayLayer, pos: BlockPos) { + chunkData[ChunkPos(pos)]?.clear(layer, pos) + } + + override fun notifyBlockUpdate(world: World, pos: BlockPos, oldState: IBlockState, newState: IBlockState, flags: Int) { + if (chunkData.containsKey(ChunkPos(pos))) layers.forEach { layer -> layer.onBlockUpdate(world, pos) } + } + + @SubscribeEvent + fun handleLoadWorld(event: WorldEvent.Load) { + if (event.world is WorldClient) { + event.world.addEventListener(this) + } + } + + @SubscribeEvent + fun handleLoadChunk(event: ChunkEvent.Load) { + if (event.world is WorldClient && event.chunk !is EmptyChunk) { + chunkData[event.chunk.pos] = ChunkOverlayData(layers) + } + } + @SubscribeEvent + fun handleUnloadChunk(event: ChunkEvent.Unload) { + if (event.world is WorldClient) { + chunkData.remove(event.chunk.pos) + } + } +} + +class ChunkOverlayData(layers: List>) { + val rawData = layers.associateWith { emptyOverlay() } + fun get(layer: ChunkOverlayLayer, pos: BlockPos): T? = rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.get(pos.y) as T? + fun set(layer: ChunkOverlayLayer, pos: BlockPos, data: T) = rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, data) + fun clear(layer: ChunkOverlayLayer, pos: BlockPos) = rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, UNCALCULATED) + + companion object { + val UNCALCULATED = object {} + fun emptyOverlay() = Array(16) { Array(16) { Array(256) { UNCALCULATED }}} + } +} + +/** + * IWorldEventListener helper subclass + * No-op for everything except notifyBlockUpdate() + */ +interface IBlockUpdateListener : IWorldEventListener { + override fun playSoundToAllNearExcept(player: EntityPlayer?, soundIn: SoundEvent, category: SoundCategory, x: Double, y: Double, z: Double, volume: Float, pitch: Float) {} + override fun onEntityAdded(entityIn: Entity) {} + override fun broadcastSound(soundID: Int, pos: BlockPos, data: Int) {} + override fun playEvent(player: EntityPlayer?, type: Int, blockPosIn: BlockPos, data: Int) {} + override fun onEntityRemoved(entityIn: Entity) {} + override fun notifyLightSet(pos: BlockPos) {} + override fun spawnParticle(particleID: Int, ignoreRange: Boolean, xCoord: Double, yCoord: Double, zCoord: Double, xSpeed: Double, ySpeed: Double, zSpeed: Double, vararg parameters: Int) {} + override fun spawnParticle(id: Int, ignoreRange: Boolean, minimiseParticleLevel: Boolean, x: Double, y: Double, z: Double, xSpeed: Double, ySpeed: Double, zSpeed: Double, vararg parameters: Int) {} + override fun playRecord(soundIn: SoundEvent, pos: BlockPos) {} + override fun sendBlockBreakProgress(breakerId: Int, pos: BlockPos, progress: Int) {} + override fun markBlockRangeForRenderUpdate(x1: Int, y1: Int, z1: Int, x2: Int, y2: Int, z2: Int) {} +} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/AbstractRenderColumn.kt b/src/main/kotlin/mods/betterfoliage/client/render/AbstractRenderColumn.kt index 15af549..97a5689 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/AbstractRenderColumn.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/AbstractRenderColumn.kt @@ -1,11 +1,14 @@ package mods.betterfoliage.client.render import mods.betterfoliage.client.Client +import mods.betterfoliage.client.chunk.ChunkOverlayLayer +import mods.betterfoliage.client.chunk.ChunkOverlayManager import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.integration.OptifineCTM import mods.betterfoliage.client.integration.ShadersModIntegration -import mods.betterfoliage.client.render.AbstractRenderColumn.BlockType.* -import mods.betterfoliage.client.render.AbstractRenderColumn.QuadrantType.* +import mods.betterfoliage.client.integration.ShadersModIntegration.renderAs +import mods.betterfoliage.client.render.ColumnLayerData.SpecialRender.BlockType.* +import mods.betterfoliage.client.render.ColumnLayerData.SpecialRender.QuadrantType +import mods.betterfoliage.client.render.ColumnLayerData.SpecialRender.QuadrantType.* import mods.octarinecore.client.render.* import mods.octarinecore.common.* import net.minecraft.block.state.IBlockState @@ -14,6 +17,8 @@ import net.minecraft.client.renderer.BufferBuilder import net.minecraft.client.renderer.texture.TextureAtlasSprite import net.minecraft.util.BlockRenderLayer import net.minecraft.util.EnumFacing.* +import net.minecraft.util.math.BlockPos +import net.minecraft.world.IBlockAccess import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.SideOnly @@ -25,6 +30,41 @@ interface IColumnTextureInfo { val side: QuadIconResolver } +/** + * Sealed class hierarchy for all possible render outcomes + */ +@SideOnly(Side.CLIENT) +sealed class ColumnLayerData { + /** + * Data structure to cache texture and world neighborhood data relevant to column rendering + */ + @Suppress("ArrayInDataClass") // not used in comparisons anywhere + @SideOnly(Side.CLIENT) + data class SpecialRender( + val column: IColumnTextureInfo, + val upType: BlockType, + val downType: BlockType, + val quadrants: Array, + val quadrantsTop: Array, + val quadrantsBottom: Array + ) : ColumnLayerData() { + enum class BlockType { SOLID, NONSOLID, PARALLEL, PERPENDICULAR } + enum class QuadrantType { SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE } + } + + /** Column block should not be rendered at all */ + @SideOnly(Side.CLIENT) + object SkipRender : ColumnLayerData() + + /** Column block must be rendered normally */ + @SideOnly(Side.CLIENT) + object NormalRender : ColumnLayerData() + + /** Error while resolving render data, column block must be rendered normally */ + @SideOnly(Side.CLIENT) + object ResolveError : ColumnLayerData() +} + @SideOnly(Side.CLIENT) interface IColumnRegistry { operator fun get(state: IBlockState, rand: Int): IColumnTextureInfo? @@ -60,21 +100,16 @@ const val SW = 3 @Suppress("NOTHING_TO_INLINE") abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandler(modId) { - enum class BlockType { SOLID, NONSOLID, PARALLEL, PERPENDICULAR } - enum class QuadrantType { SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE } - /** The rotations necessary to bring the models in position for the 4 quadrants */ val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it } // ============================ // Configuration // ============================ + abstract val overlayLayer: ColumnRenderLayer + abstract val connectPerpendicular: Boolean abstract val radiusSmall: Double abstract val radiusLarge: Double - abstract val surroundPredicate: (IBlockState) -> Boolean - abstract val connectPerpendicular: Boolean - abstract val connectSolids: Boolean - abstract val lenientConnect: Boolean // ============================ // Models @@ -133,54 +168,45 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl inline fun continuous(q1: QuadrantType, q2: QuadrantType) = q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE)) - abstract val blockPredicate: (IBlockState)->Boolean - - abstract val registry: IColumnRegistry - @Suppress("NON_EXHAUSTIVE_WHEN") override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean { - if (ctx.isSurroundedBy(surroundPredicate) ) return false - val columnTextures = registry[ctx.blockState(Int3.zero), ctx.random(0)] - if (columnTextures == null) { - Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos) + val roundLog = ChunkOverlayManager.get(overlayLayer, ctx.world!!, ctx.pos) + when(roundLog) { + ColumnLayerData.SkipRender -> return true + ColumnLayerData.NormalRender -> return renderWorldBlockBase(ctx, dispatcher, renderer, null) + ColumnLayerData.ResolveError, null -> { + Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos) + return renderWorldBlockBase(ctx, dispatcher, renderer, null) + } + } + + // if log axis is not defined and "Default to vertical" config option is not set, render normally + if ((roundLog as ColumnLayerData.SpecialRender).column.axis == null && !overlayLayer.defaultToY) { return renderWorldBlockBase(ctx, dispatcher, renderer, null) } // get AO data modelRenderer.updateShading(Int3.zero, allFaces) - // check log neighborhood - // if log axis is not defined and "Default to vertical" config option is not set, render normally - val logAxis = columnTextures.axis ?: if (Config.roundLogs.defaultY) Axis.Y else return renderWorldBlockBase(ctx, dispatcher, renderer, null) - val baseRotation = rotationFromUp[(logAxis to AxisDirection.POSITIVE).face.ordinal] - - val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0)) - val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0)) - - val quadrants = Array(4) { SMALL_RADIUS }.checkNeighbors(ctx, baseRotation, logAxis, 0) - val quadrantsTop = Array(4) { SMALL_RADIUS } - if (upType == PARALLEL) quadrantsTop.checkNeighbors(ctx, baseRotation, logAxis, 1) - val quadrantsBottom = Array(4) { SMALL_RADIUS } - if (downType == PARALLEL) quadrantsBottom.checkNeighbors(ctx, baseRotation, logAxis, -1) - - ShadersModIntegration.renderAs(ctx.blockState(Int3.zero), renderer) { + val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal] + renderAs(ctx.blockState(Int3.zero), renderer) { quadrantRotations.forEachIndexed { idx, quadrantRotation -> // set rotation for the current quadrant val rotation = baseRotation + quadrantRotation // disallow sharp discontinuities in the chamfer radius, or tapering-in where inappropriate - if (quadrants[idx] == LARGE_RADIUS && - upType == PARALLEL && quadrantsTop[idx] != LARGE_RADIUS && - downType == PARALLEL && quadrantsBottom[idx] != LARGE_RADIUS) { - quadrants[idx] = SMALL_RADIUS + if (roundLog.quadrants[idx] == LARGE_RADIUS && + roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] != LARGE_RADIUS && + roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] != LARGE_RADIUS) { + roundLog.quadrants[idx] = SMALL_RADIUS } // render side of current quadrant - val sideModel = when (quadrants[idx]) { + val sideModel = when (roundLog.quadrants[idx]) { SMALL_RADIUS -> sideRoundSmall.model - LARGE_RADIUS -> if (upType == PARALLEL && quadrantsTop[idx] == SMALL_RADIUS) transitionTop.model - else if (downType == PARALLEL && quadrantsBottom[idx] == SMALL_RADIUS) transitionBottom.model + LARGE_RADIUS -> if (roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] == SMALL_RADIUS) transitionTop.model + else if (roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] == SMALL_RADIUS) transitionBottom.model else sideRoundLarge.model SQUARE -> sideSquare.model else -> null @@ -190,51 +216,51 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl renderer, sideModel, rotation, - icon = columnTextures.side, + icon = roundLog.column.side, postProcess = noPost ) // render top and bottom end of current quadrant var upModel: Model? = null var downModel: Model? = null - var upIcon = columnTextures.top - var downIcon = columnTextures.bottom + var upIcon = roundLog.column.top + var downIcon = roundLog.column.bottom var isLidUp = true var isLidDown = true - when (upType) { - NONSOLID -> upModel = flatTop(quadrants[idx]) + when (roundLog.upType) { + NONSOLID -> upModel = flatTop(roundLog.quadrants[idx]) PERPENDICULAR -> { if (!connectPerpendicular) { - upModel = flatTop(quadrants[idx]) + upModel = flatTop(roundLog.quadrants[idx]) } else { - upIcon = columnTextures.side - upModel = extendTop(quadrants[idx]) + upIcon = roundLog.column.side + upModel = extendTop(roundLog.quadrants[idx]) isLidUp = false } } PARALLEL -> { - if (!continuous(quadrants[idx], quadrantsTop[idx])) { - if (quadrants[idx] == SQUARE || quadrants[idx] == INVISIBLE) { + if (!continuous(roundLog.quadrants[idx], roundLog.quadrantsTop[idx])) { + if (roundLog.quadrants[idx] == SQUARE || roundLog.quadrants[idx] == INVISIBLE) { upModel = topSquare.model } } } } - when (downType) { - NONSOLID -> downModel = flatBottom(quadrants[idx]) + when (roundLog.downType) { + NONSOLID -> downModel = flatBottom(roundLog.quadrants[idx]) PERPENDICULAR -> { if (!connectPerpendicular) { - downModel = flatBottom(quadrants[idx]) + downModel = flatBottom(roundLog.quadrants[idx]) } else { - downIcon = columnTextures.side - downModel = extendBottom(quadrants[idx]) + downIcon = roundLog.column.side + downModel = extendBottom(roundLog.quadrants[idx]) isLidDown = false } } PARALLEL -> { - if (!continuous(quadrants[idx], quadrantsBottom[idx]) && - (quadrants[idx] == SQUARE || quadrants[idx] == INVISIBLE)) { + if (!continuous(roundLog.quadrants[idx], roundLog.quadrantsBottom[idx]) && + (roundLog.quadrants[idx] == SQUARE || roundLog.quadrants[idx] == INVISIBLE)) { downModel = bottomSquare.model } } @@ -247,7 +273,7 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl icon = upIcon, postProcess = { _, _, _, _, _ -> if (isLidUp) { - rotateUV(idx + if (logAxis == Axis.X) 1 else 0) + rotateUV(idx + if (roundLog.column.axis == Axis.X) 1 else 0) } } ) @@ -258,7 +284,7 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl icon = downIcon, postProcess = { _, _, _, _, _ -> if (isLidDown) { - rotateUV((if (logAxis == Axis.X) 0 else 3) - idx) + rotateUV((if (roundLog.column.axis == Axis.X) 0 else 3) - idx) } } ) @@ -266,6 +292,45 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl } return true } +} + +abstract class ColumnRenderLayer : ChunkOverlayLayer { + + abstract val registry: IColumnRegistry + abstract val blockPredicate: (IBlockState)->Boolean + abstract val surroundPredicate: (IBlockState) -> Boolean + abstract val connectSolids: Boolean + abstract val lenientConnect: Boolean + abstract val defaultToY: Boolean + + val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}} + + override fun onBlockUpdate(world: IBlockAccess, pos: BlockPos) { + allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(this, pos + offset) } + } + + override fun calculate(world: IBlockAccess, pos: BlockPos) = calculate(BlockContext(world, pos)) + + fun calculate(ctx: BlockContext): ColumnLayerData { + if (ctx.isSurroundedBy(surroundPredicate)) return ColumnLayerData.SkipRender + val columnTextures = registry[ctx.blockState(Int3.zero), ctx.random(0)] ?: return ColumnLayerData.ResolveError + + // if log axis is not defined and "Default to vertical" config option is not set, render normally + val logAxis = columnTextures.axis ?: if (defaultToY) Axis.Y else return ColumnLayerData.NormalRender + + // check log neighborhood + val baseRotation = rotationFromUp[(logAxis to AxisDirection.POSITIVE).face.ordinal] + + val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0)) + val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0)) + + val quadrants = Array(4) { SMALL_RADIUS }.checkNeighbors(ctx, baseRotation, logAxis, 0) + val quadrantsTop = Array(4) { SMALL_RADIUS } + if (upType == PARALLEL) quadrantsTop.checkNeighbors(ctx, baseRotation, logAxis, 1) + val quadrantsBottom = Array(4) { SMALL_RADIUS } + if (downType == PARALLEL) quadrantsBottom.checkNeighbors(ctx, baseRotation, logAxis, -1) + return ColumnLayerData.SpecialRender(columnTextures, upType, downType, quadrants, quadrantsTop, quadrantsBottom) + } /** Sets the type of the given quadrant only if the new value is "stronger" (larger ordinal). */ inline fun Array.upgrade(idx: Int, value: QuadrantType) { @@ -340,7 +405,7 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl /** * Get the type of the block at the given offset in a rotated reference frame. */ - fun BlockContext.blockType(rotation: Rotation, axis: Axis, offset: Int3): BlockType { + fun BlockContext.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType { val offsetRot = offset.rotate(rotation) val state = blockState(offsetRot) return if (!blockPredicate(state)) { @@ -351,4 +416,5 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl } ?: SOLID } } -} \ No newline at end of file +} + diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt index 380971f..45bca91 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt @@ -1,6 +1,7 @@ package mods.betterfoliage.client.render import mods.betterfoliage.BetterFoliageMod +import mods.betterfoliage.client.chunk.ChunkOverlayManager import mods.betterfoliage.client.config.Config import mods.octarinecore.client.render.BlockContext import mods.octarinecore.client.resource.ModelVariant @@ -27,17 +28,23 @@ class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) { Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(ctx.block) - override val registry: IColumnRegistry get() = LogRegistry + override val overlayLayer = RoundLogOverlayLayer() + override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular + override val radiusSmall: Double get() = Config.roundLogs.radiusSmall + override val radiusLarge: Double get() = Config.roundLogs.radiusLarge + init { + ChunkOverlayManager.layers.add(overlayLayer) + } +} +class RoundLogOverlayLayer : ColumnRenderLayer() { + override val registry: IColumnRegistry get() = LogRegistry override val blockPredicate = { state: IBlockState -> Config.blocks.logClasses.matchesClass(state.block) } override val surroundPredicate = { state: IBlockState -> state.isOpaqueCube && !Config.blocks.logClasses.matchesClass(state.block) } - override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular override val connectSolids: Boolean get() = Config.roundLogs.connectSolids override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect - override val radiusLarge: Double get() = Config.roundLogs.radiusLarge - override val radiusSmall: Double get() = Config.roundLogs.radiusSmall - + override val defaultToY: Boolean get() = Config.roundLogs.defaultY } @SideOnly(Side.CLIENT) diff --git a/src/main/kotlin/mods/octarinecore/Utils.kt b/src/main/kotlin/mods/octarinecore/Utils.kt index 0b6cf14..e21f15d 100644 --- a/src/main/kotlin/mods/octarinecore/Utils.kt +++ b/src/main/kotlin/mods/octarinecore/Utils.kt @@ -2,6 +2,7 @@ @file:Suppress("NOTHING_TO_INLINE") package mods.octarinecore +import mods.betterfoliage.loader.Refs import net.minecraft.tileentity.TileEntity import net.minecraft.util.ResourceLocation import net.minecraft.util.math.BlockPos @@ -101,11 +102,12 @@ fun nextPowerOf2(x: Int): Int { /** * Check if the Chunk containing the given [BlockPos] is loaded. - * Works for both [World] and [ChunkCache] instances. + * Works for both [World] and [ChunkCache] (vanilla and OptiFine) instances. */ -fun IBlockAccess.isBlockLoaded(pos: BlockPos) = when(this) { - is World -> isBlockLoaded(pos, false) - is ChunkCache -> world.isBlockLoaded(pos, false) +fun IBlockAccess.isBlockLoaded(pos: BlockPos) = when { + this is World -> isBlockLoaded(pos, false) + this is ChunkCache -> world.isBlockLoaded(pos, false) + Refs.OptifineChunkCache.isInstance(this) -> (Refs.CCOFChunkCache.get(this) as ChunkCache).world.isBlockLoaded(pos, false) else -> false } @@ -113,6 +115,6 @@ fun IBlockAccess.isBlockLoaded(pos: BlockPos) = when(this) { * Get the [TileEntity] at the given position, suppressing exceptions. * Also returns null if the chunk is unloaded, which can happen because of multithreaded rendering. */ -fun IBlockAccess.getTileEntitySafe(pos: BlockPos): TileEntity? = tryDefault(null) { +fun IBlockAccess.getTileEntitySafe(pos: BlockPos): TileEntity? = tryDefault(null as TileEntity?) { if (isBlockLoaded(pos)) getTileEntity(pos) else null } \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/AbstractBlockRenderingHandler.kt b/src/main/kotlin/mods/octarinecore/client/render/AbstractBlockRenderingHandler.kt index ae62e95..c244484 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/AbstractBlockRenderingHandler.kt +++ b/src/main/kotlin/mods/octarinecore/client/render/AbstractBlockRenderingHandler.kt @@ -71,10 +71,10 @@ data class BlockData(val state: IBlockState, val color: Int, val brightness: Int * Represents the block being rendered. Has properties and methods to query the neighborhood of the block in * block-relative coordinates. */ -class BlockContext { - var world: IBlockAccess? = null - var pos = BlockPos.ORIGIN - +class BlockContext( + var world: IBlockAccess? = null, + var pos: BlockPos = BlockPos.ORIGIN +) { fun set(world: IBlockAccess, pos: BlockPos) { this.world = world; this.pos = pos; } val block: Block get() = block(Int3.zero)