[WIP] Roll all rendering parameters into a single context

+ split project into platform-dependent and -independent parts
This commit is contained in:
octarine-noise
2020-01-03 21:36:08 +01:00
parent 2ba99f40e7
commit 2a06c18884
47 changed files with 907 additions and 1007 deletions

View File

@@ -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

View File

@@ -10,17 +10,14 @@ import mods.betterfoliage.client.integration.TechRebornRubberIntegration
import mods.betterfoliage.client.render.* import mods.betterfoliage.client.render.*
import mods.betterfoliage.client.texture.* import mods.betterfoliage.client.texture.*
import mods.octarinecore.client.gui.textComponent 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.CenteringTextureGenerator
import mods.octarinecore.client.resource.GeneratorPack
import mods.octarinecore.client.resource.IConfigChangeListener import mods.octarinecore.client.resource.IConfigChangeListener
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft import net.minecraft.client.Minecraft
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.text.TextFormatting import net.minecraft.util.text.TextFormatting
import net.minecraft.util.text.TranslationTextComponent import net.minecraft.util.text.TranslationTextComponent
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.config.ModConfig
import net.minecraftforge.registries.ForgeRegistries import net.minecraftforge.registries.ForgeRegistries
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
@@ -29,7 +26,7 @@ import org.apache.logging.log4j.Level
* except for the call hooks. * except for the call hooks.
*/ */
object Client { object Client {
var renderers= emptyList<AbstractBlockRenderingHandler>() var renderers= emptyList<RenderDecorator>()
var configListeners = emptyList<IConfigChangeListener>() var configListeners = emptyList<IConfigChangeListener>()
val suppressRenderErrors = mutableSetOf<BlockState>() val suppressRenderErrors = mutableSetOf<BlockState>()

View File

@@ -7,7 +7,10 @@ import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.* import mods.betterfoliage.client.render.*
import mods.betterfoliage.loader.Refs 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.client.resource.LoadModelDataEvent
import mods.octarinecore.common.plus import mods.octarinecore.common.plus
import mods.octarinecore.metaprog.allAvailable 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) return state.func_215702_a(reader, pos, dir)
} }
val lightingCtx by ThreadLocalDelegate { DefaultLightingCtx(BasicBlockCtx(NonNullWorld, BlockPos.ZERO)) }
fun renderWorldBlock(dispatcher: BlockRendererDispatcher, fun renderWorldBlock(dispatcher: BlockRendererDispatcher,
state: BlockState, state: BlockState,
pos: BlockPos, pos: BlockPos,
@@ -84,21 +88,32 @@ fun renderWorldBlock(dispatcher: BlockRendererDispatcher,
modelData: IModelData, modelData: IModelData,
layer: BlockRenderLayer layer: BlockRenderLayer
): Boolean { ): 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)) val doBaseRender = state.canRenderInLayer(layer) || (layer == targetCutoutLayer && state.canRenderInLayer(otherCutoutLayer))
blockContext.let { ctx ->
ctx.set(reader, pos)
Client.renderers.forEach { renderer -> Client.renderers.forEach { renderer ->
if (renderer.isEligible(ctx)) { if (renderer.isEligible(combinedCtx)) {
// render on the block's default layer // render on the block's default layer
// also render on the cutout layer if the renderer requires it // 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) 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 fun canRenderInLayerOverride(state: BlockState, layer: BlockRenderLayer) = state.canRenderInLayer(layer) || layer == targetCutoutLayer

View File

@@ -3,12 +3,12 @@ package mods.betterfoliage.client.chunk
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client import mods.betterfoliage.client.Client
import mods.betterfoliage.loader.Refs 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.renderer.chunk.ChunkRenderCache
import net.minecraft.client.world.ClientWorld import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.ChunkPos import net.minecraft.util.math.ChunkPos
import net.minecraft.world.IBlockReader
import net.minecraft.world.IEnviromentBlockReader import net.minecraft.world.IEnviromentBlockReader
import net.minecraft.world.IWorldReader import net.minecraft.world.IWorldReader
import net.minecraft.world.dimension.DimensionType 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 * Represents some form of arbitrary non-persistent data that can be calculated and cached for each block position
*/ */
interface ChunkOverlayLayer<T> { interface ChunkOverlayLayer<T> {
abstract fun calculate(reader: IEnviromentBlockReader, pos: BlockPos): T fun calculate(ctx: BlockCtx): T
abstract fun onBlockUpdate(reader: IEnviromentBlockReader, pos: BlockPos) 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 reader World to use if calculation of overlay value is necessary
* @param pos Block position * @param pos Block position
*/ */
fun <T> get(layer: ChunkOverlayLayer<T>, reader: IEnviromentBlockReader, pos: BlockPos): T? { fun <T> get(layer: ChunkOverlayLayer<T>, ctx: BlockCtx): T? {
val data = chunkData[reader.dimType]?.get(ChunkPos(pos)) ?: return null val data = chunkData[ctx.world.dimType]?.get(ChunkPos(ctx.pos)) ?: return null
data.get(layer, pos).let { value -> data.get(layer, ctx.pos).let { value ->
if (value !== ChunkOverlayData.UNCALCULATED) return value if (value !== ChunkOverlayData.UNCALCULATED) return value
val newValue = layer.calculate(reader, pos) val newValue = layer.calculate(ctx)
data.set(layer, pos, newValue) data.set(layer, ctx.pos, newValue)
return newValue return newValue
} }
} }

View File

@@ -3,7 +3,7 @@ package mods.betterfoliage.client.integration
import mods.betterfoliage.client.Client import mods.betterfoliage.client.Client
import mods.betterfoliage.loader.Refs import mods.betterfoliage.loader.Refs
import mods.octarinecore.ThreadLocalDelegate import mods.octarinecore.ThreadLocalDelegate
import mods.octarinecore.client.render.BlockContext import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.common.Int3 import mods.octarinecore.common.Int3
import mods.octarinecore.metaprog.allAvailable import mods.octarinecore.metaprog.allAvailable
import mods.octarinecore.metaprog.reflectField import mods.octarinecore.metaprog.reflectField
@@ -33,12 +33,12 @@ object OptifineCustomColors {
val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() } val renderEnv by ThreadLocalDelegate { OptifineRenderEnv() }
val fakeQuad = BakedQuad(IntArray(0), 1, UP, null, true, DefaultVertexFormats.BLOCK) 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<Boolean>("ofCustomColors") == true) { val ofColor = if (isColorAvailable && Minecraft.getInstance().gameSettings.reflectField<Boolean>("ofCustomColors") == true) {
renderEnv.reset(ctx.blockState(Int3.zero), ctx.pos) renderEnv.reset(ctx.state, ctx.pos)
Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.blockState(Int3.zero), ctx.reader!!, ctx.pos, renderEnv.wrapped) as? Int Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.state, ctx.world, ctx.pos, renderEnv.wrapped) as? Int
} else null } 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
} }
} }

View File

@@ -5,10 +5,10 @@ import mods.betterfoliage.client.Client
import mods.betterfoliage.client.render.LogRegistry import mods.betterfoliage.client.render.LogRegistry
import mods.betterfoliage.client.render.column.ColumnTextureInfo import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.render.Quad import mods.octarinecore.client.render.Quad
import mods.octarinecore.client.render.QuadIconResolver import mods.octarinecore.client.render.lighting.QuadIconResolver
import mods.octarinecore.client.render.ShadingContext import mods.octarinecore.client.render.lighting.LightingCtx
import mods.octarinecore.client.render.blockContext
import mods.octarinecore.client.resource.* import mods.octarinecore.client.resource.*
import mods.octarinecore.common.rotate import mods.octarinecore.common.rotate
import mods.octarinecore.metaprog.ClassRef 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.Direction.* import net.minecraft.util.Direction.*
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.IModel
import net.minecraftforge.fml.ModList import net.minecraftforge.fml.ModList
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Level.DEBUG import org.apache.logging.log4j.Level.DEBUG
@@ -61,10 +60,10 @@ class RubberLogInfo(
sideTextures: List<TextureAtlasSprite> sideTextures: List<TextureAtlasSprite>
) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) { ) : SimpleColumnInfo(axis, topTexture, bottomTexture, sideTextures) {
override val side: QuadIconResolver = { ctx: ShadingContext, idx: Int, quad: Quad -> override val side: QuadIconResolver = { ctx: CombinedContext, idx: Int, quad: Quad ->
val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.rotation) val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.modelRotation)
if (worldFace == spotDir) spotTexture else { 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] this.sideTextures[sideIdx]
} }
} }

View File

