[WIP] major rewrite, grass and leaves working already

This commit is contained in:
octarine-noise
2021-05-06 22:40:32 +02:00
parent 09ccb83e8b
commit f44d2a7a50
96 changed files with 2348 additions and 3531 deletions

View File

@@ -1,10 +1,8 @@
package mods.betterfoliage.render
import mods.betterfoliage.config.Config
import mods.betterfoliage.texture.LeafParticleRegistry
import mods.betterfoliage.texture.LeafRegistry
import mods.betterfoliage.render.old.AbstractEntityFX
import mods.betterfoliage.render.lighting.HSB
import mods.betterfoliage.render.old.HSB
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.minmax
@@ -18,7 +16,7 @@ import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.event.TickEvent
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.eventbus.api.SubscribeEvent
import java.util.*
import java.util.Random
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
@@ -47,15 +45,15 @@ class EntityFallingLeavesFX(
particleScale = Config.fallingLeaves.size.toFloat() * 0.1f
val state = world.getBlockState(pos)
val leafInfo = LeafRegistry[state, world, pos]
// val leafInfo = LeafRegistry[state, world, pos]
val blockColor = Minecraft.getInstance().blockColors.getColor(state, world, pos, 0)
if (leafInfo != null) {
sprite = leafInfo.particleTextures[rand.nextInt(1024)]
calculateParticleColor(leafInfo.averageColor, blockColor)
} else {
sprite = LeafParticleRegistry["default"][rand.nextInt(1024)]
setColor(blockColor)
}
// if (leafInfo != null) {
// sprite = leafInfo.particleTextures[rand.nextInt(1024)]
// calculateParticleColor(leafInfo.averageColor, blockColor)
// } else {
// sprite = LeafParticleRegistry["default"][rand.nextInt(1024)]
// setColor(blockColor)
// }
}
override val isValid: Boolean get() = (sprite != null)

View File

@@ -2,13 +2,10 @@ package mods.betterfoliage.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.render.old.AbstractEntityFX
import mods.betterfoliage.resource.ResourceHandler
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.Double3
import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.MathHelper
import net.minecraft.world.World
@@ -23,7 +20,7 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.to
init {
motionY = 0.1
particleGravity = 0.0f
sprite = RisingSoulTextures.headIcons[rand.nextInt(256)]
// sprite = RisingSoulTextures.headIcons[rand.nextInt(256)]
maxAge = MathHelper.floor((0.6 + 0.4 * rand.nextDouble()) * Config.risingSoul.lifetime * 20.0)
}
@@ -63,7 +60,7 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.to
}
}
object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus, targetAtlas = Atlas.PARTICLES) {
val headIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "rising_soul_$idx") }
val trackIcon by sprite(Identifier(BetterFoliageMod.MOD_ID, "soul_track"))
}
//object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus, targetAtlas = Atlas.PARTICLES) {
// val headIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "rising_soul_$idx") }
// val trackIcon by sprite(Identifier(BetterFoliageMod.MOD_ID, "soul_track"))
//}

View File

@@ -1,158 +0,0 @@
@file:JvmName("ModelColumn")
package mods.betterfoliage.render
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.lighting.CornerSingleFallback
import mods.betterfoliage.render.lighting.EdgeInterpolateFallback
import mods.betterfoliage.render.lighting.FaceCenter
import mods.betterfoliage.render.lighting.FaceFlat
import mods.betterfoliage.render.lighting.cornerAo
import mods.betterfoliage.render.lighting.cornerFlat
import mods.betterfoliage.render.lighting.cornerInterpolate
import mods.betterfoliage.render.lighting.faceOrientedAuto
import mods.betterfoliage.render.lighting.faceOrientedInterpolate
import mods.betterfoliage.render.old.Model
import mods.betterfoliage.render.old.Quad
import mods.betterfoliage.render.old.UV
import mods.betterfoliage.render.old.Vertex
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.exchange
import net.minecraft.util.Direction.*
/** Weight of the same-side AO values on the outer edges of the 45deg chamfered column faces. */
const val chamferAffinity = 0.9f
/** Amount to shrink column extension bits to stop Z-fighting. */
val zProtectionScale: Double3 get() = Double3(Config.roundLogs.zProtection, 1.0, Config.roundLogs.zProtection)
fun Model.columnSide(radius: Double, yBottom: Double, yTop: Double, transform: (Quad) -> Quad = { it }) {
val halfRadius = radius * 0.5
listOf(
verticalRectangle(x1 = 0.0, z1 = 0.5, x2 = 0.5 - radius, z2 = 0.5, yBottom = yBottom, yTop = yTop)
.clampUV(minU = 0.0, maxU = 0.5 - radius)
.setAoShader(faceOrientedInterpolate(overrideFace = SOUTH))
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 1 || vi == 2}),
verticalRectangle(x1 = 0.5 - radius, z1 = 0.5, x2 = 0.5 - halfRadius, z2 = 0.5 - halfRadius, yBottom = yBottom, yTop = yTop)
.clampUV(minU = 0.5 - radius)
.setAoShader(
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming.toFloat()))
)
.setAoShader(
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming.toFloat())),
predicate = { v, vi -> vi == 1 || vi == 2}
)
).forEach { transform(it.setFlatShader(FaceFlat(SOUTH))).add() }
listOf(
verticalRectangle(x1 = 0.5 - halfRadius, z1 = 0.5 - halfRadius, x2 = 0.5, z2 = 0.5 - radius, yBottom = yBottom, yTop = yTop)
.clampUV(maxU = radius - 0.5)
.setAoShader(
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming.toFloat()))
)
.setAoShader(
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming.toFloat())),
predicate = { v, vi -> vi == 0 || vi == 3}
),
verticalRectangle(x1 = 0.5, z1 = 0.5 - radius, x2 = 0.5, z2 = 0.0, yBottom = yBottom, yTop = yTop)
.clampUV(minU = radius - 0.5, maxU = 0.0)
.setAoShader(faceOrientedInterpolate(overrideFace = EAST))
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 0 || vi == 3})
).forEach { transform(it.setFlatShader(FaceFlat(EAST))).add() }
quads.exchange(1, 2)
}
/**
* Create a model of the side of a square column quadrant.
*
* @param[transform] transformation to apply to the model
*/
fun Model.columnSideSquare(yBottom: Double, yTop: Double, transform: (Quad) -> Quad = { it }) {
listOf(
verticalRectangle(x1 = 0.0, z1 = 0.5, x2 = 0.5, z2 = 0.5, yBottom = yBottom, yTop = yTop)
.clampUV(minU = 0.0)
.setAoShader(faceOrientedInterpolate(overrideFace = SOUTH))
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 1 || vi == 2}),
verticalRectangle(x1 = 0.5, z1 = 0.5, x2 = 0.5, z2 = 0.0, yBottom = yBottom, yTop = yTop)
.clampUV(maxU = 0.0)
.setAoShader(faceOrientedInterpolate(overrideFace = EAST))
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 0 || vi == 3})
).forEach {
transform(it.setFlatShader(faceOrientedAuto(corner = cornerFlat))).add()
}
}
/**
* Create a model of the top lid of a chamfered column quadrant.
*
* @param[radius] the chamfer radius
* @param[transform] transformation to apply to the model
*/
fun Model.columnLid(radius: Double, transform: (Quad)-> Quad = { it }) {
val v1 = Vertex(Double3(0.0, 0.5, 0.0), UV(0.0, 0.0))
val v2 = Vertex(Double3(0.0, 0.5, 0.5), UV(0.0, 0.5))
val v3 = Vertex(Double3(0.5 - radius, 0.5, 0.5), UV(0.5 - radius, 0.5))
val v4 = Vertex(Double3(0.5 - radius * 0.5, 0.5, 0.5 - radius * 0.5), UV(0.5, 0.5))
val v5 = Vertex(Double3(0.5, 0.5, 0.5 - radius), UV(0.5, 0.5 - radius))
val v6 = Vertex(Double3(0.5, 0.5, 0.0), UV(0.5, 0.0))
val q1 = Quad(v1, v2, v3, v4).setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
.transformVI { vertex, idx -> vertex.copy(aoShader = when(idx) {
0 -> FaceCenter(UP)
1 -> EdgeInterpolateFallback(UP, SOUTH, 0.0)
else -> vertex.aoShader
})}
.cycleVertices(if (Config.nVidia) 0 else 1)
val q2 = Quad(v1, v4, v5, v6).setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
.transformVI { vertex, idx -> vertex.copy(aoShader = when(idx) {
0 -> FaceCenter(UP)
3 -> EdgeInterpolateFallback(UP, EAST, 0.0)
else -> vertex.aoShader
})}
.cycleVertices(if (Config.nVidia) 0 else 1)
listOf(q1, q2).forEach { transform(it.setFlatShader(FaceFlat(UP))).add() }
}
/**
* Create a model of the top lid of a square column quadrant.
*
* @param[transform] transformation to apply to the model
*/
fun Model.columnLidSquare(transform: (Quad)-> Quad = { it }) {
transform(
horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = 0.5)
.transformVI { vertex, idx -> vertex.copy(uv = UV(vertex.xyz.x, vertex.xyz.z), aoShader = when(idx) {
0 -> FaceCenter(UP)
1 -> EdgeInterpolateFallback(UP, SOUTH, 0.0)
2 -> CornerSingleFallback(UP, SOUTH, EAST, UP)
else -> EdgeInterpolateFallback(UP, EAST, 0.0)
}) }
.setFlatShader(FaceFlat(UP))
).add()
}
/**
* Transform a chamfered side quadrant model of a column that extends from the top of the block.
* (clamp UV coordinates, apply some scaling to avoid Z-fighting).
*
* @param[size] amount that the model extends from the top
*/
fun topExtension(size: Double) = { q: Quad ->
q.clampUV(minV = 0.5 - size).transformVI { vertex, idx ->
if (idx < 2) vertex else vertex.copy(xyz = vertex.xyz * zProtectionScale)
}
}
/**
* Transform a chamfered side quadrant model of a column that extends from the bottom of the block.
* (clamp UV coordinates, apply some scaling to avoid Z-fighting).
*
* @param[size] amount that the model extends from the bottom
*/
fun bottomExtension(size: Double) = { q: Quad ->
q.clampUV(maxV = -0.5 + size).transformVI { vertex, idx ->
if (idx > 1) vertex else vertex.copy(xyz = vertex.xyz * zProtectionScale)
}
}

View File

@@ -0,0 +1,48 @@
package mods.betterfoliage.render
import mods.betterfoliage.render.pipeline.RenderCtxBase
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.Material
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.model.VariantList
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.WeightedRandom
import java.util.Random
import java.util.function.Function
interface ISpecialRenderModel : IBakedModel {
fun render(ctx: RenderCtxBase, noDecorations: Boolean = false)
}
open class SpecialRenderWrapper(val baseModel: IBakedModel) : IBakedModel by baseModel, ISpecialRenderModel {
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
ctx.renderFallback(baseModel)
}
}
/**
* If any of the variants in this [VariantList] bake to [ISpecialRenderModel], give back a
* [SpecialRenderVariantList] so that variants can take advantage of extra features.
* Otherwise, give back null.
*/
fun VariantList.bakeSpecial(bakery: ModelBakery, spriteGetter: Function<Material, TextureAtlasSprite>): SpecialRenderVariantList? {
val bakedModels = variantList.map { bakery.getBakedModel(it.modelLocation, it, spriteGetter) }
if (bakedModels.all { it !is ISpecialRenderModel }) return null
val weightedItems = (variantList zip bakedModels)
.filter { it.second != null }
.map { (variant, model) ->
val modelWrapped = (model!! as? ISpecialRenderModel) ?: SpecialRenderWrapper(model)
SpecialRenderVariantList.WeightedModel(modelWrapped, variant.weight)
}
return SpecialRenderVariantList(weightedItems, weightedItems[0].model)
}
open class SpecialRenderVariantList(
val models: List<WeightedModel>, baseModel: ISpecialRenderModel
): IBakedModel by baseModel, ISpecialRenderModel {
class WeightedModel(val model: ISpecialRenderModel, weight: Int) : WeightedRandom.Item(weight)
val totalWeight = models.sumBy { it.itemWeight }
fun getModel(random: Random) = WeightedRandom.getRandomItem(models, random.nextInt(totalWeight))
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) = getModel(ctx.random).model.render(ctx, noDecorations)
}

View File

@@ -1,78 +0,0 @@
@file:JvmName("Utils")
package mods.betterfoliage.render
import mods.betterfoliage.render.lighting.PostProcessLambda
import mods.betterfoliage.render.old.Model
import mods.betterfoliage.render.old.Quad
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.times
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.util.Direction
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.EAST
import net.minecraft.util.Direction.NORTH
import net.minecraft.util.Direction.SOUTH
import net.minecraft.util.Direction.UP
import net.minecraft.util.Direction.WEST
import kotlin.math.cos
import kotlin.math.sin
val up1 = Int3(1 to UP)
val up2 = Int3(2 to UP)
val down1 = Int3(1 to DOWN)
val snowOffset = UP * 0.0625
val normalLeavesRot = arrayOf(Rotation.identity)
val denseLeavesRot = arrayOf(Rotation.identity, Rotation.rot90[EAST.ordinal], Rotation.rot90[SOUTH.ordinal])
val whitewash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.4f) }
val greywash: PostProcessLambda = { _, _, _, _, _ -> setGrey(1.0f) }
val BlockState.isSnow: Boolean get() = material.let { it == Material.SNOW }
val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT)
fun Quad.toCross(rotAxis: Direction, trans: (Quad) -> Quad) =
(0..3).map { rotIdx ->
trans(rotate(Rotation.rot90[rotAxis.ordinal] * rotIdx).mirrorUV(rotIdx > 1, false))
}
fun Quad.toCross(rotAxis: Direction) = toCross(rotAxis) { it }
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(cos(it), 0.0, sin(it)) }
val rotationFromUp = arrayOf(
Rotation.rot90[EAST.ordinal] * 2,
Rotation.identity,
Rotation.rot90[WEST.ordinal],
Rotation.rot90[EAST.ordinal],
Rotation.rot90[SOUTH.ordinal],
Rotation.rot90[NORTH.ordinal]
)
fun Model.mix(first: Model, second: Model, predicate: (Int) -> Boolean) {
first.quads.forEachIndexed { qi, quad ->
val otherQuad = second.quads[qi]
Quad(
if (predicate(0)) otherQuad.v1.copy() else quad.v1.copy(),
if (predicate(1)) otherQuad.v2.copy() else quad.v2.copy(),
if (predicate(2)) otherQuad.v3.copy() else quad.v3.copy(),
if (predicate(3)) otherQuad.v4.copy() else quad.v4.copy()
).add()
}
}
val RenderType.isCutout: Boolean get() = (this == RenderType.getCutout()) || (this == RenderType.getCutoutMipped())
fun BlockState.canRenderInLayer(layer: RenderType) = RenderTypeLookup.canRenderInLayer(this, layer)
fun BlockState.canRenderInCutout() =
RenderTypeLookup.canRenderInLayer(this, RenderType.getCutout()) ||
RenderTypeLookup.canRenderInLayer(this, RenderType.getCutoutMipped())

