Cache round log neighborhood data for faster chunk re-rendering

This commit is contained in:
octarine-noise
2019-12-21 15:08:29 +01:00
parent d0265483d2
commit d9cc03511a
6 changed files with 274 additions and 75 deletions

View File

@@ -1,6 +1,7 @@
package mods.betterfoliage.client package mods.betterfoliage.client
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.gui.ConfigGuiFactory import mods.betterfoliage.client.gui.ConfigGuiFactory
import mods.betterfoliage.client.integration.* import mods.betterfoliage.client.integration.*
import mods.betterfoliage.client.render.* import mods.betterfoliage.client.render.*
@@ -66,6 +67,7 @@ object Client {
// init singletons // init singletons
val singletons = listOf( val singletons = listOf(
ChunkOverlayManager,
LeafRegistry, LeafRegistry,
GrassRegistry, GrassRegistry,
LeafWindTracker, LeafWindTracker,

View File

@@ -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<T> {
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<ChunkPos, ChunkOverlayData>()
val layers = mutableListOf<ChunkOverlayLayer<*>>()
/**
* 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 <T> get(layer: ChunkOverlayLayer<T>, 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 <T> clear(layer: ChunkOverlayLayer<T>, 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<ChunkOverlayLayer<*>>) {
val rawData = layers.associateWith { emptyOverlay() }
fun <T> get(layer: ChunkOverlayLayer<T>, pos: BlockPos): T? = rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.get(pos.y) as T?
fun <T> set(layer: ChunkOverlayLayer<T>, pos: BlockPos, data: T) = rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, data)
fun <T> clear(layer: ChunkOverlayLayer<T>, 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<Any?>(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) {}
}

View File

@@ -1,11 +1,14 @@
package mods.betterfoliage.client.render package mods.betterfoliage.client.render
import mods.betterfoliage.client.Client 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.config.Config
import mods.betterfoliage.client.integration.OptifineCTM
import mods.betterfoliage.client.integration.ShadersModIntegration import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.render.AbstractRenderColumn.BlockType.* import mods.betterfoliage.client.integration.ShadersModIntegration.renderAs
import mods.betterfoliage.client.render.AbstractRenderColumn.QuadrantType.* 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.client.render.*
import mods.octarinecore.common.* import mods.octarinecore.common.*
import net.minecraft.block.state.IBlockState 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.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.BlockRenderLayer import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.EnumFacing.* 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.Side
import net.minecraftforge.fml.relauncher.SideOnly import net.minecraftforge.fml.relauncher.SideOnly
@@ -25,6 +30,41 @@ interface IColumnTextureInfo {
val side: QuadIconResolver 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<QuadrantType>,
val quadrantsTop: Array<QuadrantType>,
val quadrantsBottom: Array<QuadrantType>
) : 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) @SideOnly(Side.CLIENT)
interface IColumnRegistry { interface IColumnRegistry {
operator fun get(state: IBlockState, rand: Int): IColumnTextureInfo? operator fun get(state: IBlockState, rand: Int): IColumnTextureInfo?
@@ -60,21 +100,16 @@ const val SW = 3
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandler(modId) { 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 */ /** The rotations necessary to bring the models in position for the 4 quadrants */
val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it } val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it }
// ============================ // ============================
// Configuration // Configuration
// ============================ // ============================
abstract val overlayLayer: ColumnRenderLayer
abstract val connectPerpendicular: Boolean
abstract val radiusSmall: Double abstract val radiusSmall: Double
abstract val radiusLarge: Double abstract val radiusLarge: Double
abstract val surroundPredicate: (IBlockState) -> Boolean
abstract val connectPerpendicular: Boolean
abstract val connectSolids: Boolean
abstract val lenientConnect: Boolean
// ============================ // ============================
// Models // Models
@@ -133,54 +168,45 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
inline fun continuous(q1: QuadrantType, q2: QuadrantType) = inline fun continuous(q1: QuadrantType, q2: QuadrantType) =
q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE)) q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE))
abstract val blockPredicate: (IBlockState)->Boolean
abstract val registry: IColumnRegistry
@Suppress("NON_EXHAUSTIVE_WHEN") @Suppress("NON_EXHAUSTIVE_WHEN")
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, layer: BlockRenderLayer): Boolean { 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)] val roundLog = ChunkOverlayManager.get(overlayLayer, ctx.world!!, ctx.pos)
if (columnTextures == null) { when(roundLog) {
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos) 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) return renderWorldBlockBase(ctx, dispatcher, renderer, null)
} }
// get AO data // get AO data
modelRenderer.updateShading(Int3.zero, allFaces) modelRenderer.updateShading(Int3.zero, allFaces)
// check log neighborhood val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal]
// if log axis is not defined and "Default to vertical" config option is not set, render normally renderAs(ctx.blockState(Int3.zero), renderer) {
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) {
quadrantRotations.forEachIndexed { idx, quadrantRotation -> quadrantRotations.forEachIndexed { idx, quadrantRotation ->
// set rotation for the current quadrant // set rotation for the current quadrant
val rotation = baseRotation + quadrantRotation val rotation = baseRotation + quadrantRotation
// disallow sharp discontinuities in the chamfer radius, or tapering-in where inappropriate // disallow sharp discontinuities in the chamfer radius, or tapering-in where inappropriate
if (quadrants[idx] == LARGE_RADIUS && if (roundLog.quadrants[idx] == LARGE_RADIUS &&
upType == PARALLEL && quadrantsTop[idx] != LARGE_RADIUS && roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] != LARGE_RADIUS &&
downType == PARALLEL && quadrantsBottom[idx] != LARGE_RADIUS) { roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] != LARGE_RADIUS) {
quadrants[idx] = SMALL_RADIUS roundLog.quadrants[idx] = SMALL_RADIUS
} }
// render side of current quadrant // render side of current quadrant
val sideModel = when (quadrants[idx]) { val sideModel = when (roundLog.quadrants[idx]) {
SMALL_RADIUS -> sideRoundSmall.model SMALL_RADIUS -> sideRoundSmall.model
LARGE_RADIUS -> if (upType == PARALLEL && quadrantsTop[idx] == SMALL_RADIUS) transitionTop.model LARGE_RADIUS -> if (roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] == SMALL_RADIUS) transitionTop.model
else if (downType == PARALLEL && quadrantsBottom[idx] == SMALL_RADIUS) transitionBottom.model else if (roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] == SMALL_RADIUS) transitionBottom.model
else sideRoundLarge.model else sideRoundLarge.model
SQUARE -> sideSquare.model SQUARE -> sideSquare.model
else -> null else -> null
@@ -190,51 +216,51 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
renderer, renderer,
sideModel, sideModel,
rotation, rotation,
icon = columnTextures.side, icon = roundLog.column.side,
postProcess = noPost postProcess = noPost
) )
// render top and bottom end of current quadrant // render top and bottom end of current quadrant
var upModel: Model? = null var upModel: Model? = null
var downModel: Model? = null var downModel: Model? = null
var upIcon = columnTextures.top var upIcon = roundLog.column.top
var downIcon = columnTextures.bottom var downIcon = roundLog.column.bottom
var isLidUp = true var isLidUp = true
var isLidDown = true var isLidDown = true
when (upType) { when (roundLog.upType) {
NONSOLID -> upModel = flatTop(quadrants[idx]) NONSOLID -> upModel = flatTop(roundLog.quadrants[idx])
PERPENDICULAR -> { PERPENDICULAR -> {
if (!connectPerpendicular) { if (!connectPerpendicular) {
upModel = flatTop(quadrants[idx]) upModel = flatTop(roundLog.quadrants[idx])
} else { } else {
upIcon = columnTextures.side upIcon = roundLog.column.side
upModel = extendTop(quadrants[idx]) upModel = extendTop(roundLog.quadrants[idx])
isLidUp = false isLidUp = false
} }
} }
PARALLEL -> { PARALLEL -> {
if (!continuous(quadrants[idx], quadrantsTop[idx])) { if (!continuous(roundLog.quadrants[idx], roundLog.quadrantsTop[idx])) {
if (quadrants[idx] == SQUARE || quadrants[idx] == INVISIBLE) { if (roundLog.quadrants[idx] == SQUARE || roundLog.quadrants[idx] == INVISIBLE) {
upModel = topSquare.model upModel = topSquare.model
} }
} }
} }
} }
when (downType) { when (roundLog.downType) {
NONSOLID -> downModel = flatBottom(quadrants[idx]) NONSOLID -> downModel = flatBottom(roundLog.quadrants[idx])
PERPENDICULAR -> { PERPENDICULAR -> {
if (!connectPerpendicular) { if (!connectPerpendicular) {
downModel = flatBottom(quadrants[idx]) downModel = flatBottom(roundLog.quadrants[idx])
} else { } else {
downIcon = columnTextures.side downIcon = roundLog.column.side
downModel = extendBottom(quadrants[idx]) downModel = extendBottom(roundLog.quadrants[idx])
isLidDown = false isLidDown = false
} }
} }
PARALLEL -> { PARALLEL -> {
if (!continuous(quadrants[idx], quadrantsBottom[idx]) && if (!continuous(roundLog.quadrants[idx], roundLog.quadrantsBottom[idx]) &&
(quadrants[idx] == SQUARE || quadrants[idx] == INVISIBLE)) { (roundLog.quadrants[idx] == SQUARE || roundLog.quadrants[idx] == INVISIBLE)) {
downModel = bottomSquare.model downModel = bottomSquare.model
} }
} }
@@ -247,7 +273,7 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
icon = upIcon, icon = upIcon,
postProcess = { _, _, _, _, _ -> postProcess = { _, _, _, _, _ ->
if (isLidUp) { 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, icon = downIcon,
postProcess = { _, _, _, _, _ -> postProcess = { _, _, _, _, _ ->
if (isLidDown) { 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 return true
} }
}
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
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). */ /** Sets the type of the given quadrant only if the new value is "stronger" (larger ordinal). */
inline fun Array<QuadrantType>.upgrade(idx: Int, value: QuadrantType) { inline fun Array<QuadrantType>.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. * 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 offsetRot = offset.rotate(rotation)
val state = blockState(offsetRot) val state = blockState(offsetRot)
return if (!blockPredicate(state)) { return if (!blockPredicate(state)) {
@@ -351,4 +416,5 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
} ?: SOLID } ?: SOLID
} }
} }
} }