@@ -4,6 +4,7 @@ import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.loader.Refs import mods.betterfoliage.loader.Refs
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.metaprog.allAvailable import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.BlockRenderType import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockRenderType.MODEL 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. */ /** 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) { 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)) { if ((isAvailable && enabled)) {
val vertexBuilder = Refs.sVertexBuilder.get(renderer)!! val vertexBuilder = Refs.sVertexBuilder.get(renderer)!!
Refs.pushEntity_num.invoke(vertexBuilder, blockId) 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. */ /** Quads rendered inside this block will use the given block entity data in shader programs. */
// temporarily NO-OP // temporarily NO-OP
inline fun renderAs(state: BlockState, renderType: BlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) = func() 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. */ /** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
inline fun grass(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) = inline fun grass(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(Config.shaders.grassId, MODEL, renderer, enabled, func) renderAs(Config.shaders.grassId, MODEL, ctx.renderCtx!!.renderBuffer, enabled, func)
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */ /** Quads rendered inside this block will behave as leaf blocks in shader programs. */
inline fun leaves(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) = inline fun leaves(ctx: CombinedContext, enabled: Boolean = true, func: ()->Unit) =
renderAs(Config.shaders.leavesId, MODEL, renderer, enabled, func) renderAs(Config.shaders.leavesId, MODEL, ctx.renderCtx!!.renderBuffer, enabled, func)
} }

View File

@@ -5,7 +5,7 @@ import mods.betterfoliage.client.texture.LeafParticleRegistry
import mods.betterfoliage.client.texture.LeafRegistry import mods.betterfoliage.client.texture.LeafRegistry
import mods.octarinecore.PI2 import mods.octarinecore.PI2
import mods.octarinecore.client.render.AbstractEntityFX 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.common.Double3
import mods.octarinecore.minmax import mods.octarinecore.minmax
import mods.octarinecore.random import mods.octarinecore.random

View File

@@ -3,11 +3,10 @@ package mods.betterfoliage.client.render
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.* import mods.octarinecore.client.render.*
import mods.octarinecore.client.render.lighting.*
import mods.octarinecore.common.Double3 import mods.octarinecore.common.Double3
import mods.octarinecore.exchange import mods.octarinecore.exchange
import net.minecraft.util.Direction
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. */ /** Weight of the same-side AO values on the outer edges of the 45deg chamfered column faces. */
const val chamferAffinity = 0.9f const val chamferAffinity = 0.9f

View File

@@ -2,25 +2,17 @@ package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.octarinecore.client.render.* import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.common.Int3 import mods.octarinecore.client.render.RenderDecorator
import mods.octarinecore.common.Rotation
import net.minecraft.block.Block
import net.minecraft.block.material.Material 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.tags.BlockTags
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraft.world.biome.Biome import net.minecraft.world.biome.Biome
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG 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() val noise = simplexNoise()
@@ -31,31 +23,23 @@ class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFo
Client.log(DEBUG, "Registered ${algaeIcons.num} algae textures") Client.log(DEBUG, "Registered ${algaeIcons.num} algae textures")
} }
override fun isEligible(ctx: BlockContext) = override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.algae.enabled && Config.enabled && Config.algae.enabled &&
ctx.blockState(up2).material == Material.WATER && ctx.state(up2).material == Material.WATER &&
ctx.blockState(up1).material == Material.WATER && ctx.state(up1).material == Material.WATER &&
BlockTags.DIRT_LIKE.contains(ctx.block) && BlockTags.DIRT_LIKE.contains(ctx.state.block) &&
ctx.biome.category.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH || it == Biome.Category.RIVER } && ctx.biome.category.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH || it == Biome.Category.RIVER } &&
noise[ctx.pos] < Config.algae.population noise[ctx.pos] < Config.algae.population
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { override fun render(ctx: CombinedContext) {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer) ctx.render()
if (!layer.isCutout) return baseRender if (!ctx.isCutout) return
modelRenderer.updateShading(Int3.zero, allFaces)
val rand = ctx.semiRandomArray(3) val rand = ctx.semiRandomArray(3)
ShadersModIntegration.grass(ctx, Config.algae.shaderWind) {
ShadersModIntegration.grass(renderer, Config.algae.shaderWind) { ctx.render(
modelRenderer.render(
renderer,
algaeModels[rand[2]], algaeModels[rand[2]],
Rotation.identity, icon = { _, qi, _ -> algaeIcons[rand[qi and 1]]!! }
icon = { _, qi, _ -> algaeIcons[rand[qi and 1]]!! },
postProcess = noPost
) )
} }
return true
} }
} }

View File

@@ -2,11 +2,11 @@ package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.column.ColumnTextureInfo import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.octarinecore.client.render.* import mods.octarinecore.client.render.*
import mods.octarinecore.client.render.lighting.*
import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable
import mods.octarinecore.common.Int3 import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation import mods.octarinecore.common.Rotation
@@ -31,7 +31,7 @@ object StandardCactusRegistry : ModelRenderRegistryConfigurable<ColumnTextureInf
init { BetterFoliage.modBus.register(this) } init { BetterFoliage.modBus.register(this) }
} }
class RenderCactus : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) { class RenderCactus : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
val cactusStemRadius = 0.4375 val cactusStemRadius = 0.4375
val cactusArmRotation = listOf(NORTH, SOUTH, EAST, WEST).map { Rotation.rot90[it.ordinal] } val cactusArmRotation = listOf(NORTH, SOUTH, EAST, WEST).map { Rotation.rot90[it.ordinal] }
@@ -71,41 +71,30 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterF
Client.log(DEBUG, "Registered ${iconArm.num} cactus arm textures") Client.log(DEBUG, "Registered ${iconArm.num} cactus arm textures")
} }
override fun isEligible(ctx: BlockContext): Boolean = override fun isEligible(ctx: CombinedContext): Boolean =
Config.enabled && Config.cactus.enabled && Config.enabled && Config.cactus.enabled &&
StandardCactusRegistry[ctx] != null StandardCactusRegistry[ctx] != null
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { override val onlyOnCutout get() = true
// render the whole block on the cutout layer
if (!layer.isCutout) return false
// get AO data override fun render(ctx: CombinedContext) {
modelRenderer.updateShading(Int3.zero, allFaces) val icons = StandardCactusRegistry[ctx]!!
val icons = StandardCactusRegistry[ctx] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null)
modelRenderer.render( ctx.render(
renderer,
modelStem.model, modelStem.model,
Rotation.identity,
icon = { ctx, qi, q -> when(qi) { icon = { ctx, qi, q -> when(qi) {
0 -> icons.bottom(ctx, qi, q); 1 -> icons.top(ctx, qi, q); else -> icons.side(ctx, qi, q) 0 -> icons.bottom(ctx, qi, q); 1 -> icons.top(ctx, qi, q); else -> icons.side(ctx, qi, q)
} }, } }
postProcess = noPost
) )
modelRenderer.render( ctx.render(
renderer, modelCross[ctx.semiRandom(0)],
modelCross[ctx.random(0)], icon = { _, _, _ -> iconCross.icon!!}
Rotation.identity,
icon = { _, _, _ -> iconCross.icon!!},
postProcess = noPost
) )
modelRenderer.render(
renderer, ctx.render(
modelArm[ctx.random(1)], modelArm[ctx.semiRandom(1)],
cactusArmRotation[ctx.random(2) % 4], cactusArmRotation[ctx.semiRandom(2) % 4],
icon = { _, _, _ -> iconArm[ctx.random(3)]!!}, icon = { _, _, _ -> iconArm[ctx.semiRandom(3)]!!}
postProcess = noPost
) )
return true
} }
} }

View File

@@ -1,36 +1,45 @@
package mods.betterfoliage.client.render package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.texture.GrassRegistry import mods.betterfoliage.client.texture.GrassRegistry
import mods.octarinecore.client.render.AbstractBlockRenderingHandler import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.render.BlockContext import mods.octarinecore.client.render.RenderDecorator
import mods.octarinecore.client.render.offset
import mods.octarinecore.common.Int3 import mods.octarinecore.common.Int3
import mods.octarinecore.common.forgeDirsHorizontal import mods.octarinecore.common.horizontalDirections
import mods.octarinecore.common.offset 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.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) { class RenderConnectedGrass : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
override fun isEligible(ctx: BlockContext) = override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.connectedGrass.enabled && Config.enabled && Config.connectedGrass.enabled &&
BlockTags.DIRT_LIKE.contains(ctx.block) && BlockTags.DIRT_LIKE.contains(ctx.state.block) &&
GrassRegistry[ctx, up1] != null && 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 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 -> class RenderConnectedGrassLog : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
renderWorldBlockBase(offsetCtx, dispatcher, renderer, random, modelData, layer)
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()
} }
} }
} }

View File

@@ -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)
}
}
}

View File