View File

@@ -0,0 +1,65 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.Client
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.ISpecialRenderModel
import mods.betterfoliage.render.old.HalfBakedSpecialWrapper
import mods.betterfoliage.render.old.HalfBakedWrapKey
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.ModelBakeKey
import mods.betterfoliage.resource.discovery.ModelReplacer
import mods.betterfoliage.util.offset
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.renderer.model.BlockModel
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
object StandardDirtDiscovery : ModelReplacer() {
val dirtBlocks = listOf(Blocks.DIRT, Blocks.COARSE_DIRT, Blocks.PODZOL)
override fun processModel(
bakery: ModelBakery,
state: BlockState,
location: ResourceLocation,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakeKey>
): Boolean {
val model = bakery.getUnbakedModel(location)
if (model is BlockModel && state.block in dirtBlocks) {
Client.blockTypes.dirt.add(state)
replacements[location] = StandardDirtKey
RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutout())
return true
}
return super.processModel(bakery, state, location, sprites, replacements)
}
}
object StandardDirtKey : HalfBakedWrapKey() {
override fun replace(wrapped: ISpecialRenderModel) = StandardDirtModel(wrapped)
}
class StandardDirtModel(
wrapped: ISpecialRenderModel
) : HalfBakedSpecialWrapper(wrapped) {
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
if (!Config.enabled || noDecorations) return super.render(ctx, false)
val stateUp = ctx.offset(UP).state
val isConnectedGrass = Config.connectedGrass.enabled && stateUp in Client.blockTypes.grass
if (isConnectedGrass) {
(ctx.blockModelShapes.getModel(stateUp) as? ISpecialRenderModel)?.let { grassModel ->
ctx.renderMasquerade(UP.offset) {
grassModel.render(ctx, true)
}
return
}
return super.render(ctx, false)
}
super.render(ctx, false)
}
}

View File

@@ -0,0 +1,123 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.Client
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.ConfigurableBlockMatcher
import mods.betterfoliage.config.ModelTextureList
import mods.betterfoliage.render.ISpecialRenderModel
import mods.betterfoliage.render.lighting.LightingPreferredFace
import mods.betterfoliage.render.old.Color
import mods.betterfoliage.render.old.HalfBakedSpecialWrapper
import mods.betterfoliage.render.old.HalfBakedWrapKey
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.RenderCtxVanilla
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableModelReplacer
import mods.betterfoliage.resource.discovery.ModelBakeKey
import mods.betterfoliage.resource.model.SpriteSetDelegate
import mods.betterfoliage.resource.model.buildTufts
import mods.betterfoliage.resource.model.fullCubeTextured
import mods.betterfoliage.resource.model.fullCubeTinted
import mods.betterfoliage.resource.model.tuftModelSet
import mods.betterfoliage.resource.model.tuftShapeSet
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyInvalidatable
import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.get
import mods.betterfoliage.util.isSnow
import mods.betterfoliage.util.randomI
import net.minecraft.block.BlockState
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
object StandardGrassDiscovery : ConfigurableModelReplacer() {
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.grassBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.grassModels.modelList
override fun processModel(
state: BlockState,
location: ResourceLocation,
textureMatch: List<ResourceLocation>,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakeKey>
): Boolean {
replacements[location] = StandardGrassKey(textureMatch[0])
Client.blockTypes.grass.add(state)
RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutout())
return true
}
}
data class StandardGrassKey(
val grassLocation: ResourceLocation
) : HalfBakedWrapKey() {
override fun replace(wrapped: ISpecialRenderModel): ISpecialRenderModel {
Atlas.BLOCKS[grassLocation].logColorOverride(detailLogger, Config.shortGrass.saturationThreshold)
return StandardGrassModel(wrapped, this)
}
}
class StandardGrassModel(
wrapped: ISpecialRenderModel,
key: StandardGrassKey
) : HalfBakedSpecialWrapper(wrapped) {
val tuftNormal by grassTuftMeshesNormal.delegate(key)
val tuftSnowed by grassTuftMeshesSnowed.delegate(key)
val fullBlock by grassFullBlockMeshes.delegate(key)
val upNormal = arrayOf(0.0f, 1.0f, 0.0f, 0.0f).toFloatArray()
val vanillaTuftLighting = LightingPreferredFace(UP)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
if (!Config.enabled || noDecorations) return super.render(ctx, noDecorations)
val stateBelow = ctx.state(DOWN)
val stateAbove = ctx.state(UP)
val isSnowed = stateAbove.isSnow
val connected = Config.connectedGrass.enabled &&
(!isSnowed || Config.connectedGrass.snowEnabled) &&
Client.blockTypes.run { stateBelow in grass || stateBelow in dirt }
if (connected) {
ctx.render(if (isSnowed) snowFullBlockMeshes[ctx.random] else fullBlock[ctx.random])
} else {
super.render(ctx, noDecorations)
}
if (Config.shortGrass.enabled(ctx.random) && !ctx.isNeighborSolid(UP)) {
(ctx as? RenderCtxVanilla)?.let { it.vertexLighter = vanillaTuftLighting }
ctx.render(if (isSnowed) tuftSnowed[ctx.random] else tuftNormal[ctx.random])
}
}
companion object {
val grassTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_grass_long_$idx")
}
val grassTuftShapes by LazyInvalidatable(BakeWrapperManager) {
Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
}
val grassTuftMeshesNormal = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
val overrideColor = Atlas.BLOCKS[key.grassLocation].getColorOverride(Config.shortGrass.saturationThreshold)
tuftModelSet(grassTuftShapes, overrideColor) { idx -> grassTuftSprites[randomI()] }.buildTufts()
}
val grassTuftMeshesSnowed = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
tuftModelSet(grassTuftShapes, Color.white) { idx -> grassTuftSprites[randomI()] }.buildTufts()
}
val grassFullBlockMeshes = LazyMapInvalidatable(BakeWrapperManager) { key: StandardGrassKey ->
Array(64) { fullCubeTinted(key.grassLocation, Config.shortGrass.saturationThreshold) }
}
val snowFullBlockMeshes by LazyInvalidatable(BakeWrapperManager) {
Array(64) { fullCubeTextured(ResourceLocation("block/snow"), Color.white) }
}
}
}

View File

@@ -0,0 +1,112 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.Client
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.config.ConfigurableBlockMatcher
import mods.betterfoliage.config.ModelTextureList
import mods.betterfoliage.render.ISpecialRenderModel
import mods.betterfoliage.render.lighting.RoundLeafLighting
import mods.betterfoliage.render.old.Color
import mods.betterfoliage.render.old.HalfBakedSpecialWrapper
import mods.betterfoliage.render.old.HalfBakedWrapKey
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.render.pipeline.RenderCtxVanilla
import mods.betterfoliage.resource.discovery.BakeWrapperManager
import mods.betterfoliage.resource.discovery.ConfigurableModelReplacer
import mods.betterfoliage.resource.discovery.ModelBakeKey
import mods.betterfoliage.resource.generated.GeneratedLeaf
import mods.betterfoliage.resource.model.SpriteSetDelegate
import mods.betterfoliage.resource.model.crossModelsRaw
import mods.betterfoliage.resource.model.crossModelsTextured
import mods.betterfoliage.resource.model.crossModelsTinted
import mods.betterfoliage.texture.LeafParticleRegistry
import mods.betterfoliage.util.Atlas
import mods.betterfoliage.util.LazyMapInvalidatable
import mods.betterfoliage.util.averageColor
import mods.betterfoliage.util.isSnow
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Logger
object StandardLeafDiscovery : ConfigurableModelReplacer() {
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.leafBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.leafModels.modelList
override fun processModel(
state: BlockState,
location: ResourceLocation,
textureMatch: List<ResourceLocation>,
sprites: MutableSet<ResourceLocation>,
replacements: MutableMap<ResourceLocation, ModelBakeKey>
): Boolean {
val leafType = LeafParticleRegistry.typeMappings.getType(textureMatch[0]) ?: "default"
val generated = GeneratedLeaf(textureMatch[0], leafType)
.register(Client.asyncPack)
.apply { sprites.add(this) }
detailLogger.log(INFO, " particle $leafType")
replacements[location] = StandardLeafKey(generated, leafType)
return true
}
}
fun TextureAtlasSprite.logColorOverride(logger: Logger, threshold: Double) {
val hsb = averageColor
return if (hsb.saturation >= threshold) {
logger.log(INFO, " brightness ${hsb.brightness}")
logger.log(INFO, " saturation ${hsb.saturation} >= ${threshold}, will use texture color")
} else {
logger.log(INFO, " saturation ${hsb.saturation} < ${threshold}, will use block color")
}
}
fun TextureAtlasSprite.getColorOverride(threshold: Double) = averageColor.let {
if (it.saturation < threshold) null else it.copy(brightness = (it.brightness * 2.0f).coerceAtMost(0.9f))
}?.asColor?.let { Color(it) }
data class StandardLeafKey(
val roundLeafTexture: ResourceLocation,
val leafType: String
) : HalfBakedWrapKey() {
override fun replace(wrapped: ISpecialRenderModel): ISpecialRenderModel {
Atlas.BLOCKS[roundLeafTexture].logColorOverride(BetterFoliageMod.detailLogger(this), 0.1)
return StandardLeafModel(wrapped, this)
}
}
class StandardLeafModel(
model: ISpecialRenderModel,
key: StandardLeafKey
) : HalfBakedSpecialWrapper(model) {
val leafNormal by leafModelsNormal.delegate(key)
val leafSnowed by leafModelsSnowed.delegate(key)
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
super.render(ctx, noDecorations)
if (!Config.enabled || !Config.leaves.enabled || noDecorations) return
(ctx as? RenderCtxVanilla)?.let { it.vertexLighter = RoundLeafLighting }
ctx.render(leafNormal[ctx.random.nextInt(64)])
if (ctx.state(UP).isSnow) ctx.render(leafSnowed[ctx.random.nextInt(64)])
}
companion object {
val leafSpritesSnowed by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_leaves_snowed_$idx")
}
val leafModelsBase = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
Config.leaves.let { crossModelsRaw(64, it.size, it.hOffset, it.vOffset) }
}
val leafModelsNormal = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTinted(leafModelsBase[key], Config.shortGrass.saturationThreshold) { key.roundLeafTexture }
}
val leafModelsSnowed = LazyMapInvalidatable(BakeWrapperManager) { key: StandardLeafKey ->
crossModelsTextured(leafModelsBase[key], Color.white, false) { leafSpritesSnowed[it].name }
}
}
}

View File

@@ -0,0 +1,13 @@
package mods.betterfoliage.render.block.vanilla
import mods.betterfoliage.render.column.ColumnBlockKey
import mods.betterfoliage.resource.discovery.ModelBakeKey
import net.minecraft.util.Direction
import net.minecraft.util.ResourceLocation
data class RoundLogKey(
override val axis: Direction.Axis?,
val barkSprite: ResourceLocation,
val endSprite: ResourceLocation
) : ColumnBlockKey, ModelBakeKey {
}

View File

@@ -1,42 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.render.DIRT_BLOCKS
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.up1
import mods.betterfoliage.render.up2
import mods.betterfoliage.render.old.RenderDecorator
import net.minecraft.block.material.Material
import net.minecraft.util.ResourceLocation
import net.minecraft.world.biome.Biome
class RenderAlgae : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val noise = simplexNoise()
val algaeIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_algae_$idx") }
val algaeModels = modelSet(64) { idx -> RenderGrass.grassTopQuads(Config.algae.heightMin, Config.algae.heightMax)(idx) }
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.algae.enabled &&
ctx.state(up2).material == Material.WATER &&
ctx.state(up1).material == Material.WATER &&
DIRT_BLOCKS.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: CombinedContext) {
ctx.render()
if (!ctx.isCutout) return
val rand = ctx.semiRandomArray(3)
ShadersModIntegration.grass(ctx, Config.algae.shaderWind) {
ctx.render(
algaeModels[rand[2]],
icon = { _, qi, _ -> algaeIcons[rand[qi and 1]] }
)
}
}
}

View File

@@ -1,111 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.column.ColumnTextureInfo
import mods.betterfoliage.render.column.SimpleColumnInfo
import mods.betterfoliage.render.lighting.cornerAo
import mods.betterfoliage.render.lighting.cornerAoMaxGreen
import mods.betterfoliage.render.lighting.edgeOrientedAuto
import mods.betterfoliage.render.lighting.faceOrientedAuto
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.xzDisk
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.octarinecore.client.resource.*
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.config.ModelTextureList
import mods.betterfoliage.config.SimpleBlockMatcher
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.old.Vertex
import net.minecraft.block.BlockState
import net.minecraft.block.CactusBlock
import net.minecraft.util.Direction.*
import java.util.concurrent.CompletableFuture
object AsyncCactusDiscovery : ConfigurableModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override val matchClasses = SimpleBlockMatcher(CactusBlock::class.java)
override val modelTextures = listOf(ModelTextureList("block/cactus", "top", "bottom", "side"))
override fun processModel(state: BlockState, textures: List<Identifier>, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo>? {
val sprites = textures.map { atlas.sprite(it) }
return atlas.mapAfter {
SimpleColumnInfo(
Axis.Y,
sprites[0].get(),
sprites[1].get(),
sprites.drop(2).map { it.get() }
)
}
}
fun init() {
BetterFoliage.blockSprites.providers.add(this)
}
}
class RenderCactus : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val cactusStemRadius = 0.4375
val cactusArmRotation = listOf(NORTH, SOUTH, EAST, WEST).map { Rotation.rot90[it.ordinal] }
val iconCross by sprite(Identifier(BetterFoliageMod.MOD_ID, "blocks/better_cactus"))
val iconArm = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_cactus_arm_$idx") }
val modelStem = model {
horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5)
.scaleUV(cactusStemRadius * 2.0)
.let { listOf(it.flipped.move(1.0 to DOWN), it) }
.forEach { it.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y), edge = null)).add() }
verticalRectangle(x1 = -0.5, z1 = cactusStemRadius, x2 = 0.5, z2 = cactusStemRadius, yBottom = -0.5, yTop = 0.5)
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y), edge = null))
.toCross(UP).addAll()
}
val modelCross = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
.setAoShader(edgeOrientedAuto(corner = cornerAoMaxGreen))
.scale(1.4)
.transformV { v ->
val perturb = xzDisk(modelIdx) * Config.cactus.sizeVariation
Vertex(v.xyz + (if (v.uv.u < 0.0) perturb else -perturb), v.uv, v.aoShader)
}
.toCross(UP).addAll()
}
val modelArm = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
.scale(Config.cactus.size).move(0.5 to UP)
.setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y), edge = null))
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.cactus.hOffset) }.addAll()
}
override fun isEligible(ctx: CombinedContext): Boolean =
Config.enabled && Config.cactus.enabled &&
AsyncCactusDiscovery[ctx] != null
override val onlyOnCutout get() = true
override fun render(ctx: CombinedContext) {
val icons = AsyncCactusDiscovery[ctx]!!
ctx.render(
modelStem.model,
icon = { ctx, qi, q -> when(qi) {
0 -> icons.bottom(ctx, qi, q); 1 -> icons.top(ctx, qi, q); else -> icons.side(ctx, qi, q)
} }
)
ctx.render(
modelCross[ctx.semiRandom(0)],
icon = { _, _, _ -> iconCross }
)
ctx.render(
modelArm[ctx.semiRandom(1)],
cactusArmRotation[ctx.semiRandom(2) % 4],
icon = { _, _, _ -> iconArm[ctx.semiRandom(3)] }
)
}
}

