[WIP] Roll all rendering parameters into a single context
+ split project into platform-dependent and -independent parts
This commit is contained in:
10
src/forge/kotlin/mods/octarinecore/resource/Aliases.kt
Normal file
10
src/forge/kotlin/mods/octarinecore/resource/Aliases.kt
Normal 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
|
||||
@@ -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>()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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 = { _, _, _, _, _ -> }
|
||||
@@ -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)
|
||||
//}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@file:JvmName("PixelFormat")
|
||||
package mods.octarinecore.client.render.lighting
|
||||
|
||||
import java.awt.Color
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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?
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user