@@ -5,9 +5,11 @@ import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.* import mods.octarinecore.client.render.*
import mods.octarinecore.client.render.lighting.*
import mods.octarinecore.common.Int3 import mods.octarinecore.common.Int3
import mods.octarinecore.common.forgeDirOffsets import mods.octarinecore.common.allDirOffsets
import mods.octarinecore.common.forgeDirs import mods.octarinecore.common.allDirections
import mods.octarinecore.common.offset
import mods.octarinecore.random import mods.octarinecore.random
import net.minecraft.block.material.Material import net.minecraft.block.material.Material
import net.minecraft.client.renderer.BlockRendererDispatcher 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.Direction.UP
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraft.world.biome.Biome import net.minecraft.world.biome.Biome
import net.minecraftforge.api.distmarker.Dist
import net.minecraftforge.client.model.data.IModelData import net.minecraftforge.client.model.data.IModelData
import net.minecraftforge.fml.common.Mod
import org.apache.logging.log4j.Level.DEBUG import org.apache.logging.log4j.Level.DEBUG
import java.util.* import java.util.*
class RenderCoral : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) { class RenderCoral : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
val noise = simplexNoise() val noise = simplexNoise()
@@ -49,33 +49,27 @@ class RenderCoral : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFo
Client.log(DEBUG, "Registered ${crustIcons.num} coral crust textures") Client.log(DEBUG, "Registered ${crustIcons.num} coral crust textures")
} }
override fun isEligible(ctx: BlockContext) = override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.coral.enabled && Config.enabled && Config.coral.enabled &&
(ctx.blockState(up2).material == Material.WATER || Config.coral.shallowWater) && (ctx.state(up2).material == Material.WATER || Config.coral.shallowWater) &&
ctx.blockState(up1).material == Material.WATER && ctx.state(up1).material == Material.WATER &&
BlockConfig.sand.matchesClass(ctx.block) && BlockConfig.sand.matchesClass(ctx.state.block) &&
ctx.biome.category.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH } && ctx.biome.category.let { it == Biome.Category.OCEAN || it == Biome.Category.BEACH } &&
noise[ctx.pos] < Config.coral.population noise[ctx.pos] < Config.coral.population
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { override fun render(ctx: CombinedContext) {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer) val baseRender = ctx.render()
if (!layer.isCutout) return baseRender if (!ctx.isCutout) return
modelRenderer.updateShading(Int3.zero, allFaces) allDirections.forEachIndexed { idx, face ->
if (ctx.state(face).material == Material.WATER && ctx.semiRandom(idx) < Config.coral.chance) {
forgeDirs.forEachIndexed { idx, face -> var variation = ctx.semiRandom(6)
if (!ctx.isNormalCube(forgeDirOffsets[idx]) && blockContext.random(idx) < Config.coral.chance) { ctx.render(
var variation = blockContext.random(6)
modelRenderer.render(
renderer,
coralModels[variation++], coralModels[variation++],
rotationFromUp[idx], rotationFromUp[idx],
icon = { _, qi, _ -> if (qi == 4) crustIcons[variation]!! else coralIcons[variation + (qi and 1)]!!}, icon = { _, qi, _ -> if (qi == 4) crustIcons[variation]!! else coralIcons[variation + (qi and 1)]!!}
postProcess = noPost
) )
} }
} }
return true
} }
} }

View File

@@ -6,24 +6,22 @@ import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.OptifineCustomColors import mods.betterfoliage.client.integration.OptifineCustomColors
import mods.betterfoliage.client.integration.ShadersModIntegration import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.texture.GrassRegistry import mods.betterfoliage.client.texture.GrassRegistry
import mods.octarinecore.client.render.* import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.common.* 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 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.tags.BlockTags
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.* import net.minecraft.util.Direction.*
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG 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 { companion object {
@JvmStatic fun grassTopQuads(heightMin: Double, heightMax: Double): Model.(Int)->Unit = { modelIdx -> @JvmStatic fun grassTopQuads(heightMin: Double, heightMax: Double): Model.(Int)->Unit = { modelIdx ->
@@ -50,38 +48,27 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFo
Client.log(DEBUG, "Registered ${snowedIcons.num} snowed grass textures") Client.log(DEBUG, "Registered ${snowedIcons.num} snowed grass textures")
} }
override fun isEligible(ctx: BlockContext) = override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.enabled &&
(Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) && (Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) &&
GrassRegistry[ctx] != null GrassRegistry[ctx] != null
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { override val onlyOnCutout get() = true
// render the whole block on the cutout layer
if (!layer.isCutout) return false
val isConnected = BlockTags.DIRT_LIKE.contains(ctx.block(down1)) || GrassRegistry[ctx, down1] != null override fun render(ctx: CombinedContext) {
val isSnowed = ctx.blockState(up1).isSnow 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 connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled)
val grass = GrassRegistry[ctx] 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 blockColor = OptifineCustomColors.getBlockColor(ctx) val blockColor = OptifineCustomColors.getBlockColor(ctx)
if (connectedGrass) { if (connectedGrass) {
// get full AO data
modelRenderer.updateShading(Int3.zero, allFaces)
// check occlusion // check occlusion
val isVisible = forgeDirs.map { ctx.shouldSideBeRendered(it) } val isVisible = allDirections.map { ctx.shouldSideBeRendered(it) }
// render full grass block // render full grass block
ShadersModIntegration.renderAs(ctx.blockState(Int3.zero), MODEL, renderer) { ctx.render(
modelRenderer.render(
renderer,
fullCube, fullCube,
quadFilter = { qi, _ -> isVisible[qi] }, quadFilter = { qi, _ -> isVisible[qi] },
icon = { _, _, _ -> grass.grassTopTexture }, icon = { _, _, _ -> grass.grassTopTexture },
@@ -92,35 +79,27 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFo
} else if (ctx.aoEnabled && grass.overrideColor == null) multiplyColor(blockColor) } else if (ctx.aoEnabled && grass.overrideColor == null) multiplyColor(blockColor)
} }
) )
}
} else { } else {
renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null) ctx.render()
// get AO data only for block top
modelRenderer.updateShading(Int3.zero, topOnly)
} }
if (!Config.shortGrass.grassEnabled) return true if (!Config.shortGrass.grassEnabled) return
if (isSnowed && !Config.shortGrass.snowEnabled) return true if (isSnowed && !Config.shortGrass.snowEnabled) return
if (ctx.isNormalCube(up1)) return true if (ctx.offset(UP).isNormalCube) return
if (Config.shortGrass.population < 64 && noise[ctx.pos] >= Config.shortGrass.population) return true if (Config.shortGrass.population < 64 && noise[ctx.pos] >= Config.shortGrass.population) return
// render grass quads // render grass quads
val iconset = if (isSnowed) snowedIcons else normalIcons val iconset = if (isSnowed) snowedIcons else normalIcons
val iconGen = if (isSnowed) snowedGenIcon else normalGenIcon val iconGen = if (isSnowed) snowedGenIcon else normalGenIcon
val rand = ctx.semiRandomArray(2) val rand = ctx.semiRandomArray(2)
ShadersModIntegration.grass(renderer, Config.shortGrass.shaderWind) { ShadersModIntegration.grass(ctx, Config.shortGrass.shaderWind) {
modelRenderer.render( ctx.render(
renderer,
grassModels[rand[0]], grassModels[rand[0]],
Rotation.identity, translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = { _, qi, _ -> if (Config.shortGrass.useGenerated) iconGen.icon!! else iconset[rand[qi and 1]]!! }, 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) } postProcess = { _, _, _, _, _ -> if (isSnowed) setGrey(1.0f) else multiplyColor(grass.overrideColor ?: blockColor) }
) )
} }
return true
} }
} }

View File

@@ -1,30 +1,27 @@
package mods.betterfoliage.client.render package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.OptifineCustomColors import mods.betterfoliage.client.integration.OptifineCustomColors
import mods.betterfoliage.client.integration.ShadersModIntegration import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.texture.LeafRegistry import mods.betterfoliage.client.texture.LeafRegistry
import mods.octarinecore.PI2 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.Double3
import mods.octarinecore.common.Int3 import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation import mods.octarinecore.common.allDirections
import mods.octarinecore.common.vec import mods.octarinecore.common.vec
import mods.octarinecore.random import mods.octarinecore.random
import net.minecraft.block.material.Material import net.minecraft.util.Direction.UP
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.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import java.lang.Math.cos import java.lang.Math.cos
import java.lang.Math.sin 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 { 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) 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 UP.vec * random(-1.0, 1.0) * Config.leaves.vOffset
} }
override fun isEligible(ctx: BlockContext) = override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.enabled &&
Config.leaves.enabled && Config.leaves.enabled &&
LeafRegistry[ctx] != null && 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 { override val onlyOnCutout get() = true
val isSnowed = ctx.blockState(up1).isSnow
val leafInfo = LeafRegistry[ctx] override fun render(ctx: CombinedContext) {
if (leafInfo == null) { val isSnowed = ctx.state(UP).isSnow
// shouldn't happen val leafInfo = LeafRegistry[ctx]!!
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer)
}
val blockColor = OptifineCustomColors.getBlockColor(ctx) val blockColor = OptifineCustomColors.getBlockColor(ctx)
renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer) ctx.render(force = true)
if (!layer.isCutout) return true
modelRenderer.updateShading(Int3.zero, allFaces) ShadersModIntegration.leaves(ctx) {
ShadersModIntegration.leaves(renderer) {
val rand = ctx.semiRandomArray(2) val rand = ctx.semiRandomArray(2)
(if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation -> (if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation ->
modelRenderer.render( ctx.render(
renderer,
leavesModel.model, leavesModel.model,
rotation, rotation,
ctx.blockCenter + perturbs[rand[0]], translation = ctx.blockCenter + perturbs[rand[0]],
icon = { _, _, _ -> leafInfo.roundLeafTexture }, icon = { _, _, _ -> leafInfo.roundLeafTexture },
postProcess = { _, _, _, _, _ -> postProcess = { _, _, _, _, _ ->
rotateUV(rand[1]) rotateUV(rand[1])
@@ -76,16 +67,12 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterF
} }
) )
} }
if (isSnowed && Config.leaves.snowEnabled) modelRenderer.render( if (isSnowed && Config.leaves.snowEnabled) ctx.render(
renderer,
leavesModel.model, leavesModel.model,
Rotation.identity, translation = ctx.blockCenter + perturbs[rand[0]],
ctx.blockCenter + perturbs[rand[0]],
icon = { _, _, _ -> snowedIcon[rand[1]]!! }, icon = { _, _, _ -> snowedIcon[rand[1]]!! },
postProcess = whitewash postProcess = whitewash
) )
} }
return true
} }
} }