View File

@@ -1,48 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.DIRT_BLOCKS
import mods.betterfoliage.render.isSnow
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.up1
import mods.betterfoliage.render.up2
import mods.betterfoliage.texture.GrassRegistry
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.horizontalDirections
import mods.betterfoliage.util.offset
class RenderConnectedGrass : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.connectedGrass.enabled &&
DIRT_BLOCKS.contains(ctx.state.block) &&
GrassRegistry[ctx, up1] != null &&
(Config.connectedGrass.snowEnabled || !ctx.state(up2).isSnow)
override fun render(ctx: CombinedContext) {
// if the block sides are not visible anyway, render normally
if (horizontalDirections.none { ctx.shouldSideBeRendered(it) }) {
ctx.render()
} else {
ctx.exchange(Int3.zero, up1).exchange(up1, up2).render()
}
}
}
class RenderConnectedGrassLog : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass &&
DIRT_BLOCKS.contains(ctx.state.block) &&
LogRegistry[ctx, up1] != null
override fun render(ctx: CombinedContext) {
val grassDir = horizontalDirections.find { GrassRegistry[ctx, it.offset] != null }
if (grassDir == null) {
ctx.render()
} else {
ctx.exchange(Int3.zero, grassDir.offset).render()
}
}
}

View File

@@ -1,69 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.lighting.cornerAo
import mods.betterfoliage.render.lighting.cornerFlat
import mods.betterfoliage.render.lighting.faceOrientedAuto
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.rotationFromUp
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.up1
import mods.betterfoliage.render.up2
import mods.betterfoliage.render.xzDisk
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.randomD
import net.minecraft.block.material.Material
import net.minecraft.tags.BlockTags
import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.UP
import net.minecraft.util.ResourceLocation
import net.minecraft.world.biome.Biome
class RenderCoral : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val noise = simplexNoise()
val coralIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_coral_$idx") }
val crustIcons = spriteSet { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_crust_$idx") }
val coralModels = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
.scale(Config.coral.size).move(0.5 to UP)
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.coral.hOffset) }.addAll()
val separation = randomD(0.01, Config.coral.vOffset)
horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0)
.scale(Config.coral.crustSize).move(0.5 + separation to UP).add()
transformQ {
it.setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
.setFlatShader(faceOrientedAuto(overrideFace = UP, corner = cornerFlat))
}
}
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.coral.enabled &&
(ctx.state(up2).material == Material.WATER || Config.coral.shallowWater) &&
ctx.state(up1).material == Material.WATER &&
BlockTags.SAND.contains(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: CombinedContext) {
val baseRender = ctx.render()
if (!ctx.isCutout) return
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)] }
)
}
}
}
}

View File

@@ -1,105 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.integration.OptifineCustomColors
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.render.DIRT_BLOCKS
import mods.betterfoliage.render.down1
import mods.betterfoliage.render.isSnow
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.resource.generated.GeneratedGrass
import mods.betterfoliage.texture.GrassRegistry
import mods.betterfoliage.render.old.Model
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.old.fullCube
import mods.betterfoliage.render.lighting.cornerAo
import mods.betterfoliage.render.lighting.cornerFlat
import mods.betterfoliage.render.lighting.faceOrientedAuto
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.snowOffset
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.xzDisk
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.randomD
import net.minecraft.util.Direction.*
class RenderGrass : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
companion object {
@JvmStatic fun grassTopQuads(heightMin: Double, heightMax: Double): Model.(Int)->Unit = { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5,
yTop = 0.5 + randomD(heightMin, heightMax)
)
.setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
.setFlatShader(faceOrientedAuto(overrideFace = UP, corner = cornerFlat))
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.shortGrass.hOffset) }.addAll()
}
}
val noise = simplexNoise()
val normalIcons = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_grass_long_$idx") }
val snowedIcons = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_grass_snowed_$idx") }
val normalGenIcon by sprite { GeneratedGrass(sprite = "minecraft:blocks/tall_grass_top", isSnowed = false).register(BetterFoliage.asyncPack) }
val snowedGenIcon by sprite { GeneratedGrass(sprite = "minecraft:blocks/tall_grass_top", isSnowed = true).register(BetterFoliage.asyncPack) }
val grassModels = modelSet(64) { idx -> grassTopQuads(Config.shortGrass.heightMin, Config.shortGrass.heightMax)(idx) }
override fun isEligible(ctx: CombinedContext) =
Config.enabled &&
(Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) &&
GrassRegistry[ctx] != null
override val onlyOnCutout get() = true
override fun render(ctx: CombinedContext) {
val isConnected = DIRT_BLOCKS.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]!!
val blockColor = OptifineCustomColors.getBlockColor(ctx)
if (connectedGrass) {
// check occlusion
val isVisible = allDirections.map { ctx.shouldSideBeRendered(it) }
// render full grass block
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 {
ctx.render()
}
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(ctx, Config.shortGrass.shaderWind) {
ctx.render(
grassModels[rand[0]],
translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = { _, qi, _ -> if (Config.shortGrass.useGenerated) iconGen else iconset[rand[qi and 1]] },
postProcess = { _, _, _, _, _ -> if (isSnowed) setGrey(1.0f) else multiplyColor(grass.overrideColor ?: blockColor) }
)
}
}
}

View File

@@ -1,83 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.integration.OptifineCustomColors
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.render.denseLeavesRot
import mods.betterfoliage.render.isSnow
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.texture.LeafRegistry
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.lighting.FlatOffset
import mods.betterfoliage.render.lighting.cornerAoMaxGreen
import mods.betterfoliage.render.lighting.edgeOrientedAuto
import mods.betterfoliage.render.normalLeavesRot
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.whitewash
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.PI2
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.randomD
import mods.betterfoliage.util.vec
import net.minecraft.util.Direction.UP
import java.lang.Math.cos
import java.lang.Math.sin
class RenderLeaves : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
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)
.setAoShader(edgeOrientedAuto(corner = cornerAoMaxGreen))
.setFlatShader(FlatOffset(Int3.zero))
.scale(Config.leaves.size)
.toCross(UP).addAll()
}
val snowedIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_leaves_snowed_$idx") }
val perturbs = vectorSet(64) { idx ->
val angle = PI2 * idx / 64.0
Double3(cos(angle), 0.0, sin(angle)) * Config.leaves.hOffset +
UP.vec * randomD(-1.0, 1.0) * Config.leaves.vOffset
}
override fun isEligible(ctx: CombinedContext) =
Config.enabled &&
Config.leaves.enabled &&
LeafRegistry[ctx] != null &&
!(Config.leaves.hideInternal && allDirections.all { ctx.offset(it).isNormalCube } )
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)
ctx.render(force = true)
ShadersModIntegration.leaves(ctx) {
val rand = ctx.semiRandomArray(2)
(if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation ->
ctx.render(
leavesModel.model,
rotation,
translation = ctx.blockCenter + perturbs[rand[0]],
icon = { _, _, _ -> leafInfo.roundLeafTexture },
postProcess = { _, _, _, _, _ ->
rotateUV(rand[1])
multiplyColor(blockColor)
}
)
}
if (isSnowed && Config.leaves.snowEnabled) ctx.render(
leavesModel.model,
translation = ctx.blockCenter + perturbs[rand[0]],
icon = { _, _, _ -> snowedIcon[rand[1]] },
postProcess = whitewash
)
}
}
}

View File

@@ -1,58 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.lighting.FlatOffsetNoColor
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.xzDisk
import mods.betterfoliage.util.Int3
import net.minecraft.util.Direction.DOWN
import net.minecraft.util.Direction.UP
class RenderLilypad : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val rootModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -1.5, yTop = -0.5)
.setFlatShader(FlatOffsetNoColor(Int3.zero))
.toCross(UP).addAll()
}
val flowerModel = model {
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
.scale(0.5).move(0.5 to DOWN)
.setFlatShader(FlatOffsetNoColor(Int3.zero))
.toCross(UP).addAll()
}
val rootIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_roots_$idx") }
val flowerIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_lilypad_flower_$idx") }
val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset }
override fun isEligible(ctx: CombinedContext): Boolean =
Config.enabled && Config.lilypad.enabled &&
BlockConfig.lilypad.matchesClass(ctx.state.block)
override fun render(ctx: CombinedContext) {
ctx.render()
val rand = ctx.semiRandomArray(5)
ShadersModIntegration.grass(ctx) {
ctx.render(
rootModel.model,
translation = ctx.blockCenter.add(perturbs[rand[2]]),
forceFlat = true,
icon = { ctx, qi, q -> rootIcon[rand[qi and 1]] }
)
}
if (rand[3] < Config.lilypad.flowerChance) ctx.render(
flowerModel.model,
translation = ctx.blockCenter.add(perturbs[rand[4]]),
forceFlat = true,
icon = { _, _, _ -> flowerIcon[rand[0]] }
)
}
}

View File

@@ -1,89 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.column.AbstractRenderColumn
import mods.betterfoliage.render.column.ColumnRenderLayer
import mods.betterfoliage.render.column.ColumnTextureInfo
import mods.betterfoliage.render.column.SimpleColumnInfo
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.resource.discovery.ConfigurableModelDiscovery
import mods.betterfoliage.resource.discovery.ModelRenderRegistry
import mods.betterfoliage.resource.discovery.ModelRenderRegistryRoot
import mods.octarinecore.client.resource.*
import mods.betterfoliage.config.ConfigurableBlockMatcher
import mods.betterfoliage.config.ModelTextureList
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.util.tryDefault
import net.minecraft.block.BlockState
import net.minecraft.block.LogBlock
import net.minecraft.util.Direction.Axis
import org.apache.logging.log4j.Level
import java.util.concurrent.CompletableFuture
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
override val renderOnCutout: Boolean get() = false
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.roundLogs.enabled &&
LogRegistry[ctx] != null
override val overlayLayer = RoundLogOverlayLayer()
override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular
override val radiusSmall: Double get() = Config.roundLogs.radiusSmall
override val radiusLarge: Double get() = Config.roundLogs.radiusLarge
init {
ChunkOverlayManager.layers.add(overlayLayer)
}
}
class RoundLogOverlayLayer : ColumnRenderLayer() {
override val registry: ModelRenderRegistry<ColumnTextureInfo> get() = LogRegistry
override val blockPredicate = { state: BlockState -> BlockConfig.logBlocks.matchesClass(state.block) }
override val connectSolids: Boolean get() = Config.roundLogs.connectSolids
override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect
override val defaultToY: Boolean get() = Config.roundLogs.defaultY
}
object LogRegistry : ModelRenderRegistryRoot<ColumnTextureInfo>()
object AsyncLogDiscovery : ConfigurableModelDiscovery<ColumnTextureInfo>() {
override val logger = BetterFoliage.logDetail
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.logBlocks
override val modelTextures: List<ModelTextureList> get() = BlockConfig.logModels.modelList
override fun processModel(state: BlockState, textures: List<Identifier>, atlas: AtlasFuture): CompletableFuture<ColumnTextureInfo> {
val axis = getAxis(state)
logger.log(Level.DEBUG, "$logName: axis $axis")
val spriteList = textures.map { atlas.sprite(it) }
return atlas.mapAfter {
SimpleColumnInfo(
axis,
spriteList[0].get(),
spriteList[1].get(),
spriteList.drop(2).map { it.get() }
)
}
}
fun getAxis(state: BlockState): Axis? {
val axis = tryDefault(null) { state.get(LogBlock.AXIS).toString() } ?:
state.values.entries.find { it.key.getName().toLowerCase() == "axis" }?.value?.toString()
return when (axis) {
"x" -> Axis.X
"y" -> Axis.Y
"z" -> Axis.Z
else -> null
}
}
fun init() {
LogRegistry.registries.add(this)
BetterFoliage.blockSprites.providers.add(this)
}
}

View File

@@ -1,45 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.BlockConfig
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.isSnow
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.old.noPost
import mods.betterfoliage.render.snowOffset
import mods.betterfoliage.render.whitewash
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.util.Double3
import net.minecraft.util.Direction.UP
class RenderMycelium : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val myceliumIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_mycel_$idx") }
val myceliumModel = modelSet(64) { idx -> RenderGrass.grassTopQuads(
Config.shortGrass.heightMin,
Config.shortGrass.heightMax
)(idx) }
override fun isEligible(ctx: CombinedContext): Boolean {
if (!Config.enabled || !Config.shortGrass.myceliumEnabled) return false
return BlockConfig.mycelium.matchesClass(ctx.state.block)
}
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)
ctx.render(
myceliumModel[rand[0]],
translation = ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = { _, qi, _ -> myceliumIcon[rand[qi and 1]] },
postProcess = if (isSnowed) whitewash else noPost
)
}
}

View File

@@ -1,44 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.lighting.cornerAo
import mods.betterfoliage.render.lighting.cornerFlat
import mods.betterfoliage.render.lighting.faceOrientedAuto
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.xzDisk
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.util.randomD
import net.minecraft.block.Blocks
import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.*
class RenderNetherrack : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val netherrackIcon = spriteSet { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_netherrack_$idx") }
val netherrackModel = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yTop = -0.5,
yBottom = -0.5 - randomD(Config.netherrack.heightMin, Config.netherrack.heightMax))
.setAoShader(faceOrientedAuto(overrideFace = DOWN, corner = cornerAo(Axis.Y)))
.setFlatShader(faceOrientedAuto(overrideFace = DOWN, corner = cornerFlat))
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.shortGrass.hOffset) }.addAll()
}
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.netherrack.enabled && ctx.state.block == Blocks.NETHERRACK
override fun render(ctx: CombinedContext) {
ctx.render()
if (!ctx.isCutout) return
if (ctx.offset(DOWN).isNormalCube) return
val rand = ctx.semiRandomArray(2)
ctx.render(
netherrackModel[rand[0]],
icon = { _, qi, _ -> netherrackIcon[rand[qi and 1]] }
)
}
}

View File

