[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.texture.*
import mods.octarinecore.client.gui.textComponent
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
import mods.octarinecore.client.render.RenderDecorator
import mods.octarinecore.client.resource.CenteringTextureGenerator
import mods.octarinecore.client.resource.GeneratorPack
import mods.octarinecore.client.resource.IConfigChangeListener
import net.minecraft.block.BlockState
import net.minecraft.client.Minecraft
import net.minecraft.util.math.BlockPos
import net.minecraft.util.text.TextFormatting
import net.minecraft.util.text.TranslationTextComponent
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.config.ModConfig
import net.minecraftforge.registries.ForgeRegistries
import org.apache.logging.log4j.Level
@@ -29,7 +26,7 @@ import org.apache.logging.log4j.Level
* except for the call hooks.
*/
object Client {
var renderers= emptyList<AbstractBlockRenderingHandler>()
var renderers= emptyList<RenderDecorator>()
var configListeners = emptyList<IConfigChangeListener>()
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.render.*
import mods.betterfoliage.loader.Refs
import mods.octarinecore.client.render.blockContext
import mods.octarinecore.ThreadLocalDelegate
import mods.octarinecore.client.render.*
import mods.octarinecore.client.render.lighting.DefaultLightingCtx
import mods.octarinecore.client.render.lighting.LightingCtx
import mods.octarinecore.client.resource.LoadModelDataEvent
import mods.octarinecore.common.plus
import mods.octarinecore.metaprog.allAvailable
@@ -75,6 +78,7 @@ fun getVoxelShapeOverride(state: BlockState, reader: IBlockReader, pos: BlockPos
return state.func_215702_a(reader, pos, dir)
}
val lightingCtx by ThreadLocalDelegate { DefaultLightingCtx(BasicBlockCtx(NonNullWorld, BlockPos.ZERO)) }
fun renderWorldBlock(dispatcher: BlockRendererDispatcher,
state: BlockState,
pos: BlockPos,
@@ -84,21 +88,32 @@ fun renderWorldBlock(dispatcher: BlockRendererDispatcher,
modelData: IModelData,
layer: BlockRenderLayer
): Boolean {
// build context
val blockCtx = CachedBlockCtx(reader, pos)
val renderCtx = RenderCtx(dispatcher, buffer, layer, random)
lightingCtx.reset(blockCtx)
val combinedCtx = CombinedContext(blockCtx, renderCtx, lightingCtx)
// loop render decorators
val doBaseRender = state.canRenderInLayer(layer) || (layer == targetCutoutLayer && state.canRenderInLayer(otherCutoutLayer))
blockContext.let { ctx ->
ctx.set(reader, pos)
Client.renderers.forEach { renderer ->
if (renderer.isEligible(ctx)) {
// render on the block's default layer
// also render on the cutout layer if the renderer requires it
if (doBaseRender || (renderer.addToCutout && layer == targetCutoutLayer)) {
return renderer.render(ctx, dispatcher, buffer, random, modelData, layer)
}
Client.renderers.forEach { renderer ->
if (renderer.isEligible(combinedCtx)) {
// render on the block's default layer
// also render on the cutout layer if the renderer requires it
val doCutoutRender = renderer.renderOnCutout && layer == targetCutoutLayer
val stopRender = renderer.onlyOnCutout && !layer.isCutout
if ((doBaseRender || doCutoutRender) && !stopRender) {
renderer.render(combinedCtx)
return combinedCtx.hasRendered
}
}
}
return if (doBaseRender) dispatcher.renderBlock(state, pos, reader, buffer, random, modelData) else false
// no render decorators have taken on this block, proceed to normal rendering
combinedCtx.render()
return combinedCtx.hasRendered
}
fun canRenderInLayerOverride(state: BlockState, layer: BlockRenderLayer) = state.canRenderInLayer(layer) || layer == targetCutoutLayer

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import mods.betterfoliage.client.texture.LeafParticleRegistry
import mods.betterfoliage.client.texture.LeafRegistry
import mods.octarinecore.PI2
import mods.octarinecore.client.render.AbstractEntityFX
import mods.octarinecore.client.render.HSB
import mods.octarinecore.client.render.lighting.HSB
import mods.octarinecore.common.Double3
import mods.octarinecore.minmax
import mods.octarinecore.random

View File

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

View File

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

View File

@@ -2,11 +2,11 @@ package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.render.column.ColumnTextureInfo
import mods.betterfoliage.client.render.column.SimpleColumnInfo
import mods.octarinecore.client.render.*
import mods.octarinecore.client.render.lighting.*
import mods.octarinecore.client.resource.ModelRenderRegistryConfigurable
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
@@ -31,7 +31,7 @@ object StandardCactusRegistry : ModelRenderRegistryConfigurable<ColumnTextureInf
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 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")
}
override fun isEligible(ctx: BlockContext): Boolean =
override fun isEligible(ctx: CombinedContext): Boolean =
Config.enabled && Config.cactus.enabled &&
StandardCactusRegistry[ctx] != null
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
// render the whole block on the cutout layer
if (!layer.isCutout) return false
override val onlyOnCutout get() = true
// get AO data
modelRenderer.updateShading(Int3.zero, allFaces)
val icons = StandardCactusRegistry[ctx] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null)
override fun render(ctx: CombinedContext) {
val icons = StandardCactusRegistry[ctx]!!
modelRenderer.render(
renderer,
ctx.render(
modelStem.model,
Rotation.identity,
icon = { ctx, qi, q -> when(qi) {
0 -> icons.bottom(ctx, qi, q); 1 -> icons.top(ctx, qi, q); else -> icons.side(ctx, qi, q)
} },
postProcess = noPost
} }
)
modelRenderer.render(
renderer,
modelCross[ctx.random(0)],
Rotation.identity,
icon = { _, _, _ -> iconCross.icon!!},
postProcess = noPost
ctx.render(
modelCross[ctx.semiRandom(0)],
icon = { _, _, _ -> iconCross.icon!!}
)
modelRenderer.render(
renderer,
modelArm[ctx.random(1)],
cactusArmRotation[ctx.random(2) % 4],
icon = { _, _, _ -> iconArm[ctx.random(3)]!!},
postProcess = noPost
ctx.render(
modelArm[ctx.semiRandom(1)],
cactusArmRotation[ctx.semiRandom(2) % 4],
icon = { _, _, _ -> iconArm[ctx.semiRandom(3)]!!}
)
return true
}
}

View File

@@ -1,36 +1,45 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.texture.GrassRegistry
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.render.offset
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.render.RenderDecorator
import mods.octarinecore.common.Int3
import mods.octarinecore.common.forgeDirsHorizontal
import mods.octarinecore.common.horizontalDirections
import mods.octarinecore.common.offset
import net.minecraft.block.Block
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.tags.BlockTags
import net.minecraft.util.BlockRenderLayer
import net.minecraftforge.client.model.data.IModelData
import java.util.*
class RenderConnectedGrass : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
override fun isEligible(ctx: BlockContext) =
class RenderConnectedGrass : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.connectedGrass.enabled &&
BlockTags.DIRT_LIKE.contains(ctx.block) &&
BlockTags.DIRT_LIKE.contains(ctx.state.block) &&
GrassRegistry[ctx, up1] != null &&
(Config.connectedGrass.snowEnabled || !ctx.blockState(up2).isSnow)
(Config.connectedGrass.snowEnabled || !ctx.state(up2).isSnow)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
override fun render(ctx: CombinedContext) {
// if the block sides are not visible anyway, render normally
if (forgeDirsHorizontal.none { ctx.shouldSideBeRendered(it) }) return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer)
if (horizontalDirections.none { ctx.shouldSideBeRendered(it) }) {
ctx.render()
} else {
ctx.exchange(Int3.zero, up1).exchange(up1, up2).render()
}
}
}
return ctx.offset(Int3.zero, up1).offset(up1, up2).let { offsetCtx ->
renderWorldBlockBase(offsetCtx, dispatcher, renderer, random, modelData, layer)
class RenderConnectedGrassLog : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass &&
BlockTags.DIRT_LIKE.contains(ctx.state.block) &&
LogRegistry[ctx, up1] != null
override fun render(ctx: CombinedContext) {
val grassDir = horizontalDirections.find { GrassRegistry[ctx, it.offset] != null }
if (grassDir == null) {
ctx.render()
} else {
ctx.exchange(Int3.zero, grassDir.offset).render()
}
}
}

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

View File

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

View File

@@ -1,30 +1,27 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.OptifineCustomColors
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.texture.LeafRegistry
import mods.octarinecore.PI2
import mods.octarinecore.client.render.*
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.render.RenderDecorator
import mods.octarinecore.client.render.lighting.FlatOffset
import mods.octarinecore.client.render.lighting.cornerAoMaxGreen
import mods.octarinecore.client.render.lighting.edgeOrientedAuto
import mods.octarinecore.common.Double3
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import mods.octarinecore.common.allDirections
import mods.octarinecore.common.vec
import mods.octarinecore.random
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.Direction.*
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import java.lang.Math.cos
import java.lang.Math.sin
import java.util.*
class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderLeaves : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
val leavesModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
@@ -41,34 +38,28 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterF
UP.vec * random(-1.0, 1.0) * Config.leaves.vOffset
}
override fun isEligible(ctx: BlockContext) =
override fun isEligible(ctx: CombinedContext) =
Config.enabled &&
Config.leaves.enabled &&
LeafRegistry[ctx] != null &&
!(Config.leaves.hideInternal && ctx.isSurroundedByNormal)
!(Config.leaves.hideInternal && allDirections.all { ctx.offset(it).isNormalCube } )
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
val isSnowed = ctx.blockState(up1).isSnow
val leafInfo = LeafRegistry[ctx]
if (leafInfo == null) {
// shouldn't happen
Client.logRenderError(ctx.blockState(Int3.zero), ctx.pos)
return renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer)
}
override val onlyOnCutout get() = true
override fun render(ctx: CombinedContext) {
val isSnowed = ctx.state(UP).isSnow
val leafInfo = LeafRegistry[ctx]!!
val blockColor = OptifineCustomColors.getBlockColor(ctx)
renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, layer)
if (!layer.isCutout) return true
ctx.render(force = true)
modelRenderer.updateShading(Int3.zero, allFaces)
ShadersModIntegration.leaves(renderer) {
ShadersModIntegration.leaves(ctx) {
val rand = ctx.semiRandomArray(2)
(if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation ->
modelRenderer.render(
renderer,
ctx.render(
leavesModel.model,
rotation,
ctx.blockCenter + perturbs[rand[0]],
translation = ctx.blockCenter + perturbs[rand[0]],
icon = { _, _, _ -> leafInfo.roundLeafTexture },
postProcess = { _, _, _, _, _ ->
rotateUV(rand[1])
@@ -76,16 +67,12 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterF
}
)
}
if (isSnowed && Config.leaves.snowEnabled) modelRenderer.render(
renderer,
if (isSnowed && Config.leaves.snowEnabled) ctx.render(
leavesModel.model,
Rotation.identity,
ctx.blockCenter + perturbs[rand[0]],
translation = ctx.blockCenter + perturbs[rand[0]],
icon = { _, _, _ -> snowedIcon[rand[1]]!! },
postProcess = whitewash
)
}
return true
}
}

View File

@@ -5,20 +5,16 @@ import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.octarinecore.client.render.*
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.render.RenderDecorator
import mods.octarinecore.client.render.lighting.FlatOffsetNoColor
import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG
import java.util.*
class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderLilypad : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
val rootModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -1.5, yTop = -0.5)
@@ -40,41 +36,28 @@ class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, Better
Client.log(DEBUG, "Registered ${flowerIcon.num} lilypad flower textures")
}
override fun isEligible(ctx: BlockContext): Boolean =
override fun isEligible(ctx: CombinedContext): Boolean =
Config.enabled && Config.lilypad.enabled &&
BlockConfig.lilypad.matchesClass(ctx.block)
BlockConfig.lilypad.matchesClass(ctx.state.block)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
// render the whole block on the cutout layer
if (!layer.isCutout) return false
renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null)
modelRenderer.updateShading(Int3.zero, allFaces)
override fun render(ctx: CombinedContext) {
ctx.render()
val rand = ctx.semiRandomArray(5)
ShadersModIntegration.grass(renderer) {
modelRenderer.render(
renderer,
ShadersModIntegration.grass(ctx) {
ctx.render(
rootModel.model,
Rotation.identity,
ctx.blockCenter.add(perturbs[rand[2]]),
translation = ctx.blockCenter.add(perturbs[rand[2]]),
forceFlat = true,
icon = { ctx, qi, q -> rootIcon[rand[qi and 1]]!! },
postProcess = noPost
icon = { ctx, qi, q -> rootIcon[rand[qi and 1]]!! }
)
}
if (rand[3] < Config.lilypad.flowerChance) modelRenderer.render(
renderer,
if (rand[3] < Config.lilypad.flowerChance) ctx.render(
flowerModel.model,
Rotation.identity,
ctx.blockCenter.add(perturbs[rand[4]]),
translation = ctx.blockCenter.add(perturbs[rand[4]]),
forceFlat = true,
icon = { _, _, _ -> flowerIcon[rand[0]]!! },
postProcess = noPost
icon = { _, _, _ -> flowerIcon[rand[0]]!! }
)
return true
}
}

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

View File

@@ -4,21 +4,15 @@ import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.AbstractBlockRenderingHandler
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.render.modelRenderer
import mods.octarinecore.client.render.CombinedContext
import mods.octarinecore.client.render.RenderDecorator
import mods.octarinecore.client.render.noPost
import mods.octarinecore.common.Double3
import mods.octarinecore.common.Rotation
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.data.IModelData
import org.apache.logging.log4j.Level.DEBUG
import java.util.*
class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
class RenderMycelium : RenderDecorator(BetterFoliage.MOD_ID, BetterFoliage.modBus) {
val myceliumIcon = iconSet { idx -> ResourceLocation(BetterFoliage.MOD_ID, "blocks/better_mycel_$idx") }
val myceliumModel = modelSet(64) { idx -> RenderGrass.grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)(idx) }
@@ -27,32 +21,25 @@ class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, Bette
Client.log(DEBUG, "Registered ${myceliumIcon.num} mycelium textures")
}
override fun isEligible(ctx: BlockContext): Boolean {
override fun isEligible(ctx: CombinedContext): Boolean {
if (!Config.enabled || !Config.shortGrass.myceliumEnabled) return false
return BlockConfig.mycelium.matchesClass(ctx.block)
return BlockConfig.mycelium.matchesClass(ctx.state.block)
}
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: BufferBuilder, random: Random, modelData: IModelData, layer: BlockRenderLayer): Boolean {
// render the whole block on the cutout layer
if (!layer.isCutout) return false
val isSnowed = ctx.blockState(up1).isSnow
renderWorldBlockBase(ctx, dispatcher, renderer, random, modelData, null)
if (isSnowed && !Config.shortGrass.snowEnabled) return true
if (ctx.isNormalCube(up1)) return true
override fun render(ctx: CombinedContext) {
ctx.render()
if (!ctx.isCutout) return
val isSnowed = ctx.state(UP).isSnow
if (isSnowed && !Config.shortGrass.snowEnabled) return
if (ctx.offset(UP).isNormalCube) return
val rand = ctx.semiRandomArray(2)
modelRenderer.render(
renderer,
ctx.render(
myceliumModel[rand[0]],
Rotation.identity,
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = { _, qi, _ -> myceliumIcon[rand[qi and 1]]!! },
postProcess = if (isSnowed) whitewash else noPost
)
return true
}
}

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ package mods.betterfoliage.client.render
import mods.octarinecore.PI2
import mods.octarinecore.client.render.Model
import mods.octarinecore.client.render.PostProcessLambda
import mods.octarinecore.client.render.lighting.PostProcessLambda
import mods.octarinecore.client.render.Quad
import mods.octarinecore.common.Double3
import mods.octarinecore.common.Int3

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

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.HSB
import mods.octarinecore.client.render.lighting.HSB
import mods.octarinecore.client.resource.*
import mods.octarinecore.common.config.ConfigurableBlockMatcher
import mods.octarinecore.common.config.ModelTextureList

View File

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

View File

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

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

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

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

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 net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import java.lang.Math.min
import java.util.*
typealias EdgeShaderFactory = (Direction, Direction) -> Shader
typealias CornerShaderFactory = (Direction, Direction, Direction) -> Shader
typealias ShaderFactory = (Quad, Vertex) -> Shader
typealias EdgeShaderFactory = (Direction, Direction) -> ModelLighter
typealias CornerShaderFactory = (Direction, Direction, Direction) -> ModelLighter
typealias ShaderFactory = (Quad, Vertex) -> ModelLighter
/** Holds shading values for block corners as calculated by vanilla Minecraft rendering. */
class AoData() {
/** Holds lighting values for block corners as calculated by vanilla Minecraft rendering. */
class CornerLightData {
var valid = false
var brightness = 0
var red: Float = 0.0f
@@ -40,51 +38,7 @@ class AoData() {
}
companion object {
val black = AoData()
}
}
class AoFaceData(val face: Direction) {
companion object {
private val aoFactory = BlockModelRenderer.AmbientOcclusionFace::class.java.let {
it.getDeclaredConstructor(BlockModelRenderer::class.java).apply { isAccessible = true }
}.let { ctor -> { ctor.newInstance(Minecraft.getInstance().blockRendererDispatcher.blockModelRenderer) } }
}
val ao = aoFactory()
val top = faceCorners[face.ordinal].topLeft.first
val left = faceCorners[face.ordinal].topLeft.second
val topLeft = AoData()
val topRight = AoData()
val bottomLeft = AoData()
val bottomRight = AoData()
val ordered = when(face) {
DOWN -> listOf(topLeft, bottomLeft, bottomRight, topRight)
UP -> listOf(bottomRight, topRight, topLeft, bottomLeft)
NORTH -> listOf(bottomLeft, bottomRight, topRight, topLeft)
SOUTH -> listOf(topLeft, bottomLeft, bottomRight, topRight)
WEST -> listOf(bottomLeft, bottomRight, topRight, topLeft)
EAST -> listOf(topRight, topLeft, bottomLeft, bottomRight)
}
fun update(offset: Int3, useBounds: Boolean = false, multiplier: Float = 1.0f) {
val ctx = blockContext
val blockState = ctx.blockState(offset)
val quadBounds: FloatArray = FloatArray(12)
val flags = BitSet(3).apply { set(0) }
ao.updateVertexBrightness(ctx.reader!!, blockState, ctx.pos + offset, face, quadBounds, flags)
ordered.forEachIndexed { idx, aoData -> aoData.set(ao.vertexBrightness[idx], ao.vertexColorMultiplier[idx] * multiplier) }
}
operator fun get(dir1: Direction, dir2: Direction): AoData {
val isTop = top == dir1 || top == dir2
val isLeft = left == dir1 || left == dir2
return if (isTop) {
if (isLeft) topLeft else topRight
} else {
if (isLeft) bottomLeft else bottomRight
}
val black: CornerLightData get() = CornerLightData()
}
}
@@ -92,27 +46,27 @@ class AoFaceData(val face: Direction) {
* Instances of this interface are associated with [Model] vertices, and used to apply brightness and color
* values to a [RenderVertex].
*/
interface Shader {
interface ModelLighter {
/**
* Set shading values of a [RenderVertex]
*
* @param[context] context that can be queried for shading data in a [Model]-relative frame of reference
* @param[context] context that can be queried for lighting data in a [Model]-relative frame of reference
* @param[vertex] the [RenderVertex] to manipulate
*/
fun shade(context: ShadingContext, vertex: RenderVertex)
fun shade(context: LightingCtx, vertex: RenderVertex)
/**
* Return a new rotated version of this [Shader]. Used during [Model] setup when rotating the model itself.
* Return a new rotated version of this [ModelLighter]. Used during [Model] setup when rotating the model itself.
*/
fun rotate(rot: Rotation): Shader
fun rotate(rot: Rotation): ModelLighter
/** Set all shading values on the [RenderVertex] to match the given [AoData]. */
fun RenderVertex.shade(shading: AoData) {
/** Set all lighting values on the [RenderVertex] to match the given [CornerLightData]. */
fun RenderVertex.shade(shading: CornerLightData) {
brightness = shading.brightness; red = shading.red; green = shading.green; blue = shading.blue
}
/** Set the shading values on the [RenderVertex] to a weighted average of the two [AoData] instances. */
fun RenderVertex.shade(shading1: AoData, shading2: AoData, weight1: Float = 0.5f, weight2: Float = 0.5f) {
/** Set the lighting values on the [RenderVertex] to a weighted average of the two [CornerLightData] instances. */
fun RenderVertex.shade(shading1: CornerLightData, shading2: CornerLightData, weight1: Float = 0.5f, weight2: Float = 0.5f) {
red = min(shading1.red * weight1 + shading2.red * weight2, 1.0f)
green = min(shading1.green * weight1 + shading2.green * weight2, 1.0f)
blue = min(shading1.blue * weight1 + shading2.blue * weight2, 1.0f)
@@ -120,7 +74,7 @@ interface Shader {
}
/**
* Set the shading values on the [RenderVertex] directly.
* Set the lighting values on the [RenderVertex] directly.
*
* @param[brightness] packed brightness value
* @param[color] packed color value
@@ -131,23 +85,23 @@ interface Shader {
}
/**
* Returns a shader resolver for quads that point towards one of the 6 block faces.
* Returns a [ModelLighter] resolver for quads that point towards one of the 6 block faces.
* The resolver works the following way:
* - determines which face the _quad_ normal points towards (if not overridden)
* - determines the distance of the _vertex_ to the corners and edge midpoints on that block face
* - if _corner_ is given, and the _vertex_ is closest to a block corner, returns the [Shader] created by _corner_
* - if _edge_ is given, and the _vertex_ is closest to an edge midpoint, returns the [Shader] created by _edge_
* - if _corner_ is given, and the _vertex_ is closest to a block corner, returns the [ModelLighter] created by _corner_
* - if _edge_ is given, and the _vertex_ is closest to an edge midpoint, returns the [ModelLighter] created by _edge_
*
* @param[overrideFace] assume the given face instead of going by the _quad_ normal
* @param[corner] shader instantiation lambda for corner vertices
* @param[edge] shader instantiation lambda for edge midpoint vertices
* @param[corner] [ModelLighter] instantiation lambda for corner vertices
* @param[edge] [ModelLighter] instantiation lambda for edge midpoint vertices
*/
fun faceOrientedAuto(overrideFace: Direction? = null,
corner: CornerShaderFactory? = null,
edge: EdgeShaderFactory? = null) =
fun(quad: Quad, vertex: Vertex): Shader {
fun(quad: Quad, vertex: Vertex): ModelLighter {
val quadFace = overrideFace ?: quad.normal.nearestCardinal
val nearestCorner = nearestPosition(vertex.xyz, faceCorners[quadFace.ordinal].asList) {
val nearestCorner = nearestPosition(vertex.xyz, boxFaces[quadFace].allCorners) {
(quadFace.vec + it.first.vec + it.second.vec) * 0.5
}
val nearestEdge = nearestPosition(vertex.xyz, quadFace.perpendiculars) {
@@ -159,32 +113,32 @@ fun faceOrientedAuto(overrideFace: Direction? = null,
}
/**
* Returns a shader resolver for quads that point towards one of the 12 block edges.
* Returns a ModelLighter resolver for quads that point towards one of the 12 block edges.
* The resolver works the following way:
* - determines which edge the _quad_ normal points towards (if not overridden)
* - determines which face midpoint the _vertex_ is closest to, of the 2 block faces that share this edge
* - determines which block corner _of this face_ the _vertex_ is closest to
* - returns the [Shader] created by _corner_
* - returns the [ModelLighter] created by _corner_
*
* @param[overrideEdge] assume the given edge instead of going by the _quad_ normal
* @param[corner] shader instantiation lambda
* @param[corner] ModelLighter instantiation lambda
*/
fun edgeOrientedAuto(overrideEdge: Pair<Direction, Direction>? = null,
corner: CornerShaderFactory) =
fun(quad: Quad, vertex: Vertex): Shader {
fun(quad: Quad, vertex: Vertex): ModelLighter {
val edgeDir = overrideEdge ?: nearestAngle(quad.normal, boxEdges) { it.first.vec + it.second.vec }.first
val nearestFace = nearestPosition(vertex.xyz, edgeDir.toList()) { it.vec }.first
val nearestCorner = nearestPosition(vertex.xyz, faceCorners[nearestFace.ordinal].asList) {
val nearestCorner = nearestPosition(vertex.xyz, boxFaces[nearestFace].allCorners) {
(nearestFace.vec + it.first.vec + it.second.vec) * 0.5
}.first
return corner(nearestFace, nearestCorner.first, nearestCorner.second)
}
fun faceOrientedInterpolate(overrideFace: Direction? = null) =
fun(quad: Quad, vertex: Vertex): Shader {
fun(quad: Quad, vertex: Vertex): ModelLighter {
val resolver = faceOrientedAuto(overrideFace, edge = { face, edgeDir ->
val axis = axes.find { it != face.axis && it != edgeDir.axis }!!
val vec = Double3((axis to Direction.AxisDirection.POSITIVE).face)
val vec = Double3((axis to AxisDirection.POSITIVE).face)
val pos = vertex.xyz.dot(vec)
EdgeInterpolateFallback(face, edgeDir, pos)
})

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

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

View File

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

View File

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

View File

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