View File

@@ -1,6 +1,7 @@
package mods.betterfoliage.client.render package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.chunk.ChunkOverlayManager
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.BlockContext import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.resource.ModelVariant import mods.octarinecore.client.resource.ModelVariant
@@ -27,17 +28,23 @@ class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
Config.enabled && Config.roundLogs.enabled && Config.enabled && Config.roundLogs.enabled &&
Config.blocks.logClasses.matchesClass(ctx.block) 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 blockPredicate = { state: IBlockState -> Config.blocks.logClasses.matchesClass(state.block) }
override val surroundPredicate = { state: IBlockState -> state.isOpaqueCube && !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 connectSolids: Boolean get() = Config.roundLogs.connectSolids
override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect
override val radiusLarge: Double get() = Config.roundLogs.radiusLarge override val defaultToY: Boolean get() = Config.roundLogs.defaultY
override val radiusSmall: Double get() = Config.roundLogs.radiusSmall
} }
@SideOnly(Side.CLIENT) @SideOnly(Side.CLIENT)

View File

@@ -2,6 +2,7 @@
@file:Suppress("NOTHING_TO_INLINE") @file:Suppress("NOTHING_TO_INLINE")
package mods.octarinecore package mods.octarinecore
import mods.betterfoliage.loader.Refs
import net.minecraft.tileentity.TileEntity import net.minecraft.tileentity.TileEntity
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos 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. * 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) { fun IBlockAccess.isBlockLoaded(pos: BlockPos) = when {
is World -> isBlockLoaded(pos, false) this is World -> isBlockLoaded(pos, false)
is ChunkCache -> 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 else -> false
} }
@@ -113,6 +115,6 @@ fun IBlockAccess.isBlockLoaded(pos: BlockPos) = when(this) {
* Get the [TileEntity] at the given position, suppressing exceptions. * Get the [TileEntity] at the given position, suppressing exceptions.
* Also returns null if the chunk is unloaded, which can happen because of multithreaded rendering. * 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 if (isBlockLoaded(pos)) getTileEntity(pos) else null
} }

View File

@@ -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 * Represents the block being rendered. Has properties and methods to query the neighborhood of the block in
* block-relative coordinates. * block-relative coordinates.
*/ */
class BlockContext { class BlockContext(
var world: IBlockAccess? = null var world: IBlockAccess? = null,
var pos = BlockPos.ORIGIN var pos: BlockPos = BlockPos.ORIGIN
) {
fun set(world: IBlockAccess, pos: BlockPos) { this.world = world; this.pos = pos; } fun set(world: IBlockAccess, pos: BlockPos) { this.world = world; this.pos = pos; }
val block: Block get() = block(Int3.zero) val block: Block get() = block(Int3.zero)