@@ -1,70 +0,0 @@
package mods.betterfoliage.render.block.vanillaold
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.config.Config
import mods.betterfoliage.integration.ShadersModIntegration
import mods.betterfoliage.render.DIRT_BLOCKS
import mods.betterfoliage.resource.Identifier
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.lighting.FlatOffsetNoColor
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.toCross
import mods.betterfoliage.render.up1
import mods.betterfoliage.render.up2
import mods.betterfoliage.render.xzDisk
import mods.betterfoliage.resource.generated.CenteredSprite
import mods.betterfoliage.util.randomD
import net.minecraft.block.material.Material
import net.minecraft.util.Direction.UP
class RenderReeds : RenderDecorator(BetterFoliageMod.MOD_ID, BetterFoliageMod.bus) {
val noise = simplexNoise()
val reedIcons = spriteSetTransformed(
check = { idx -> Identifier(BetterFoliageMod.MOD_ID, "blocks/better_reed_$idx")},
register = { CenteredSprite(it).register(BetterFoliage.asyncPack) }
)
val reedModels = modelSet(64) { modelIdx ->
val height = randomD(Config.reed.heightMin, Config.reed.heightMax)
val waterline = 0.875f
val vCutLine = 0.5 - waterline / height
listOf(
// below waterline
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5, yTop = 0.5 + waterline)
.setFlatShader(FlatOffsetNoColor(up1)).clampUV(minV = vCutLine),
// above waterline
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5 + waterline, yTop = 0.5 + height)
.setFlatShader(FlatOffsetNoColor(up2)).clampUV(maxV = vCutLine)
).forEach {
it.clampUV(minU = -0.25, maxU = 0.25)
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.reed.hOffset) }.addAll()
}
}
override fun isEligible(ctx: CombinedContext) =
Config.enabled && Config.reed.enabled &&
ctx.state(up2).material == Material.AIR &&
ctx.state(UP).material == Material.WATER &&
DIRT_BLOCKS.contains(ctx.state.block) &&
ctx.biome
?.let { it.downfall > Config.reed.minBiomeRainfall && it.defaultTemperature >= Config.reed.minBiomeTemp } ?: false &&
noise[ctx.pos] < Config.reed.population
override val onlyOnCutout get() = false
override fun render(ctx: CombinedContext) {
ctx.render()
if (!ctx.isCutout) return
val iconVar = ctx.semiRandom(1)
ShadersModIntegration.grass(ctx, Config.reed.shaderWind) {
ctx.render(
reedModels[ctx.semiRandom(0)],
forceFlat = true,
icon = { _, _, _ -> reedIcons[iconVar] }
)
}
}
}

View File

@@ -1,209 +0,0 @@
package mods.betterfoliage.render.column
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.integration.ShadersModIntegration.renderAs
import mods.betterfoliage.render.*
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.*
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.old.Model
import mods.betterfoliage.render.old.RenderDecorator
import mods.betterfoliage.render.old.noPost
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.face
import mods.betterfoliage.util.rot
import net.minecraft.block.BlockRenderType.MODEL
import net.minecraft.util.Direction.*
import net.minecraftforge.eventbus.api.IEventBus
@Suppress("NOTHING_TO_INLINE")
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 }
// ============================
// Configuration
// ============================
abstract val overlayLayer: ColumnRenderLayer
abstract val connectPerpendicular: Boolean
abstract val radiusSmall: Double
abstract val radiusLarge: Double
// ============================
// Models
// ============================
val sideSquare = model { columnSideSquare(-0.5, 0.5) }
val sideRoundSmall = model { columnSide(radiusSmall, -0.5, 0.5) }
val sideRoundLarge = model { columnSide(radiusLarge, -0.5, 0.5) }
val extendTopSquare = model { columnSideSquare(0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) }
val extendTopRoundSmall = model { columnSide(radiusSmall, 0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) }
val extendTopRoundLarge = model { columnSide(radiusLarge, 0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) }
inline fun extendTop(type: QuadrantType) = when(type) {
SMALL_RADIUS -> extendTopRoundSmall.model
LARGE_RADIUS -> extendTopRoundLarge.model
SQUARE -> extendTopSquare.model
INVISIBLE -> extendTopSquare.model
else -> null
}
val extendBottomSquare = model { columnSideSquare(-0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) }
val extendBottomRoundSmall = model { columnSide(radiusSmall, -0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) }
val extendBottomRoundLarge = model { columnSide(radiusLarge, -0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) }
inline fun extendBottom(type: QuadrantType) = when (type) {
SMALL_RADIUS -> extendBottomRoundSmall.model
LARGE_RADIUS -> extendBottomRoundLarge.model
SQUARE -> extendBottomSquare.model
INVISIBLE -> extendBottomSquare.model
else -> null
}
val topSquare = model { columnLidSquare() }
val topRoundSmall = model { columnLid(radiusSmall) }
val topRoundLarge = model { columnLid(radiusLarge) }
inline fun flatTop(type: QuadrantType) = when(type) {
SMALL_RADIUS -> topRoundSmall.model
LARGE_RADIUS -> topRoundLarge.model
SQUARE -> topSquare.model
INVISIBLE -> topSquare.model
else -> null
}
val bottomSquare = model { columnLidSquare() { it.rotate(rot(EAST) * 2 + rot(UP)).mirrorUV(true, true) } }
val bottomRoundSmall = model { columnLid(radiusSmall) { it.rotate(rot(EAST) * 2 + rot(UP)).mirrorUV(true, true) } }
val bottomRoundLarge = model { columnLid(radiusLarge) { it.rotate(rot(EAST) * 2 + rot(UP)).mirrorUV(true, true) } }
inline fun flatBottom(type: QuadrantType) = when(type) {
SMALL_RADIUS -> bottomRoundSmall.model
LARGE_RADIUS -> bottomRoundLarge.model
SQUARE -> bottomSquare.model
INVISIBLE -> bottomSquare.model
else -> null
}
val transitionTop = model { mix(sideRoundLarge.model, sideRoundSmall.model) { it > 1 } }
val transitionBottom = model { mix(sideRoundSmall.model, sideRoundLarge.model) { it > 1 } }
inline fun continuous(q1: QuadrantType, q2: QuadrantType) =
q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE))
@Suppress("NON_EXHAUSTIVE_WHEN")
override fun render(ctx: CombinedContext) {
val roundLog = ChunkOverlayManager.get(overlayLayer, ctx)
when(roundLog) {
ColumnLayerData.SkipRender -> return
ColumnLayerData.NormalRender -> return ctx.render()
ColumnLayerData.ResolveError, null -> {
BetterFoliage.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 ctx.render()
}
val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal]
renderAs(ctx, MODEL) {
quadrantRotations.forEachIndexed { idx, quadrantRotation ->
// set rotation for the current quadrant
val rotation = baseRotation + quadrantRotation
// disallow sharp discontinuities in the chamfer radius, or tapering-in where inappropriate
if (roundLog.quadrants[idx] == LARGE_RADIUS &&
roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] != LARGE_RADIUS &&
roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] != LARGE_RADIUS) {
roundLog.quadrants[idx] = SMALL_RADIUS
}
// render side of current quadrant
val sideModel = when (roundLog.quadrants[idx]) {
SMALL_RADIUS -> sideRoundSmall.model
LARGE_RADIUS -> if (roundLog.upType == PARALLEL && roundLog.quadrantsTop[idx] == SMALL_RADIUS) transitionTop.model
else if (roundLog.downType == PARALLEL && roundLog.quadrantsBottom[idx] == SMALL_RADIUS) transitionBottom.model
else sideRoundLarge.model
SQUARE -> sideSquare.model
else -> null
}
if (sideModel != null) ctx.render(
sideModel,
rotation,
icon = roundLog.column.side,
postProcess = noPost
)
// render top and bottom end of current quadrant
var upModel: Model? = null
var downModel: Model? = null
var upIcon = roundLog.column.top
var downIcon = roundLog.column.bottom
var isLidUp = true
var isLidDown = true
when (roundLog.upType) {
NONSOLID -> upModel = flatTop(roundLog.quadrants[idx])
PERPENDICULAR -> {
if (!connectPerpendicular) {
upModel = flatTop(roundLog.quadrants[idx])
} else {
upIcon = roundLog.column.side
upModel = extendTop(roundLog.quadrants[idx])
isLidUp = false
}
}
PARALLEL -> {
if (!continuous(roundLog.quadrants[idx], roundLog.quadrantsTop[idx])) {
if (roundLog.quadrants[idx] == SQUARE || roundLog.quadrants[idx] == INVISIBLE) {
upModel = topSquare.model
}
}
}
}
when (roundLog.downType) {
NONSOLID -> downModel = flatBottom(roundLog.quadrants[idx])
PERPENDICULAR -> {
if (!connectPerpendicular) {
downModel = flatBottom(roundLog.quadrants[idx])
} else {
downIcon = roundLog.column.side
downModel = extendBottom(roundLog.quadrants[idx])
isLidDown = false
}
}
PARALLEL -> {
if (!continuous(roundLog.quadrants[idx], roundLog.quadrantsBottom[idx]) &&
(roundLog.quadrants[idx] == SQUARE || roundLog.quadrants[idx] == INVISIBLE)) {
downModel = bottomSquare.model
}
}
}
if (upModel != null) ctx.render(
upModel,
rotation,
icon = upIcon,
postProcess = { _, _, _, _, _ ->
if (isLidUp) {
rotateUV(idx + if (roundLog.column.axis == Axis.X) 1 else 0)
}
}
)
if (downModel != null) ctx.render(
downModel,
rotation,
icon = downIcon,
postProcess = { _, _, _, _, _ ->
if (isLidDown) {
rotateUV((if (roundLog.column.axis == Axis.X) 0 else 3) - idx)
}
}
)
}
}
}
}

View File

