diff --git a/src/main/kotlin/mods/betterfoliage/BetterFoliage.kt b/src/forge/kotlin/mods/betterfoliage/BetterFoliage.kt similarity index 100% rename from src/main/kotlin/mods/betterfoliage/BetterFoliage.kt rename to src/forge/kotlin/mods/betterfoliage/BetterFoliage.kt diff --git a/src/forge/kotlin/mods/octarinecore/resource/Aliases.kt b/src/forge/kotlin/mods/octarinecore/resource/Aliases.kt new file mode 100644 index 0000000..822c1dc --- /dev/null +++ b/src/forge/kotlin/mods/octarinecore/resource/Aliases.kt @@ -0,0 +1,10 @@ +package mods.octarinecore.resource + +import net.minecraft.client.renderer.model.ModelResourceLocation +import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.util.ResourceLocation + +typealias Identifier = ResourceLocation +typealias ModelIdentifier = ModelResourceLocation + +typealias Sprite = TextureAtlasSprite diff --git a/src/main/kotlin/mods/betterfoliage/client/Client.kt b/src/main/kotlin/mods/betterfoliage/client/Client.kt index eecfa31..73b1248 100644 --- a/src/main/kotlin/mods/betterfoliage/client/Client.kt +++ b/src/main/kotlin/mods/betterfoliage/client/Client.kt @@ -10,17 +10,14 @@ import mods.betterfoliage.client.integration.TechRebornRubberIntegration import mods.betterfoliage.client.render.* import mods.betterfoliage.client.texture.* import mods.octarinecore.client.gui.textComponent -import mods.octarinecore.client.render.AbstractBlockRenderingHandler +import mods.octarinecore.client.render.RenderDecorator import mods.octarinecore.client.resource.CenteringTextureGenerator -import mods.octarinecore.client.resource.GeneratorPack import mods.octarinecore.client.resource.IConfigChangeListener import net.minecraft.block.BlockState import net.minecraft.client.Minecraft import net.minecraft.util.math.BlockPos import net.minecraft.util.text.TextFormatting import net.minecraft.util.text.TranslationTextComponent -import net.minecraftforge.eventbus.api.SubscribeEvent -import net.minecraftforge.fml.config.ModConfig import net.minecraftforge.registries.ForgeRegistries import org.apache.logging.log4j.Level @@ -29,7 +26,7 @@ import org.apache.logging.log4j.Level * except for the call hooks. */ object Client { - var renderers= emptyList() + var renderers= emptyList() var configListeners = emptyList() val suppressRenderErrors = mutableSetOf() diff --git a/src/main/kotlin/mods/betterfoliage/client/Hooks.kt b/src/main/kotlin/mods/betterfoliage/client/Hooks.kt index 845da93..50f5fb5 100644 --- a/src/main/kotlin/mods/betterfoliage/client/Hooks.kt +++ b/src/main/kotlin/mods/betterfoliage/client/Hooks.kt @@ -7,7 +7,10 @@ import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.render.* import mods.betterfoliage.loader.Refs -import mods.octarinecore.client.render.blockContext +import mods.octarinecore.ThreadLocalDelegate +import mods.octarinecore.client.render.* +import mods.octarinecore.client.render.lighting.DefaultLightingCtx +import mods.octarinecore.client.render.lighting.LightingCtx import mods.octarinecore.client.resource.LoadModelDataEvent import mods.octarinecore.common.plus import mods.octarinecore.metaprog.allAvailable @@ -75,6 +78,7 @@ fun getVoxelShapeOverride(state: BlockState, reader: IBlockReader, pos: BlockPos return state.func_215702_a(reader, pos, dir) } +val lightingCtx by ThreadLocalDelegate { DefaultLightingCtx(BasicBlockCtx(NonNullWorld, BlockPos.ZERO)) } fun renderWorldBlock(dispatcher: BlockRendererDispatcher, state: BlockState, pos: BlockPos, @@ -84,21 +88,32 @@ fun renderWorldBlock(dispatcher: BlockRendererDispatcher, modelData: IModelData, layer: BlockRenderLayer ): Boolean { + // build context + val blockCtx = CachedBlockCtx(reader, pos) + val renderCtx = RenderCtx(dispatcher, buffer, layer, random) + lightingCtx.reset(blockCtx) + val combinedCtx = CombinedContext(blockCtx, renderCtx, lightingCtx) + + // loop render decorators val doBaseRender = state.canRenderInLayer(layer) || (layer == targetCutoutLayer && state.canRenderInLayer(otherCutoutLayer)) - blockContext.let { ctx -> - ctx.set(reader, pos) - Client.renderers.forEach { renderer -> - if (renderer.isEligible(ctx)) { - // render on the block's default layer - // also render on the cutout layer if the renderer requires it - if (doBaseRender || (renderer.addToCutout && layer == targetCutoutLayer)) { - return renderer.render(ctx, dispatcher, buffer, random, modelData, layer) - } + Client.renderers.forEach { renderer -> + if (renderer.isEligible(combinedCtx)) { + // render on the block's default layer + // also render on the cutout layer if the renderer requires it + + val doCutoutRender = renderer.renderOnCutout && layer == targetCutoutLayer + val stopRender = renderer.onlyOnCutout && !layer.isCutout + + if ((doBaseRender || doCutoutRender) && !stopRender) { + renderer.render(combinedCtx) + return combinedCtx.hasRendered } } } - return if (doBaseRender) dispatcher.renderBlock(state, pos, reader, buffer, random, modelData) else false + // no render decorators have taken on this block, proceed to normal rendering + combinedCtx.render() + return combinedCtx.hasRendered } fun canRenderInLayerOverride(state: BlockState, layer: BlockRenderLayer) = state.canRenderInLayer(layer) || layer == targetCutoutLayer diff --git a/src/main/kotlin/mods/betterfoliage/client/chunk/Overlay.kt b/src/main/kotlin/mods/betterfoliage/client/chunk/Overlay.kt index c1310f7..091b01d 100644 --- a/src/main/kotlin/mods/betterfoliage/client/chunk/Overlay.kt +++ b/src/main/kotlin/mods/betterfoliage/client/chunk/Overlay.kt @@ -3,12 +3,12 @@ package mods.betterfoliage.client.chunk import mods.betterfoliage.BetterFoliage import mods.betterfoliage.client.Client import mods.betterfoliage.loader.Refs -import net.minecraft.block.BlockState +import mods.octarinecore.client.render.BasicBlockCtx +import mods.octarinecore.client.render.BlockCtx import net.minecraft.client.renderer.chunk.ChunkRenderCache import net.minecraft.client.world.ClientWorld import net.minecraft.util.math.BlockPos import net.minecraft.util.math.ChunkPos -import net.minecraft.world.IBlockReader import net.minecraft.world.IEnviromentBlockReader import net.minecraft.world.IWorldReader import net.minecraft.world.dimension.DimensionType @@ -30,8 +30,8 @@ val IEnviromentBlockReader.dimType: DimensionType get() = when { * Represents some form of arbitrary non-persistent data that can be calculated and cached for each block position */ interface ChunkOverlayLayer { - abstract fun calculate(reader: IEnviromentBlockReader, pos: BlockPos): T - abstract fun onBlockUpdate(reader: IEnviromentBlockReader, pos: BlockPos) + fun calculate(ctx: BlockCtx): T + fun onBlockUpdate(world: IEnviromentBlockReader, pos: BlockPos) } /** @@ -56,12 +56,12 @@ object ChunkOverlayManager { * @param reader World to use if calculation of overlay value is necessary * @param pos Block position */ - fun get(layer: ChunkOverlayLayer, reader: IEnviromentBlockReader, pos: BlockPos): T? { - val data = chunkData[reader.dimType]?.get(ChunkPos(pos)) ?: return null - data.get(layer, pos).let { value -> + fun get(layer: ChunkOverlayLayer, ctx: BlockCtx): T? { + val data = chunkData[ctx.world.dimType]?.get(ChunkPos(ctx.pos)) ?: return null + data.get(layer, ctx.pos).let { value -> if (value !== ChunkOverlayData.UNCALCULATED) return value - val newValue = layer.calculate(reader, pos) - data.set(layer, pos, newValue) + val newValue = layer.calculate(ctx) + data.set(layer, ctx.pos, newValue) return newValue } } diff --git a/src/main/kotlin/mods/betterfoliage/client/integration/OptifineCustomColors.kt b/src/main/kotlin/mods/betterfoliage/client/integration/OptifineCustomColors.kt index b3b14b3..517c971 100644 --- a/src/main/kotlin/mods/betterfoliage/client/integration/OptifineCustomColors.kt +++ b/src/main/kotlin/mods/betterfoliage/client/integration/OptifineCustomColors.kt @@ -3,7 +3,7 @@ package mods.betterfoliage.client.integration import mods.betterfoliage.client.Client import mods.betterfoliage.loader.Refs import mods.octarinecore.ThreadLocalDelegate -import mods.octarinecore.client.render.BlockContext +import mods.octarinecore.client.render.CombinedContext import mods.octarinecore.common.Int3 import mods.octarinecore.metaprog.allAvailable import mods.octarinecore.metaprog.reflectField @@ -33,12 +33,12 @@ object OptifineCustomColors { val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() } val fakeQuad = BakedQuad(IntArray(0), 1, UP, null, true, DefaultVertexFormats.BLOCK) - fun getBlockColor(ctx: BlockContext): Int { + fun getBlockColor(ctx: CombinedContext): Int { val ofColor = if (isColorAvailable && Minecraft.getInstance().gameSettings.reflectField("ofCustomColors") == true) { - renderEnv.reset(ctx.blockState(Int3.zero), ctx.pos) - Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.blockState(Int3.zero), ctx.reader!!, ctx.pos, renderEnv.wrapped) as? Int + renderEnv.reset(ctx.state, ctx.pos) + Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.state, ctx.world, ctx.pos, renderEnv.wrapped) as? Int } else null - return if (ofColor == null || ofColor == -1) ctx.blockData(Int3.zero).color else ofColor + return if (ofColor == null || ofColor == -1) ctx.lightingCtx.color else ofColor } } diff --git a/src/main/kotlin/mods/betterfoliage/client/integration/RubberIntegration.kt b/src/main/kotlin/mods/betterfoliage/client/integration/RubberIntegration.kt index 642a3d7..90986fe 100644 --- a/src/main/kotlin/mods/betterfoliage/client/integration/RubberIntegration.kt +++ b/src/main/kotlin/mods/betterfoliage/client/integration/RubberIntegration.kt @@ -5,10 +5,10 @@ import mods.betterfoliage.client.Client import mods.betterfoliage.client.render.LogRegistry import mods.betterfoliage.client.render.column.ColumnTextureInfo import mods.betterfoliage.client.render.column.SimpleColumnInfo +import mods.octarinecore.client.render.CombinedContext import mods.octarinecore.client.render.Quad -import mods.octarinecore.client.render.QuadIconResolver -import mods.octarinecore.client.render.ShadingContext -import mods.octarinecore.client.render.blockContext +import mods.octarinecore.client.render.lighting.QuadIconResolver +import mods.octarinecore.client.render.lighting.LightingCtx import mods.octarinecore.client.resource.* import mods.octarinecore.common.rotate import mods.octarinecore.metaprog.ClassRef @@ -22,7 +22,6 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite import net.minecraft.util.Direction import net.minecraft.util.Direction.* import net.minecraft.util.ResourceLocation -import net.minecraftforge.client.model.IModel import net.minecraftforge.fml.ModList import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level.DEBUG @@ -61,10 +60,10 @@ class RubberLogInfo( sideTextures: List ) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) { - override val side: QuadIconResolver = { ctx: ShadingContext, idx: Int, quad: Quad -> - val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.rotation) + override val side: QuadIconResolver = { ctx: CombinedContext, idx: Int, quad: Quad -> + val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.modelRotation) if (worldFace == spotDir) spotTexture else { - val sideIdx = if (this.sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0 + val sideIdx = if (this.sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % this.sideTextures.size else 0 this.sideTextures[sideIdx] } } diff --git a/src/main/kotlin/mods/betterfoliage/client/integration/ShadersModIntegration.kt b/src/main/kotlin/mods/betterfoliage/client/integration/ShadersModIntegration.kt index f91f37b..d8cf6d0 100644 --- a/src/main/kotlin/mods/betterfoliage/client/integration/ShadersModIntegration.kt +++ b/src/main/kotlin/mods/betterfoliage/client/integration/ShadersModIntegration.kt @@ -4,6 +4,7 @@ import mods.betterfoliage.client.Client import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.Config import mods.betterfoliage.loader.Refs +import mods.octarinecore.client.render.CombinedContext import mods.octarinecore.metaprog.allAvailable import net.minecraft.block.BlockRenderType import net.minecraft.block.BlockRenderType.MODEL @@ -40,7 +41,6 @@ object ShadersModIntegration { /** Quads rendered inside this block will use the given block entity data in shader programs. */ inline fun renderAs(blockId: Long, renderType: BlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) { - val blockData = blockId or (renderType.ordinal shl 16).toLong() if ((isAvailable && enabled)) { val vertexBuilder = Refs.sVertexBuilder.get(renderer)!! Refs.pushEntity_num.invoke(vertexBuilder, blockId) @@ -54,12 +54,13 @@ object ShadersModIntegration { /** Quads rendered inside this block will use the given block entity data in shader programs. */ // temporarily NO-OP inline fun renderAs(state: BlockState, renderType: BlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) = func() + inline fun renderAs(ctx: CombinedContext, renderType: BlockRenderType, enabled: Boolean = true, func: ()->Unit) = func() /** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */ - inline fun grass(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) = - renderAs(Config.shaders.grassId, MODEL, renderer, enabled, func) + inline fun grass(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) = + renderAs(Config.shaders.grassId, MODEL, ctx.renderCtx!!.renderBuffer, enabled, func) /** Quads rendered inside this block will behave as leaf blocks in shader programs. */ - inline fun leaves(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) = - renderAs(Config.shaders.leavesId, MODEL, renderer, enabled, func) + inline fun leaves(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) = + renderAs(Config.shaders.leavesId, MODEL, ctx.renderCtx!!.renderBuffer, enabled, func) } diff --git a/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt b/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt index c883a7d..107abb9 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt @@ -5,7 +5,7 @@ import mods.betterfoliage.client.texture.LeafParticleRegistry import mods.betterfoliage.client.texture.LeafRegistry import mods.octarinecore.PI2 import mods.octarinecore.client.render.AbstractEntityFX -import mods.octarinecore.client.render.HSB +import mods.octarinecore.client.render.lighting.HSB import mods.octarinecore.common.Double3 import mods.octarinecore.minmax import mods.octarinecore.random diff --git a/src/main/kotlin/mods/betterfoliage/client/render/ModelColumn.kt b/src/main/kotlin/mods/betterfoliage/client/render/ModelColumn.kt index 747efe3..6dc851f 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/ModelColumn.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/ModelColumn.kt @@ -3,11 +3,10 @@ package mods.betterfoliage.client.render import mods.betterfoliage.client.config.Config import mods.octarinecore.client.render.* +import mods.octarinecore.client.render.lighting.* import mods.octarinecore.common.Double3 import mods.octarinecore.exchange -import net.minecraft.util.Direction import net.minecraft.util.Direction.* -import org.lwjgl.opengl.GL11 /** Weight of the same-side AO values on the outer edges of the 45deg chamfered column faces. */ const val chamferAffinity = 0.9f diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt index 25b7f98..7168b4c 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt @@ -2,25 +2,17 @@ package mods.betterfoliage.client.render import mods.betterfoliage.BetterFoliage import mods.betterfoliage.client.Client -import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.integration.ShadersModIntegration -import mods.octarinecore.client.render.* -import mods.octarinecore.common.Int3 -import mods.octarinecore.common.Rotation -import net.minecraft.block.Block +import mods.octarinecore.client.render.CombinedContext +import mods.octarinecore.client.render.RenderDecorator import net.minecraft.block.material.Material -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder import net.minecraft.tags.BlockTags -import net.minecraft.util.BlockRenderLayer import net.minecraft.util.ResourceLocation import net.minecraft.world.biome.Biome -import net.minecraftforge.client.model.data.IModelData import org.apache.logging.log4j.Level.DEBUG -import java.util.* -class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) { +class RenderAlgae : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) { val noise = simplexNoise() @@ -31,31 +23,23 @@ class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFo Client.log(DEBUG, "Registered ${algaeIcons.num} algae textures") } - override fun isEligible(ctx: BlockContext) = + override fun isEligible(ctx: CombinedContext) = Config.enabled && Config.algae.enabled && - ctx.blockState(up2).material == Material.WATER && - ctx.blockState(up1).material == Material.WATER && - BlockTags.DIRT_LIKE.contains(ctx.block) && + ctx.state(up2).material == Material.WATER && + ctx.state(up1).material == Material.WATER && + BlockTags.DIRT_LIKE.contains(ctx.state.block) && ctx.biome.category.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH || it == Biome.Category.RIVER } && noise[ctx.pos] < Config.algae.population - override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { - val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer) - if (!layer.isCutout) return baseRender - - modelRenderer.updateShading(Int3.zero, allFaces) - + override fun render(ctx: CombinedContext) { + ctx.render() + if (!ctx.isCutout) return val rand = ctx.semiRandomArray(3) - - ShadersModIntegration.grass(renderer, Config.algae.shaderWind) { - modelRenderer.render( - renderer, + ShadersModIntegration.grass(ctx, Config.algae.shaderWind) { + ctx.render( algaeModels[rand[2]], - Rotation.identity, - icon = { _, qi, _ -> algaeIcons[rand[qi and 1]]!! }, - postProcess = noPost + icon = { _, qi, _ -> algaeIcons[rand[qi and 1]]!! } ) } - return true } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt index 78daf80..c216cb0 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt @@ -2,11 +2,11 @@ package mods.betterfoliage.client.render import mods.betterfoliage.BetterFoliage import mods.betterfoliage.client.Client -import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.render.column.ColumnTextureInfo import mods.betterfoliage.client.render.column.SimpleColumnInfo import mods.octarinecore.client.render.* +import mods.octarinecore.client.render.lighting.* import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable import mods.octarinecore.common.Int3 import mods.octarinecore.common.Rotation @@ -31,7 +31,7 @@ object StandardCactusRegistry : ModelRenderRegistryConfigurable when(qi) { 0 -> icons.bottom(ctx, qi, q); 1 -> icons.top(ctx, qi, q); else -> icons.side(ctx, qi, q) - } }, - postProcess = noPost + } } ) - modelRenderer.render( - renderer, - modelCross[ctx.random(0)], - Rotation.identity, - icon = { _, _, _ -> iconCross.icon!!}, - postProcess = noPost + ctx.render( + modelCross[ctx.semiRandom(0)], + icon = { _, _, _ -> iconCross.icon!!} ) - modelRenderer.render( - renderer, - modelArm[ctx.random(1)], - cactusArmRotation[ctx.random(2) % 4], - icon = { _, _, _ -> iconArm[ctx.random(3)]!!}, - postProcess = noPost + + ctx.render( + modelArm[ctx.semiRandom(1)], + cactusArmRotation[ctx.semiRandom(2) % 4], + icon = { _, _, _ -> iconArm[ctx.semiRandom(3)]!!} ) - return true } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrass.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrass.kt index a6586a0..dddf6d9 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrass.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrass.kt @@ -1,36 +1,45 @@ package mods.betterfoliage.client.render import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.texture.GrassRegistry -import mods.octarinecore.client.render.AbstractBlockRenderingHandler -import mods.octarinecore.client.render.BlockContext -import mods.octarinecore.client.render.offset +import mods.octarinecore.client.render.CombinedContext +import mods.octarinecore.client.render.RenderDecorator import mods.octarinecore.common.Int3 -import mods.octarinecore.common.forgeDirsHorizontal +import mods.octarinecore.common.horizontalDirections import mods.octarinecore.common.offset -import net.minecraft.block.Block -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder import net.minecraft.tags.BlockTags -import net.minecraft.util.BlockRenderLayer -import net.minecraftforge.client.model.data.IModelData -import java.util.* -class RenderConnectedGrass : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) { - override fun isEligible(ctx: BlockContext) = +class RenderConnectedGrass : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) { + override fun isEligible(ctx: CombinedContext) = Config.enabled && Config.connectedGrass.enabled && - BlockTags.DIRT_LIKE.contains(ctx.block) && + BlockTags.DIRT_LIKE.contains(ctx.state.block) && GrassRegistry[ctx, up1] != null && - (Config.connectedGrass.snowEnabled || !ctx.blockState(up2).isSnow) + (Config.connectedGrass.snowEnabled || !ctx.state(up2).isSnow) - override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { + override fun render(ctx: CombinedContext) { // if the block sides are not visible anyway, render normally - if (forgeDirsHorizontal.none { ctx.shouldSideBeRendered(it) }) return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer) + if (horizontalDirections.none { ctx.shouldSideBeRendered(it) }) { + ctx.render() + } else { + ctx.exchange(Int3.zero, up1).exchange(up1, up2).render() + } + } +} - return ctx.offset(Int3.zero, up1).offset(up1, up2).let { offsetCtx -> - renderWorldBlockBase(offsetCtx, dispatcher, renderer, random, modelData, layer) +class RenderConnectedGrassLog : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) { + + override fun isEligible(ctx: CombinedContext) = + Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass && + BlockTags.DIRT_LIKE.contains(ctx.state.block) && + LogRegistry[ctx, up1] != null + + override fun render(ctx: CombinedContext) { + val grassDir = horizontalDirections.find { GrassRegistry[ctx, it.offset] != null } + if (grassDir == null) { + ctx.render() + } else { + ctx.exchange(Int3.zero, grassDir.offset).render() } } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrassLog.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrassLog.kt deleted file mode 100644 index 4ca7650..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrassLog.kt +++ /dev/null @@ -1,39 +0,0 @@ -package mods.betterfoliage.client.render - -import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.client.config.BlockConfig -import mods.betterfoliage.client.config.Config -import mods.betterfoliage.client.texture.GrassRegistry -import mods.octarinecore.client.render.AbstractBlockRenderingHandler -import mods.octarinecore.client.render.BlockContext -import mods.octarinecore.client.render.offset -import mods.octarinecore.common.Int3 -import mods.octarinecore.common.offset -import net.minecraft.block.Block -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.tags.BlockTags -import net.minecraft.util.BlockRenderLayer -import net.minecraft.util.Direction.* -import net.minecraftforge.client.model.data.IModelData -import java.util.* - -class RenderConnectedGrassLog : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) { - - val grassCheckDirs = listOf(EAST, WEST, NORTH, SOUTH) - - override fun isEligible(ctx: BlockContext) = - Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass && - BlockTags.DIRT_LIKE.contains(ctx.block) && - LogRegistry[ctx, up1] != null - - override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { - val grassDir = grassCheckDirs.find { - GrassRegistry[ctx, it.offset] != null - } ?: return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer) - - return ctx.offset(Int3.zero, grassDir.offset).let { offsetCtx -> - renderWorldBlockBase(offsetCtx, dispatcher, renderer, random, modelData, layer) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt index 0d9b60c..f0aa74a 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt @@ -5,9 +5,11 @@ import mods.betterfoliage.client.Client import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.Config import mods.octarinecore.client.render.* +import mods.octarinecore.client.render.lighting.* import mods.octarinecore.common.Int3 -import mods.octarinecore.common.forgeDirOffsets -import mods.octarinecore.common.forgeDirs +import mods.octarinecore.common.allDirOffsets +import mods.octarinecore.common.allDirections +import mods.octarinecore.common.offset import mods.octarinecore.random import net.minecraft.block.material.Material import net.minecraft.client.renderer.BlockRendererDispatcher @@ -17,13 +19,11 @@ import net.minecraft.util.Direction.Axis import net.minecraft.util.Direction.UP import net.minecraft.util.ResourceLocation import net.minecraft.world.biome.Biome -import net.minecraftforge.api.distmarker.Dist import net.minecraftforge.client.model.data.IModelData -import net.minecraftforge.fml.common.Mod import org.apache.logging.log4j.Level.DEBUG import java.util.* -class RenderCoral : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) { +class RenderCoral : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) { val noise = simplexNoise() @@ -49,33 +49,27 @@ class RenderCoral : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFo Client.log(DEBUG, "Registered ${crustIcons.num} coral crust textures") } - override fun isEligible(ctx: BlockContext) = + override fun isEligible(ctx: CombinedContext) = Config.enabled && Config.coral.enabled && - (ctx.blockState(up2).material == Material.WATER || Config.coral.shallowWater) && - ctx.blockState(up1).material == Material.WATER && - BlockConfig.sand.matchesClass(ctx.block) && + (ctx.state(up2).material == Material.WATER || Config.coral.shallowWater) && + ctx.state(up1).material == Material.WATER && + BlockConfig.sand.matchesClass(ctx.state.block) && ctx.biome.category.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH } && noise[ctx.pos] < Config.coral.population - override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { - val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer) - if (!layer.isCutout) return baseRender + override fun render(ctx: CombinedContext) { + val baseRender = ctx.render() + if (!ctx.isCutout) return - modelRenderer.updateShading(Int3.zero, allFaces) - - forgeDirs.forEachIndexed { idx, face -> - if (!ctx.isNormalCube(forgeDirOffsets[idx]) && blockContext.random(idx) < Config.coral.chance) { - var variation = blockContext.random(6) - modelRenderer.render( - renderer, + allDirections.forEachIndexed { idx, face -> + if (ctx.state(face).material == Material.WATER && ctx.semiRandom(idx) < Config.coral.chance) { + var variation = ctx.semiRandom(6) + ctx.render( coralModels[variation++], rotationFromUp[idx], - icon = { _, qi, _ -> if (qi == 4) crustIcons[variation]!! else coralIcons[variation + (qi and 1)]!!}, - postProcess = noPost + icon = { _, qi, _ -> if (qi == 4) crustIcons[variation]!! else coralIcons[variation + (qi and 1)]!!} ) } } - - return true } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt index 431730a..22251f0 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt @@ -6,24 +6,22 @@ import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.integration.OptifineCustomColors import mods.betterfoliage.client.integration.ShadersModIntegration import mods.betterfoliage.client.texture.GrassRegistry -import mods.octarinecore.client.render.* -import mods.octarinecore.common.* +import mods.octarinecore.client.render.CombinedContext +import mods.octarinecore.client.render.Model +import mods.octarinecore.client.render.RenderDecorator +import mods.octarinecore.client.render.fullCube +import mods.octarinecore.client.render.lighting.cornerAo +import mods.octarinecore.client.render.lighting.cornerFlat +import mods.octarinecore.client.render.lighting.faceOrientedAuto +import mods.octarinecore.common.Double3 +import mods.octarinecore.common.allDirections import mods.octarinecore.random -import net.minecraft.block.Block -import net.minecraft.block.BlockRenderType -import net.minecraft.block.BlockRenderType.MODEL -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder import net.minecraft.tags.BlockTags -import net.minecraft.util.BlockRenderLayer -import net.minecraft.util.Direction.Axis import net.minecraft.util.Direction.* import net.minecraft.util.ResourceLocation -import net.minecraftforge.client.model.data.IModelData import org.apache.logging.log4j.Level.DEBUG -import java.util.* -class RenderGrass : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) { +class RenderGrass : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) { companion object { @JvmStatic fun grassTopQuads(heightMin: Double, heightMax: Double): Model.(Int)->Unit = { modelIdx -> @@ -50,77 +48,58 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFo Client.log(DEBUG, "Registered ${snowedIcons.num} snowed grass textures") } - override fun isEligible(ctx: BlockContext) = + override fun isEligible(ctx: CombinedContext) = Config.enabled && (Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) && GrassRegistry[ctx] != null - override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { - // render the whole block on the cutout layer - if (!layer.isCutout) return false + override val onlyOnCutout get() = true - val isConnected = BlockTags.DIRT_LIKE.contains(ctx.block(down1)) || GrassRegistry[ctx, down1] != null - val isSnowed = ctx.blockState(up1).isSnow + override fun render(ctx: CombinedContext) { + val isConnected = BlockTags.DIRT_LIKE.contains(ctx.state(DOWN).block) || GrassRegistry[ctx, down1] != null + val isSnowed = ctx.state(UP).isSnow val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled) - val grass = GrassRegistry[ctx] - if (grass == null) { - // shouldn't happen - Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos) - return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null) - } + val grass = GrassRegistry[ctx]!! val blockColor = OptifineCustomColors.getBlockColor(ctx) if (connectedGrass) { - // get full AO data - modelRenderer.updateShading(Int3.zero, allFaces) - // check occlusion - val isVisible = forgeDirs.map { ctx.shouldSideBeRendered(it) } + val isVisible = allDirections.map { ctx.shouldSideBeRendered(it) } // render full grass block - ShadersModIntegration.renderAs(ctx.blockState(Int3.zero), MODEL, renderer) { - modelRenderer.render( - renderer, - fullCube, - quadFilter = { qi, _ -> isVisible[qi] }, - icon = { _, _, _ -> grass.grassTopTexture }, - postProcess = { ctx, _, _, _, _ -> - rotateUV(2) - if (isSnowed) { - if (!ctx.aoEnabled) setGrey(1.4f) - } else if (ctx.aoEnabled && grass.overrideColor == null) multiplyColor(blockColor) - } - ) - } + ctx.render( + fullCube, + quadFilter = { qi, _ -> isVisible[qi] }, + icon = { _, _, _ -> grass.grassTopTexture }, + postProcess = { ctx, _, _, _, _ -> + rotateUV(2) + if (isSnowed) { + if (!ctx.aoEnabled) setGrey(1.4f) + } else if (ctx.aoEnabled && grass.overrideColor == null) multiplyColor(blockColor) + } + ) } else { - renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null) - - // get AO data only for block top - modelRenderer.updateShading(Int3.zero, topOnly) + ctx.render() } - if (!Config.shortGrass.grassEnabled) return true - if (isSnowed && !Config.shortGrass.snowEnabled) return true - if (ctx.isNormalCube(up1)) return true - if (Config.shortGrass.population < 64 && noise[ctx.pos] >= Config.shortGrass.population) return true + if (!Config.shortGrass.grassEnabled) return + if (isSnowed && !Config.shortGrass.snowEnabled) return + if (ctx.offset(UP).isNormalCube) return + if (Config.shortGrass.population < 64 && noise[ctx.pos] >= Config.shortGrass.population) return // render grass quads val iconset = if (isSnowed) snowedIcons else normalIcons val iconGen = if (isSnowed) snowedGenIcon else normalGenIcon val rand = ctx.semiRandomArray(2) - ShadersModIntegration.grass(renderer, Config.shortGrass.shaderWind) { - modelRenderer.render( - renderer, + ShadersModIntegration.grass(ctx, Config.shortGrass.shaderWind) { + ctx.render( grassModels[rand[0]], - Rotation.identity, - ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero), + translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero), icon = { _, qi, _ -> if (Config.shortGrass.useGenerated) iconGen.icon!! else iconset[rand[qi and 1]]!! }, postProcess = { _, _, _, _, _ -> if (isSnowed) setGrey(1.0f) else multiplyColor(grass.overrideColor ?: blockColor) } ) } - - return true } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt index eeca63b..6fa5f58 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt @@ -1,30 +1,27 @@ package mods.betterfoliage.client.render import mods.betterfoliage.BetterFoliage -import mods.betterfoliage.client.Client import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.integration.OptifineCustomColors import mods.betterfoliage.client.integration.ShadersModIntegration import mods.betterfoliage.client.texture.LeafRegistry import mods.octarinecore.PI2 -import mods.octarinecore.client.render.* +import mods.octarinecore.client.render.CombinedContext +import mods.octarinecore.client.render.RenderDecorator +import mods.octarinecore.client.render.lighting.FlatOffset +import mods.octarinecore.client.render.lighting.cornerAoMaxGreen +import mods.octarinecore.client.render.lighting.edgeOrientedAuto import mods.octarinecore.common.Double3 import mods.octarinecore.common.Int3 -import mods.octarinecore.common.Rotation +import mods.octarinecore.common.allDirections import mods.octarinecore.common.vec import mods.octarinecore.random -import net.minecraft.block.material.Material -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.util.BlockRenderLayer -import net.minecraft.util.Direction.* +import net.minecraft.util.Direction.UP import net.minecraft.util.ResourceLocation -import net.minecraftforge.client.model.data.IModelData import java.lang.Math.cos import java.lang.Math.sin -import java.util.* -class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) { +class RenderLeaves : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) { val leavesModel = model { verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41) @@ -41,34 +38,28 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterF UP.vec * random(-1.0, 1.0) * Config.leaves.vOffset } - override fun isEligible(ctx: BlockContext) = + override fun isEligible(ctx: CombinedContext) = Config.enabled && Config.leaves.enabled && LeafRegistry[ctx] != null && - !(Config.leaves.hideInternal && ctx.isSurroundedByNormal) + !(Config.leaves.hideInternal && allDirections.all { ctx.offset(it).isNormalCube } ) - override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { - val isSnowed = ctx.blockState(up1).isSnow - val leafInfo = LeafRegistry[ctx] - if (leafInfo == null) { - // shouldn't happen - Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos) - return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer) - } + override val onlyOnCutout get() = true + + override fun render(ctx: CombinedContext) { + val isSnowed = ctx.state(UP).isSnow + val leafInfo = LeafRegistry[ctx]!! val blockColor = OptifineCustomColors.getBlockColor(ctx) - renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer) - if (!layer.isCutout) return true + ctx.render(force = true) - modelRenderer.updateShading(Int3.zero, allFaces) - ShadersModIntegration.leaves(renderer) { + ShadersModIntegration.leaves(ctx) { val rand = ctx.semiRandomArray(2) (if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation -> - modelRenderer.render( - renderer, + ctx.render( leavesModel.model, rotation, - ctx.blockCenter + perturbs[rand[0]], + translation = ctx.blockCenter + perturbs[rand[0]], icon = { _, _, _ -> leafInfo.roundLeafTexture }, postProcess = { _, _, _, _, _ -> rotateUV(rand[1]) @@ -76,16 +67,12 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterF } ) } - if (isSnowed && Config.leaves.snowEnabled) modelRenderer.render( - renderer, + if (isSnowed && Config.leaves.snowEnabled) ctx.render( leavesModel.model, - Rotation.identity, - ctx.blockCenter + perturbs[rand[0]], + translation = ctx.blockCenter + perturbs[rand[0]], icon = { _, _, _ -> snowedIcon[rand[1]]!! }, postProcess = whitewash ) } - - return true } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt index a74fa31..e123276 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt @@ -5,20 +5,16 @@ import mods.betterfoliage.client.Client import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.integration.ShadersModIntegration -import mods.octarinecore.client.render.* +import mods.octarinecore.client.render.CombinedContext +import mods.octarinecore.client.render.RenderDecorator +import mods.octarinecore.client.render.lighting.FlatOffsetNoColor import mods.octarinecore.common.Int3 -import mods.octarinecore.common.Rotation -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.util.BlockRenderLayer import net.minecraft.util.Direction.DOWN import net.minecraft.util.Direction.UP import net.minecraft.util.ResourceLocation -import net.minecraftforge.client.model.data.IModelData import org.apache.logging.log4j.Level.DEBUG -import java.util.* -class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) { +class RenderLilypad : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) { val rootModel = model { verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -1.5, yTop = -0.5) @@ -40,41 +36,28 @@ class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, Better Client.log(DEBUG, "Registered ${flowerIcon.num} lilypad flower textures") } - override fun isEligible(ctx: BlockContext): Boolean = + override fun isEligible(ctx: CombinedContext): Boolean = Config.enabled && Config.lilypad.enabled && - BlockConfig.lilypad.matchesClass(ctx.block) + BlockConfig.lilypad.matchesClass(ctx.state.block) - override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { - // render the whole block on the cutout layer - if (!layer.isCutout) return false - - renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null) - modelRenderer.updateShading(Int3.zero, allFaces) + override fun render(ctx: CombinedContext) { + ctx.render() val rand = ctx.semiRandomArray(5) - - ShadersModIntegration.grass(renderer) { - modelRenderer.render( - renderer, + ShadersModIntegration.grass(ctx) { + ctx.render( rootModel.model, - Rotation.identity, - ctx.blockCenter.add(perturbs[rand[2]]), + translation = ctx.blockCenter.add(perturbs[rand[2]]), forceFlat = true, - icon = { ctx, qi, q -> rootIcon[rand[qi and 1]]!! }, - postProcess = noPost + icon = { ctx, qi, q -> rootIcon[rand[qi and 1]]!! } ) } - if (rand[3] < Config.lilypad.flowerChance) modelRenderer.render( - renderer, + if (rand[3] < Config.lilypad.flowerChance) ctx.render( flowerModel.model, - Rotation.identity, - ctx.blockCenter.add(perturbs[rand[4]]), + translation = ctx.blockCenter.add(perturbs[rand[4]]), forceFlat = true, - icon = { _, _, _ -> flowerIcon[rand[0]]!! }, - postProcess = noPost + icon = { _, _, _ -> flowerIcon[rand[0]]!! } ) - - return true } } \ 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 449079b..1e8924f 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt @@ -8,8 +8,8 @@ import mods.betterfoliage.client.render.column.AbstractRenderColumn import mods.betterfoliage.client.render.column.ColumnRenderLayer import mods.betterfoliage.client.render.column.ColumnTextureInfo import mods.betterfoliage.client.render.column.SimpleColumnInfo -import mods.betterfoliage.client.texture.GrassRegistry -import mods.octarinecore.client.render.BlockContext +import mods.octarinecore.client.render.BlockCtx +import mods.octarinecore.client.render.CombinedContext import mods.octarinecore.client.resource.ModelRenderRegistry import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable import mods.octarinecore.client.resource.ModelRenderRegistryRoot @@ -19,12 +19,13 @@ import mods.octarinecore.tryDefault import net.minecraft.block.BlockState import net.minecraft.block.LogBlock import net.minecraft.util.Direction.Axis +import net.minecraft.world.IEnviromentBlockReader class RenderLog : AbstractRenderColumn(BetterFoliage.MOD_ID, BetterFoliage.modBus) { - override val addToCutout: Boolean get() = false + override val renderOnCutout: Boolean get() = false - override fun isEligible(ctx: BlockContext) = + override fun isEligible(ctx: CombinedContext) = Config.enabled && Config.roundLogs.enabled && LogRegistry[ctx] != null @@ -40,7 +41,6 @@ class RenderLog : AbstractRenderColumn(BetterFoliage.MOD_ID, BetterFoliage.modBu class RoundLogOverlayLayer : ColumnRenderLayer() { override val registry: ModelRenderRegistry get() = LogRegistry override val blockPredicate = { state: BlockState -> BlockConfig.logBlocks.matchesClass(state.block) } - override val surroundPredicate = { state: BlockState -> !BlockConfig.logBlocks.matchesClass(state.block) } override val connectSolids: Boolean get() = Config.roundLogs.connectSolids override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt index 89e2d7a..4792c5e 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt @@ -4,21 +4,15 @@ import mods.betterfoliage.BetterFoliage import mods.betterfoliage.client.Client import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.Config -import mods.octarinecore.client.render.AbstractBlockRenderingHandler -import mods.octarinecore.client.render.BlockContext -import mods.octarinecore.client.render.modelRenderer +import mods.octarinecore.client.render.CombinedContext +import mods.octarinecore.client.render.RenderDecorator import mods.octarinecore.client.render.noPost import mods.octarinecore.common.Double3 -import mods.octarinecore.common.Rotation -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.util.BlockRenderLayer +import net.minecraft.util.Direction.UP import net.minecraft.util.ResourceLocation -import net.minecraftforge.client.model.data.IModelData import org.apache.logging.log4j.Level.DEBUG -import java.util.* -class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) { +class RenderMycelium : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) { val myceliumIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_mycel_$idx") } val myceliumModel = modelSet(64) { idx -> RenderGrass.grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)(idx) } @@ -27,32 +21,25 @@ class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, Bette Client.log(DEBUG, "Registered ${myceliumIcon.num} mycelium textures") } - override fun isEligible(ctx: BlockContext): Boolean { + override fun isEligible(ctx: CombinedContext): Boolean { if (!Config.enabled || !Config.shortGrass.myceliumEnabled) return false - return BlockConfig.mycelium.matchesClass(ctx.block) + return BlockConfig.mycelium.matchesClass(ctx.state.block) } - override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { - // render the whole block on the cutout layer - if (!layer.isCutout) return false - - val isSnowed = ctx.blockState(up1).isSnow - - renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null) - - if (isSnowed && !Config.shortGrass.snowEnabled) return true - if (ctx.isNormalCube(up1)) return true + override fun render(ctx: CombinedContext) { + ctx.render() + if (!ctx.isCutout) return + val isSnowed = ctx.state(UP).isSnow + if (isSnowed && !Config.shortGrass.snowEnabled) return + if (ctx.offset(UP).isNormalCube) return val rand = ctx.semiRandomArray(2) - modelRenderer.render( - renderer, + + ctx.render( myceliumModel[rand[0]], - Rotation.identity, - ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero), + translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero), icon = { _, qi, _ -> myceliumIcon[rand[qi and 1]]!! }, postProcess = if (isSnowed) whitewash else noPost ) - - return true } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt index 8cbf528..f88b663 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt @@ -5,6 +5,7 @@ import mods.betterfoliage.client.Client import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.Config import mods.octarinecore.client.render.* +import mods.octarinecore.client.render.lighting.* import mods.octarinecore.common.Int3 import mods.octarinecore.common.Rotation import mods.octarinecore.random @@ -18,7 +19,7 @@ import net.minecraftforge.client.model.data.IModelData import org.apache.logging.log4j.Level.DEBUG import java.util.* -class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) { +class RenderNetherrack : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) { val netherrackIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_netherrack_$idx") } val netherrackModel = modelSet(64) { modelIdx -> @@ -34,28 +35,20 @@ class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, Bet Client.log(DEBUG, "Registered ${netherrackIcon.num} netherrack textures") } - override fun isEligible(ctx: BlockContext): Boolean { + override fun isEligible(ctx: CombinedContext): Boolean { if (!Config.enabled || !Config.netherrack.enabled) return false - return BlockConfig.netherrack.matchesClass(ctx.block) + return BlockConfig.netherrack.matchesClass(ctx.state.block) } - override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { - val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer) - if (!layer.isCutout) return baseRender - - if (ctx.isNormalCube(down1)) return baseRender - - modelRenderer.updateShading(Int3.zero, allFaces) + override fun render(ctx: CombinedContext) { + ctx.render() + if (!ctx.isCutout) return + if (ctx.offset(DOWN).isNormalCube) return val rand = ctx.semiRandomArray(2) - modelRenderer.render( - renderer, + ctx.render( netherrackModel[rand[0]], - Rotation.identity, - icon = { _, qi, _ -> netherrackIcon[rand[qi and 1]]!! }, - postProcess = noPost + icon = { _, qi, _ -> netherrackIcon[rand[qi and 1]]!! } ) - - return true } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt index e33288c..e5939b7 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt @@ -2,26 +2,19 @@ package mods.betterfoliage.client.render import mods.betterfoliage.BetterFoliage import mods.betterfoliage.client.Client -import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.integration.ShadersModIntegration -import mods.octarinecore.client.render.* -import mods.octarinecore.common.Int3 -import mods.octarinecore.common.Rotation +import mods.octarinecore.client.render.CombinedContext +import mods.octarinecore.client.render.RenderDecorator +import mods.octarinecore.client.render.lighting.FlatOffsetNoColor import mods.octarinecore.random -import net.minecraft.block.Block import net.minecraft.block.material.Material -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder import net.minecraft.tags.BlockTags -import net.minecraft.util.BlockRenderLayer import net.minecraft.util.Direction.UP import net.minecraft.util.ResourceLocation -import net.minecraftforge.client.model.data.IModelData import org.apache.logging.log4j.Level.DEBUG -import java.util.* -class RenderReeds : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) { +class RenderReeds : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) { val noise = simplexNoise() val reedIcons = iconSet { idx -> Client.genReeds.registerResource(ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_reed_$idx")) } @@ -47,31 +40,27 @@ class RenderReeds : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFo Client.log(DEBUG, "Registered ${reedIcons.num} reed textures") } - override fun isEligible(ctx: BlockContext) = + override fun isEligible(ctx: CombinedContext) = Config.enabled && Config.reed.enabled && - ctx.blockState(up2).material == Material.AIR && - ctx.blockState(up1).material == Material.WATER && - BlockTags.DIRT_LIKE.contains(ctx.block) && + ctx.state(up2).material == Material.AIR && + ctx.state(UP).material == Material.WATER && + BlockTags.DIRT_LIKE.contains(ctx.state.block) && ctx.biome.let { it.downfall > Config.reed.minBiomeRainfall && it.defaultTemperature >= Config.reed.minBiomeTemp } && noise[ctx.pos] < Config.reed.population - override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { - val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer) - if (!layer.isCutout) return baseRender + override val onlyOnCutout get() = false - modelRenderer.updateShading(Int3.zero, allFaces) + override fun render(ctx: CombinedContext) { + ctx.render() + if (!ctx.isCutout) return - val iconVar = ctx.random(1) - ShadersModIntegration.grass(renderer, Config.reed.shaderWind) { - modelRenderer.render( - renderer, - reedModels[ctx.random(0)], - Rotation.identity, + val iconVar = ctx.semiRandom(1) + ShadersModIntegration.grass(ctx, Config.reed.shaderWind) { + ctx.render( + reedModels[ctx.semiRandom(0)], forceFlat = true, - icon = { _, _, _ -> reedIcons[iconVar]!! }, - postProcess = noPost + icon = { _, _, _ -> reedIcons[iconVar]!! } ) } - return true } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/Utils.kt b/src/main/kotlin/mods/betterfoliage/client/render/Utils.kt index 3490d29..78f5aff 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/Utils.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/Utils.kt @@ -3,7 +3,7 @@ package mods.betterfoliage.client.render import mods.octarinecore.PI2 import mods.octarinecore.client.render.Model -import mods.octarinecore.client.render.PostProcessLambda +import mods.octarinecore.client.render.lighting.PostProcessLambda import mods.octarinecore.client.render.Quad import mods.octarinecore.common.Double3 import mods.octarinecore.common.Int3 diff --git a/src/main/kotlin/mods/betterfoliage/client/render/column/AbstractRenderer.kt b/src/main/kotlin/mods/betterfoliage/client/render/column/AbstractRenderer.kt index 2b60efc..884f32a 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/column/AbstractRenderer.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/column/AbstractRenderer.kt @@ -7,22 +7,19 @@ import mods.betterfoliage.client.render.* import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.BlockType.* import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.* -import mods.octarinecore.client.render.* -import mods.octarinecore.common.Int3 +import mods.octarinecore.client.render.CombinedContext +import mods.octarinecore.client.render.Model +import mods.octarinecore.client.render.RenderDecorator +import mods.octarinecore.client.render.noPost import mods.octarinecore.common.Rotation import mods.octarinecore.common.face import mods.octarinecore.common.rot import net.minecraft.block.BlockRenderType.MODEL -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.util.BlockRenderLayer import net.minecraft.util.Direction.* -import net.minecraftforge.client.model.data.IModelData import net.minecraftforge.eventbus.api.IEventBus -import java.util.* @Suppress("NOTHING_TO_INLINE") -abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : AbstractBlockRenderingHandler(modId, modBus) { +abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : RenderDecorator(modId, modBus) { /** The rotations necessary to bring the models in position for the 4 quadrants */ val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it } @@ -93,28 +90,25 @@ abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : Abstract q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE)) @Suppress("NON_EXHAUSTIVE_WHEN") - override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { + override fun render(ctx: CombinedContext) { - val roundLog = ChunkOverlayManager.get(overlayLayer, ctx.reader!!, ctx.pos) + val roundLog = ChunkOverlayManager.get(overlayLayer, ctx) when(roundLog) { - ColumnLayerData.SkipRender -> return true - ColumnLayerData.NormalRender -> return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null) + ColumnLayerData.SkipRender -> return + ColumnLayerData.NormalRender -> return ctx.render() ColumnLayerData.ResolveError, null -> { - Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos) - return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null) + Client.logRenderError(ctx.state, ctx.pos) + return ctx.render() } } // 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, random, modelData, null) + return ctx.render() } - // get AO data - modelRenderer.updateShading(Int3.zero, allFaces) - val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal] - renderAs(ctx.blockState(Int3.zero), MODEL, renderer) { + renderAs(ctx, MODEL) { quadrantRotations.forEachIndexed { idx, quadrantRotation -> // set rotation for the current quadrant val rotation = baseRotation + quadrantRotation @@ -136,8 +130,7 @@ abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : Abstract else -> null } - if (sideModel != null) modelRenderer.render( - renderer, + if (sideModel != null) ctx.render( sideModel, rotation, icon = roundLog.column.side, @@ -190,8 +183,7 @@ abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : Abstract } } - if (upModel != null) modelRenderer.render( - renderer, + if (upModel != null) ctx.render( upModel, rotation, icon = upIcon, @@ -201,8 +193,7 @@ abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : Abstract } } ) - if (downModel != null) modelRenderer.render( - renderer, + if (downModel != null) ctx.render( downModel, rotation, icon = downIcon, @@ -214,6 +205,5 @@ abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : Abstract ) } } - return true } } diff --git a/src/main/kotlin/mods/betterfoliage/client/render/column/OverlayLayer.kt b/src/main/kotlin/mods/betterfoliage/client/render/column/OverlayLayer.kt index 778f1f7..678af49 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/column/OverlayLayer.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/column/OverlayLayer.kt @@ -8,12 +8,9 @@ import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.Blo import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.* import mods.betterfoliage.client.render.rotationFromUp -import mods.octarinecore.client.render.BlockContext +import mods.octarinecore.client.render.BlockCtx import mods.octarinecore.client.resource.ModelRenderRegistry -import mods.octarinecore.common.Int3 -import mods.octarinecore.common.Rotation -import mods.octarinecore.common.face -import mods.octarinecore.common.plus +import mods.octarinecore.common.* import net.minecraft.block.BlockState import net.minecraft.util.Direction.Axis import net.minecraft.util.Direction.AxisDirection @@ -64,21 +61,18 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer { abstract val registry: ModelRenderRegistry abstract val blockPredicate: (BlockState)->Boolean - abstract val surroundPredicate: (BlockState) -> 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(reader: IEnviromentBlockReader, pos: BlockPos) { - allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(reader.dimType, this, pos + offset) } + override fun onBlockUpdate(world: IEnviromentBlockReader, pos: BlockPos) { + allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) } } - override fun calculate(reader: IEnviromentBlockReader, pos: BlockPos) = calculate(BlockContext(reader, pos)) - - fun calculate(ctx: BlockContext): ColumnLayerData { - if (ctx.isSurroundedBy(surroundPredicate) && ctx.isSurroundedByNormal) return ColumnLayerData.SkipRender + override fun calculate(ctx: BlockCtx): ColumnLayerData { + if (allDirections.all { ctx.offset(it).isNormalCube }) return ColumnLayerData.SkipRender val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError // if log axis is not defined and "Default to vertical" config option is not set, render normally @@ -104,7 +98,7 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer { } /** Fill the array of [QuadrantType]s based on the blocks to the sides of this one. */ - fun Array.checkNeighbors(ctx: BlockContext, rotation: Rotation, logAxis: Axis, yOff: Int): Array { + fun Array.checkNeighbors(ctx: BlockCtx, rotation: Rotation, logAxis: Axis, yOff: Int): Array { val blkS = ctx.blockType(rotation, logAxis, Int3(0, yOff, 1)) val blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0)) val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1)) @@ -171,13 +165,13 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer { /** * Get the type of the block at the given offset in a rotated reference frame. */ - fun BlockContext.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType { + fun BlockCtx.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType { val offsetRot = offset.rotate(rotation) - val state = blockState(offsetRot) + val state = state(offsetRot) return if (!blockPredicate(state)) { - if (isNormalCube(offsetRot)) SOLID else NONSOLID + if (offset(offsetRot).isNormalCube) SOLID else NONSOLID } else { - (registry[state, reader!!, pos + offsetRot]?.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let { + (registry[state, world, pos + offsetRot]?.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let { if (it == axis) PARALLEL else PERPENDICULAR } ?: SOLID } diff --git a/src/main/kotlin/mods/betterfoliage/client/render/column/RenderData.kt b/src/main/kotlin/mods/betterfoliage/client/render/column/RenderData.kt index 3781c5f..8b13ebc 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/column/RenderData.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/column/RenderData.kt @@ -1,7 +1,6 @@ package mods.betterfoliage.client.render.column -import mods.octarinecore.client.render.QuadIconResolver -import mods.octarinecore.client.render.blockContext +import mods.octarinecore.client.render.lighting.QuadIconResolver import mods.octarinecore.client.resource.ModelRenderKey import mods.octarinecore.client.resource.get import mods.octarinecore.client.resource.missingSprite @@ -31,8 +30,8 @@ open class SimpleColumnInfo( override val top: QuadIconResolver = { _, _, _ -> topTexture } override val bottom: QuadIconResolver = { _, _, _ -> bottomTexture } override val side: QuadIconResolver = { ctx, idx, _ -> - val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.rotation) - val sideIdx = if (sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0 + val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.modelRotation) + val sideIdx = if (sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0 sideTextures[sideIdx] } diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/GrassGenerator.kt b/src/main/kotlin/mods/betterfoliage/client/texture/GrassGenerator.kt index 4eb6a27..b0e116b 100644 --- a/src/main/kotlin/mods/betterfoliage/client/texture/GrassGenerator.kt +++ b/src/main/kotlin/mods/betterfoliage/client/texture/GrassGenerator.kt @@ -1,6 +1,7 @@ package mods.betterfoliage.client.texture import mods.octarinecore.client.resource.* +import mods.octarinecore.client.resource.Atlas import net.minecraft.util.ResourceLocation import net.minecraftforge.resource.VanillaResourceType.TEXTURES import java.awt.image.BufferedImage diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt b/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt index c50051a..1ff7a8f 100644 --- a/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt +++ b/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt @@ -3,7 +3,7 @@ package mods.betterfoliage.client.texture import mods.betterfoliage.BetterFoliage import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.Config -import mods.octarinecore.client.render.HSB +import mods.octarinecore.client.render.lighting.HSB import mods.octarinecore.client.resource.* import mods.octarinecore.common.config.ConfigurableBlockMatcher import mods.octarinecore.common.config.ModelTextureList diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/LeafGenerator.kt b/src/main/kotlin/mods/betterfoliage/client/texture/LeafGenerator.kt index 394d30e..a61aaa4 100644 --- a/src/main/kotlin/mods/betterfoliage/client/texture/LeafGenerator.kt +++ b/src/main/kotlin/mods/betterfoliage/client/texture/LeafGenerator.kt @@ -2,7 +2,7 @@ package mods.betterfoliage.client.texture import mods.betterfoliage.BetterFoliage import mods.octarinecore.client.resource.* -import mods.octarinecore.stripStart +import mods.octarinecore.client.resource.Atlas import net.minecraft.resources.IResource import net.minecraft.util.ResourceLocation import net.minecraftforge.resource.VanillaResourceType.TEXTURES diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/LeafParticleRegistry.kt b/src/main/kotlin/mods/betterfoliage/client/texture/LeafParticleRegistry.kt index 3577e03..fe0e8ee 100644 --- a/src/main/kotlin/mods/betterfoliage/client/texture/LeafParticleRegistry.kt +++ b/src/main/kotlin/mods/betterfoliage/client/texture/LeafParticleRegistry.kt @@ -3,10 +3,9 @@ package mods.betterfoliage.client.texture import mods.betterfoliage.BetterFoliage import mods.octarinecore.client.resource.* import mods.octarinecore.stripStart +import mods.octarinecore.client.resource.Atlas import net.minecraft.util.ResourceLocation import net.minecraftforge.client.event.TextureStitchEvent -import net.minecraftforge.common.MinecraftForge -import net.minecraftforge.eventbus.api.EventPriority import net.minecraftforge.eventbus.api.SubscribeEvent object LeafParticleRegistry { diff --git a/src/main/kotlin/mods/octarinecore/client/render/AbstractBlockRenderingHandler.kt b/src/main/kotlin/mods/octarinecore/client/render/AbstractBlockRenderingHandler.kt deleted file mode 100644 index 461d9c5..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/AbstractBlockRenderingHandler.kt +++ /dev/null @@ -1,132 +0,0 @@ -@file:JvmName("RendererHolder") -package mods.octarinecore.client.render - -import mods.betterfoliage.client.render.canRenderInCutout -import mods.betterfoliage.client.render.isCutout -import mods.octarinecore.ThreadLocalDelegate -import mods.octarinecore.client.resource.ResourceHandler -import mods.octarinecore.common.Double3 -import mods.octarinecore.common.Int3 -import mods.octarinecore.common.forgeDirOffsets -import mods.octarinecore.common.plus -import mods.octarinecore.semiRandom -import net.minecraft.block.Block -import net.minecraft.block.BlockState -import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.BlockRendererDispatcher -import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.client.renderer.color.BlockColors -import net.minecraft.util.BlockRenderLayer -import net.minecraft.util.Direction -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.MathHelper -import net.minecraft.world.IBlockReader -import net.minecraft.world.IEnviromentBlockReader -import net.minecraft.world.biome.Biome -import net.minecraftforge.client.model.data.IModelData -import net.minecraftforge.eventbus.api.IEventBus -import java.util.* -import kotlin.math.abs - -/** - * [ThreadLocal] instance of [BlockContext] representing the block being rendered. - */ -val blockContext by ThreadLocalDelegate { BlockContext() } - -/** - * [ThreadLocal] instance of [ModelRenderer]. - */ -val modelRenderer by ThreadLocalDelegate { ModelRenderer() } - -val blockColors = ThreadLocal() - -abstract class AbstractBlockRenderingHandler(modId: String, modBus: IEventBus) : ResourceHandler(modId, modBus) { - - open val addToCutout: Boolean get() = true - - // ============================ - // Custom rendering - // ============================ - abstract fun isEligible(ctx: BlockContext): Boolean - abstract fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean - - // ============================ - // Vanilla rendering wrapper - // ============================ - /** - * Render the block in the current [BlockContext] - */ - fun renderWorldBlockBase(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer?): Boolean { - ctx.blockState(Int3.zero).let { state -> - if (layer == null || - state.canRenderInLayer(layer) || - (state.canRenderInCutout() && layer.isCutout)) { - return dispatcher.renderBlock(state, ctx.pos, ctx.reader!!, renderer, random, modelData) - } - } - return false - } - -} - -data class BlockData(val state: BlockState, 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. - */ -@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") -open class BlockContext( - var reader: IEnviromentBlockReader? = null, - open var pos: BlockPos = BlockPos.ZERO -) { - fun set(blockReader: IEnviromentBlockReader, pos: BlockPos) { this.reader = blockReader; this.pos = pos; } - - val block: Block get() = block(Int3.zero) - fun block(offset: Int3) = blockState(offset).block - fun blockState(offset: Int3) = (pos + offset).let { reader!!.getBlockState(it) } - fun isNormalCube(offset: Int3) = (pos + offset).let { reader!!.getBlockState(it).isNormalCube(reader, it) } - - /** Get the centerpoint of the block being rendered. */ - val blockCenter: Double3 get() = Double3(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5) - - val chunkBase: Double3 get() { - val cX = if (pos.x >= 0) pos.x / 16 else (pos.x + 1) / 16 - 1 - val cY = pos.y / 16 - val cZ = if (pos.z >= 0) pos.z / 16 else (pos.z + 1) / 16 - 1 - return Double3(cX * 16.0, cY * 16.0, cZ * 16.0) - } - - /** Get the biome at the block position. */ - val biome: Biome get() = reader!!.getBiome(pos) - - fun blockData(offset: Int3) = (pos + offset).let { pos -> - reader!!.getBlockState(pos).let { state -> - BlockData( - state, - Minecraft.getInstance().blockColors.getColor(state, reader!!, pos, 0), - state.getPackedLightmapCoords(reader!!, pos) - ) - } - } - - fun shouldSideBeRendered(dir: Direction) = Block.shouldSideBeRendered(blockState(Int3.zero), reader, pos, dir) - - /** Is the block surrounded by other blocks that satisfy the predicate on all sides? */ - fun isSurroundedBy(predicate: (BlockState)->Boolean) = forgeDirOffsets.all { predicate(blockState(it)) } - val isSurroundedByNormal: Boolean get() = forgeDirOffsets.all { isNormalCube(it) } - - /** Get a semi-random value based on the block coordinate and the given seed. */ - fun random(seed: Int) = semiRandom(pos.x, pos.y, pos.z, seed) - - /** Get an array of semi-random values based on the block coordinate. */ - fun semiRandomArray(num: Int): Array = Array(num) { random(it) } - - /** Get the distance of the block from the camera (player). */ - val cameraDistance: Int get() { - val camera = Minecraft.getInstance().renderViewEntity ?: return 0 - return abs(pos.x - MathHelper.floor(camera.posX)) + - abs(pos.y - MathHelper.floor(camera.posY)) + - abs(pos.z - MathHelper.floor(camera.posZ)) - } -} diff --git a/src/main/kotlin/mods/octarinecore/client/render/BlockContext.kt b/src/main/kotlin/mods/octarinecore/client/render/BlockContext.kt new file mode 100644 index 0000000..4299e35 --- /dev/null +++ b/src/main/kotlin/mods/octarinecore/client/render/BlockContext.kt @@ -0,0 +1,71 @@ +package mods.octarinecore.client.render + +import mods.octarinecore.common.* +import mods.octarinecore.semiRandom +import net.minecraft.block.Block +import net.minecraft.block.BlockState +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.BufferBuilder +import net.minecraft.util.BlockRenderLayer +import net.minecraft.util.Direction +import net.minecraft.util.math.BlockPos +import net.minecraft.world.IBlockReader +import net.minecraft.world.IEnviromentBlockReader +import net.minecraft.world.World +import net.minecraft.world.biome.Biome +import java.util.* + +/** + * Represents the block being rendered. Has properties and methods to query the neighborhood of the block in + * block-relative coordinates. + */ +interface BlockCtx { + val world: IEnviromentBlockReader + val pos: BlockPos + + fun offset(dir: Direction) = offset(dir.offset) + fun offset(offset: Int3): BlockCtx + + val state: BlockState get() = world.getBlockState(pos) + fun state(dir: Direction) = world.getBlockState(pos + dir.offset) + fun state(offset: Int3) = world.getBlockState(pos + offset) + + val biome: Biome get() = world.getBiome(pos) + + val isNormalCube: Boolean get() = state.isNormalCube(world, pos) + + fun shouldSideBeRendered(side: Direction) = Block.shouldSideBeRendered(state, world, pos, side) + + /** Get a semi-random value based on the block coordinate and the given seed. */ + fun semiRandom(seed: Int) = semiRandom(pos.x, pos.y, pos.z, seed) + + /** Get an array of semi-random values based on the block coordinate. */ + fun semiRandomArray(num: Int): Array = Array(num) { semiRandom(it) } +} + +open class BasicBlockCtx( + override val world: IEnviromentBlockReader, + override val pos: BlockPos +) : BlockCtx { + override var state: BlockState = world.getBlockState(pos) + protected set + override fun offset(offset: Int3) = BasicBlockCtx(world, pos + offset) + fun cache() = CachedBlockCtx(world, pos) +} + +open class CachedBlockCtx(world: IEnviromentBlockReader, pos: BlockPos) : BasicBlockCtx(world, pos) { + var neighbors = Array(6) { world.getBlockState(pos + allDirections[it].offset) } + override var biome: Biome = world.getBiome(pos) + override fun state(dir: Direction) = neighbors[dir.ordinal] +} + + +data class RenderCtx( + val dispatcher: BlockRendererDispatcher, + val renderBuffer: BufferBuilder, + val layer: BlockRenderLayer, + val random: Random +) { + fun render(worldBlock: BlockCtx) = dispatcher.renderBlock(worldBlock.state, worldBlock.pos, worldBlock.world, renderBuffer, random, null) +} + diff --git a/src/main/kotlin/mods/octarinecore/client/render/CombinedContext.kt b/src/main/kotlin/mods/octarinecore/client/render/CombinedContext.kt new file mode 100644 index 0000000..6e41725 --- /dev/null +++ b/src/main/kotlin/mods/octarinecore/client/render/CombinedContext.kt @@ -0,0 +1,102 @@ +package mods.octarinecore.client.render + +import mods.betterfoliage.client.render.canRenderInCutout +import mods.betterfoliage.client.render.isCutout +import mods.betterfoliage.loader.Refs +import mods.octarinecore.client.render.lighting.* +import mods.octarinecore.common.Double3 +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.Rotation +import mods.octarinecore.common.plus +import net.minecraft.block.Blocks +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.BufferBuilder +import net.minecraft.fluid.Fluids +import net.minecraft.util.BlockRenderLayer +import net.minecraft.util.Direction +import net.minecraft.util.math.BlockPos +import net.minecraft.world.IEnviromentBlockReader +import net.minecraft.world.LightType +import net.minecraft.world.biome.Biomes +import java.util.* + +class CombinedContext( + val blockCtx: BlockCtx, val renderCtx: RenderCtx, val lightingCtx: DefaultLightingCtx +) : BlockCtx by blockCtx, LightingCtx by lightingCtx { + + var hasRendered = false + + fun render(force: Boolean = false) = renderCtx.let { + if (force || state.canRenderInLayer(it.layer) || (state.canRenderInCutout() && it.layer.isCutout)) { + it.render(blockCtx) + hasRendered = true + } + Unit + } + + fun exchange(moddedOffset: Int3, targetOffset: Int3) = CombinedContext( + BasicBlockCtx(OffsetEnvBlockReader(blockCtx.world, pos + moddedOffset, pos + targetOffset), pos), + renderCtx, + lightingCtx + ) + + val isCutout = renderCtx.layer.isCutout + + /** Get the centerpoint of the block being rendered. */ + val blockCenter: Double3 get() = Double3(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5) + + /** Holds final vertex data before it goes to the [Tessellator]. */ + val temp = RenderVertex() + + fun render( + model: Model, + rotation: Rotation = Rotation.identity, + translation: Double3 = blockCenter, + forceFlat: Boolean = false, + quadFilter: (Int, Quad) -> Boolean = { _, _ -> true }, + icon: QuadIconResolver, + postProcess: PostProcessLambda = noPost + ) { + lightingCtx.modelRotation = rotation + model.quads.forEachIndexed { quadIdx, quad -> + if (quadFilter(quadIdx, quad)) { + val drawIcon = icon(this, quadIdx, quad) + if (drawIcon != null) { + // let OptiFine know the texture we're using, so it can + // transform UV coordinates to quad-relative + Refs.quadSprite.set(renderCtx!!.renderBuffer, drawIcon) + + quad.verts.forEachIndexed { vertIdx, vert -> + temp.init(vert).rotate(lightingCtx.modelRotation).translate(translation) + val shader = if (lightingCtx.aoEnabled && !forceFlat) vert.aoShader else vert.flatShader + shader.shade(lightingCtx, temp) + temp.postProcess(this, quadIdx, quad, vertIdx, vert) + temp.setIcon(drawIcon) + + renderCtx.renderBuffer + .pos(temp.x, temp.y, temp.z) + .color(temp.red, temp.green, temp.blue, 1.0f) + .tex(temp.u, temp.v) + .lightmap(temp.brightness shr 16 and 65535, temp.brightness and 65535) + .endVertex() + } + } + } + } + hasRendered = true + } +} + +val allFaces: (Direction) -> Boolean = { true } +val topOnly: (Direction) -> Boolean = { it == Direction.UP } + +/** Perform no post-processing */ +val noPost: PostProcessLambda = { _, _, _, _, _ -> } + +object NonNullWorld : IEnviromentBlockReader { + override fun getBlockState(pos: BlockPos) = Blocks.AIR.defaultState + override fun getLightFor(type: LightType, pos: BlockPos) = 0 + override fun getFluidState(pos: BlockPos) = Fluids.EMPTY.defaultState + override fun getTileEntity(pos: BlockPos) = null + override fun getBiome(pos: BlockPos) = Biomes.THE_VOID +} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/Model.kt b/src/main/kotlin/mods/octarinecore/client/render/Model.kt index 91313bb..f9e5e19 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/Model.kt +++ b/src/main/kotlin/mods/octarinecore/client/render/Model.kt @@ -1,5 +1,6 @@ package mods.octarinecore.client.render +import mods.octarinecore.client.render.lighting.* import mods.octarinecore.common.* import mods.octarinecore.minmax import mods.octarinecore.replace @@ -40,13 +41,13 @@ data class UV(val u: Double, val v: Double) { * * @param[xyz] x, y, z coordinates * @param[uv] u, v coordinates - * @param[aoShader] [Shader] instance to use with AO rendering - * @param[flatShader] [Shader] instance to use with non-AO rendering + * @param[aoShader] [ModelLighter] instance to use with AO rendering + * @param[flatShader] [ModelLighter] instance to use with non-AO rendering */ data class Vertex(val xyz: Double3 = Double3(0.0, 0.0, 0.0), val uv: UV = UV(0.0, 0.0), - val aoShader: Shader = NoShader, - val flatShader: Shader = NoShader) + val aoShader: ModelLighter = NoLighting, + val flatShader: ModelLighter = NoLighting) /** * Model quad @@ -78,7 +79,7 @@ data class Quad(val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex) transformVI { vertex, idx -> if (!predicate(vertex, idx)) vertex else vertex.copy(flatShader = factory(this@Quad, vertex)) } - fun setFlatShader(shader: Shader) = transformVI { vertex, idx -> vertex.copy(flatShader = shader) } + fun setFlatShader(shader: ModelLighter) = transformVI { vertex, idx -> vertex.copy(flatShader = shader) } val flipped: Quad get() = Quad(v4, v3, v2, v1) fun cycleVertices(n: Int) = when(n % 4) { @@ -125,8 +126,8 @@ class Model() { fun faceQuad(face: Direction): Quad { val base = face.vec * 0.5 - val top = faceCorners[face.ordinal].topLeft.first.vec * 0.5 - val left = faceCorners[face.ordinal].topLeft.second.vec * 0.5 + val top = boxFaces[face].top * 0.5 + val left = boxFaces[face].left * 0.5 return Quad( Vertex(base + top + left, UV.topLeft), Vertex(base - top + left, UV.bottomLeft), @@ -137,7 +138,7 @@ class Model() { } val fullCube = Model().apply { - forgeDirs.forEach { + allDirections.forEach { faceQuad(it) .setAoShader(faceOrientedAuto(corner = cornerAo(it.axis), edge = null)) .setFlatShader(faceOrientedAuto(corner = cornerFlat, edge = null)) diff --git a/src/main/kotlin/mods/octarinecore/client/render/ModelRenderer.kt b/src/main/kotlin/mods/octarinecore/client/render/ModelRenderer.kt deleted file mode 100644 index d043b19..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/ModelRenderer.kt +++ /dev/null @@ -1,181 +0,0 @@ -package mods.octarinecore.client.render - -import mods.betterfoliage.loader.Refs -import mods.octarinecore.common.* -import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.BufferBuilder -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import net.minecraft.util.Direction -import net.minecraft.util.Direction.* - -typealias QuadIconResolver = (ShadingContext, Int, Quad) -> TextureAtlasSprite? -typealias PostProcessLambda = RenderVertex.(ShadingContext, Int, Quad, Int, Vertex) -> Unit - -class ModelRenderer : ShadingContext() { - - /** Holds final vertex data before it goes to the [Tessellator]. */ - val temp = RenderVertex() - - /** - * Render a [Model]. - * The [blockContext] and [renderBlocks] need to be set up correctly, including first rendering the - * corresponding block to capture shading values! - * - * @param[model] model to render - * @param[rot] rotation to apply to the model - * @param[trans] translation to apply to the model - * @param[forceFlat] force flat shading even if AO is enabled - * @param[icon] lambda to resolve the texture to use for each quad - * @param[rotateUV] lambda to get amount of UV rotation for each quad - * @param[postProcess] lambda to perform arbitrary modifications on the [RenderVertex] just before it goes to the [Tessellator] - */ - fun render( - worldRenderer: BufferBuilder, - model: Model, - rot: Rotation = Rotation.identity, - trans: Double3 = blockContext.blockCenter, - forceFlat: Boolean = false, - quadFilter: (Int, Quad) -> Boolean = { _, _ -> true }, - icon: QuadIconResolver, - postProcess: PostProcessLambda - ) { - rotation = rot - aoEnabled = Minecraft.isAmbientOcclusionEnabled() - - // make sure we have space in the buffer for our quads plus one -// worldRenderer.ensureSpaceForQuads(model.quads.size + 1) - - model.quads.forEachIndexed { quadIdx, quad -> - if (quadFilter(quadIdx, quad)) { - val drawIcon = icon(this, quadIdx, quad) - if (drawIcon != null) { - // let OptiFine know the texture we're using, so it can - // transform UV coordinates to quad-relative - Refs.quadSprite.set(worldRenderer, drawIcon) - - quad.verts.forEachIndexed { vertIdx, vert -> - temp.init(vert).rotate(rotation).translate(trans) - val shader = if (aoEnabled && !forceFlat) vert.aoShader else vert.flatShader - shader.shade(this, temp) - temp.postProcess(this, quadIdx, quad, vertIdx, vert) - temp.setIcon(drawIcon) - - worldRenderer - .pos(temp.x, temp.y, temp.z) - .color(temp.red, temp.green, temp.blue, 1.0f) - .tex(temp.u, temp.v) - .lightmap(temp.brightness shr 16 and 65535, temp.brightness and 65535) - .endVertex() - } - } - } - } - } -} - -/** - * Queried by [Shader] objects to get rendering-relevant data of the current block in a rotated frame of reference. - */ -open class ShadingContext { - var rotation = Rotation.identity - var aoEnabled = Minecraft.isAmbientOcclusionEnabled() - val aoFaces = Array(6) { AoFaceData(forgeDirs[it]) } - - val Direction.aoMultiplier: Float get() = when(this) { - UP -> 1.0f - DOWN -> 0.5f - NORTH, SOUTH -> 0.8f - EAST, WEST -> 0.6f - } - - fun updateShading(offset: Int3, predicate: (Direction) -> Boolean = { true }) { - forgeDirs.forEach { if (predicate(it)) aoFaces[it.ordinal].update(offset, multiplier = it.aoMultiplier) } - } - - fun aoShading(face: Direction, corner1: Direction, corner2: Direction) = - aoFaces[face.rotate(rotation).ordinal][corner1.rotate(rotation), corner2.rotate(rotation)] - - fun blockData(offset: Int3) = blockContext.blockData(offset.rotate(rotation)) -} - -/** - * - */ -@Suppress("NOTHING_TO_INLINE") -class RenderVertex() { - var x: Double = 0.0 - var y: Double = 0.0 - var z: Double = 0.0 - var u: Double = 0.0 - var v: Double = 0.0 - var brightness: Int = 0 - var red: Float = 0.0f - var green: Float = 0.0f - var blue: Float = 0.0f - - val rawData = IntArray(7) - - fun init(vertex: Vertex, rot: Rotation, trans: Double3): RenderVertex { - val result = vertex.xyz.rotate(rot) + trans - x = result.x; y = result.y; z = result.z - return this - } - fun init(vertex: Vertex): RenderVertex { - x = vertex.xyz.x; y = vertex.xyz.y; z = vertex.xyz.z; - u = vertex.uv.u; v = vertex.uv.v - return this - } - fun translate(trans: Double3): RenderVertex { x += trans.x; y += trans.y; z += trans.z; return this } - fun rotate(rot: Rotation): RenderVertex { - if (rot === Rotation.identity) return this - val rotX = rot.rotatedComponent(EAST, x, y, z) - val rotY = rot.rotatedComponent(UP, x, y, z) - val rotZ = rot.rotatedComponent(SOUTH, x, y, z) - x = rotX; y = rotY; z = rotZ - return this - } - inline fun rotateUV(n: Int): RenderVertex { - when (n % 4) { - 1 -> { val t = v; v = -u; u = t; return this } - 2 -> { u = -u; v = -v; return this } - 3 -> { val t = -v; v = u; u = t; return this } - else -> { return this } - } - } - inline fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) { - if (mirrorU) u = -u - if (mirrorV) v = -v - } - inline fun setIcon(icon: TextureAtlasSprite): RenderVertex { - u = (icon.maxU - icon.minU) * (u + 0.5) + icon.minU - v = (icon.maxV - icon.minV) * (v + 0.5) + icon.minV - return this - } - - inline fun setGrey(level: Float) { - val grey = Math.min((red + green + blue) * 0.333f * level, 1.0f) - red = grey; green = grey; blue = grey - } - inline fun multiplyColor(color: Int) { - red *= (color shr 16 and 255) / 256.0f - green *= (color shr 8 and 255) / 256.0f - blue *= (color and 255) / 256.0f - } - inline fun setColor(color: Int) { - red = (color shr 16 and 255) / 256.0f - green = (color shr 8 and 255) / 256.0f - blue = (color and 255) / 256.0f - } - -} - -//fun BufferBuilder.ensureSpaceForQuads(num: Int) { -// rawIntBuffer.position(bufferSize) -// growBuffer(num * vertexFormat.size) -//} - -val allFaces: (Direction) -> Boolean = { true } -val topOnly: (Direction) -> Boolean = { it == UP } - -/** Perform no post-processing */ -val noPost: PostProcessLambda = { _, _, _, _, _ -> } \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/OffsetBlockReader.kt b/src/main/kotlin/mods/octarinecore/client/render/OffsetBlockReader.kt index e6654f8..f0df03a 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/OffsetBlockReader.kt +++ b/src/main/kotlin/mods/octarinecore/client/render/OffsetBlockReader.kt @@ -49,6 +49,4 @@ class OffsetEnvBlockReader(val original: IEnviromentBlockReader, val modded: Blo // val result = func() // reader = original // return result -//} - -inline fun BlockContext.offset(modded: Int3, target: Int3) = BlockContext(OffsetEnvBlockReader(reader!!, pos + modded, pos + target), pos) \ No newline at end of file +//} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/PixelFormat.kt b/src/main/kotlin/mods/octarinecore/client/render/PixelFormat.kt deleted file mode 100644 index 6ad2024..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/PixelFormat.kt +++ /dev/null @@ -1,69 +0,0 @@ -@file:JvmName("PixelFormat") -package mods.octarinecore.client.render - -import java.awt.Color - -/** List of bit-shift offsets in packed brightness values where meaningful (4-bit) data is contained. */ -var brightnessComponents = listOf(20, 4) - -/** Multiply the components of this packed brightness value with the given [Float]. */ -infix fun Int.brMul(f: Float): Int { - val weight = (f * 256.0f).toInt() - var result = 0 - brightnessComponents.forEach { shift -> - val raw = (this shr shift) and 15 - val weighted = (raw) * weight / 256 - result = result or (weighted shl shift) - } - return result -} - -/** Multiply the components of this packed color value with the given [Float]. */ -infix fun Int.colorMul(f: Float): Int { - val weight = (f * 256.0f).toInt() - val red = (this shr 16 and 255) * weight / 256 - val green = (this shr 8 and 255) * weight / 256 - val blue = (this and 255) * weight / 256 - return (red shl 16) or (green shl 8) or blue -} - -/** Sum the components of all packed brightness values given. */ -fun brSum(multiplier: Float?, vararg brightness: Int): Int { - val sum = Array(brightnessComponents.size) { 0 } - brightnessComponents.forEachIndexed { idx, shift -> brightness.forEach { br -> - val comp = (br shr shift) and 15 - sum[idx] += comp - } } - var result = 0 - brightnessComponents.forEachIndexed { idx, shift -> - val comp = if (multiplier == null) - ((sum[idx]) shl shift) - else - ((sum[idx].toFloat() * multiplier).toInt() shl shift) - result = result or comp - } - return result -} - -fun brWeighted(br1: Int, weight1: Float, br2: Int, weight2: Float): Int { - val w1int = (weight1 * 256.0f + 0.5f).toInt() - val w2int = (weight2 * 256.0f + 0.5f).toInt() - var result = 0 - brightnessComponents.forEachIndexed { idx, shift -> - val comp1 = (br1 shr shift) and 15 - val comp2 = (br2 shr shift) and 15 - val compWeighted = (comp1 * w1int + comp2 * w2int) / 256 - result = result or ((compWeighted and 15) shl shift) - } - return result -} - -data class HSB(var hue: Float, var saturation: Float, var brightness: Float) { - companion object { - fun fromColor(color: Int): HSB { - val hsbVals = Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null) - return HSB(hsbVals[0], hsbVals[1], hsbVals[2]) - } - } - val asColor: Int get() = Color.HSBtoRGB(hue, saturation, brightness) -} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/RenderDecorator.kt b/src/main/kotlin/mods/octarinecore/client/render/RenderDecorator.kt new file mode 100644 index 0000000..15ac383 --- /dev/null +++ b/src/main/kotlin/mods/octarinecore/client/render/RenderDecorator.kt @@ -0,0 +1,45 @@ +@file:JvmName("RendererHolder") +package mods.octarinecore.client.render + +import mods.betterfoliage.client.render.canRenderInCutout +import mods.betterfoliage.client.render.isCutout +import mods.octarinecore.ThreadLocalDelegate +import mods.octarinecore.client.resource.ResourceHandler +import mods.octarinecore.common.Double3 +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.allDirOffsets +import mods.octarinecore.common.plus +import mods.octarinecore.semiRandom +import net.minecraft.block.Block +import net.minecraft.block.BlockState +import net.minecraft.client.Minecraft +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.BufferBuilder +import net.minecraft.client.renderer.color.BlockColors +import net.minecraft.util.BlockRenderLayer +import net.minecraft.util.Direction +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.MathHelper +import net.minecraft.world.IEnviromentBlockReader +import net.minecraft.world.biome.Biome +import net.minecraftforge.client.model.data.IModelData +import net.minecraftforge.eventbus.api.IEventBus +import java.util.* +import kotlin.math.abs + +abstract class RenderDecorator(modId: String, modBus: IEventBus) : ResourceHandler(modId, modBus) { + + open val renderOnCutout: Boolean get() = true + open val onlyOnCutout: Boolean get() = false + + // ============================ + // Custom rendering + // ============================ + abstract fun isEligible(ctx: CombinedContext): Boolean + abstract fun render(ctx: CombinedContext) + +} + +data class BlockData(val state: BlockState, val color: Int, val brightness: Int) + + diff --git a/src/main/kotlin/mods/octarinecore/client/render/Shading.kt b/src/main/kotlin/mods/octarinecore/client/render/lighting/Lighting.kt similarity index 51% rename from src/main/kotlin/mods/octarinecore/client/render/Shading.kt rename to src/main/kotlin/mods/octarinecore/client/render/lighting/Lighting.kt index 3118967..5b39066 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/Shading.kt +++ b/src/main/kotlin/mods/octarinecore/client/render/lighting/Lighting.kt @@ -1,19 +1,17 @@ -package mods.octarinecore.client.render +package mods.octarinecore.client.render.lighting +import mods.octarinecore.client.render.* import mods.octarinecore.common.* -import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.BlockModelRenderer import net.minecraft.util.Direction import net.minecraft.util.Direction.* import java.lang.Math.min -import java.util.* -typealias EdgeShaderFactory = (Direction, Direction) -> Shader -typealias CornerShaderFactory = (Direction, Direction, Direction) -> Shader -typealias ShaderFactory = (Quad, Vertex) -> Shader +typealias EdgeShaderFactory = (Direction, Direction) -> ModelLighter +typealias CornerShaderFactory = (Direction, Direction, Direction) -> ModelLighter +typealias ShaderFactory = (Quad, Vertex) -> ModelLighter -/** Holds shading values for block corners as calculated by vanilla Minecraft rendering. */ -class AoData() { +/** Holds lighting values for block corners as calculated by vanilla Minecraft rendering. */ +class CornerLightData { var valid = false var brightness = 0 var red: Float = 0.0f @@ -40,51 +38,7 @@ class AoData() { } companion object { - val black = AoData() - } -} - -class AoFaceData(val face: Direction) { - companion object { - private val aoFactory = BlockModelRenderer.AmbientOcclusionFace::class.java.let { - it.getDeclaredConstructor(BlockModelRenderer::class.java).apply { isAccessible = true } - }.let { ctor -> { ctor.newInstance(Minecraft.getInstance().blockRendererDispatcher.blockModelRenderer) } } - } - val ao = aoFactory() - val top = faceCorners[face.ordinal].topLeft.first - val left = faceCorners[face.ordinal].topLeft.second - - val topLeft = AoData() - val topRight = AoData() - val bottomLeft = AoData() - val bottomRight = AoData() - val ordered = when(face) { - DOWN -> listOf(topLeft, bottomLeft, bottomRight, topRight) - UP -> listOf(bottomRight, topRight, topLeft, bottomLeft) - NORTH -> listOf(bottomLeft, bottomRight, topRight, topLeft) - SOUTH -> listOf(topLeft, bottomLeft, bottomRight, topRight) - WEST -> listOf(bottomLeft, bottomRight, topRight, topLeft) - EAST -> listOf(topRight, topLeft, bottomLeft, bottomRight) - } - - fun update(offset: Int3, useBounds: Boolean = false, multiplier: Float = 1.0f) { - val ctx = blockContext - val blockState = ctx.blockState(offset) - val quadBounds: FloatArray = FloatArray(12) - val flags = BitSet(3).apply { set(0) } - - ao.updateVertexBrightness(ctx.reader!!, blockState, ctx.pos + offset, face, quadBounds, flags) - ordered.forEachIndexed { idx, aoData -> aoData.set(ao.vertexBrightness[idx], ao.vertexColorMultiplier[idx] * multiplier) } - } - - operator fun get(dir1: Direction, dir2: Direction): AoData { - val isTop = top == dir1 || top == dir2 - val isLeft = left == dir1 || left == dir2 - return if (isTop) { - if (isLeft) topLeft else topRight - } else { - if (isLeft) bottomLeft else bottomRight - } + val black: CornerLightData get() = CornerLightData() } } @@ -92,27 +46,27 @@ class AoFaceData(val face: Direction) { * Instances of this interface are associated with [Model] vertices, and used to apply brightness and color * values to a [RenderVertex]. */ -interface Shader { +interface ModelLighter { /** * Set shading values of a [RenderVertex] * - * @param[context] context that can be queried for shading data in a [Model]-relative frame of reference + * @param[context] context that can be queried for lighting data in a [Model]-relative frame of reference * @param[vertex] the [RenderVertex] to manipulate */ - fun shade(context: ShadingContext, vertex: RenderVertex) + fun shade(context: LightingCtx, vertex: RenderVertex) /** - * Return a new rotated version of this [Shader]. Used during [Model] setup when rotating the model itself. + * Return a new rotated version of this [ModelLighter]. Used during [Model] setup when rotating the model itself. */ - fun rotate(rot: Rotation): Shader + fun rotate(rot: Rotation): ModelLighter - /** Set all shading values on the [RenderVertex] to match the given [AoData]. */ - fun RenderVertex.shade(shading: AoData) { + /** Set all lighting values on the [RenderVertex] to match the given [CornerLightData]. */ + fun RenderVertex.shade(shading: CornerLightData) { brightness = shading.brightness; red = shading.red; green = shading.green; blue = shading.blue } - /** Set the shading values on the [RenderVertex] to a weighted average of the two [AoData] instances. */ - fun RenderVertex.shade(shading1: AoData, shading2: AoData, weight1: Float = 0.5f, weight2: Float = 0.5f) { + /** Set the lighting values on the [RenderVertex] to a weighted average of the two [CornerLightData] instances. */ + fun RenderVertex.shade(shading1: CornerLightData, shading2: CornerLightData, weight1: Float = 0.5f, weight2: Float = 0.5f) { red = min(shading1.red * weight1 + shading2.red * weight2, 1.0f) green = min(shading1.green * weight1 + shading2.green * weight2, 1.0f) blue = min(shading1.blue * weight1 + shading2.blue * weight2, 1.0f) @@ -120,7 +74,7 @@ interface Shader { } /** - * Set the shading values on the [RenderVertex] directly. + * Set the lighting values on the [RenderVertex] directly. * * @param[brightness] packed brightness value * @param[color] packed color value @@ -131,23 +85,23 @@ interface Shader { } /** - * Returns a shader resolver for quads that point towards one of the 6 block faces. + * Returns a [ModelLighter] resolver for quads that point towards one of the 6 block faces. * The resolver works the following way: * - determines which face the _quad_ normal points towards (if not overridden) * - determines the distance of the _vertex_ to the corners and edge midpoints on that block face - * - if _corner_ is given, and the _vertex_ is closest to a block corner, returns the [Shader] created by _corner_ - * - if _edge_ is given, and the _vertex_ is closest to an edge midpoint, returns the [Shader] created by _edge_ + * - if _corner_ is given, and the _vertex_ is closest to a block corner, returns the [ModelLighter] created by _corner_ + * - if _edge_ is given, and the _vertex_ is closest to an edge midpoint, returns the [ModelLighter] created by _edge_ * * @param[overrideFace] assume the given face instead of going by the _quad_ normal - * @param[corner] shader instantiation lambda for corner vertices - * @param[edge] shader instantiation lambda for edge midpoint vertices + * @param[corner] [ModelLighter] instantiation lambda for corner vertices + * @param[edge] [ModelLighter] instantiation lambda for edge midpoint vertices */ fun faceOrientedAuto(overrideFace: Direction? = null, corner: CornerShaderFactory? = null, edge: EdgeShaderFactory? = null) = - fun(quad: Quad, vertex: Vertex): Shader { + fun(quad: Quad, vertex: Vertex): ModelLighter { val quadFace = overrideFace ?: quad.normal.nearestCardinal - val nearestCorner = nearestPosition(vertex.xyz, faceCorners[quadFace.ordinal].asList) { + val nearestCorner = nearestPosition(vertex.xyz, boxFaces[quadFace].allCorners) { (quadFace.vec + it.first.vec + it.second.vec) * 0.5 } val nearestEdge = nearestPosition(vertex.xyz, quadFace.perpendiculars) { @@ -159,32 +113,32 @@ fun faceOrientedAuto(overrideFace: Direction? = null, } /** - * Returns a shader resolver for quads that point towards one of the 12 block edges. + * Returns a ModelLighter resolver for quads that point towards one of the 12 block edges. * The resolver works the following way: * - determines which edge the _quad_ normal points towards (if not overridden) * - determines which face midpoint the _vertex_ is closest to, of the 2 block faces that share this edge * - determines which block corner _of this face_ the _vertex_ is closest to - * - returns the [Shader] created by _corner_ + * - returns the [ModelLighter] created by _corner_ * * @param[overrideEdge] assume the given edge instead of going by the _quad_ normal - * @param[corner] shader instantiation lambda + * @param[corner] ModelLighter instantiation lambda */ fun edgeOrientedAuto(overrideEdge: Pair? = null, corner: CornerShaderFactory) = - fun(quad: Quad, vertex: Vertex): Shader { + fun(quad: Quad, vertex: Vertex): ModelLighter { val edgeDir = overrideEdge ?: nearestAngle(quad.normal, boxEdges) { it.first.vec + it.second.vec }.first val nearestFace = nearestPosition(vertex.xyz, edgeDir.toList()) { it.vec }.first - val nearestCorner = nearestPosition(vertex.xyz, faceCorners[nearestFace.ordinal].asList) { + val nearestCorner = nearestPosition(vertex.xyz, boxFaces[nearestFace].allCorners) { (nearestFace.vec + it.first.vec + it.second.vec) * 0.5 }.first return corner(nearestFace, nearestCorner.first, nearestCorner.second) } fun faceOrientedInterpolate(overrideFace: Direction? = null) = - fun(quad: Quad, vertex: Vertex): Shader { + fun(quad: Quad, vertex: Vertex): ModelLighter { val resolver = faceOrientedAuto(overrideFace, edge = { face, edgeDir -> val axis = axes.find { it != face.axis && it != edgeDir.axis }!! - val vec = Double3((axis to Direction.AxisDirection.POSITIVE).face) + val vec = Double3((axis to AxisDirection.POSITIVE).face) val pos = vertex.xyz.dot(vec) EdgeInterpolateFallback(face, edgeDir, pos) }) diff --git a/src/main/kotlin/mods/octarinecore/client/render/lighting/LightingContext.kt b/src/main/kotlin/mods/octarinecore/client/render/lighting/LightingContext.kt new file mode 100644 index 0000000..d253d41 --- /dev/null +++ b/src/main/kotlin/mods/octarinecore/client/render/lighting/LightingContext.kt @@ -0,0 +1,111 @@ +package mods.octarinecore.client.render.lighting + +import mods.octarinecore.client.render.BlockCtx +import mods.octarinecore.common.* +import net.minecraft.client.Minecraft +import net.minecraft.client.renderer.BlockModelRenderer +import net.minecraft.util.Direction +import net.minecraft.world.IEnviromentBlockReader +import java.util.* + +val Direction.aoMultiplier: Float get() = when(this) { + Direction.UP -> 1.0f + Direction.DOWN -> 0.5f + Direction.NORTH, Direction.SOUTH -> 0.8f + Direction.EAST, Direction.WEST -> 0.6f +} + +interface LightingCtx { + val modelRotation: Rotation + val blockContext: BlockCtx + val aoEnabled: Boolean + + val brightness get() = brightness(Int3.zero) + val color get() = color(Int3.zero) + fun brightness(face: Direction) = brightness(face.offset) + fun color(face: Direction) = color(face.offset) + + fun brightness(offset: Int3) = offset.rotate(modelRotation).let { + blockContext.state(it).getPackedLightmapCoords(blockContext.world, blockContext.pos + it) + } + fun color(offset: Int3) = blockContext.offset(offset.rotate(modelRotation)).let { Minecraft.getInstance().blockColors.getColor(it.state, it.world, it.pos, 0) } + + fun lighting(face: Direction, corner1: Direction, corner2: Direction): CornerLightData +} + +class DefaultLightingCtx(blockContext: BlockCtx) : LightingCtx { + override var modelRotation = Rotation.identity + + override var aoEnabled = false + protected set + override var blockContext: BlockCtx = blockContext + protected set + override var brightness = brightness(Int3.zero) + protected set + override var color = color(Int3.zero) + protected set + + override fun brightness(face: Direction) = brightness(face.offset) + override fun color(face: Direction) = color(face.offset) + + // smooth lighting stuff + val lightingData = Array(6) { FaceLightData(allDirections[it]) } + override fun lighting(face: Direction, corner1: Direction, corner2: Direction): CornerLightData = lightingData[face.rotate(modelRotation)].let { faceData -> + if (!faceData.isValid) faceData.update(blockContext, faceData.face.aoMultiplier) + return faceData[corner1.rotate(modelRotation), corner2.rotate(modelRotation)] + } + + fun reset(blockContext: BlockCtx) { + this.blockContext = blockContext + brightness = brightness(Int3.zero) + color = color(Int3.zero) + modelRotation = Rotation.identity + lightingData.forEach { it.isValid = false } + aoEnabled = Minecraft.isAmbientOcclusionEnabled() +// allDirections.forEach { lightingData[it].update(blockContext, it.aoMultiplier) } + } +} + +private val vanillaAOFactory = BlockModelRenderer.AmbientOcclusionFace::class.java.let { + it.getDeclaredConstructor(BlockModelRenderer::class.java).apply { isAccessible = true } +}.let { ctor -> { ctor.newInstance(Minecraft.getInstance().blockRendererDispatcher.blockModelRenderer) } } + +class FaceLightData(val face: Direction) { + val topDir = boxFaces[face].top + val leftDir = boxFaces[face].left + + val topLeft = CornerLightData() + val topRight = CornerLightData() + val bottomLeft = CornerLightData() + val bottomRight = CornerLightData() + + val vanillaOrdered = when(face) { + Direction.DOWN -> listOf(topLeft, bottomLeft, bottomRight, topRight) + Direction.UP -> listOf(bottomRight, topRight, topLeft, bottomLeft) + Direction.NORTH -> listOf(bottomLeft, bottomRight, topRight, topLeft) + Direction.SOUTH -> listOf(topLeft, bottomLeft, bottomRight, topRight) + Direction.WEST -> listOf(bottomLeft, bottomRight, topRight, topLeft) + Direction.EAST -> listOf(topRight, topLeft, bottomLeft, bottomRight) + } + + val delegate = vanillaAOFactory() + var isValid = false + + fun update(blockCtx: BlockCtx, multiplier: Float) { + val quadBounds = FloatArray(12) + val flags = BitSet(3).apply { set(0) } + delegate.updateVertexBrightness(blockCtx.world, blockCtx.state, blockCtx.pos, face, quadBounds, flags) + vanillaOrdered.forEachIndexed { idx, corner -> corner.set(delegate.vertexBrightness[idx], delegate.vertexColorMultiplier[idx] * multiplier) } + isValid = true + } + + operator fun get(dir1: Direction, dir2: Direction): CornerLightData { + val isTop = topDir == dir1 || topDir == dir2 + val isLeft = leftDir == dir1 || leftDir == dir2 + return if (isTop) { + if (isLeft) topLeft else topRight + } else { + if (isLeft) bottomLeft else bottomRight + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/Shaders.kt b/src/main/kotlin/mods/octarinecore/client/render/lighting/ModelLighters.kt similarity index 55% rename from src/main/kotlin/mods/octarinecore/client/render/Shaders.kt rename to src/main/kotlin/mods/octarinecore/client/render/lighting/ModelLighters.kt index 865c135..2cfa656 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/Shaders.kt +++ b/src/main/kotlin/mods/octarinecore/client/render/lighting/ModelLighters.kt @@ -1,4 +1,4 @@ -package mods.octarinecore.client.render +package mods.octarinecore.client.render.lighting import mods.octarinecore.common.* import net.minecraft.util.Direction @@ -15,7 +15,7 @@ fun cornerAo(fallbackAxis: Direction.Axis): CornerShaderFactory = { face, dir1, CornerSingleFallback(face, dir1, dir2, fallbackDir) } val cornerFlat = { face: Direction, dir1: Direction, dir2: Direction -> FaceFlat(face) } -fun cornerAoTri(func: (AoData, AoData)-> AoData) = { face: Direction, dir1: Direction, dir2: Direction -> +fun cornerAoTri(func: (CornerLightData, CornerLightData)-> CornerLightData) = { face: Direction, dir1: Direction, dir2: Direction -> CornerTri(face, dir1, dir2, func) } val cornerAoMaxGreen = cornerAoTri { s1, s2 -> if (s1.green > s2.green) s1 else s2 } @@ -29,25 +29,25 @@ fun cornerInterpolate(edgeAxis: Direction.Axis, weight: Float, dimming: Float): // ================================ // Shaders // ================================ -object NoShader : Shader { - override fun shade(context: ShadingContext, vertex: RenderVertex) = vertex.shade(AoData.black) +object NoLighting : ModelLighter { + override fun shade(context: LightingCtx, vertex: RenderVertex) = vertex.shade(CornerLightData.black) override fun rotate(rot: Rotation) = this } -class CornerSingleFallback(val face: Direction, val dir1: Direction, val dir2: Direction, val fallbackDir: Direction, val fallbackDimming: Float = defaultCornerDimming) : Shader { +class CornerSingleFallback(val face: Direction, val dir1: Direction, val dir2: Direction, val fallbackDir: Direction, val fallbackDimming: Float = defaultCornerDimming) : ModelLighter { val offset = Int3(fallbackDir) - override fun shade(context: ShadingContext, vertex: RenderVertex) { - val shading = context.aoShading(face, dir1, dir2) + override fun shade(context: LightingCtx, vertex: RenderVertex) { + val shading = context.lighting(face, dir1, dir2) if (shading.valid) vertex.shade(shading) - else context.blockData(offset).let { - vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming) + else { + vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming) } } override fun rotate(rot: Rotation) = CornerSingleFallback(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), fallbackDir.rotate(rot), fallbackDimming) } -inline fun accumulate(v1: AoData?, v2: AoData?, func: ((AoData, AoData)-> AoData)): AoData? { +inline fun accumulate(v1: CornerLightData?, v2: CornerLightData?, func: ((CornerLightData, CornerLightData)-> CornerLightData)): CornerLightData? { val v1ok = v1 != null && v1.valid val v2ok = v2 != null && v2.valid if (v1ok && v2ok) return func(v1!!, v2!!) @@ -57,32 +57,32 @@ inline fun accumulate(v1: AoData?, v2: AoData?, func: ((AoData, AoData)-> AoData } class CornerTri(val face: Direction, val dir1: Direction, val dir2: Direction, - val func: ((AoData, AoData)-> AoData)) : Shader { - override fun shade(context: ShadingContext, vertex: RenderVertex) { + val func: ((CornerLightData, CornerLightData)-> CornerLightData)) : ModelLighter { + override fun shade(context: LightingCtx, vertex: RenderVertex) { var acc = accumulate( - context.aoShading(face, dir1, dir2), - context.aoShading(dir1, face, dir2), + context.lighting(face, dir1, dir2), + context.lighting(dir1, face, dir2), func) acc = accumulate( acc, - context.aoShading(dir2, face, dir1), + context.lighting(dir2, face, dir1), func) - vertex.shade(acc ?: AoData.black) + vertex.shade(acc ?: CornerLightData.black) } override fun rotate(rot: Rotation) = CornerTri(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), func) } -class EdgeInterpolateFallback(val face: Direction, val edgeDir: Direction, val pos: Double, val fallbackDimming: Float = defaultEdgeDimming): Shader { +class EdgeInterpolateFallback(val face: Direction, val edgeDir: Direction, val pos: Double, val fallbackDimming: Float = defaultEdgeDimming): ModelLighter { val offset = Int3(edgeDir) val edgeAxis = axes.find { it != face.axis && it != edgeDir.axis }!! val weightN = (0.5 - pos).toFloat() val weightP = (0.5 + pos).toFloat() - override fun shade(context: ShadingContext, vertex: RenderVertex) { - val shadingP = context.aoShading(face, edgeDir, (edgeAxis to Direction.AxisDirection.POSITIVE).face) - val shadingN = context.aoShading(face, edgeDir, (edgeAxis to Direction.AxisDirection.NEGATIVE).face) - if (!shadingP.valid && !shadingN.valid) context.blockData(offset).let { - return vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming) + override fun shade(context: LightingCtx, vertex: RenderVertex) { + val shadingP = context.lighting(face, edgeDir, (edgeAxis to Direction.AxisDirection.POSITIVE).face) + val shadingN = context.lighting(face, edgeDir, (edgeAxis to Direction.AxisDirection.NEGATIVE).face) + if (!shadingP.valid && !shadingN.valid) { + return vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming) } if (!shadingP.valid) return vertex.shade(shadingN) if (!shadingN.valid) return vertex.shade(shadingP) @@ -92,15 +92,15 @@ class EdgeInterpolateFallback(val face: Direction, val edgeDir: Direction, val p } class CornerInterpolateDimming(val face1: Direction, val face2: Direction, val edgeDir: Direction, - val weight: Float, val dimming: Float, val fallbackDimming: Float = defaultCornerDimming) : Shader { + val weight: Float, val dimming: Float, val fallbackDimming: Float = defaultCornerDimming) : ModelLighter { val offset = Int3(edgeDir) - override fun shade(context: ShadingContext, vertex: RenderVertex) { - var shading1 = context.aoShading(face1, edgeDir, face2) - var shading2 = context.aoShading(face2, edgeDir, face1) + override fun shade(context: LightingCtx, vertex: RenderVertex) { + var shading1 = context.lighting(face1, edgeDir, face2) + var shading2 = context.lighting(face2, edgeDir, face1) var weight1 = weight var weight2 = 1.0f - weight - if (!shading1.valid && !shading2.valid) context.blockData(offset).let { - return vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming) + if (!shading1.valid && !shading2.valid) { + return vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming) } if (!shading1.valid) { shading1 = shading2; weight1 *= dimming } if (!shading2.valid) { shading2 = shading1; weight2 *= dimming } @@ -111,12 +111,12 @@ class CornerInterpolateDimming(val face1: Direction, val face2: Direction, val e CornerInterpolateDimming(face1.rotate(rot), face2.rotate(rot), edgeDir.rotate(rot), weight, dimming, fallbackDimming) } -class FaceCenter(val face: Direction): Shader { - override fun shade(context: ShadingContext, vertex: RenderVertex) { +class FaceCenter(val face: Direction): ModelLighter { + override fun shade(context: LightingCtx, vertex: RenderVertex) { vertex.red = 0.0f; vertex.green = 0.0f; vertex.blue = 0.0f; val b = IntArray(4) - faceCorners[face.ordinal].asList.forEachIndexed { idx, corner -> - val shading = context.aoShading(face, corner.first, corner.second) + boxFaces[face].allCorners.forEachIndexed { idx, corner -> + val shading = context.lighting(face, corner.first, corner.second) vertex.red += shading.red vertex.green += shading.green vertex.blue += shading.blue @@ -128,28 +128,25 @@ class FaceCenter(val face: Direction): Shader { override fun rotate(rot: Rotation) = FaceCenter(face.rotate(rot)) } -class FaceFlat(val face: Direction): Shader { - override fun shade(context: ShadingContext, vertex: RenderVertex) { - val color = context.blockData(Int3.zero).color - vertex.shade(context.blockData(face.offset).brightness, color) +class FaceFlat(val face: Direction): ModelLighter { + override fun shade(context: LightingCtx, vertex: RenderVertex) { + vertex.shade(context.brightness(face.offset), context.color(Int3.zero)) } - override fun rotate(rot: Rotation): Shader = FaceFlat(face.rotate(rot)) + override fun rotate(rot: Rotation): ModelLighter = FaceFlat(face.rotate(rot)) } -class FlatOffset(val offset: Int3): Shader { - override fun shade(context: ShadingContext, vertex: RenderVertex) { - context.blockData(offset).let { - vertex.brightness = it.brightness - vertex.setColor(it.color) - } +class FlatOffset(val offset: Int3): ModelLighter { + override fun shade(context: LightingCtx, vertex: RenderVertex) { + vertex.brightness = context.brightness(offset) + vertex.setColor(context.color(offset)) } - override fun rotate(rot: Rotation): Shader = this + override fun rotate(rot: Rotation): ModelLighter = this } -class FlatOffsetNoColor(val offset: Int3): Shader { - override fun shade(context: ShadingContext, vertex: RenderVertex) { - vertex.brightness = context.blockData(offset).brightness +class FlatOffsetNoColor(val offset: Int3): ModelLighter { + override fun shade(context: LightingCtx, vertex: RenderVertex) { + vertex.brightness = context.brightness(offset) vertex.red = 1.0f; vertex.green = 1.0f; vertex.blue = 1.0f } - override fun rotate(rot: Rotation): Shader = this + override fun rotate(rot: Rotation): ModelLighter = this } \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/lighting/PixelFormat.kt b/src/main/kotlin/mods/octarinecore/client/render/lighting/PixelFormat.kt new file mode 100644 index 0000000..9168075 --- /dev/null +++ b/src/main/kotlin/mods/octarinecore/client/render/lighting/PixelFormat.kt @@ -0,0 +1,5 @@ +@file:JvmName("PixelFormat") +package mods.octarinecore.client.render.lighting + +import java.awt.Color + diff --git a/src/main/kotlin/mods/octarinecore/client/render/lighting/Vertex.kt b/src/main/kotlin/mods/octarinecore/client/render/lighting/Vertex.kt new file mode 100644 index 0000000..f69023a --- /dev/null +++ b/src/main/kotlin/mods/octarinecore/client/render/lighting/Vertex.kt @@ -0,0 +1,146 @@ +package mods.octarinecore.client.render.lighting + +import mods.octarinecore.client.render.CombinedContext +import mods.octarinecore.client.render.Quad +import mods.octarinecore.client.render.Vertex +import mods.octarinecore.common.Double3 +import mods.octarinecore.common.Rotation +import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.util.Direction.* +import java.awt.Color + +typealias QuadIconResolver = (CombinedContext, Int, Quad) -> TextureAtlasSprite? +typealias PostProcessLambda = RenderVertex.(CombinedContext, Int, Quad, Int, Vertex) -> Unit + +@Suppress("NOTHING_TO_INLINE") +class RenderVertex { + var x: Double = 0.0 + var y: Double = 0.0 + var z: Double = 0.0 + var u: Double = 0.0 + var v: Double = 0.0 + var brightness: Int = 0 + var red: Float = 0.0f + var green: Float = 0.0f + var blue: Float = 0.0f + + val rawData = IntArray(7) + + fun init(vertex: Vertex, rot: Rotation, trans: Double3): RenderVertex { + val result = vertex.xyz.rotate(rot) + trans + x = result.x; y = result.y; z = result.z + return this + } + fun init(vertex: Vertex): RenderVertex { + x = vertex.xyz.x; y = vertex.xyz.y; z = vertex.xyz.z; + u = vertex.uv.u; v = vertex.uv.v + return this + } + fun translate(trans: Double3): RenderVertex { x += trans.x; y += trans.y; z += trans.z; return this } + fun rotate(rot: Rotation): RenderVertex { + if (rot === Rotation.identity) return this + val rotX = rot.rotatedComponent(EAST, x, y, z) + val rotY = rot.rotatedComponent(UP, x, y, z) + val rotZ = rot.rotatedComponent(SOUTH, x, y, z) + x = rotX; y = rotY; z = rotZ + return this + } + inline fun rotateUV(n: Int): RenderVertex { + when (n % 4) { + 1 -> { val t = v; v = -u; u = t; return this } + 2 -> { u = -u; v = -v; return this } + 3 -> { val t = -v; v = u; u = t; return this } + else -> { return this } + } + } + inline fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) { + if (mirrorU) u = -u + if (mirrorV) v = -v + } + inline fun setIcon(icon: TextureAtlasSprite): RenderVertex { + u = (icon.maxU - icon.minU) * (u + 0.5) + icon.minU + v = (icon.maxV - icon.minV) * (v + 0.5) + icon.minV + return this + } + + inline fun setGrey(level: Float) { + val grey = Math.min((red + green + blue) * 0.333f * level, 1.0f) + red = grey; green = grey; blue = grey + } + inline fun multiplyColor(color: Int) { + red *= (color shr 16 and 255) / 256.0f + green *= (color shr 8 and 255) / 256.0f + blue *= (color and 255) / 256.0f + } + inline fun setColor(color: Int) { + red = (color shr 16 and 255) / 256.0f + green = (color shr 8 and 255) / 256.0f + blue = (color and 255) / 256.0f + } + +} + +/** List of bit-shift offsets in packed brightness values where meaningful (4-bit) data is contained. */ +var brightnessComponents = listOf(20, 4) + +/** Multiply the components of this packed brightness value with the given [Float]. */ +infix fun Int.brMul(f: Float): Int { + val weight = (f * 256.0f).toInt() + var result = 0 + brightnessComponents.forEach { shift -> + val raw = (this shr shift) and 15 + val weighted = (raw) * weight / 256 + result = result or (weighted shl shift) + } + return result +} + +/** Multiply the components of this packed color value with the given [Float]. */ +infix fun Int.colorMul(f: Float): Int { + val weight = (f * 256.0f).toInt() + val red = (this shr 16 and 255) * weight / 256 + val green = (this shr 8 and 255) * weight / 256 + val blue = (this and 255) * weight / 256 + return (red shl 16) or (green shl 8) or blue +} + +/** Sum the components of all packed brightness values given. */ +fun brSum(multiplier: Float?, vararg brightness: Int): Int { + val sum = Array(brightnessComponents.size) { 0 } + brightnessComponents.forEachIndexed { idx, shift -> brightness.forEach { br -> + val comp = (br shr shift) and 15 + sum[idx] += comp + } } + var result = 0 + brightnessComponents.forEachIndexed { idx, shift -> + val comp = if (multiplier == null) + ((sum[idx]) shl shift) + else + ((sum[idx].toFloat() * multiplier).toInt() shl shift) + result = result or comp + } + return result +} + +fun brWeighted(br1: Int, weight1: Float, br2: Int, weight2: Float): Int { + val w1int = (weight1 * 256.0f + 0.5f).toInt() + val w2int = (weight2 * 256.0f + 0.5f).toInt() + var result = 0 + brightnessComponents.forEachIndexed { idx, shift -> + val comp1 = (br1 shr shift) and 15 + val comp2 = (br2 shr shift) and 15 + val compWeighted = (comp1 * w1int + comp2 * w2int) / 256 + result = result or ((compWeighted and 15) shl shift) + } + return result +} + +data class HSB(var hue: Float, var saturation: Float, var brightness: Float) { + companion object { + fun fromColor(color: Int): HSB { + val hsbVals = Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null) + return HSB(hsbVals[0], hsbVals[1], hsbVals[2]) + } + } + val asColor: Int get() = Color.HSBtoRGB(hue, saturation, brightness) +} \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/resource/ModelProcessor.kt b/src/main/kotlin/mods/octarinecore/client/resource/ModelProcessor.kt index fcca56a..923623b 100644 --- a/src/main/kotlin/mods/octarinecore/client/resource/ModelProcessor.kt +++ b/src/main/kotlin/mods/octarinecore/client/resource/ModelProcessor.kt @@ -1,7 +1,8 @@ package mods.octarinecore.client.resource import com.google.common.base.Joiner -import mods.octarinecore.client.render.BlockContext +import mods.octarinecore.client.render.BlockCtx +import mods.octarinecore.client.render.CombinedContext import mods.octarinecore.common.Int3 import mods.octarinecore.common.config.IBlockMatcher import mods.octarinecore.common.config.ModelTextureList @@ -14,8 +15,8 @@ import net.minecraft.client.renderer.texture.AtlasTexture import net.minecraft.util.ResourceLocation import net.minecraft.util.math.BlockPos import net.minecraft.world.IBlockReader +import net.minecraft.world.IEnviromentBlockReader import net.minecraftforge.client.event.TextureStitchEvent -import net.minecraftforge.client.model.ModelLoader import net.minecraftforge.eventbus.api.Event import net.minecraftforge.eventbus.api.EventPriority import net.minecraftforge.eventbus.api.SubscribeEvent @@ -26,8 +27,8 @@ import org.apache.logging.log4j.Logger class LoadModelDataEvent(val bakery: ModelBakery) : Event() interface ModelRenderRegistry { - operator fun get(ctx: BlockContext) = get(ctx.blockState(Int3.zero), ctx.reader!!, ctx.pos) - operator fun get(ctx: BlockContext, offset: Int3) = get(ctx.blockState(offset), ctx.reader!!, ctx.pos + offset) + operator fun get(ctx: BlockCtx) = get(ctx.state(Int3.zero), ctx.world, ctx.pos) + operator fun get(ctx: BlockCtx, offset: Int3) = get(ctx.state(offset), ctx.world, ctx.pos + offset) operator fun get(state: BlockState, world: IBlockReader, pos: BlockPos): T? } diff --git a/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt b/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt index 9a93a11..d1a43a6 100644 --- a/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt +++ b/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt @@ -3,21 +3,19 @@ package mods.octarinecore.client.resource import mods.octarinecore.client.render.Model import mods.octarinecore.common.Double3 import mods.octarinecore.common.Int3 +import mods.octarinecore.resource.Identifier +import mods.octarinecore.resource.Sprite import mods.octarinecore.stripEnd import mods.octarinecore.stripStart import net.minecraft.client.renderer.texture.AtlasTexture -import net.minecraft.client.renderer.texture.TextureAtlasSprite -import net.minecraft.util.ResourceLocation import net.minecraft.util.math.BlockPos import net.minecraft.util.math.MathHelper import net.minecraft.world.IWorld import net.minecraft.world.gen.SimplexNoiseGenerator import net.minecraftforge.client.event.TextureStitchEvent -import net.minecraftforge.common.MinecraftForge import net.minecraftforge.event.world.WorldEvent import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.eventbus.api.SubscribeEvent -import net.minecraftforge.fml.client.event.ConfigChangedEvent import net.minecraftforge.fml.config.ModConfig import java.util.* @@ -25,8 +23,8 @@ enum class Atlas(val basePath: String) { BLOCKS("textures"), PARTICLES("textures/particle"); - fun wrap(resource: ResourceLocation) = ResourceLocation(resource.namespace, "$basePath/${resource.path}.png") - fun unwrap(resource: ResourceLocation) = resource.stripStart("$basePath/").stripEnd(".png") + fun wrap(resource: Identifier) = Identifier(resource.namespace, "$basePath/${resource.path}.png") + fun unwrap(resource: Identifier) = resource.stripStart("$basePath/").stripEnd(".png") fun matches(event: TextureStitchEvent) = event.map.basePath == basePath } @@ -65,10 +63,10 @@ open class ResourceHandler( // ============================ // Resource declarations // ============================ - fun iconStatic(location: ()->ResourceLocation) = IconHolder(location).apply { resources.add(this) } - fun iconStatic(location: ResourceLocation) = iconStatic { location } - fun iconStatic(domain: String, path: String) = iconStatic(ResourceLocation(domain, path)) - fun iconSet(targetAtlas: Atlas = Atlas.BLOCKS, location: (Int)->ResourceLocation) = IconSet(targetAtlas, location).apply { this@ResourceHandler.resources.add(this) } + fun iconStatic(location: ()->Identifier) = IconHolder(location).apply { resources.add(this) } + fun iconStatic(location: Identifier) = iconStatic { location } + fun iconStatic(domain: String, path: String) = iconStatic(Identifier(domain, path)) + fun iconSet(targetAtlas: Atlas = Atlas.BLOCKS, location: (Int)->Identifier) = IconSet(targetAtlas, location).apply { this@ResourceHandler.resources.add(this) } fun model(init: Model.()->Unit) = ModelHolder(init).apply { resources.add(this) } fun modelSet(num: Int, init: Model.(Int)->Unit) = ModelSet(num, init).apply { resources.add(this) } fun vectorSet(num: Int, init: (Int)-> Double3) = VectorSet(num, init).apply { resources.add(this) } @@ -104,9 +102,9 @@ open class ResourceHandler( // ============================ // Resource container classes // ============================ -class IconHolder(val location: ()->ResourceLocation) : IStitchListener { - var iconRes: ResourceLocation? = null - var icon: TextureAtlasSprite? = null +class IconHolder(val location: ()->Identifier) : IStitchListener { + var iconRes: Identifier? = null + var icon: Sprite? = null override fun onPreStitch(event: TextureStitchEvent.Pre) { iconRes = location() event.addSprite(iconRes) @@ -121,9 +119,9 @@ class ModelHolder(val init: Model.()->Unit): IConfigChangeListener { override fun onConfigChange() { model = Model().apply(init) } } -class IconSet(val targetAtlas: Atlas, val location: (Int)->ResourceLocation) : IStitchListener { - val resources = arrayOfNulls(16) - val icons = arrayOfNulls(16) +class IconSet(val targetAtlas: Atlas, val location: (Int)->Identifier) : IStitchListener { + val resources = arrayOfNulls(16) + val icons = arrayOfNulls(16) var num = 0 override fun onPreStitch(event: TextureStitchEvent.Pre) { @@ -161,4 +159,4 @@ class SimplexNoise : IWorldLoadListener { operator fun get(x: Int, z: Int) = MathHelper.floor((noise.getValue(x.toDouble(), z.toDouble()) + 1.0) * 32.0) operator fun get(pos: Int3) = get(pos.x, pos.z) operator fun get(pos: BlockPos) = get(pos.x, pos.z) -} \ No newline at end of file +} diff --git a/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt b/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt index a035d5d..308d04b 100644 --- a/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt +++ b/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt @@ -1,10 +1,8 @@ @file:JvmName("Utils") package mods.octarinecore.client.resource -import mods.betterfoliage.loader.Refs import mods.octarinecore.PI2 -import mods.octarinecore.client.render.HSB -import mods.octarinecore.stripEnd +import mods.octarinecore.client.render.lighting.HSB import mods.octarinecore.stripStart import mods.octarinecore.tryDefault import net.minecraft.client.Minecraft @@ -16,7 +14,6 @@ import net.minecraft.resources.IResource import net.minecraft.resources.IResourceManager import net.minecraft.resources.SimpleReloadableResourceManager import net.minecraft.util.ResourceLocation -import net.minecraftforge.client.model.IModel import java.awt.image.BufferedImage import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream diff --git a/src/main/kotlin/mods/octarinecore/common/Geometry.kt b/src/main/kotlin/mods/octarinecore/common/Geometry.kt index 8b369aa..f82390b 100644 --- a/src/main/kotlin/mods/octarinecore/common/Geometry.kt +++ b/src/main/kotlin/mods/octarinecore/common/Geometry.kt @@ -15,9 +15,9 @@ val axes = listOf(X, Y, Z) val axisDirs = listOf(POSITIVE, NEGATIVE) val Direction.dir: AxisDirection get() = axisDirection val AxisDirection.sign: String get() = when(this) { POSITIVE -> "+"; NEGATIVE -> "-" } -val forgeDirs = Direction.values() -val forgeDirsHorizontal = listOf(NORTH, SOUTH, EAST, WEST) -val forgeDirOffsets = forgeDirs.map { Int3(it) } +val allDirections = Direction.values() +val horizontalDirections = listOf(NORTH, SOUTH, EAST, WEST) +val allDirOffsets = allDirections.map { Int3(it) } val Pair.face: Direction get() = when(this) { X to POSITIVE -> EAST; X to NEGATIVE -> WEST; Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN; @@ -25,7 +25,7 @@ val Pair.face: Direction get() = when(this) { } val Direction.perpendiculars: List get() = axes.filter { it != this.axis }.cross(axisDirs).map { it.face } -val Direction.offset: Int3 get() = forgeDirOffsets[ordinal] +val Direction.offset: Int3 get() = allDirOffsets[ordinal] /** Old ForgeDirection rotation matrix yanked from 1.7.10 */ val ROTATION_MATRIX: Array get() = arrayOf( @@ -43,6 +43,7 @@ val ROTATION_MATRIX: Array get() = arrayOf( operator fun Direction.times(scale: Double) = Double3(directionVec.x.toDouble() * scale, directionVec.y.toDouble() * scale, directionVec.z.toDouble() * scale) val Direction.vec: Double3 get() = Double3(directionVec.x.toDouble(), directionVec.y.toDouble(), directionVec.z.toDouble()) + operator fun BlockPos.plus(other: Int3) = BlockPos(x + other.x, y + other.y, z + other.z) /** 3D vector of [Double]s. Offers both mutable operations, and immutable operations in operator notation. */ @@ -92,7 +93,7 @@ data class Double3(var x: Double, var y: Double, var z: Double) { infix fun cross(o: Double3) = Double3(y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x) val length: Double get() = Math.sqrt(x * x + y * y + z * z) val normalize: Double3 get() = (1.0 / length).let { Double3(x * it, y * it, z * it) } - val nearestCardinal: Direction get() = nearestAngle(this, forgeDirs.asIterable()) { it.vec }.first + val nearestCardinal: Direction get() = nearestAngle(this, allDirections.asIterable()) { it.vec }.first } /** 3D vector of [Int]s. Offers both mutable operations, and immutable operations in operator notation. */ @@ -170,8 +171,8 @@ class Rotation(val forward: Array, val reverse: Array) { companion object { // Forge rotation matrix is left-hand - val rot90 = Array(6) { idx -> Rotation(forgeDirs[idx].opposite.rotations, forgeDirs[idx].rotations) } - val identity = Rotation(forgeDirs, forgeDirs) + val rot90 = Array(6) { idx -> Rotation(allDirections[idx].opposite.rotations, allDirections[idx].rotations) } + val identity = Rotation(allDirections, allDirections) } } @@ -179,8 +180,24 @@ class Rotation(val forward: Array, val reverse: Array) { // ================================ // Miscellaneous // ================================ + +inline operator fun Array.get(face: Direction): T = get(face.ordinal) + +data class BoxFace(val top: Direction, val left: Direction) { + + val allCorners = listOf(top to left, top to left.opposite, top.opposite to left, top.opposite to left.opposite) +} +val boxFaces = allDirections.map { when(it) { + DOWN -> BoxFace(SOUTH, WEST) + UP -> BoxFace(SOUTH, EAST) + NORTH -> BoxFace(WEST, UP) + SOUTH -> BoxFace(UP, WEST) + WEST -> BoxFace(SOUTH, UP) + EAST -> BoxFace(SOUTH, DOWN) +}}.toTypedArray() + /** List of all 12 box edges, represented as a [Pair] of [ForgeDirection]s */ -val boxEdges = forgeDirs.flatMap { face1 -> forgeDirs.filter { it.axis > face1.axis }.map { face1 to it } } +val boxEdges = allDirections.flatMap { face1 -> allDirections.filter { it.axis > face1.axis }.map { face1 to it } } /** * Get the closest object to the specified point from a list of objects. @@ -203,23 +220,3 @@ fun nearestPosition(vertex: Double3, objs: Iterable, objPos: (T)-> Double */ fun nearestAngle(vector: Double3, objs: Iterable, objAngle: (T)-> Double3): Pair = objs.map { it to objAngle(it).dot(vector) }.maxBy { it.second }!! - -data class FaceCorners(val topLeft: Pair, - val topRight: Pair, - val bottomLeft: Pair, - val bottomRight: Pair) { - constructor(top: Direction, left: Direction) : - this(top to left, top to left.opposite, top.opposite to left, top.opposite to left.opposite) - - val asArray = arrayOf(topLeft, topRight, bottomLeft, bottomRight) - val asList = listOf(topLeft, topRight, bottomLeft, bottomRight) -} - -val faceCorners = forgeDirs.map { when(it) { - DOWN -> FaceCorners(SOUTH, WEST) - UP -> FaceCorners(SOUTH, EAST) - NORTH -> FaceCorners(WEST, UP) - SOUTH -> FaceCorners(UP, WEST) - WEST -> FaceCorners(SOUTH, UP) - EAST -> FaceCorners(SOUTH, DOWN) -}}