View File

@@ -5,20 +5,16 @@ import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration 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.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.DOWN
import net.minecraft.util.Direction.UP import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG 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 { val rootModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -1.5, yTop = -0.5) 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") 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 && 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 { override fun render(ctx: CombinedContext) {
// render the whole block on the cutout layer ctx.render()
if (!layer.isCutout) return false
renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null)
modelRenderer.updateShading(Int3.zero, allFaces)
val rand = ctx.semiRandomArray(5) val rand = ctx.semiRandomArray(5)
ShadersModIntegration.grass(ctx) {
ShadersModIntegration.grass(renderer) { ctx.render(
modelRenderer.render(
renderer,
rootModel.model, rootModel.model,
Rotation.identity, translation = ctx.blockCenter.add(perturbs[rand[2]]),
ctx.blockCenter.add(perturbs[rand[2]]),
forceFlat = true, forceFlat = true,
icon = { ctx, qi, q -> rootIcon[rand[qi and 1]]!! }, icon = { ctx, qi, q -> rootIcon[rand[qi and 1]]!! }
postProcess = noPost
) )
} }
if (rand[3] < Config.lilypad.flowerChance) modelRenderer.render( if (rand[3] < Config.lilypad.flowerChance) ctx.render(
renderer,
flowerModel.model, flowerModel.model,
Rotation.identity, translation = ctx.blockCenter.add(perturbs[rand[4]]),
ctx.blockCenter.add(perturbs[rand[4]]),
forceFlat = true, forceFlat = true,
icon = { _, _, _ -> flowerIcon[rand[0]]!! }, icon = { _, _, _ -> flowerIcon[rand[0]]!! }
postProcess = noPost
) )
return true
} }
} }

View File

@@ -8,8 +8,8 @@ import mods.betterfoliage.client.render.column.AbstractRenderColumn
import mods.betterfoliage.client.render.column.ColumnRenderLayer import mods.betterfoliage.client.render.column.ColumnRenderLayer
import mods.betterfoliage.client.render.column.ColumnTextureInfo import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.betterfoliage.client.texture.GrassRegistry import mods.octarinecore.client.render.BlockCtx
import mods.octarinecore.client.render.BlockContext import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.resource.ModelRenderRegistry import mods.octarinecore.client.resource.ModelRenderRegistry
import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable
import mods.octarinecore.client.resource.ModelRenderRegistryRoot import mods.octarinecore.client.resource.ModelRenderRegistryRoot
@@ -19,12 +19,13 @@ import mods.octarinecore.tryDefault
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.LogBlock import net.minecraft.block.LogBlock
import net.minecraft.util.Direction.Axis import net.minecraft.util.Direction.Axis
import net.minecraft.world.IEnviromentBlockReader
class RenderLog : AbstractRenderColumn(BetterFoliage.MOD_ID, BetterFoliage.modBus) { 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 && Config.enabled && Config.roundLogs.enabled &&
LogRegistry[ctx] != null LogRegistry[ctx] != null
@@ -40,7 +41,6 @@ class RenderLog : AbstractRenderColumn(BetterFoliage.MOD_ID, BetterFoliage.modBu
class RoundLogOverlayLayer : ColumnRenderLayer() { class RoundLogOverlayLayer : ColumnRenderLayer() {
override val registry: ModelRenderRegistry<ColumnTextureInfo> get() = LogRegistry override val registry: ModelRenderRegistry<ColumnTextureInfo> get() = LogRegistry
override val blockPredicate = { state: BlockState -> BlockConfig.logBlocks.matchesClass(state.block) } 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 connectSolids: Boolean get() = Config.roundLogs.connectSolids
override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect

View File

@@ -4,21 +4,15 @@ import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.AbstractBlockRenderingHandler import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.render.BlockContext import mods.octarinecore.client.render.RenderDecorator
import mods.octarinecore.client.render.modelRenderer
import mods.octarinecore.client.render.noPost import mods.octarinecore.client.render.noPost
import mods.octarinecore.common.Double3 import mods.octarinecore.common.Double3
import mods.octarinecore.common.Rotation import net.minecraft.util.Direction.UP
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG 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 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) } 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") 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 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 { override fun render(ctx: CombinedContext) {
// render the whole block on the cutout layer ctx.render()
if (!layer.isCutout) return false if (!ctx.isCutout) return
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
val isSnowed = ctx.state(UP).isSnow
if (isSnowed && !Config.shortGrass.snowEnabled) return
if (ctx.offset(UP).isNormalCube) return
val rand = ctx.semiRandomArray(2) val rand = ctx.semiRandomArray(2)
modelRenderer.render(
renderer, ctx.render(
myceliumModel[rand[0]], myceliumModel[rand[0]],
Rotation.identity, translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = { _, qi, _ -> myceliumIcon[rand[qi and 1]]!! }, icon = { _, qi, _ -> myceliumIcon[rand[qi and 1]]!! },
postProcess = if (isSnowed) whitewash else noPost postProcess = if (isSnowed) whitewash else noPost
) )
return true
} }
} }

View File

@@ -5,6 +5,7 @@ import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.* import mods.octarinecore.client.render.*
import mods.octarinecore.client.render.lighting.*
import mods.octarinecore.common.Int3 import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation import mods.octarinecore.common.Rotation
import mods.octarinecore.random import mods.octarinecore.random
@@ -18,7 +19,7 @@ import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG import org.apache.logging.log4j.Level.DEBUG
import java.util.* 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 netherrackIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_netherrack_$idx") }
val netherrackModel = modelSet(64) { modelIdx -> val netherrackModel = modelSet(64) { modelIdx ->
@@ -34,28 +35,20 @@ class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, Bet
Client.log(DEBUG, "Registered ${netherrackIcon.num} netherrack textures") 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 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 { override fun render(ctx: CombinedContext) {
val baseRender = renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer) ctx.render()
if (!layer.isCutout) return baseRender if (!ctx.isCutout) return
if (ctx.offset(DOWN).isNormalCube) return
if (ctx.isNormalCube(down1)) return baseRender
modelRenderer.updateShading(Int3.zero, allFaces)
val rand = ctx.semiRandomArray(2) val rand = ctx.semiRandomArray(2)
modelRenderer.render( ctx.render(
renderer,
netherrackModel[rand[0]], netherrackModel[rand[0]],
Rotation.identity, icon = { _, qi, _ -> netherrackIcon[rand[qi and 1]]!! }
icon = { _, qi, _ -> netherrackIcon[rand[qi and 1]]!! },
postProcess = noPost
) )
return true
} }
} }

View File

@@ -2,26 +2,19 @@ package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.octarinecore.client.render.* import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.common.Int3 import mods.octarinecore.client.render.RenderDecorator
import mods.octarinecore.common.Rotation import mods.octarinecore.client.render.lighting.FlatOffsetNoColor
import mods.octarinecore.random import mods.octarinecore.random
import net.minecraft.block.Block
import net.minecraft.block.material.Material 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.tags.BlockTags
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.Direction.UP import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG 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 noise = simplexNoise()
val reedIcons = iconSet { idx -> Client.genReeds.registerResource(ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_reed_$idx")) } 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") Client.log(DEBUG, "Registered ${reedIcons.num} reed textures")
} }
override fun isEligible(ctx: BlockContext) = override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.reed.enabled && Config.enabled && Config.reed.enabled &&
ctx.blockState(up2).material == Material.AIR && ctx.state(up2).material == Material.AIR &&
ctx.blockState(up1).material == Material.WATER && ctx.state(UP).material == Material.WATER &&
BlockTags.DIRT_LIKE.contains(ctx.block) && BlockTags.DIRT_LIKE.contains(ctx.state.block) &&
ctx.biome.let { it.downfall > Config.reed.minBiomeRainfall && it.defaultTemperature >= Config.reed.minBiomeTemp } && ctx.biome.let { it.downfall > Config.reed.minBiomeRainfall && it.defaultTemperature >= Config.reed.minBiomeTemp } &&
noise[ctx.pos] < Config.reed.population noise[ctx.pos] < Config.reed.population
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean { override val onlyOnCutout get() = false
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 iconVar = ctx.random(1) val iconVar = ctx.semiRandom(1)
ShadersModIntegration.grass(renderer, Config.reed.shaderWind) { ShadersModIntegration.grass(ctx, Config.reed.shaderWind) {
modelRenderer.render( ctx.render(
renderer, reedModels[ctx.semiRandom(0)],
reedModels[ctx.random(0)],
Rotation.identity,
forceFlat = true, forceFlat = true,
icon = { _, _, _ -> reedIcons[iconVar]!! }, icon = { _, _, _ -> reedIcons[iconVar]!! }
postProcess = noPost
) )
} }
return true
} }
} }