@@ -1,23 +1,27 @@
package mods.betterfoliage.render.column
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.chunk.ChunkOverlayLayer
import mods.betterfoliage.chunk.ChunkOverlayManager
import mods.betterfoliage.chunk.dimType
import mods.betterfoliage.config.Config
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.*
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.NONSOLID
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PARALLEL
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.PERPENDICULAR
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.BlockType.SOLID
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.*
import mods.betterfoliage.render.rotationFromUp
import mods.betterfoliage.render.old.BlockCtx
import mods.betterfoliage.resource.discovery.ModelRenderRegistry
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.INVISIBLE
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.LARGE_RADIUS
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SMALL_RADIUS
import mods.betterfoliage.render.column.ColumnLayerData.SpecialRender.QuadrantType.SQUARE
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.face
import mods.betterfoliage.util.plus
import net.minecraft.block.BlockState
import net.minecraft.util.Direction
import net.minecraft.util.Direction.Axis
import net.minecraft.util.Direction.AxisDirection
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
@@ -30,6 +34,10 @@ const val NW = 2
/** Index of SOUTH-WEST quadrant. */
const val SW = 3
interface ColumnBlockKey {
val axis: Axis?
}
/**
* Sealed class hierarchy for all possible render outcomes
*/
@@ -39,7 +47,7 @@ sealed class ColumnLayerData {
*/
@Suppress("ArrayInDataClass") // not used in comparisons anywhere
data class SpecialRender(
val column: ColumnTextureInfo,
val column: ColumnBlockKey,
val upType: BlockType,
val downType: BlockType,
val quadrants: Array<QuadrantType>,
@@ -47,7 +55,12 @@ sealed class ColumnLayerData {
val quadrantsBottom: Array<QuadrantType>
) : ColumnLayerData() {
enum class BlockType { SOLID, NONSOLID, PARALLEL, PERPENDICULAR }
enum class QuadrantType { SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE }
enum class QuadrantType {
SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE;
infix fun continuousWith(other: QuadrantType) =
this == other || ((this == SQUARE || this == INVISIBLE) && (other == SQUARE || other == INVISIBLE))
infix fun discontinuousWith(other: QuadrantType) = !continuousWith(other)
}
}
/** Column block should not be rendered at all */
@@ -60,15 +73,14 @@ sealed class ColumnLayerData {
object ResolveError : ColumnLayerData()
}
abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
abstract val registry: ModelRenderRegistry<ColumnTextureInfo>
abstract val blockPredicate: (BlockState)->Boolean
abstract val connectSolids: Boolean
abstract val lenientConnect: Boolean
abstract val defaultToY: Boolean
abstract fun getColumnKey(state: BlockState): ColumnBlockKey?
val allNeighborOffsets = (-1..1).flatMap { offsetX -> (-1..1).flatMap { offsetY -> (-1..1).map { offsetZ -> Int3(offsetX, offsetY, offsetZ) }}}
override fun onBlockUpdate(world: ILightReader, pos: BlockPos) {
@@ -76,15 +88,16 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
}
override fun calculate(ctx: BlockCtx): ColumnLayerData {
if (allDirections.all { dir -> ctx.offset(dir).let { it.isNormalCube && registry[ctx] == null }}) return ColumnLayerData.SkipRender
val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError
// TODO detect round logs
if (allDirections.all { dir -> ctx.offset(dir).let { it.isNormalCube } }) return ColumnLayerData.SkipRender
// val columnTextures = registry[ctx] ?: return ColumnLayerData.ResolveError
val columnTextures = getColumnKey(ctx.state) ?: return ColumnLayerData.ResolveError
// if log axis is not defined and "Default to vertical" config option is not set, render normally
val logAxis = columnTextures.axis ?: if (defaultToY) Axis.Y else return ColumnLayerData.NormalRender
// check log neighborhood
val baseRotation = rotationFromUp[(logAxis to AxisDirection.POSITIVE).face.ordinal]
val baseRotation = Rotation.fromUp[(logAxis to Direction.AxisDirection.POSITIVE).face.ordinal]
val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0))
val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0))
@@ -171,11 +184,11 @@ abstract class ColumnRenderLayer : ChunkOverlayLayer<ColumnLayerData> {
*/
fun BlockCtx.blockType(rotation: Rotation, axis: Axis, offset: Int3): ColumnLayerData.SpecialRender.BlockType {
val offsetRot = offset.rotate(rotation)
val state = state(offsetRot)
return if (!blockPredicate(state)) {
val key = getColumnKey(state(offsetRot))
return if (key == null) {
if (offset(offsetRot).isNormalCube) SOLID else NONSOLID
} else {
(registry[state, world, pos + offsetRot]?.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let {
(key.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let {
if (it == axis) PARALLEL else PERPENDICULAR
} ?: SOLID
}

View File

@@ -1,32 +0,0 @@
package mods.betterfoliage.render.column
import mods.betterfoliage.render.lighting.QuadIconResolver
import mods.betterfoliage.util.rotate
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction.*
interface ColumnTextureInfo {
val axis: Axis?
val top: QuadIconResolver
val bottom: QuadIconResolver
val side: QuadIconResolver
}
open class SimpleColumnInfo(
override val axis: Axis?,
val topTexture: TextureAtlasSprite,
val bottomTexture: TextureAtlasSprite,
val sideTextures: List<TextureAtlasSprite>
) : ColumnTextureInfo {
// index offsets for EnumFacings, to make it less likely for neighboring faces to get the same bark texture
val dirToIdx = arrayOf(0, 1, 2, 4, 3, 5)
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.modelRotation)
val sideIdx = if (sideTextures.size > 1) (ctx.semiRandom(1) + dirToIdx[worldFace.ordinal]) % sideTextures.size else 0
sideTextures[sideIdx]
}
}

View File

@@ -0,0 +1,138 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.util.get
import mods.betterfoliage.util.mapArray
import mods.betterfoliage.util.perpendiculars
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
typealias BoxCorner = Triple<Direction, Direction, Direction>
fun BoxCorner.equalsUnordered(other: BoxCorner) = contains(other.first) && contains(other.second) && contains(other.third)
fun BoxCorner.contains(dir: Direction) = first == dir || second == dir || third == dir
fun Array<BoxCorner>.findIdx(corner: BoxCorner): Int? {
forEachIndexed { idx, test -> if (test.contains(corner.first) && test.contains(corner.second) && test.contains(corner.third)) return idx }
return null
}
fun Array<BoxCorner>.findIdx(predicate: (BoxCorner)->Boolean): Int? {
forEachIndexed { idx, test -> if (predicate(test)) return idx }
return null
}
class AoSideHelper private constructor(face: Direction) {
val sides = faceSides[face]
val cornerSideDirections = faceCorners[face]
val aoIndex = faceCornersIdx.mapArray { corner ->
boxCornersDirIdx[face][sides[corner.first]][sides[corner.second]]!!
}
companion object {
/**
* Indexing for undirected box corners (component order does not matter).
* Array contains [Direction] triplets fully defining the corner.
*/
@JvmField
val boxCornersUndir = Array(8) { idx -> Triple(
if (idx and 1 != 0) EAST else WEST,
if (idx and 2 != 0) UP else DOWN,
if (idx and 4 != 0) SOUTH else NORTH
) }
/**
* Reverse lookup for [boxCornersUndir]. Index 3 times with the corner's cardinal directions.
* A null value indicates an invalid corner (multiple indexing along the same axis)
*/
@JvmField
val boxCornersUndirIdx = Array(6) { idx1 -> Array(6) { idx2 -> Array(6) { idx3 ->
boxCornersUndir.findIdx(BoxCorner(
Direction.values()[idx1],
Direction.values()[idx2],
Direction.values()[idx3]
))
} } }
/**
* Indexing for directed face sides
* First index is the face, second is index of side on face
*/
@JvmField
val faceSides = Array(6) { faceIdx -> Array(4) { sideIdx ->
Direction.values()[faceIdx].perpendiculars[sideIdx]
} }
/**
* Pairs of [faceSides] side indexes that form a valid pair describing a corner
*/
@JvmField
val faceCornersIdx = arrayOf(0 to 2, 0 to 3, 1 to 2, 1 to 3)
/**
* Indexing for directed face corners
* First index is the face, second is index of corner on face
*/
@JvmField
val faceCorners = Array(6) { faceIdx -> Array(4) { cornerIdx ->
faceCornersIdx[cornerIdx].let { faceSides[faceIdx][it.first] to faceSides[faceIdx][it.second] }
} }
/**
* Indexing scheme for directed box corners.
* The first direction - the face - matters, the other two are unordered.
* 1:1 correspondence with possible AO values.
* Array contains triplets defining the corner fully.
*/
@JvmField
val boxCornersDir = Array(24) { idx ->
val faceIdx = idx / 4; val face = Direction.values()[faceIdx]
val cornerIdx = idx % 4; val corner = faceCorners[faceIdx][cornerIdx]
BoxCorner(face, corner.first, corner.second)
}
/**
* Reverse lookup for [boxCornersDir]. Index 3 times with the corner's cardinal directions.
* The first direction - the face - matters, the other two are unordered.
* A null value indicates an invalid corner (multiple indexing along the same axis)
*/
@JvmField
val boxCornersDirIdx = Array(6) { face -> Array(6) { side1 -> Array(6) { side2 ->
boxCornersDir.findIdx { boxCorner ->
boxCorner.first.ordinal == face && boxCorner.equalsUnordered(BoxCorner(
Direction.values()[face],
Direction.values()[side1],
Direction.values()[side2]
))
}
} } }
/**
* Reverse lookup for [cornersDir].
* 1st index: primary face
* 2nd index: undirected corner index.
* value: directed corner index
* A null value indicates an invalid corner (primary face not shared by corner).
*/
@JvmField
val boxCornersDirFromUndir = Array(6) { faceIdx -> Array(8) { undirIdx ->
val face = Direction.values()[faceIdx]
val corner = boxCornersUndir[undirIdx]
if (!corner.contains(face)) null
else boxCornersDir.findIdx { it.first == face && it.equalsUnordered(corner) }
} }
@JvmField
val forSide = Direction.values().mapArray { AoSideHelper(it) }
/**
* Get corner index for vertex coordinates
*/
@JvmStatic
fun getCornerUndir(x: Double, y: Double, z: Double): Int {
var result = 0
if (x > 0.0) result += 1
if (y > 0.0) result += 2
if (z > 0.0) result += 4
return result
}
}
}

View File

@@ -0,0 +1,33 @@
package mods.betterfoliage.render.lighting
interface ForgeVertexLighterAccess {
var vertexLighter: ForgeVertexLighter
}
interface ForgeVertexLighter {
fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float)
fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int)
}
fun ForgeVertexLighter.grass() = object: ForgeVertexLighter {
override fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float) {
this@grass.updateVertexLightmap(normal, lightmap, x * 0.5f, 1.0f, z * 0.5f)
}
override fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int) {
this@grass.updateVertexColor(normal, color, x * 0.5f, 1.0f, z * 0.5f, tint, multiplier
)
}
}
fun ForgeVertexLighter.grassSimple() = object: ForgeVertexLighter {
val normalUp = floatArrayOf(0.0f, 1.0f, 0.0f, 0.0f)
override fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float) {
this@grassSimple.updateVertexLightmap(normalUp, lightmap, 0.0f, 1.0f, 0.0f)
}
override fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int) {
this@grassSimple.updateVertexColor(normalUp, color, 0.0f, 1.0f, 0.0f, tint, multiplier
)
}
}

View File

@@ -1,158 +0,0 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.render.old.Quad
import mods.betterfoliage.render.old.Vertex
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.axes
import mods.betterfoliage.util.boxEdges
import mods.betterfoliage.util.boxFaces
import mods.betterfoliage.util.face
import mods.betterfoliage.util.get
import mods.betterfoliage.util.nearestAngle
import mods.betterfoliage.util.nearestPosition
import mods.betterfoliage.util.perpendiculars
import mods.betterfoliage.util.vec
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import java.lang.Math.min
typealias EdgeShaderFactory = (Direction, Direction) -> ModelLighter
typealias CornerShaderFactory = (Direction, Direction, Direction) -> ModelLighter
typealias ShaderFactory = (Quad, Vertex) -> ModelLighter
/** 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
var green: Float = 0.0f
var blue: Float = 0.0f
fun reset() { valid = false }
fun set(brightness: Int, red: Float, green: Float, blue: Float) {
if (valid) return
this.valid = true
this.brightness = brightness
this.red = red
this.green = green
this.blue = blue
}
fun set(brightness: Int, colorMultiplier: Float) {
this.valid = true
this.brightness = brightness
this.red = colorMultiplier
this.green = colorMultiplier
this.blue = colorMultiplier
}
companion object {
val black: CornerLightData get() = CornerLightData()
}
}
/**
* Instances of this interface are associated with [Model] vertices, and used to apply brightness and color
* values to a [RenderVertex].
*/
interface ModelLighter {
/**
* Set shading values of a [RenderVertex]
*
* @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: LightingCtx, vertex: RenderVertex)
/**
* Return a new rotated version of this [ModelLighter]. Used during [Model] setup when rotating the model itself.
*/
fun rotate(rot: Rotation): ModelLighter
/** 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 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)
brightness = brWeighted(shading1.brightness, weight1, shading2.brightness, weight2)
}
/**
* Set the lighting values on the [RenderVertex] directly.
*
* @param[brightness] packed brightness value
* @param[color] packed color value
*/
fun RenderVertex.shade(brightness: Int, color: Int) {
this.brightness = brightness; setColor(color)
}
}
/**
* 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 [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] [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): ModelLighter {
val quadFace = overrideFace ?: quad.normal.nearestCardinal
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) {
(quadFace.vec + it.vec) * 0.5
}
if (edge != null && (nearestEdge.second < nearestCorner.second || corner == null))
return edge(quadFace, nearestEdge.first)
else return corner!!(quadFace, nearestCorner.first.first, nearestCorner.first.second)
}
/**
* 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 [ModelLighter] created by _corner_
*
* @param[overrideEdge] assume the given edge instead of going by the _quad_ normal
* @param[corner] ModelLighter instantiation lambda
*/
fun edgeOrientedAuto(overrideEdge: Pair<Direction, Direction>? = null,
corner: CornerShaderFactory
) =
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, 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): ModelLighter {
val resolver = faceOrientedAuto(overrideFace, edge = { face, edgeDir ->
val axis = axes.find { it != face.axis && it != edgeDir.axis }!!
val vec = Double3((axis to AxisDirection.POSITIVE).face)
val pos = vertex.xyz.dot(vec)
EdgeInterpolateFallback(face, edgeDir, pos)
})
return resolver(quad, vertex)
}

View File

@@ -1,118 +0,0 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.render.old.BlockCtx
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.boxFaces
import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.rotate
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.client.renderer.WorldRenderer
import net.minecraft.util.Direction
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 {
WorldRenderer.getCombinedLight(blockContext.world, blockContext.pos + it)
}
fun color(offset: Int3) = blockContext.offset(offset.rotate(modelRotation)).let { Minecraft.getInstance().blockColors.getColor(it.state, it.world, it.pos, 0) }
fun lighting(face: Direction, corner1: Direction, corner2: Direction): CornerLightData
}
class DefaultLightingCtx(blockContext: BlockCtx) : LightingCtx {
override var modelRotation = Rotation.identity
override var aoEnabled = false
protected set
override var blockContext: BlockCtx = blockContext
protected set
override var brightness = brightness(Int3.zero)
protected set
override var color = color(Int3.zero)
protected set
override fun brightness(face: Direction) = brightness(face.offset)
override fun color(face: Direction) = color(face.offset)
// smooth lighting stuff
val lightingData = Array(6) { FaceLightData(allDirections[it]) }
override fun lighting(face: Direction, corner1: Direction, corner2: Direction): CornerLightData = lightingData[face.rotate(modelRotation)].let { faceData ->
if (!faceData.isValid) faceData.update(blockContext, faceData.face.aoMultiplier)
return faceData[corner1.rotate(modelRotation), corner2.rotate(modelRotation)]
}
fun reset(blockContext: BlockCtx) {
this.blockContext = blockContext
brightness = brightness(Int3.zero)
color = color(Int3.zero)
modelRotation = Rotation.identity
lightingData.forEach { it.isValid = false }
aoEnabled = Minecraft.isAmbientOcclusionEnabled()
// allDirections.forEach { lightingData[it].update(blockContext, it.aoMultiplier) }
}
}
private val vanillaAOFactory = BlockModelRenderer.AmbientOcclusionFace::class.java.let {
it.getDeclaredConstructor(BlockModelRenderer::class.java).apply { isAccessible = true }
}.let { ctor -> { ctor.newInstance(Minecraft.getInstance().blockRendererDispatcher.blockModelRenderer) } }
class FaceLightData(val face: Direction) {
val topDir = boxFaces[face].top
val leftDir = boxFaces[face].left
val topLeft = CornerLightData()
val topRight = CornerLightData()
val bottomLeft = CornerLightData()
val bottomRight = CornerLightData()
val vanillaOrdered = when(face) {
Direction.DOWN -> listOf(topLeft, bottomLeft, bottomRight, topRight)
Direction.UP -> listOf(bottomRight, topRight, topLeft, bottomLeft)
Direction.NORTH -> listOf(bottomLeft, bottomRight, topRight, topLeft)
Direction.SOUTH -> listOf(topLeft, bottomLeft, bottomRight, topRight)
Direction.WEST -> listOf(bottomLeft, bottomRight, topRight, topLeft)
Direction.EAST -> listOf(topRight, topLeft, bottomLeft, bottomRight)
}
val delegate = vanillaAOFactory()
var isValid = false
fun update(blockCtx: BlockCtx, multiplier: Float) {
val quadBounds = FloatArray(12)
val flags = BitSet(3).apply { set(0) }
// delegate.updateVertexBrightness(blockCtx.world, blockCtx.state, blockCtx.pos, face, quadBounds, flags)
vanillaOrdered.forEachIndexed { idx, corner -> corner.set(delegate.vertexBrightness[idx], delegate.vertexColorMultiplier[idx] * multiplier) }
isValid = true
}
operator fun get(dir1: Direction, dir2: Direction): CornerLightData {
val isTop = topDir == dir1 || topDir == dir2
val isLeft = leftDir == dir1 || leftDir == dir2
return if (isTop) {
if (isLeft) topLeft else topRight
} else {
if (isLeft) bottomLeft else bottomRight
}
}
}

View File

@@ -1,162 +0,0 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.axes
import mods.betterfoliage.util.boxFaces
import mods.betterfoliage.util.face
import mods.betterfoliage.util.get
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.rotate
import net.minecraft.util.Direction
const val defaultCornerDimming = 0.5f
const val defaultEdgeDimming = 0.8f
// ================================
// Shader instantiation lambdas
// ================================
fun cornerAo(fallbackAxis: Direction.Axis): CornerShaderFactory = { face, dir1, dir2 ->
val fallbackDir = listOf(face, dir1, dir2).find { it.axis == fallbackAxis }!!
CornerSingleFallback(face, dir1, dir2, fallbackDir)
}
val cornerFlat = { face: Direction, dir1: Direction, dir2: Direction -> FaceFlat(face) }
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 }
fun cornerInterpolate(edgeAxis: Direction.Axis, weight: Float, dimming: Float): CornerShaderFactory = { dir1, dir2, dir3 ->
val edgeDir = listOf(dir1, dir2, dir3).find { it.axis == edgeAxis }!!
val faceDirs = listOf(dir1, dir2, dir3).filter { it.axis != edgeAxis }
CornerInterpolateDimming(faceDirs[0], faceDirs[1], edgeDir, weight, dimming)
}
// ================================
// Shaders
// ================================
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) :
ModelLighter {
val offset = Int3(fallbackDir)
override fun shade(context: LightingCtx, vertex: RenderVertex) {
val shading = context.lighting(face, dir1, dir2)
if (shading.valid)
vertex.shade(shading)
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: 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!!)
if (v1ok) return v1
if (v2ok) return v2
return null
}
class CornerTri(val face: Direction, val dir1: Direction, val dir2: Direction,
val func: ((CornerLightData, CornerLightData)-> CornerLightData)) : ModelLighter {
override fun shade(context: LightingCtx, vertex: RenderVertex) {
var acc = accumulate(
context.lighting(face, dir1, dir2),
context.lighting(dir1, face, dir2),
func)
acc = accumulate(
acc,
context.lighting(dir2, face, dir1),
func)
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):
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: 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)
vertex.shade(shadingP, shadingN, weightP, weightN)
}
override fun rotate(rot: Rotation) = EdgeInterpolateFallback(face.rotate(rot), edgeDir.rotate(rot), pos)
}
class CornerInterpolateDimming(val face1: Direction, val face2: Direction, val edgeDir: Direction,
val weight: Float, val dimming: Float, val fallbackDimming: Float = defaultCornerDimming
) : ModelLighter {
val offset = Int3(edgeDir)
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) {
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 }
vertex.shade(shading1, shading2, weight1, weight2)
}
override fun rotate(rot: Rotation) =
CornerInterpolateDimming(face1.rotate(rot), face2.rotate(rot), edgeDir.rotate(rot), weight, dimming, fallbackDimming)
}
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)
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
b[idx] = shading.brightness
}
vertex.apply { red *= 0.25f; green *= 0.25f; blue *= 0.25f }
vertex.brightness = brSum(0.25f, *b)
}
override fun rotate(rot: Rotation) = FaceCenter(face.rotate(rot))
}
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): ModelLighter = FaceFlat(face.rotate(rot))
}
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): ModelLighter = this
}
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): ModelLighter = this
}

View File

@@ -1,3 +0,0 @@
@file:JvmName("PixelFormat")
package mods.betterfoliage.render.lighting

View File

@@ -0,0 +1,147 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.chunk.BlockCtx
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
data class LightingData(
@JvmField var packedLight: Int = 0,
@JvmField var colorMultiplier: Float = 1.0f
) {
fun mixFrom(corner: LightingData, side1: LightingData, side2: LightingData, center: LightingData) {
colorMultiplier =
(center.colorMultiplier + side1.colorMultiplier + side2.colorMultiplier + corner.colorMultiplier) * 0.25f
packedLight = (
center.packedLight +
(side1.packedLight.takeUnless { it == 0 } ?: center.packedLight) +
(side2.packedLight.takeUnless { it == 0 } ?: center.packedLight) +
(corner.packedLight.takeUnless { it == 0 } ?: center.packedLight)
).let { sum -> (sum shr 2) and 0xFF00FF }
}
}
/**
* Replacement for [BlockModelRenderer.AmbientOcclusionFace]
* This gets called on a LOT, so object instantiation is avoided.
* Not thread-safe, always use a [ThreadLocal] instance
*/
class VanillaAoCalculator {
lateinit var world: ILightReader
/** [blockPos] is used to get block-related information (i.e. tint, opacity, etc.)
* [lightPos] is used to get light-related information
* this facilitates masquerade rendering of blocks */
lateinit var blockPos: BlockPos
lateinit var lightPos: BlockPos
private val probe = LightProbe(BlockModelRenderer.CACHE_COMBINED_LIGHT.get())
val isValid = BooleanArray(6)
val aoData = Array(24) { LightingData() }
// scratchpad values used during calculation
private val centerAo = LightingData()
private val sideAo = Array(4) { LightingData() }
private val cornerAo = Array(4) { LightingData() }
private val isOccluded = BooleanArray(4)
fun reset(ctx: BlockCtx) {
world = ctx.world; blockPos = ctx.pos; lightPos = ctx.pos
(0 until 6).forEach { isValid[it] = false }
}
fun fillLightData(lightFace: Direction, isOpaque: Boolean? = null) {
if (!isValid[lightFace.ordinal]) calculate(lightFace, isOpaque)
}
/**
* Replicate [BlockModelRenderer.AmbientOcclusionFace.updateVertexBrightness]
* Does not handle interpolation for non-cubic models, that should be
* done in a [VanillaVertexLighter]
* @param lightFace face of the block to calculate
* @param forceFull force full-block status for lighting calculation, null for auto
*/
private fun calculate(lightFace: Direction, forceFull: Boolean?) {
if (isValid[lightFace.ordinal]) return
val sideHelper = AoSideHelper.forSide[lightFace.ordinal]
// Bit 0 of the bitset in vanilla calculations
// true if the block model is planar with the block boundary
val isFullBlock = forceFull ?: world.getBlockState(blockPos).isCollisionShapeOpaque(world, blockPos)
val lightOrigin = if (isFullBlock) lightPos.offset(lightFace) else lightPos
// AO calculation for the face center
probe.position { setPos(lightOrigin) }.writeTo(centerAo)
if (!isFullBlock && !probe.position { move(lightFace) }.state.isOpaqueCube(world, probe.pos)) {
// if the neighboring block in the lightface direction is
// transparent (non-opaque), use its packed light instead of our own
// (if our block is a full block, we are already using this value)
centerAo.packedLight = probe.packedLight
}
// AO calculation for the 4 sides
sideHelper.sides.forEachIndexed { sideIdx, sideDir ->
// record light data in the block 1 step to the side
probe.position { setPos(lightOrigin).move(sideDir) }.writeTo(sideAo[sideIdx])
// side is considered occluded if the block 1 step to that side and
// 1 step forward (in the lightface direction) is not fully transparent
isOccluded[sideIdx] = probe.position { move(lightFace) }.isNonTransparent
}
// AO Calculation for the 4 corners
AoSideHelper.faceCornersIdx.forEachIndexed { cornerIdx, sideIndices ->
val bothOccluded = isOccluded[sideIndices.first] && isOccluded[sideIndices.second]
if (bothOccluded) cornerAo[cornerIdx].apply {
// if both sides are occluded, just use the packed light for one of the sides instead
val copyFrom = sideAo[sideIndices.first]
packedLight = copyFrom.packedLight; colorMultiplier = copyFrom.colorMultiplier
}
else {
// lookup actual packed light from the cornering block in the world
probe.position {
setPos(lightOrigin)
.move(sideHelper.sides[sideIndices.first])
.move(sideHelper.sides[sideIndices.second])
}.writeTo(cornerAo[cornerIdx])
}
}
// Calculate and store final interpolated value for each corner
AoSideHelper.faceCornersIdx.forEachIndexed { cornerIdx, sideIndices ->
val aoIdx = sideHelper.aoIndex[cornerIdx]
aoData[aoIdx].mixFrom(
cornerAo[cornerIdx],
sideAo[sideIndices.first],
sideAo[sideIndices.second],
centerAo
)
}
isValid[lightFace.ordinal] = true
}
inner class LightProbe(
val cache: BlockModelRenderer.Cache
) {
lateinit var state: BlockState
val pos = BlockPos.Mutable()
val packedLight: Int get() = cache.getPackedLight(state, world, pos)
val colorMultiplier: Float get() = cache.getBrightness(state, world, pos)
val isNonTransparent: Boolean get() = state.getOpacity(world, pos) > 0
fun writeTo(data: LightingData) {
data.packedLight = packedLight
data.colorMultiplier = colorMultiplier
}
inline fun position(func: BlockPos.Mutable.() -> Unit): LightProbe {
pos.func()
state = world.getBlockState(pos)
return this
}
}
}

View File

@@ -0,0 +1,133 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.render.old.HalfBakedQuad
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.EPSILON
import mods.betterfoliage.util.minBy
import net.minecraft.client.renderer.color.BlockColors
import net.minecraft.util.Direction
import net.minecraft.util.Direction.*
import net.minecraft.util.Direction.Axis
import net.minecraftforge.client.model.pipeline.LightUtil
import kotlin.math.abs
class VanillaQuadLighting {
val packedLight = IntArray(4)
val colorMultiplier = FloatArray(4)
val tint = FloatArray(3)
val calc = VanillaAoCalculator()
lateinit var blockColors: BlockColors
fun updateBlockTint(tintIndex: Int) {
if (tintIndex == -1) {
tint[0] = 1.0f; tint[1] = 1.0f; tint[2] = 1.0f
} else {
val state = calc.world.getBlockState(calc.blockPos)
blockColors.getColor(state, calc.world, calc.blockPos, tintIndex).let { blockTint ->
tint[0] = (blockTint shr 16 and 255).toFloat() / 255.0f
tint[1] = (blockTint shr 8 and 255).toFloat() / 255.0f
tint[2] = (blockTint and 255).toFloat() / 255.0f
}
}
}
fun applyDiffuseLighting(face: Direction) {
val factor = LightUtil.diffuseLight(face)
tint[0] *= factor; tint[1] *= factor; tint[2] *= factor
}
}
abstract class VanillaVertexLighter {
abstract fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting)
inline fun VanillaQuadLighting.updateWithCornerAo(quad: HalfBakedQuad, func: (Double3)->Int?) {
quad.raw.verts.forEachIndexed { idx, vertex ->
func(vertex.xyz)?.let {
packedLight[idx] = calc.aoData[it].packedLight
colorMultiplier[idx] = calc.aoData[it].colorMultiplier
}
}
}
}
object VanillaFullBlockLighting : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
// TODO bounds checking
val face = quad.raw.face()
lighting.calc.fillLightData(face, true)
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, face) }
lighting.updateBlockTint(quad.baked.tintIndex)
if (quad.baked.shouldApplyDiffuseLighting()) lighting.applyDiffuseLighting(face)
}
}
object RoundLeafLighting : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
val angles = getAngles45(quad)?.let { normalFaces ->
lighting.calc.fillLightData(normalFaces.first)
lighting.calc.fillLightData(normalFaces.second)
if (normalFaces.first != UP && normalFaces.second != UP) lighting.calc.fillLightData(UP)
lighting.updateWithCornerAo(quad) { vertex ->
val isUp = vertex.y > 0.5f
val cornerUndir = AoSideHelper.getCornerUndir(vertex.x, vertex.y, vertex.z)
val preferredFace = if (isUp) UP else normalFaces.minBy { faceDistance(it, vertex) }
AoSideHelper.boxCornersDirFromUndir[preferredFace.ordinal][cornerUndir]
}
lighting.updateBlockTint(quad.baked.tintIndex)
}
}
}
class LightingPreferredFace(val face: Direction) : VanillaVertexLighter() {
override fun updateLightmapAndColor(quad: HalfBakedQuad, lighting: VanillaQuadLighting) {
lighting.calc.fillLightData(face)
lighting.updateWithCornerAo(quad) { nearestCornerOnFace(it, face) }
lighting.updateBlockTint(quad.baked.tintIndex)
}
}
/**
* Return the directed box corner index for the corner nearest the given vertex,
* which is on the given face. May return null if the vertex is closest to
* one of the opposite 4 corners
*/
fun nearestCornerOnFace(pos: Double3, face: Direction): Int? {
val cornerUndir = AoSideHelper.getCornerUndir(pos.x, pos.y, pos.z)
return AoSideHelper.boxCornersDirFromUndir[face.ordinal][cornerUndir]
}
/**
* If the quad normal approximately bisects 2 axes at a 45 degree angle,
* and is approximately perpendicular to the third, returns the 2 directions
* the quad normal points towards.
* Returns null otherwise.
*/
fun getAngles45(quad: HalfBakedQuad): Pair<Direction, Direction>? {
val normal = quad.raw.normal
// one of the components must be close to zero
val zeroAxis = when {
abs(normal.x) < EPSILON -> Axis.X
abs(normal.y) < EPSILON -> Axis.Y
abs(normal.z) < EPSILON -> Axis.Z
else -> return null
}
// the other two must be of similar magnitude
val diff = when(zeroAxis) {
Axis.X -> abs(abs(normal.y) - abs(normal.z))
Axis.Y -> abs(abs(normal.x) - abs(normal.z))
Axis.Z -> abs(abs(normal.x) - abs(normal.y))
}
if (diff > EPSILON) return null
return when(zeroAxis) {
Axis.X -> Pair(if (normal.y > 0.0f) UP else DOWN, if (normal.z > 0.0f) SOUTH else NORTH)
Axis.Y -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.z > 0.0f) SOUTH else NORTH)
Axis.Z -> Pair(if (normal.x > 0.0f) EAST else WEST, if (normal.y > 0.0f) UP else DOWN)
}
}
fun faceDistance(face: Direction, pos: Double3) = when(face) {
WEST -> pos.x; EAST -> 1.0 - pos.x
DOWN -> pos.y; UP -> 1.0 - pos.y
NORTH -> pos.z; SOUTH -> 1.0 - pos.z
}

View File

@@ -1,146 +0,0 @@
package mods.betterfoliage.render.lighting
import mods.betterfoliage.render.old.CombinedContext
import mods.betterfoliage.render.old.Quad
import mods.betterfoliage.render.old.Vertex
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Rotation
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.util.Direction.*
import java.awt.Color
typealias QuadIconResolver = (CombinedContext, Int, Quad) -> TextureAtlasSprite?
typealias PostProcessLambda = RenderVertex.(CombinedContext, Int, Quad, Int, Vertex) -> Unit
@Suppress("NOTHING_TO_INLINE")
class RenderVertex {
var x: Double = 0.0
var y: Double = 0.0
var z: Double = 0.0
var u: Double = 0.0
var v: Double = 0.0
var brightness: Int = 0
var red: Float = 0.0f
var green: Float = 0.0f
var blue: Float = 0.0f
val rawData = IntArray(7)
fun init(vertex: Vertex, rot: Rotation, trans: Double3): RenderVertex {
val result = vertex.xyz.rotate(rot) + trans
x = result.x; y = result.y; z = result.z
return this
}
fun init(vertex: Vertex): RenderVertex {
x = vertex.xyz.x; y = vertex.xyz.y; z = vertex.xyz.z;
u = vertex.uv.u; v = vertex.uv.v
return this
}
fun translate(trans: Double3): RenderVertex { x += trans.x; y += trans.y; z += trans.z; return this }
fun rotate(rot: Rotation): RenderVertex {
if (rot === Rotation.identity) return this
val rotX = rot.rotatedComponent(EAST, x, y, z)
val rotY = rot.rotatedComponent(UP, x, y, z)
val rotZ = rot.rotatedComponent(SOUTH, x, y, z)
x = rotX; y = rotY; z = rotZ
return this
}
inline fun rotateUV(n: Int): RenderVertex {
when (n % 4) {
1 -> { val t = v; v = -u; u = t; return this }
2 -> { u = -u; v = -v; return this }
3 -> { val t = -v; v = u; u = t; return this }
else -> { return this }
}
}
inline fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) {
if (mirrorU) u = -u
if (mirrorV) v = -v
}
inline fun setIcon(icon: TextureAtlasSprite): RenderVertex {
u = (icon.maxU - icon.minU) * (u + 0.5) + icon.minU
v = (icon.maxV - icon.minV) * (v + 0.5) + icon.minV
return this
}
inline fun setGrey(level: Float) {
val grey = Math.min((red + green + blue) * 0.333f * level, 1.0f)
red = grey; green = grey; blue = grey
}
inline fun multiplyColor(color: Int) {
red *= (color shr 16 and 255) / 256.0f
green *= (color shr 8 and 255) / 256.0f
blue *= (color and 255) / 256.0f
}
inline fun setColor(color: Int) {
red = (color shr 16 and 255) / 256.0f
green = (color shr 8 and 255) / 256.0f
blue = (color and 255) / 256.0f
}
}
/** List of bit-shift offsets in packed brightness values where meaningful (4-bit) data is contained. */
var brightnessComponents = listOf(20, 4)
/** Multiply the components of this packed brightness value with the given [Float]. */
infix fun Int.brMul(f: Float): Int {
val weight = (f * 256.0f).toInt()
var result = 0
brightnessComponents.forEach { shift ->
val raw = (this shr shift) and 15
val weighted = (raw) * weight / 256
result = result or (weighted shl shift)
}
return result
}
/** Multiply the components of this packed color value with the given [Float]. */
infix fun Int.colorMul(f: Float): Int {
val weight = (f * 256.0f).toInt()
val red = (this shr 16 and 255) * weight / 256
val green = (this shr 8 and 255) * weight / 256
val blue = (this and 255) * weight / 256
return (red shl 16) or (green shl 8) or blue
}
/** Sum the components of all packed brightness values given. */
fun brSum(multiplier: Float?, vararg brightness: Int): Int {
val sum = Array(brightnessComponents.size) { 0 }
brightnessComponents.forEachIndexed { idx, shift -> brightness.forEach { br ->
val comp = (br shr shift) and 15
sum[idx] += comp
} }
var result = 0
brightnessComponents.forEachIndexed { idx, shift ->
val comp = if (multiplier == null)
((sum[idx]) shl shift)
else
((sum[idx].toFloat() * multiplier).toInt() shl shift)
result = result or comp
}
return result
}
fun brWeighted(br1: Int, weight1: Float, br2: Int, weight2: Float): Int {
val w1int = (weight1 * 256.0f + 0.5f).toInt()
val w2int = (weight2 * 256.0f + 0.5f).toInt()
var result = 0
brightnessComponents.forEachIndexed { idx, shift ->
val comp1 = (br1 shr shift) and 15
val comp2 = (br2 shr shift) and 15
val compWeighted = (comp1 * w1int + comp2 * w2int) / 256
result = result or ((compWeighted and 15) shl shift)
}
return result
}
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
companion object {
fun fromColor(color: Int): HSB {
val hsbVals = Color.RGBtoHSB((color shr 16) and 255, (color shr 8) and 255, color and 255, null)
return HSB(hsbVals[0], hsbVals[1], hsbVals[2])
}
}
val asColor: Int get() = Color.HSBtoRGB(hue, saturation, brightness)
}