View File

@@ -3,7 +3,7 @@ package mods.betterfoliage.client.render
import mods.octarinecore.PI2 import mods.octarinecore.PI2
import mods.octarinecore.client.render.Model 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.client.render.Quad
import mods.octarinecore.common.Double3 import mods.octarinecore.common.Double3
import mods.octarinecore.common.Int3 import mods.octarinecore.common.Int3

View File

@@ -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.BlockType.*
import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType
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.client.render.CombinedContext
import mods.octarinecore.common.Int3 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.Rotation
import mods.octarinecore.common.face import mods.octarinecore.common.face
import mods.octarinecore.common.rot import mods.octarinecore.common.rot
import net.minecraft.block.BlockRenderType.MODEL 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.minecraft.util.Direction.*
import net.minecraftforge.client.model.data.IModelData
import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.eventbus.api.IEventBus
import java.util.*
@Suppress("NOTHING_TO_INLINE") @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 */ /** The rotations necessary to bring the models in position for the 4 quadrants */
val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it } val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it }
@@ -93,28 +90,25 @@ abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : Abstract
q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE)) q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE))
@Suppress("NON_EXHAUSTIVE_WHEN") @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) { when(roundLog) {
ColumnLayerData.SkipRender -> return true ColumnLayerData.SkipRender -> return
ColumnLayerData.NormalRender -> return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null) ColumnLayerData.NormalRender -> return ctx.render()
ColumnLayerData.ResolveError, null -> { ColumnLayerData.ResolveError, null -> {
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos) Client.logRenderError(ctx.state, ctx.pos)
return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null) return ctx.render()
} }
} }
// if log axis is not defined and "Default to vertical" config option is not set, render normally // 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) { 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] 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 -> quadrantRotations.forEachIndexed { idx, quadrantRotation ->
// set rotation for the current quadrant // set rotation for the current quadrant
val rotation = baseRotation + quadrantRotation val rotation = baseRotation + quadrantRotation
@@ -136,8 +130,7 @@ abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : Abstract
else -> null else -> null
} }
if (sideModel != null) modelRenderer.render( if (sideModel != null) ctx.render(
renderer,
sideModel, sideModel,
rotation, rotation,
icon = roundLog.column.side, icon = roundLog.column.side,
@@ -190,8 +183,7 @@ abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : Abstract
} }
} }
if (upModel != null) modelRenderer.render( if (upModel != null) ctx.render(
renderer,
upModel, upModel,
rotation, rotation,
icon = upIcon, icon = upIcon,
@@ -201,8 +193,7 @@ abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : Abstract
} }
} }
) )
if (downModel != null) modelRenderer.render( if (downModel != null) ctx.render(
renderer,
downModel, downModel,
rotation, rotation,
icon = downIcon, icon = downIcon,
@@ -214,6 +205,5 @@ abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : Abstract
) )
} }
} }
return true
} }
} }

View File

@@ -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.column.ColumnLayerData.SpecialRender.QuadrantType.* import mods.betterfoliage.client.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
import mods.betterfoliage.client.render.rotationFromUp 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.client.resource.ModelRenderRegistry
import mods.octarinecore.common.Int3 import mods.octarinecore.common.*
import mods.octarinecore.common.Rotation
import mods.octarinecore.common.face
import mods.octarinecore.common.plus
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.util.Direction.Axis import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.AxisDirection import net.minecraft.util.Direction.AxisDirection
@@ -64,21 +61,18 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
abstract val registry: ModelRenderRegistry<ColumnTextureInfo> abstract val registry: ModelRenderRegistry<ColumnTextureInfo>
abstract val blockPredicate: (BlockState)->Boolean abstract val blockPredicate: (BlockState)->Boolean
abstract val surroundPredicate: (BlockState) -> Boolean
abstract val connectSolids: Boolean abstract val connectSolids: Boolean
abstract val lenientConnect: Boolean abstract val lenientConnect: Boolean
abstract val defaultToY: Boolean abstract val defaultToY: Boolean
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}} 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) { override fun onBlockUpdate(world: IEnviromentBlockReader, pos: BlockPos) {
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(reader.dimType, this, pos + offset) } allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) }
} }
override fun calculate(reader: IEnviromentBlockReader, pos: BlockPos) = calculate(BlockContext(reader, pos)) override fun calculate(ctx: BlockCtx): ColumnLayerData {
if (allDirections.all { ctx.offset(it).isNormalCube }) return ColumnLayerData.SkipRender
fun calculate(ctx: BlockContext): ColumnLayerData {
if (ctx.isSurroundedBy(surroundPredicate) && ctx.isSurroundedByNormal) return ColumnLayerData.SkipRender
val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError
// if log axis is not defined and "Default to vertical" config option is not set, render normally // 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<ColumnLayerData> {
} }
/** Fill the array of [QuadrantType]s based on the blocks to the sides of this one. */ /** Fill the array of [QuadrantType]s based on the blocks to the sides of this one. */
fun Array<QuadrantType>.checkNeighbors(ctx: BlockContext, rotation: Rotation, logAxis: Axis, yOff: Int): Array<QuadrantType> { fun Array<QuadrantType>.checkNeighbors(ctx: BlockCtx, rotation: Rotation, logAxis: Axis, yOff: Int): Array<QuadrantType> {
val blkS = ctx.blockType(rotation, logAxis, Int3(0, yOff, 1)) val blkS = ctx.blockType(rotation, logAxis, Int3(0, yOff, 1))
val blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0)) val blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0))
val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1)) val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1))
@@ -171,13 +165,13 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
/** /**
* Get the type of the block at the given offset in a rotated reference frame. * Get the type of the block at the given offset in a rotated reference frame.
*/ */
fun BlockContext.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType { fun BlockCtx.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType {
val offsetRot = offset.rotate(rotation) val offsetRot = offset.rotate(rotation)
val state = blockState(offsetRot) val state = state(offsetRot)
return if (!blockPredicate(state)) { return if (!blockPredicate(state)) {
if (isNormalCube(offsetRot)) SOLID else NONSOLID if (offset(offsetRot).isNormalCube) SOLID else NONSOLID
} else { } 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 if (it == axis) PARALLEL else PERPENDICULAR
} ?: SOLID } ?: SOLID
} }

View File

@@ -1,7 +1,6 @@
package mods.betterfoliage.client.render.column package mods.betterfoliage.client.render.column
import mods.octarinecore.client.render.QuadIconResolver import mods.octarinecore.client.render.lighting.QuadIconResolver
import mods.octarinecore.client.render.blockContext
import mods.octarinecore.client.resource.ModelRenderKey import mods.octarinecore.client.resource.ModelRenderKey
import mods.octarinecore.client.resource.get import mods.octarinecore.client.resource.get
import mods.octarinecore.client.resource.missingSprite import mods.octarinecore.client.resource.missingSprite
@@ -31,8 +30,8 @@ open class SimpleColumnInfo(
override val top: QuadIconResolver = { _, _, _ -> topTexture } override val top: QuadIconResolver = { _, _, _ -> topTexture }
override val bottom: QuadIconResolver = { _, _, _ -> bottomTexture } override val bottom: QuadIconResolver = { _, _, _ -> bottomTexture }
override val side: QuadIconResolver = { ctx, idx, _ -> override val side: QuadIconResolver = { ctx, idx, _ ->
val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.rotation) val worldFace = (if ((idx and 1) == 0) SOUTH else EAST).rotate(ctx.modelRotation)
val sideIdx = if (sideTextures.size > 1) (blockContext.random(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0 val sideIdx = if (sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0
sideTextures[sideIdx] sideTextures[sideIdx]
} }

View File

@@ -1,6 +1,7 @@
package mods.betterfoliage.client.texture package mods.betterfoliage.client.texture
import mods.octarinecore.client.resource.* import mods.octarinecore.client.resource.*
import mods.octarinecore.client.resource.Atlas
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.resource.VanillaResourceType.TEXTURES import net.minecraftforge.resource.VanillaResourceType.TEXTURES
import java.awt.image.BufferedImage import java.awt.image.BufferedImage

View File

@@ -3,7 +3,7 @@ package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config 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.client.resource.*
import mods.octarinecore.common.config.ConfigurableBlockMatcher import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.ModelTextureList import mods.octarinecore.common.config.ModelTextureList

View File

@@ -2,7 +2,7 @@ package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.octarinecore.client.resource.* import mods.octarinecore.client.resource.*
import mods.octarinecore.stripStart import mods.octarinecore.client.resource.Atlas
import net.minecraft.resources.IResource import net.minecraft.resources.IResource
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.resource.VanillaResourceType.TEXTURES import net.minecraftforge.resource.VanillaResourceType.TEXTURES

View File

@@ -3,10 +3,9 @@ package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.octarinecore.client.resource.* import mods.octarinecore.client.resource.*
import mods.octarinecore.stripStart import mods.octarinecore.stripStart
import mods.octarinecore.client.resource.Atlas
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.event.TextureStitchEvent import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.eventbus.api.EventPriority
import net.minecraftforge.eventbus.api.SubscribeEvent import net.minecraftforge.eventbus.api.SubscribeEvent
object LeafParticleRegistry { object LeafParticleRegistry {

View File

@@ -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<BlockColors>()
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<Int> = 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))
}
}

View File

@@ -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<Int> = 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<BlockState>(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)
}