View File

@@ -1,80 +0,0 @@
package mods.betterfoliage.render.old
import com.mojang.blaze3d.matrix.MatrixStack
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.offset
import mods.betterfoliage.util.plus
import mods.betterfoliage.util.semiRandom
import net.minecraft.block.Block
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.RenderType
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraft.world.IWorldReader
import net.minecraft.world.biome.Biome
import net.minecraftforge.client.model.data.IModelData
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: ILightReader
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 as? IWorldReader)?.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) = pos.semiRandom(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: ILightReader,
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: ILightReader, pos: BlockPos) : BasicBlockCtx(world, pos) {
var neighbors = Array<BlockState>(6) { world.getBlockState(pos + allDirections[it].offset) }
override var biome: Biome? = super.biome
override fun state(dir: Direction) = neighbors[dir.ordinal]
}
data class RenderCtx(
val dispatcher: BlockRendererDispatcher,
val renderBuffer: IVertexBuilder,
val matrixStack: MatrixStack,
val layer: RenderType,
val checkSides: Boolean,
val random: Random,
val modelData: IModelData
) {
fun render(worldBlock: BlockCtx) =
// dispatcher.renderBlock(worldBlock.state, worldBlock.pos, worldBlock.world, renderBuffer, random, modelData)
dispatcher.renderModel(worldBlock.state, worldBlock.pos, worldBlock.world, matrixStack, renderBuffer, checkSides, random, modelData)
}

View File

@@ -1,116 +0,0 @@
package mods.betterfoliage.render.old
import mods.betterfoliage.render.canRenderInCutout
import mods.betterfoliage.render.isCutout
import mods.betterfoliage.render.lighting.DefaultLightingCtx
import mods.betterfoliage.render.lighting.LightingCtx
import mods.betterfoliage.render.lighting.PostProcessLambda
import mods.betterfoliage.render.lighting.QuadIconResolver
import mods.betterfoliage.render.lighting.RenderVertex
import mods.octarinecore.BufferBuilder_setSprite
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.plus
import net.minecraft.block.Blocks
import net.minecraft.client.renderer.RenderTypeLookup
import net.minecraft.client.renderer.Vector3f
import net.minecraft.client.renderer.Vector4f
import net.minecraft.fluid.Fluids
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraft.world.LightType
import net.minecraft.world.level.ColorResolver
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 || RenderTypeLookup.canRenderInLayer(state, 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 and 15) + 0.5, (pos.y and 15) + 0.5, (pos.z and 15) + 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
) {
val cameraTransform = renderCtx.matrixStack.last
lightingCtx.modelRotation = rotation
model.quads.forEachIndexed { quadIdx, quad ->
if (quadFilter(quadIdx, quad)) {
val normal = quad.normal.let { Vector3f(it.x.toFloat(), it.y.toFloat(), it.z.toFloat()) }
normal.transform(cameraTransform.normal)
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
BufferBuilder_setSprite.invoke(renderCtx.renderBuffer, drawIcon)
quad.verts.forEachIndexed { vertIdx, vert ->
temp.init(vert).rotate(lightingCtx.modelRotation)
.translate(translation)
val vertex = temp.let { Vector4f(it.x.toFloat(), it.y.toFloat(), it.z.toFloat(), 0.0F) }
.apply { transform(cameraTransform.matrix) }
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)
// .pos(vertex.x.toDouble(), vertex.y.toDouble(), vertex.z.toDouble())
.color(temp.red, temp.green, temp.blue, 1.0f)
.tex(temp.u.toFloat(), temp.v.toFloat())
.lightmap(temp.brightness shr 16 and 65535, temp.brightness and 65535)
.normal(quad.normal.x.toFloat(), quad.normal.y.toFloat(), quad.normal.z.toFloat())
// .normal(normal.x, normal.y, normal.z)
.endVertex()
}
}
}
}
hasRendered = true
}
}
val allFaces: (Direction) -> Boolean = { true }
val topOnly: (Direction) -> Boolean = { it == Direction.UP }
/** Perform no post-processing */
val noPost: PostProcessLambda = { _, _, _, _, _ -> }
object NonNullWorld : ILightReader {
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 getLightManager() = null
override fun getBlockColor(p0: BlockPos, p1: ColorResolver) = 0
}

View File

@@ -0,0 +1,125 @@
package mods.betterfoliage.render.old
import mods.betterfoliage.render.ISpecialRenderModel
import mods.betterfoliage.render.pipeline.RenderCtxBase
import mods.betterfoliage.resource.discovery.ModelBakeKey
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.HasLogger
import mods.betterfoliage.util.directionsAndNull
import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.client.renderer.model.IModelTransform
import net.minecraft.client.renderer.model.IUnbakedModel
import net.minecraft.client.renderer.model.Material
import net.minecraft.client.renderer.model.ModelBakery
import net.minecraft.client.renderer.model.SimpleBakedModel
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.vertex.DefaultVertexFormats
import net.minecraft.client.renderer.vertex.VertexFormatElement
import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.pipeline.BakedQuadBuilder
import java.util.Random
import java.util.function.Function
data class HalfBakedQuad(
val raw: Quad,
val baked: BakedQuad
)
open class HalfBakedSimpleModelWrapper(baseModel: SimpleBakedModel): IBakedModel by baseModel, ISpecialRenderModel {
val baseQuads = baseModel.unbakeQuads()
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
ctx.render(baseQuads)
}
}
open class HalfBakedSpecialWrapper(val baseModel: ISpecialRenderModel): IBakedModel by baseModel, ISpecialRenderModel {
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
baseModel.render(ctx, noDecorations)
}
}
abstract class HalfBakedWrapKey : ModelBakeKey, HasLogger() {
override fun replace(
location: ResourceLocation,
unbaked: IUnbakedModel,
transform: IModelTransform,
bakery: ModelBakery,
spriteGetter: Function<Material, TextureAtlasSprite>
): IBakedModel? {
val baseModel = super.replace(location, unbaked, transform, bakery, spriteGetter)
val halfBaked = when(baseModel) {
is SimpleBakedModel -> HalfBakedSimpleModelWrapper(baseModel)
else -> null
}
return if (halfBaked == null) baseModel else replace(halfBaked)
}
abstract fun replace(wrapped: ISpecialRenderModel): ISpecialRenderModel
}
fun List<Quad>.bake(applyDiffuseLighting: Boolean) = map { quad ->
if (quad.sprite == null) throw IllegalStateException("Quad must have a texture assigned before baking")
val builder = BakedQuadBuilder(quad.sprite)
builder.setApplyDiffuseLighting(applyDiffuseLighting)
builder.setQuadOrientation(quad.face())
builder.setQuadTint(quad.colorIndex)
quad.verts.forEach { vertex ->
DefaultVertexFormats.BLOCK.elements.forEachIndexed { idx, element ->
when {
element.usage == VertexFormatElement.Usage.POSITION -> builder.put(idx,
(vertex.xyz.x + 0.5).toFloat(),
(vertex.xyz.y + 0.5).toFloat(),
(vertex.xyz.z + 0.5).toFloat(),
1.0f
)
// don't fill lightmap UV coords
element.usage == VertexFormatElement.Usage.UV && element.type == VertexFormatElement.Type.FLOAT -> builder.put(idx,
quad.sprite.minU + (quad.sprite.maxU - quad.sprite.minU) * (vertex.uv.u + 0.5).toFloat(),
quad.sprite.minV + (quad.sprite.maxV - quad.sprite.minV) * (vertex.uv.v + 0.5).toFloat(),
0.0f, 1.0f
)
element.usage == VertexFormatElement.Usage.COLOR -> builder.put(idx,
(vertex.color.red and 255).toFloat() / 255.0f,
(vertex.color.green and 255).toFloat() / 255.0f,
(vertex.color.blue and 255).toFloat() / 255.0f,
(vertex.color.alpha and 255).toFloat() / 255.0f
)
element.usage == VertexFormatElement.Usage.NORMAL -> builder.put(idx,
(vertex.normal ?: quad.normal).x.toFloat(),
(vertex.normal ?: quad.normal).y.toFloat(),
(vertex.normal ?: quad.normal).z.toFloat(),
0.0f
)
else -> builder.put(idx)
}
}
}
HalfBakedQuad(quad, builder.build())
}
fun BakedQuad.unbake(): HalfBakedQuad {
val size = DefaultVertexFormats.BLOCK.integerSize
val verts = Array(4) { vIdx ->
val x = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 0])
val y = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 1])
val z = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 2])
val color = vertexData[vIdx * size + 3]
val u = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 4])
val v = java.lang.Float.intBitsToFloat(vertexData[vIdx * size + 5])
Vertex(Double3(x, y, z), UV(u.toDouble(), v.toDouble()), Color(color))
}
val unbaked = Quad(
verts[0], verts[1], verts[2], verts[3],
colorIndex = if (hasTintIndex()) tintIndex else -1,
face = face
)
return HalfBakedQuad(unbaked, this)
}
fun SimpleBakedModel.unbakeQuads() = directionsAndNull.flatMap { face ->
getQuads(null, face, Random()).map { it.unbake() }
}

View File

@@ -1,23 +1,31 @@
package mods.betterfoliage.render.old
import mods.betterfoliage.render.lighting.ModelLighter
import mods.betterfoliage.render.lighting.NoLighting
import mods.betterfoliage.render.lighting.ShaderFactory
import mods.betterfoliage.render.lighting.cornerAo
import mods.betterfoliage.render.lighting.cornerFlat
import mods.betterfoliage.render.lighting.faceOrientedAuto
import mods.betterfoliage.util.Double3
import mods.betterfoliage.util.Rotation
import mods.betterfoliage.util.allDirections
import mods.betterfoliage.util.boxFaces
import mods.betterfoliage.util.get
import mods.betterfoliage.util.minmax
import mods.betterfoliage.util.nearestAngle
import mods.betterfoliage.util.replace
import mods.betterfoliage.util.rotate
import mods.betterfoliage.util.times
import mods.betterfoliage.util.toImmutableList
import mods.betterfoliage.util.vec
import net.minecraft.client.renderer.model.BakedQuad
import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.vertex.DefaultVertexFormats
import net.minecraft.client.renderer.vertex.VertexFormat
import net.minecraft.client.renderer.vertex.VertexFormatElement
import net.minecraft.client.renderer.vertex.VertexFormatElement.Type
import net.minecraft.client.renderer.vertex.VertexFormatElement.Usage
import net.minecraft.util.Direction
import net.minecraftforge.client.model.pipeline.BakedQuadBuilder
import java.lang.Math.max
import java.lang.Math.min
import java.util.Random
import kotlin.math.cos
import kotlin.math.sin
/**
* Vertex UV coordinates
@@ -34,7 +42,7 @@ data class UV(val u: Double, val v: Double) {
val rotate: UV get() = UV(v, -u)
fun rotate(n: Int) = when(n % 4) {
fun rotate(n: Int) = when (n % 4) {
0 -> copy()
1 -> UV(v, -u)
2 -> UV(-u, -v)
@@ -55,105 +63,153 @@ data class UV(val u: Double, val v: Double) {
* @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: ModelLighter = NoLighting,
val flatShader: ModelLighter = NoLighting
data class Vertex(
val xyz: Double3 = Double3(0.0, 0.0, 0.0),
val uv: UV = UV(0.0, 0.0),
val color: Color = Color.white,
val normal: Double3? = null
)
data class Color(val alpha: Int, val red: Int, val green: Int, val blue: Int) {
constructor(combined: Int) : this(
combined shr 24 and 255,
combined shr 16 and 255,
combined shr 8 and 255,
combined and 255
)
val asInt get() = (alpha shl 24) or (red shl 16) or (green shl 8) or blue
operator fun times(f: Float) = Color(
alpha,
(f * red.toFloat()).toInt().coerceIn(0 until 256),
(f * green.toFloat()).toInt().coerceIn(0 until 256),
(f * blue.toFloat()).toInt().coerceIn(0 until 256)
)
companion object {
val white get() = Color(255, 255, 255, 255)
}
}
data class HSB(var hue: Float, var saturation: Float, var brightness: Float) {
companion object {
fun fromColor(color: Int): HSB {
val hsbVals = java.awt.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() = java.awt.Color.HSBtoRGB(hue, saturation, brightness)
}
/**
* Model quad
* Intermediate representation of model quad
* Immutable, double-precision
* Zero-centered (both XYZ and UV) coordinates for simpler rotation/mirroring
*/
data class Quad(val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex) {
data class Quad(
val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex,
val sprite: TextureAtlasSprite? = null,
val colorIndex: Int = -1,
val face: Direction? = null
) {
val verts = arrayOf(v1, v2, v3, v4)
inline fun transformV(trans: (Vertex)-> Vertex): Quad = transformVI { vertex, idx -> trans(vertex) }
inline fun transformVI(trans: (Vertex, Int)-> Vertex): Quad =
Quad(trans(v1, 0), trans(v2, 1), trans(v3, 2), trans(v4, 3))
inline fun transformV(trans: (Vertex) -> Vertex): Quad = transformVI { vertex, idx -> trans(vertex) }
inline fun transformVI(trans: (Vertex, Int) -> Vertex): Quad = copy(
v1 = trans(v1, 0), v2 = trans(v2, 1), v3 = trans(v3, 2), v4 = trans(v4, 3)
)
val normal: Double3 get() = (v2.xyz - v1.xyz).cross(v4.xyz - v1.xyz).normalize
fun move(trans: Double3) = transformV { it.copy(xyz = it.xyz + trans) }
fun move(trans: Pair<Double, Direction>) = move(Double3(trans.second) * trans.first)
fun scale (scale: Double) = transformV { it.copy(xyz = it.xyz * scale) }
fun scale (scale: Double3) = transformV { it.copy(xyz = Double3(it.xyz.x * scale.x, it.xyz.y * scale.y, it.xyz.z * scale.z)) }
fun scaleUV (scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) }
fun rotate(rot: Rotation) = transformV {
it.copy(xyz = it.xyz.rotate(rot), aoShader = it.aoShader.rotate(rot), flatShader = it.flatShader.rotate(rot))
fun scale(scale: Double) = transformV { it.copy(xyz = it.xyz * scale) }
fun scale(scale: Double3) =
transformV { it.copy(xyz = Double3(it.xyz.x * scale.x, it.xyz.y * scale.y, it.xyz.z * scale.z)) }
fun rotate(rot: Rotation) =
transformV { it.copy(xyz = it.xyz.rotate(rot), normal = it.normal?.rotate(rot)) }.copy(face = face?.rotate(rot))
fun rotateZ(angle: Double) = transformV {
it.copy(
xyz = Double3(
it.xyz.x * cos(angle) + it.xyz.z * sin(angle),
it.xyz.y,
it.xyz.z * cos(angle) - it.xyz.x * sin(angle)
),
normal = it.normal?.let { normal ->
Double3(
normal.x * cos(angle) + normal.z * sin(angle),
normal.y,
normal.z * cos(angle) - normal.x * sin(angle)
)
}
)
}
fun scaleUV(scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) }
fun rotateUV(n: Int) = transformV { it.copy(uv = it.uv.rotate(n)) }
fun clampUV(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) =
transformV { it.copy(uv = it.uv.clamp(minU, maxU, minV, maxV)) }
fun mirrorUV(mirrorU: Boolean, mirrorV: Boolean) = transformV { it.copy(uv = it.uv.mirror(mirrorU, mirrorV)) }
fun setAoShader(factory: ShaderFactory, predicate: (Vertex, Int)->Boolean = { v, vi -> true }) =
transformVI { vertex, idx ->
if (!predicate(vertex, idx)) vertex else vertex.copy(aoShader = factory(this@Quad, vertex))
}
fun setFlatShader(factory: ShaderFactory, predicate: (Vertex, Int)->Boolean = { v, vi -> true }) =
transformVI { vertex, idx ->
if (!predicate(vertex, idx)) vertex else vertex.copy(flatShader = factory(this@Quad, vertex))
}
fun setFlatShader(shader: ModelLighter) = transformVI { vertex, idx -> vertex.copy(flatShader = shader) }
val flipped: Quad get() = Quad(v4, v3, v2, v1)
fun scrambleUV(random: Random, canFlipU: Boolean, canFlipV: Boolean, canRotate: Boolean) = this
.mirrorUV(canFlipU && random.nextBoolean(), canFlipV && random.nextBoolean())
.let { if (canRotate) it.rotateUV(random.nextInt(4)) else it }
fun cycleVertices(n: Int) = when(n % 4) {
fun sprite(sprite: TextureAtlasSprite) = copy(sprite = sprite)
fun color(color: Color) = transformV { it.copy(color = color) }
fun color(color: Int) = transformV { it.copy(color = Color(color)) }
fun colorIndex(colorIndex: Int) = copy(colorIndex = colorIndex)
fun colorAndIndex(color: Color?) = color(color ?: Color.white).colorIndex(if (color == null) 0 else -1)
fun face() = face ?: nearestAngle(normal, Direction.values().toList()) { it.vec }.first
val flipped: Quad get() = Quad(v4, v3, v2, v1, sprite, colorIndex)
fun cycleVertices(n: Int) = when (n % 4) {
1 -> Quad(v2, v3, v4, v1)
2 -> Quad(v3, v4, v1, v2)
3 -> Quad(v4, v1, v2, v3)
else -> this.copy()
}
}
/**
* Model. The basic unit of rendering blocks with OctarineCore.
*
* The model should be positioned so that (0,0,0) is the block center.
* The block extends to (-0.5, 0.5) in all directions (inclusive).
*/
class Model() {
constructor(other: List<Quad>) : this() { quads.addAll(other) }
val quads = mutableListOf<Quad>()
companion object {
fun mix(first: Quad, second: Quad, vertexFactory: (Vertex, Vertex) -> Vertex) = Quad(
v1 = vertexFactory(first.v1, second.v1),
v2 = vertexFactory(first.v2, second.v2),
v3 = vertexFactory(first.v3, second.v3),
v4 = vertexFactory(first.v4, second.v4)
)
fun Quad.add() = quads.add(this)
fun Iterable<Quad>.addAll() = forEach { quads.add(it) }
fun transformQ(trans: (Quad)-> Quad) = quads.replace(trans)
fun transformV(trans: (Vertex)-> Vertex) = quads.replace{ it.transformV(trans) }
fun verticalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, yBottom: Double, yTop: Double) = Quad(
fun verticalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, yBottom: Double, yTop: Double) = Quad(
Vertex(Double3(x1, yBottom, z1), UV.bottomLeft),
Vertex(Double3(x2, yBottom, z2), UV.bottomRight),
Vertex(Double3(x2, yTop, z2), UV.topRight),
Vertex(Double3(x1, yTop, z1), UV.topLeft)
)
fun horizontalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, y: Double): Quad {
val xMin = min(x1, x2); val xMax = max(x1, x2)
val zMin = min(z1, z2); val zMax = max(z1, z2)
return Quad(
Vertex(Double3(xMin, y, zMin), UV.topLeft),
Vertex(Double3(xMin, y, zMax), UV.bottomLeft),
Vertex(Double3(xMax, y, zMax), UV.bottomRight),
Vertex(Double3(xMax, y, zMin), UV.topRight)
)
}
fun faceQuad(face: Direction): Quad {
val base = face.vec * 0.5
val top = boxFaces[face].top * 0.5
val left = boxFaces[face].left * 0.5
return Quad(
fun horizontalRectangle(x1: Double, z1: Double, x2: Double, z2: Double, y: Double): Quad {
val xMin = min(x1, x2);
val xMax = max(x1, x2)
val zMin = min(z1, z2);
val zMax = max(z1, z2)
return Quad(
Vertex(Double3(xMin, y, zMin), UV.topLeft),
Vertex(Double3(xMin, y, zMax), UV.bottomLeft),
Vertex(Double3(xMax, y, zMax), UV.bottomRight),
Vertex(Double3(xMax, y, zMin), UV.topRight)
)
}
fun faceQuad(face: Direction): Quad {
val base = face.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),
Vertex(base - top - left, UV.bottomRight),
Vertex(base + top - left, UV.topRight)
)
)
}
}
}
val fullCube = Model().apply {
allDirections.forEach {
faceQuad(it)
.setAoShader(faceOrientedAuto(corner = cornerAo(it.axis), edge = null))
.setFlatShader(faceOrientedAuto(corner = cornerFlat, edge = null))
.add()
}
}

View File

@@ -1,23 +0,0 @@
@file:JvmName("RendererHolder")
package mods.betterfoliage.render.old
import mods.betterfoliage.resource.ResourceHandler
import net.minecraft.block.BlockState
import net.minecraftforge.eventbus.api.IEventBus
abstract class RenderDecorator(modId: String, modBus: IEventBus) : ResourceHandler(modId, modBus) {
open val renderOnCutout: Boolean get() = true
open val onlyOnCutout: Boolean get() = false
// ============================
// Custom rendering
// ============================
abstract fun isEligible(ctx: CombinedContext): Boolean
abstract fun render(ctx: CombinedContext)
}
data class BlockData(val state: BlockState, val color: Int, val brightness: Int)

View File

@@ -0,0 +1,43 @@
package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import mods.betterfoliage.chunk.BasicBlockCtx
import mods.betterfoliage.chunk.BlockCtx
import mods.betterfoliage.render.old.HalfBakedQuad
import mods.betterfoliage.util.Int3
import net.minecraft.block.Block
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.util.Direction
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraftforge.client.model.data.IModelData
import java.util.Random
abstract class RenderCtxBase(
world: ILightReader,
pos: BlockPos,
val matrixStack: MatrixStack,
val checkSides: Boolean,
val random: Random,
val modelData: IModelData
) : BlockCtx by BasicBlockCtx(world, pos) {
var hasRendered = false
val blockModelShapes = Minecraft.getInstance().blockRendererDispatcher.blockModelShapes
inline fun Direction?.shouldRender() = this == null || !checkSides || Block.shouldSideBeRendered(state, world, pos, this)
protected abstract fun renderQuad(quad: HalfBakedQuad)
abstract fun renderFallback(model: IBakedModel)
fun render(quads: Iterable<HalfBakedQuad>) {
quads.forEach { quad ->
if (quad.raw.face.shouldRender()) {
renderQuad(quad)
hasRendered = true
}
}
}
abstract fun renderMasquerade(offset: Int3, func: ()->Unit)
}

View File

@@ -0,0 +1,77 @@
package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import mods.betterfoliage.render.ISpecialRenderModel
import mods.betterfoliage.render.lighting.ForgeVertexLighter
import mods.betterfoliage.render.lighting.ForgeVertexLighterAccess
import mods.betterfoliage.render.old.HalfBakedQuad
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.directionsAndNull
import mods.betterfoliage.util.get
import mods.octarinecore.VertexLighterFlat_blockInfo
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraftforge.client.model.data.IModelData
import net.minecraftforge.client.model.pipeline.VertexLighterFlat
import java.util.Random
class RenderCtxForge(
world: ILightReader,
pos: BlockPos,
val lighter: VertexLighterFlat,
matrixStack: MatrixStack,
checkSides: Boolean,
random: Random,
modelData: IModelData
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData), ForgeVertexLighterAccess {
val blockInfo = lighter[VertexLighterFlat_blockInfo]
override var vertexLighter: ForgeVertexLighter
get() = (lighter as ForgeVertexLighterAccess).vertexLighter
set(value) { (lighter as ForgeVertexLighterAccess).vertexLighter = value }
override fun renderQuad(quad: HalfBakedQuad) { quad.baked.pipe(lighter) }
override fun renderFallback(model: IBakedModel) {
directionsAndNull.forEach { face ->
model.getQuads(state, null, random, modelData).forEach { quad ->
if (quad.face.shouldRender()) {
quad.pipe(lighter)
hasRendered = true
}
}
}
}
override fun renderMasquerade(offset: Int3, func: () -> Unit) {
TODO("Not yet implemented")
}
companion object {
@JvmStatic
fun render(
lighter: VertexLighterFlat,
world: ILightReader,
model: ISpecialRenderModel,
state: BlockState,
pos: BlockPos,
matrixStack: MatrixStack,
checkSides: Boolean,
rand: Random, seed: Long,
modelData: IModelData
): Boolean {
lighter.setWorld(world)
lighter.setState(state)
lighter.setBlockPos(pos)
rand.setSeed(seed)
lighter.updateBlockInfo()
return RenderCtxForge(world, pos, lighter, matrixStack, checkSides, rand, modelData).let {
model.render(it, false)
lighter.resetBlockInfo()
it.hasRendered
}
}
}
}

View File

@@ -0,0 +1,90 @@
package mods.betterfoliage.render.pipeline
import com.mojang.blaze3d.matrix.MatrixStack
import com.mojang.blaze3d.vertex.IVertexBuilder
import mods.betterfoliage.render.ISpecialRenderModel
import mods.betterfoliage.render.lighting.VanillaFullBlockLighting
import mods.betterfoliage.render.lighting.VanillaVertexLighter
import mods.betterfoliage.render.lighting.VanillaQuadLighting
import mods.betterfoliage.render.old.HalfBakedQuad
import mods.betterfoliage.util.Int3
import mods.betterfoliage.util.ThreadLocalDelegate
import mods.betterfoliage.util.plus
import net.minecraft.block.BlockState
import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.client.renderer.model.IBakedModel
import net.minecraft.util.math.BlockPos
import net.minecraft.world.ILightReader
import net.minecraftforge.client.model.data.IModelData
import java.util.Random
class RenderCtxVanilla(
val renderer: BlockModelRenderer,
world: ILightReader,
pos: BlockPos,
val buffer: IVertexBuilder,
val combinedOverlay: Int,
matrixStack: MatrixStack,
checkSides: Boolean,
random: Random,
val seed: Long,
modelData: IModelData,
val useAO: Boolean
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData) {
private val blockColors = renderer.blockColors
var vertexLighter: VanillaVertexLighter = VanillaFullBlockLighting
override fun renderQuad(quad: HalfBakedQuad) {
lightingData.let { lighting ->
vertexLighter.updateLightmapAndColor(quad, lighting)
buffer.addQuad(
matrixStack.last, quad.baked,
lighting.colorMultiplier,
lighting.tint[0], lighting.tint[1], lighting.tint[2],
lighting.packedLight, combinedOverlay, true
)
}
}
override fun renderFallback(model: IBakedModel) {
if (useAO) renderer.renderModelSmooth(world, model, state, pos, matrixStack, buffer, checkSides, random, seed, combinedOverlay, modelData)
else renderer.renderModelFlat(world, model, state, pos, matrixStack, buffer, checkSides, random, seed, combinedOverlay, modelData)
}
override fun renderMasquerade(offset: Int3, func: () -> Unit) {
lightingData.calc.blockPos += offset
func()
lightingData.calc.blockPos = pos
}
companion object {
@JvmStatic
fun render(
renderer: BlockModelRenderer,
world: ILightReader,
model: ISpecialRenderModel,
state: BlockState,
pos: BlockPos,
matrixStack: MatrixStack,
buffer: IVertexBuilder,
checkSides: Boolean,
random: Random,
rand: Long,
combinedOverlay: Int,
modelData: IModelData,
smooth: Boolean
): Boolean {
random.setSeed(rand)
val ctx = RenderCtxVanilla(renderer, world, pos, buffer, combinedOverlay, matrixStack, checkSides, random, rand, modelData, smooth)
lightingData.apply {
calc.reset(ctx)
blockColors = renderer.blockColors
}
model.render(ctx, false)
return ctx.hasRendered
}
val lightingData by ThreadLocalDelegate { VanillaQuadLighting() }
}
}