View File

@@ -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
}

View File

@@ -1,5 +1,6 @@
package mods.octarinecore.client.render package mods.octarinecore.client.render
import mods.octarinecore.client.render.lighting.*
import mods.octarinecore.common.* import mods.octarinecore.common.*
import mods.octarinecore.minmax import mods.octarinecore.minmax
import mods.octarinecore.replace import mods.octarinecore.replace
@@ -40,13 +41,13 @@ data class UV(val u: Double, val v: Double) {
* *
* @param[xyz] x, y, z coordinates * @param[xyz] x, y, z coordinates
* @param[uv] u, v coordinates * @param[uv] u, v coordinates
* @param[aoShader] [Shader] instance to use with AO rendering * @param[aoShader] [ModelLighter] instance to use with AO rendering
* @param[flatShader] [Shader] instance to use with non-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), data class Vertex(val xyz: Double3 = Double3(0.0, 0.0, 0.0),
val uv: UV = UV(0.0, 0.0), val uv: UV = UV(0.0, 0.0),
val aoShader: Shader = NoShader, val aoShader: ModelLighter = NoLighting,
val flatShader: Shader = NoShader) val flatShader: ModelLighter = NoLighting)
/** /**
* Model quad * Model quad
@@ -78,7 +79,7 @@ data class Quad(val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex)
transformVI { vertex, idx -> transformVI { vertex, idx ->
if (!predicate(vertex, idx)) vertex else vertex.copy(flatShader = factory(this@Quad, vertex)) 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) val flipped: Quad get() = Quad(v4, v3, v2, v1)
fun cycleVertices(n: Int) = when(n % 4) { fun cycleVertices(n: Int) = when(n % 4) {
@@ -125,8 +126,8 @@ class Model() {
fun faceQuad(face: Direction): Quad { fun faceQuad(face: Direction): Quad {
val base = face.vec * 0.5 val base = face.vec * 0.5
val top = faceCorners[face.ordinal].topLeft.first.vec * 0.5 val top = boxFaces[face].top * 0.5
val left = faceCorners[face.ordinal].topLeft.second.vec * 0.5 val left = boxFaces[face].left * 0.5
return Quad( return Quad(
Vertex(base + top + left, UV.topLeft), Vertex(base + top + left, UV.topLeft),
Vertex(base - top + left, UV.bottomLeft), Vertex(base - top + left, UV.bottomLeft),
@@ -137,7 +138,7 @@ class Model() {
} }
val fullCube = Model().apply { val fullCube = Model().apply {
forgeDirs.forEach { allDirections.forEach {
faceQuad(it) faceQuad(it)
.setAoShader(faceOrientedAuto(corner = cornerAo(it.axis), edge = null)) .setAoShader(faceOrientedAuto(corner = cornerAo(it.axis), edge = null))
.setFlatShader(faceOrientedAuto(corner = cornerFlat, edge = null)) .setFlatShader(faceOrientedAuto(corner = cornerFlat, edge = null))

View File

@@ -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 = { _, _, _, _, _ -> }

View File

@@ -50,5 +50,3 @@ class OffsetEnvBlockReader(val original: IEnviromentBlockReader, val modded: Blo
// reader = original // reader = original
// return result // return result
//} //}
inline fun BlockContext.offset(modded: Int3, target: Int3) = BlockContext(OffsetEnvBlockReader(reader!!, pos + modded, pos + target), pos)

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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 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 net.minecraft.util.Direction.* import net.minecraft.util.Direction.*
import java.lang.Math.min import java.lang.Math.min
import java.util.*
typealias EdgeShaderFactory = (Direction, Direction) -> Shader typealias EdgeShaderFactory = (Direction, Direction) -> ModelLighter
typealias CornerShaderFactory = (Direction, Direction, Direction) -> Shader typealias CornerShaderFactory = (Direction, Direction, Direction) -> ModelLighter
typealias ShaderFactory = (Quad, Vertex) -> Shader typealias ShaderFactory = (Quad, Vertex) -> ModelLighter
/** Holds shading values for block corners as calculated by vanilla Minecraft rendering. */ /** Holds lighting values for block corners as calculated by vanilla Minecraft rendering. */
class AoData() { class CornerLightData {
var valid = false var valid = false
var brightness = 0 var brightness = 0
var red: Float = 0.0f var red: Float = 0.0f
@@ -40,51 +38,7 @@ class AoData() {
} }
companion object { companion object {
val black = AoData() val black: CornerLightData get() = CornerLightData()
}
}
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
}
} }
} }
@@ -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 * Instances of this interface are associated with [Model] vertices, and used to apply brightness and color
* values to a [RenderVertex]. * values to a [RenderVertex].
*/ */
interface Shader { interface ModelLighter {
/** /**
* Set shading values of a [RenderVertex] * 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 * @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]. */ /** Set all lighting values on the [RenderVertex] to match the given [CornerLightData]. */
fun RenderVertex.shade(shading: AoData) { fun RenderVertex.shade(shading: CornerLightData) {
brightness = shading.brightness; red = shading.red; green = shading.green; blue = shading.blue 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. */ /** Set the lighting values on the [RenderVertex] to a weighted average of the two [CornerLightData] instances. */
fun RenderVertex.shade(shading1: AoData, shading2: AoData, weight1: Float = 0.5f, weight2: Float = 0.5f) { 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) red = min(shading1.red * weight1 + shading2.red * weight2, 1.0f)
green = min(shading1.green * weight1 + shading2.green * weight2, 1.0f) green = min(shading1.green * weight1 + shading2.green * weight2, 1.0f)
blue = min(shading1.blue * weight1 + shading2.blue * 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[brightness] packed brightness value
* @param[color] packed color 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: * The resolver works the following way:
* - determines which face the _quad_ normal points towards (if not overridden) * - 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 * - 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 _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 [Shader] created by _edge_ * - 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[overrideFace] assume the given face instead of going by the _quad_ normal
* @param[corner] shader instantiation lambda for corner vertices * @param[corner] [ModelLighter] instantiation lambda for corner vertices
* @param[edge] shader instantiation lambda for edge midpoint vertices * @param[edge] [ModelLighter] instantiation lambda for edge midpoint vertices
*/ */
fun faceOrientedAuto(overrideFace: Direction? = null, fun faceOrientedAuto(overrideFace: Direction? = null,
corner: CornerShaderFactory? = null, corner: CornerShaderFactory? = null,
edge: EdgeShaderFactory? = null) = edge: EdgeShaderFactory? = null) =
fun(quad: Quad, vertex: Vertex): Shader { fun(quad: Quad, vertex: Vertex): ModelLighter {
val quadFace = overrideFace ?: quad.normal.nearestCardinal 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 (quadFace.vec + it.first.vec + it.second.vec) * 0.5
} }
val nearestEdge = nearestPosition(vertex.xyz, quadFace.perpendiculars) { 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: * The resolver works the following way:
* - determines which edge the _quad_ normal points towards (if not overridden) * - 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 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 * - 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[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<Direction, Direction>? = null, fun edgeOrientedAuto(overrideEdge: Pair<Direction, Direction>? = null,
corner: CornerShaderFactory) = 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 edgeDir = overrideEdge ?: nearestAngle(quad.normal, boxEdges) { it.first.vec + it.second.vec }.first
val nearestFace = nearestPosition(vertex.xyz, edgeDir.toList()) { it.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 (nearestFace.vec + it.first.vec + it.second.vec) * 0.5
}.first }.first
return corner(nearestFace, nearestCorner.first, nearestCorner.second) return corner(nearestFace, nearestCorner.first, nearestCorner.second)
} }
fun faceOrientedInterpolate(overrideFace: Direction? = null) = 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 resolver = faceOrientedAuto(overrideFace, edge = { face, edgeDir ->
val axis = axes.find { it != face.axis && it != edgeDir.axis }!! 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) val pos = vertex.xyz.dot(vec)
EdgeInterpolateFallback(face, edgeDir, pos) EdgeInterpolateFallback(face, edgeDir, pos)
}) })

View File

@@ -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
}
}
}

View File

@@ -1,4 +1,4 @@
package mods.octarinecore.client.render package mods.octarinecore.client.render.lighting
import mods.octarinecore.common.* import mods.octarinecore.common.*
import net.minecraft.util.Direction import net.minecraft.util.Direction
@@ -15,7 +15,7 @@ fun cornerAo(fallbackAxis: Direction.Axis): CornerShaderFactory = { face, dir1,
CornerSingleFallback(face, dir1, dir2, fallbackDir) CornerSingleFallback(face, dir1, dir2, fallbackDir)
} }
val cornerFlat = { face: Direction, dir1: Direction, dir2: Direction -> FaceFlat(face) } 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) CornerTri(face, dir1, dir2, func)
} }
val cornerAoMaxGreen = cornerAoTri { s1, s2 -> if (s1.green > s2.green) s1 else s2 } 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 // Shaders
// ================================ // ================================
object NoShader : Shader { object NoLighting : ModelLighter {
override fun shade(context: ShadingContext, vertex: RenderVertex) = vertex.shade(AoData.black) override fun shade(context: LightingCtx, vertex: RenderVertex) = vertex.shade(CornerLightData.black)
override fun rotate(rot: Rotation) = this 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) val offset = Int3(fallbackDir)
override fun shade(context: ShadingContext, vertex: RenderVertex) { override fun shade(context: LightingCtx, vertex: RenderVertex) {
val shading = context.aoShading(face, dir1, dir2) val shading = context.lighting(face, dir1, dir2)
if (shading.valid) if (shading.valid)
vertex.shade(shading) vertex.shade(shading)
else context.blockData(offset).let { else {
vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming) 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) 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 v1ok = v1 != null && v1.valid
val v2ok = v2 != null && v2.valid val v2ok = v2 != null && v2.valid
if (v1ok && v2ok) return func(v1!!, v2!!) 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, class CornerTri(val face: Direction, val dir1: Direction, val dir2: Direction,
val func: ((AoData, AoData)-> AoData)) : Shader { val func: ((CornerLightData, CornerLightData)-> CornerLightData)) : ModelLighter {
override fun shade(context: ShadingContext, vertex: RenderVertex) { override fun shade(context: LightingCtx, vertex: RenderVertex) {
var acc = accumulate( var acc = accumulate(
context.aoShading(face, dir1, dir2), context.lighting(face, dir1, dir2),
context.aoShading(dir1, face, dir2), context.lighting(dir1, face, dir2),
func) func)
acc = accumulate( acc = accumulate(
acc, acc,
context.aoShading(dir2, face, dir1), context.lighting(dir2, face, dir1),
func) 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) 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 offset = Int3(edgeDir)
val edgeAxis = axes.find { it != face.axis && it != edgeDir.axis }!! val edgeAxis = axes.find { it != face.axis && it != edgeDir.axis }!!
val weightN = (0.5 - pos).toFloat() val weightN = (0.5 - pos).toFloat()
val weightP = (0.5 + pos).toFloat() val weightP = (0.5 + pos).toFloat()
override fun shade(context: ShadingContext, vertex: RenderVertex) { override fun shade(context: LightingCtx, vertex: RenderVertex) {
val shadingP = context.aoShading(face, edgeDir, (edgeAxis to Direction.AxisDirection.POSITIVE).face) val shadingP = context.lighting(face, edgeDir, (edgeAxis to Direction.AxisDirection.POSITIVE).face)
val shadingN = context.aoShading(face, edgeDir, (edgeAxis to Direction.AxisDirection.NEGATIVE).face) val shadingN = context.lighting(face, edgeDir, (edgeAxis to Direction.AxisDirection.NEGATIVE).face)
if (!shadingP.valid && !shadingN.valid) context.blockData(offset).let { if (!shadingP.valid && !shadingN.valid) {
return vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming) return vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming)
} }
if (!shadingP.valid) return vertex.shade(shadingN) if (!shadingP.valid) return vertex.shade(shadingN)
if (!shadingN.valid) return vertex.shade(shadingP) 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, 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) val offset = Int3(edgeDir)
override fun shade(context: ShadingContext, vertex: RenderVertex) { override fun shade(context: LightingCtx, vertex: RenderVertex) {
var shading1 = context.aoShading(face1, edgeDir, face2) var shading1 = context.lighting(face1, edgeDir, face2)
var shading2 = context.aoShading(face2, edgeDir, face1) var shading2 = context.lighting(face2, edgeDir, face1)
var weight1 = weight var weight1 = weight
var weight2 = 1.0f - weight var weight2 = 1.0f - weight
if (!shading1.valid && !shading2.valid) context.blockData(offset).let { if (!shading1.valid && !shading2.valid) {
return vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming) return vertex.shade(context.brightness(offset) brMul fallbackDimming, context.color(offset) colorMul fallbackDimming)
} }
if (!shading1.valid) { shading1 = shading2; weight1 *= dimming } if (!shading1.valid) { shading1 = shading2; weight1 *= dimming }
if (!shading2.valid) { shading2 = shading1; weight2 *= 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) CornerInterpolateDimming(face1.rotate(rot), face2.rotate(rot), edgeDir.rotate(rot), weight, dimming, fallbackDimming)
} }
class FaceCenter(val face: Direction): Shader { class FaceCenter(val face: Direction): ModelLighter {
override fun shade(context: ShadingContext, vertex: RenderVertex) { override fun shade(context: LightingCtx, vertex: RenderVertex) {
vertex.red = 0.0f; vertex.green = 0.0f; vertex.blue = 0.0f; vertex.red = 0.0f; vertex.green = 0.0f; vertex.blue = 0.0f;
val b = IntArray(4) val b = IntArray(4)
faceCorners[face.ordinal].asList.forEachIndexed { idx, corner -> boxFaces[face].allCorners.forEachIndexed { idx, corner ->
val shading = context.aoShading(face, corner.first, corner.second) val shading = context.lighting(face, corner.first, corner.second)
vertex.red += shading.red vertex.red += shading.red
vertex.green += shading.green vertex.green += shading.green
vertex.blue += shading.blue vertex.blue += shading.blue
@@ -128,28 +128,25 @@ class FaceCenter(val face: Direction): Shader {
override fun rotate(rot: Rotation) = FaceCenter(face.rotate(rot)) override fun rotate(rot: Rotation) = FaceCenter(face.rotate(rot))
} }
class FaceFlat(val face: Direction): Shader { class FaceFlat(val face: Direction): ModelLighter {
override fun shade(context: ShadingContext, vertex: RenderVertex) { override fun shade(context: LightingCtx, vertex: RenderVertex) {
val color = context.blockData(Int3.zero).color vertex.shade(context.brightness(face.offset), context.color(Int3.zero))
vertex.shade(context.blockData(face.offset).brightness, color)
} }
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 { class FlatOffset(val offset: Int3): ModelLighter {
override fun shade(context: ShadingContext, vertex: RenderVertex) { override fun shade(context: LightingCtx, vertex: RenderVertex) {
context.blockData(offset).let { vertex.brightness = context.brightness(offset)
vertex.brightness = it.brightness vertex.setColor(context.color(offset))
vertex.setColor(it.color)
} }
} override fun rotate(rot: Rotation): ModelLighter = this
override fun rotate(rot: Rotation): Shader = this
} }
class FlatOffsetNoColor(val offset: Int3): Shader { class FlatOffsetNoColor(val offset: Int3): ModelLighter {
override fun shade(context: ShadingContext, vertex: RenderVertex) { override fun shade(context: LightingCtx, vertex: RenderVertex) {
vertex.brightness = context.blockData(offset).brightness vertex.brightness = context.brightness(offset)
vertex.red = 1.0f; vertex.green = 1.0f; vertex.blue = 1.0f 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
} }

View File

@@ -0,0 +1,5 @@
@file:JvmName("PixelFormat")
package mods.octarinecore.client.render.lighting
import java.awt.Color

View File

@@ -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)
}

View File

@@ -1,7 +1,8 @@
package mods.octarinecore.client.resource package mods.octarinecore.client.resource
import com.google.common.base.Joiner 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.Int3
import mods.octarinecore.common.config.IBlockMatcher import mods.octarinecore.common.config.IBlockMatcher
import mods.octarinecore.common.config.ModelTextureList 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.ResourceLocation
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockReader import net.minecraft.world.IBlockReader
import net.minecraft.world.IEnviromentBlockReader
import net.minecraftforge.client.event.TextureStitchEvent import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.client.model.ModelLoader
import net.minecraftforge.eventbus.api.Event import net.minecraftforge.eventbus.api.Event
import net.minecraftforge.eventbus.api.EventPriority import net.minecraftforge.eventbus.api.EventPriority
import net.minecraftforge.eventbus.api.SubscribeEvent import net.minecraftforge.eventbus.api.SubscribeEvent
@@ -26,8 +27,8 @@ import org.apache.logging.log4j.Logger
class LoadModelDataEvent(val bakery: ModelBakery) : Event() class LoadModelDataEvent(val bakery: ModelBakery) : Event()
interface ModelRenderRegistry<T> { interface ModelRenderRegistry<T> {
operator fun get(ctx: BlockContext) = get(ctx.blockState(Int3.zero), ctx.reader!!, ctx.pos) operator fun get(ctx: BlockCtx) = get(ctx.state(Int3.zero), ctx.world, ctx.pos)
operator fun get(ctx: BlockContext, offset: Int3) = get(ctx.blockState(offset), ctx.reader!!, ctx.pos + offset) 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? operator fun get(state: BlockState, world: IBlockReader, pos: BlockPos): T?
} }

View File

@@ -3,21 +3,19 @@ package mods.octarinecore.client.resource
import mods.octarinecore.client.render.Model import mods.octarinecore.client.render.Model
import mods.octarinecore.common.Double3 import mods.octarinecore.common.Double3
import mods.octarinecore.common.Int3 import mods.octarinecore.common.Int3
import mods.octarinecore.resource.Identifier
import mods.octarinecore.resource.Sprite
import mods.octarinecore.stripEnd import mods.octarinecore.stripEnd
import mods.octarinecore.stripStart import mods.octarinecore.stripStart
import net.minecraft.client.renderer.texture.AtlasTexture 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.BlockPos
import net.minecraft.util.math.MathHelper import net.minecraft.util.math.MathHelper
import net.minecraft.world.IWorld import net.minecraft.world.IWorld
import net.minecraft.world.gen.SimplexNoiseGenerator import net.minecraft.world.gen.SimplexNoiseGenerator
import net.minecraftforge.client.event.TextureStitchEvent import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.world.WorldEvent import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.eventbus.api.SubscribeEvent import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.client.event.ConfigChangedEvent
import net.minecraftforge.fml.config.ModConfig import net.minecraftforge.fml.config.ModConfig
import java.util.* import java.util.*
@@ -25,8 +23,8 @@ enum class Atlas(val basePath: String) {
BLOCKS("textures"), BLOCKS("textures"),
PARTICLES("textures/particle"); PARTICLES("textures/particle");
fun wrap(resource: ResourceLocation) = ResourceLocation(resource.namespace, "$basePath/${resource.path}.png") fun wrap(resource: Identifier) = Identifier(resource.namespace, "$basePath/${resource.path}.png")
fun unwrap(resource: ResourceLocation) = resource.stripStart("$basePath/").stripEnd(".png") fun unwrap(resource: Identifier) = resource.stripStart("$basePath/").stripEnd(".png")
fun matches(event: TextureStitchEvent) = event.map.basePath == basePath fun matches(event: TextureStitchEvent) = event.map.basePath == basePath
} }
@@ -65,10 +63,10 @@ open class ResourceHandler(
// ============================ // ============================
// Resource declarations // Resource declarations
// ============================ // ============================
fun iconStatic(location: ()->ResourceLocation) = IconHolder(location).apply { resources.add(this) } fun iconStatic(location: ()->Identifier) = IconHolder(location).apply { resources.add(this) }
fun iconStatic(location: ResourceLocation) = iconStatic { location } fun iconStatic(location: Identifier) = iconStatic { location }
fun iconStatic(domain: String, path: String) = iconStatic(ResourceLocation(domain, path)) fun iconStatic(domain: String, path: String) = iconStatic(Identifier(domain, path))
fun iconSet(targetAtlas: Atlas = Atlas.BLOCKS, location: (Int)->ResourceLocation) = IconSet(targetAtlas, location).apply { this@ResourceHandler.resources.add(this) } 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 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 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) } fun vectorSet(num: Int, init: (Int)-> Double3) = VectorSet(num, init).apply { resources.add(this) }
@@ -104,9 +102,9 @@ open class ResourceHandler(
// ============================ // ============================
// Resource container classes // Resource container classes
// ============================ // ============================
class IconHolder(val location: ()->ResourceLocation) : IStitchListener { class IconHolder(val location: ()->Identifier) : IStitchListener {
var iconRes: ResourceLocation? = null var iconRes: Identifier? = null
var icon: TextureAtlasSprite? = null var icon: Sprite? = null
override fun onPreStitch(event: TextureStitchEvent.Pre) { override fun onPreStitch(event: TextureStitchEvent.Pre) {
iconRes = location() iconRes = location()
event.addSprite(iconRes) event.addSprite(iconRes)
@@ -121,9 +119,9 @@ class ModelHolder(val init: Model.()->Unit): IConfigChangeListener {
override fun onConfigChange() { model = Model().apply(init) } override fun onConfigChange() { model = Model().apply(init) }
} }
class IconSet(val targetAtlas: Atlas, val location: (Int)->ResourceLocation) : IStitchListener { class IconSet(val targetAtlas: Atlas, val location: (Int)->Identifier) : IStitchListener {
val resources = arrayOfNulls<ResourceLocation>(16) val resources = arrayOfNulls<Identifier>(16)
val icons = arrayOfNulls<TextureAtlasSprite>(16) val icons = arrayOfNulls<Sprite>(16)
var num = 0 var num = 0
override fun onPreStitch(event: TextureStitchEvent.Pre) { override fun onPreStitch(event: TextureStitchEvent.Pre) {

View File

@@ -1,10 +1,8 @@
@file:JvmName("Utils") @file:JvmName("Utils")
package mods.octarinecore.client.resource package mods.octarinecore.client.resource
import mods.betterfoliage.loader.Refs
import mods.octarinecore.PI2 import mods.octarinecore.PI2
import mods.octarinecore.client.render.HSB import mods.octarinecore.client.render.lighting.HSB
import mods.octarinecore.stripEnd
import mods.octarinecore.stripStart import mods.octarinecore.stripStart
import mods.octarinecore.tryDefault import mods.octarinecore.tryDefault
import net.minecraft.client.Minecraft import net.minecraft.client.Minecraft
@@ -16,7 +14,6 @@ import net.minecraft.resources.IResource
import net.minecraft.resources.IResourceManager import net.minecraft.resources.IResourceManager
import net.minecraft.resources.SimpleReloadableResourceManager import net.minecraft.resources.SimpleReloadableResourceManager
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.IModel
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream

View File

@@ -15,9 +15,9 @@ val axes = listOf(X, Y, Z)
val axisDirs = listOf(POSITIVE, NEGATIVE) val axisDirs = listOf(POSITIVE, NEGATIVE)
val Direction.dir: AxisDirection get() = axisDirection val Direction.dir: AxisDirection get() = axisDirection
val AxisDirection.sign: String get() = when(this) { POSITIVE -> "+"; NEGATIVE -> "-" } val AxisDirection.sign: String get() = when(this) { POSITIVE -> "+"; NEGATIVE -> "-" }
val forgeDirs = Direction.values() val allDirections = Direction.values()
val forgeDirsHorizontal = listOf(NORTH, SOUTH, EAST, WEST) val horizontalDirections = listOf(NORTH, SOUTH, EAST, WEST)
val forgeDirOffsets = forgeDirs.map { Int3(it) } val allDirOffsets = allDirections.map { Int3(it) }
val Pair<Axis, AxisDirection>.face: Direction get() = when(this) { val Pair<Axis, AxisDirection>.face: Direction get() = when(this) {
X to POSITIVE -> EAST; X to NEGATIVE -> WEST; X to POSITIVE -> EAST; X to NEGATIVE -> WEST;
Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN; Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN;
@@ -25,7 +25,7 @@ val Pair<Axis, AxisDirection>.face: Direction get() = when(this) {
} }
val Direction.perpendiculars: List<Direction> get() = val Direction.perpendiculars: List<Direction> get() =
axes.filter { it != this.axis }.cross(axisDirs).map { it.face } 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 */ /** Old ForgeDirection rotation matrix yanked from 1.7.10 */
val ROTATION_MATRIX: Array<IntArray> get() = arrayOf( val ROTATION_MATRIX: Array<IntArray> get() = arrayOf(
@@ -43,6 +43,7 @@ val ROTATION_MATRIX: Array<IntArray> get() = arrayOf(
operator fun Direction.times(scale: Double) = operator fun Direction.times(scale: Double) =
Double3(directionVec.x.toDouble() * scale, directionVec.y.toDouble() * scale, directionVec.z.toDouble() * scale) 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()) 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) 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. */ /** 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) 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 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 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. */ /** 3D vector of [Int]s. Offers both mutable operations, and immutable operations in operator notation. */
@@ -170,8 +171,8 @@ class Rotation(val forward: Array<Direction>, val reverse: Array<Direction>) {
companion object { companion object {
// Forge rotation matrix is left-hand // Forge rotation matrix is left-hand
val rot90 = Array(6) { idx -> Rotation(forgeDirs[idx].opposite.rotations, forgeDirs[idx].rotations) } val rot90 = Array(6) { idx -> Rotation(allDirections[idx].opposite.rotations, allDirections[idx].rotations) }
val identity = Rotation(forgeDirs, forgeDirs) val identity = Rotation(allDirections, allDirections)
} }
} }
@@ -179,8 +180,24 @@ class Rotation(val forward: Array<Direction>, val reverse: Array<Direction>) {
// ================================ // ================================
// Miscellaneous // Miscellaneous
// ================================ // ================================
inline operator fun <reified T> Array<T>.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 */ /** 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. * Get the closest object to the specified point from a list of objects.
@@ -203,23 +220,3 @@ fun <T> nearestPosition(vertex: Double3, objs: Iterable<T>, objPos: (T)-> Double
*/ */
fun <T> nearestAngle(vector: Double3, objs: Iterable<T>, objAngle: (T)-> Double3): Pair<T, Double> = fun <T> nearestAngle(vector: Double3, objs: Iterable<T>, objAngle: (T)-> Double3): Pair<T, Double> =
objs.map { it to objAngle(it).dot(vector) }.maxBy { it.second }!! objs.map { it to objAngle(it).dot(vector) }.maxBy { it.second }!!
data class FaceCorners(val topLeft: Pair<Direction, Direction>,
val topRight: Pair<Direction, Direction>,
val bottomLeft: Pair<Direction, Direction>,
val bottomRight: Pair<Direction, Direction>) {
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)
}}