[WIP] algae, reeds, mycelium, coral working
+ lots of cleanup, reorganizing
This commit is contained in:
@@ -2,7 +2,7 @@ package mods.betterfoliage.render
|
||||
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.render.old.AbstractEntityFX
|
||||
import mods.betterfoliage.render.old.HSB
|
||||
import mods.betterfoliage.model.HSB
|
||||
import mods.betterfoliage.util.Double3
|
||||
import mods.betterfoliage.util.PI2
|
||||
import mods.betterfoliage.util.minmax
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,57 +1,77 @@
|
||||
package mods.betterfoliage.render.block.vanilla
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
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.model.Color
|
||||
import mods.betterfoliage.model.HalfBakedSpecialWrapper
|
||||
import mods.betterfoliage.model.HalfBakedWrapperKey
|
||||
import mods.betterfoliage.model.SpecialRenderModel
|
||||
import mods.betterfoliage.model.SpriteSetDelegate
|
||||
import mods.betterfoliage.model.buildTufts
|
||||
import mods.betterfoliage.model.tuftModelSet
|
||||
import mods.betterfoliage.model.tuftShapeSet
|
||||
import mods.betterfoliage.render.lighting.LightingPreferredFace
|
||||
import mods.betterfoliage.render.pipeline.RenderCtxBase
|
||||
import mods.betterfoliage.resource.discovery.ModelBakeKey
|
||||
import mods.betterfoliage.resource.discovery.ModelReplacer
|
||||
import mods.betterfoliage.render.pipeline.RenderCtxVanilla
|
||||
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingKey
|
||||
import mods.betterfoliage.resource.generated.CenteredSprite
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.Int3
|
||||
import mods.betterfoliage.util.LazyInvalidatable
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.offset
|
||||
import mods.betterfoliage.util.randomI
|
||||
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.client.renderer.model.BlockModel
|
||||
import net.minecraft.client.renderer.model.ModelBakery
|
||||
import net.minecraft.util.Direction.UP
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import net.minecraft.world.biome.Biome
|
||||
|
||||
object StandardDirtDiscovery : ModelReplacer() {
|
||||
val dirtBlocks = listOf(Blocks.DIRT, Blocks.COARSE_DIRT, Blocks.PODZOL)
|
||||
object StandardDirtDiscovery : AbstractModelDiscovery() {
|
||||
val DIRT_BLOCKS = listOf(Blocks.DIRT, Blocks.COARSE_DIRT, Blocks.PODZOL)
|
||||
override fun processModel(
|
||||
bakery: ModelBakery,
|
||||
state: BlockState,
|
||||
location: ResourceLocation,
|
||||
sprites: MutableSet<ResourceLocation>,
|
||||
replacements: MutableMap<ResourceLocation, ModelBakeKey>
|
||||
replacements: MutableMap<ResourceLocation, ModelBakingKey>
|
||||
): Boolean {
|
||||
val model = bakery.getUnbakedModel(location)
|
||||
if (model is BlockModel && state.block in dirtBlocks) {
|
||||
if (model is BlockModel && state.block in DIRT_BLOCKS) {
|
||||
Client.blockTypes.dirt.add(state)
|
||||
replacements[location] = StandardDirtKey
|
||||
RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutout())
|
||||
// RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutout())
|
||||
RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutoutMipped())
|
||||
return true
|
||||
}
|
||||
return super.processModel(bakery, state, location, sprites, replacements)
|
||||
}
|
||||
}
|
||||
|
||||
object StandardDirtKey : HalfBakedWrapKey() {
|
||||
override fun replace(wrapped: ISpecialRenderModel) = StandardDirtModel(wrapped)
|
||||
object StandardDirtKey : HalfBakedWrapperKey() {
|
||||
override fun replace(wrapped: SpecialRenderModel) = StandardDirtModel(wrapped)
|
||||
}
|
||||
|
||||
class StandardDirtModel(
|
||||
wrapped: ISpecialRenderModel
|
||||
wrapped: SpecialRenderModel
|
||||
) : HalfBakedSpecialWrapper(wrapped) {
|
||||
val vanillaTuftLighting = LightingPreferredFace(UP)
|
||||
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
if (!Config.enabled || noDecorations) return super.render(ctx, false)
|
||||
if (!Config.enabled || noDecorations) return super.render(ctx, noDecorations)
|
||||
|
||||
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.blockModelShapes.getModel(stateUp) as? SpecialRenderModel)?.let { grassModel ->
|
||||
ctx.renderMasquerade(UP.offset) {
|
||||
grassModel.render(ctx, true)
|
||||
}
|
||||
@@ -61,5 +81,39 @@ class StandardDirtModel(
|
||||
}
|
||||
|
||||
super.render(ctx, false)
|
||||
|
||||
val isWater = stateUp.material == Material.WATER
|
||||
val isDeepWater = isWater && ctx.offset(Int3(2 to UP)).state.material == Material.WATER
|
||||
val isShallowWater = isWater && ctx.offset(Int3(2 to UP)).state.isAir
|
||||
val isSaltWater = isWater && ctx.biome?.category in SALTWATER_BIOMES
|
||||
|
||||
if (Config.algae.enabled(ctx.random) && isDeepWater) {
|
||||
(ctx as? RenderCtxVanilla)?.vertexLighter = vanillaTuftLighting
|
||||
ctx.render(algaeModels[ctx.random])
|
||||
} else if (Config.reed.enabled(ctx.random) && isShallowWater && !isSaltWater) {
|
||||
(ctx as? RenderCtxVanilla)?.vertexLighter = vanillaTuftLighting
|
||||
ctx.render(reedModels[ctx.random])
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val SALTWATER_BIOMES = listOf(Biome.Category.BEACH, Biome.Category.OCEAN)
|
||||
|
||||
val algaeSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_algae_$idx")
|
||||
}
|
||||
val reedSprites by SpriteSetDelegate(
|
||||
Atlas.BLOCKS,
|
||||
idFunc = { idx -> ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_reed_$idx") },
|
||||
idRegister = { id -> CenteredSprite(id, aspectHeight = 2).register(Client.generatedPack) }
|
||||
)
|
||||
val algaeModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
val shapes = Config.algae.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
|
||||
tuftModelSet(shapes, Color.white) { algaeSprites[randomI()] }.buildTufts()
|
||||
}
|
||||
val reedModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
val shapes = Config.reed.let { tuftShapeSet(2.0, it.heightMin, it.heightMax, it.hOffset) }
|
||||
tuftModelSet(shapes, Color.white) { reedSprites[randomI()] }.buildTufts()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,22 +6,22 @@ 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.model.SpecialRenderModel
|
||||
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.model.Color
|
||||
import mods.betterfoliage.model.HalfBakedSpecialWrapper
|
||||
import mods.betterfoliage.model.HalfBakedWrapperKey
|
||||
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.resource.discovery.ConfigurableModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingKey
|
||||
import mods.betterfoliage.model.SpriteSetDelegate
|
||||
import mods.betterfoliage.model.buildTufts
|
||||
import mods.betterfoliage.model.fullCubeTextured
|
||||
import mods.betterfoliage.model.fullCubeTinted
|
||||
import mods.betterfoliage.model.tuftModelSet
|
||||
import mods.betterfoliage.model.tuftShapeSet
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.LazyInvalidatable
|
||||
import mods.betterfoliage.util.LazyMapInvalidatable
|
||||
@@ -29,14 +29,11 @@ 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() {
|
||||
object StandardGrassDiscovery : ConfigurableModelDiscovery() {
|
||||
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.grassBlocks
|
||||
override val modelTextures: List<ModelTextureList> get() = BlockConfig.grassModels.modelList
|
||||
|
||||
@@ -45,36 +42,33 @@ object StandardGrassDiscovery : ConfigurableModelReplacer() {
|
||||
location: ResourceLocation,
|
||||
textureMatch: List<ResourceLocation>,
|
||||
sprites: MutableSet<ResourceLocation>,
|
||||
replacements: MutableMap<ResourceLocation, ModelBakeKey>
|
||||
replacements: MutableMap<ResourceLocation, ModelBakingKey>
|
||||
): Boolean {
|
||||
replacements[location] = StandardGrassKey(textureMatch[0])
|
||||
Client.blockTypes.grass.add(state)
|
||||
RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutout())
|
||||
// RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutout())
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
data class StandardGrassKey(
|
||||
val grassLocation: ResourceLocation
|
||||
) : HalfBakedWrapKey() {
|
||||
override fun replace(wrapped: ISpecialRenderModel): ISpecialRenderModel {
|
||||
) : HalfBakedWrapperKey() {
|
||||
override fun replace(wrapped: SpecialRenderModel): SpecialRenderModel {
|
||||
Atlas.BLOCKS[grassLocation].logColorOverride(detailLogger, Config.shortGrass.saturationThreshold)
|
||||
return StandardGrassModel(wrapped, this)
|
||||
}
|
||||
}
|
||||
|
||||
class StandardGrassModel(
|
||||
wrapped: ISpecialRenderModel,
|
||||
wrapped: SpecialRenderModel,
|
||||
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)
|
||||
val tuftLighting = LightingPreferredFace(UP)
|
||||
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
if (!Config.enabled || noDecorations) return super.render(ctx, noDecorations)
|
||||
@@ -94,7 +88,7 @@ class StandardGrassModel(
|
||||
}
|
||||
|
||||
if (Config.shortGrass.enabled(ctx.random) && !ctx.isNeighborSolid(UP)) {
|
||||
(ctx as? RenderCtxVanilla)?.let { it.vertexLighter = vanillaTuftLighting }
|
||||
(ctx as? RenderCtxVanilla)?.let { it.vertexLighter = tuftLighting }
|
||||
ctx.render(if (isSnowed) tuftSnowed[ctx.random] else tuftNormal[ctx.random])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,21 +6,21 @@ 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.model.SpecialRenderModel
|
||||
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.model.Color
|
||||
import mods.betterfoliage.model.HalfBakedSpecialWrapper
|
||||
import mods.betterfoliage.model.HalfBakedWrapperKey
|
||||
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.discovery.ConfigurableModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingKey
|
||||
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.model.SpriteSetDelegate
|
||||
import mods.betterfoliage.model.crossModelsRaw
|
||||
import mods.betterfoliage.model.crossModelsTextured
|
||||
import mods.betterfoliage.model.crossModelsTinted
|
||||
import mods.betterfoliage.texture.LeafParticleRegistry
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.LazyMapInvalidatable
|
||||
@@ -33,7 +33,7 @@ import net.minecraft.util.ResourceLocation
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
import org.apache.logging.log4j.Logger
|
||||
|
||||
object StandardLeafDiscovery : ConfigurableModelReplacer() {
|
||||
object StandardLeafDiscovery : ConfigurableModelDiscovery() {
|
||||
override val matchClasses: ConfigurableBlockMatcher get() = BlockConfig.leafBlocks
|
||||
override val modelTextures: List<ModelTextureList> get() = BlockConfig.leafModels.modelList
|
||||
|
||||
@@ -42,11 +42,11 @@ object StandardLeafDiscovery : ConfigurableModelReplacer() {
|
||||
location: ResourceLocation,
|
||||
textureMatch: List<ResourceLocation>,
|
||||
sprites: MutableSet<ResourceLocation>,
|
||||
replacements: MutableMap<ResourceLocation, ModelBakeKey>
|
||||
replacements: MutableMap<ResourceLocation, ModelBakingKey>
|
||||
): Boolean {
|
||||
val leafType = LeafParticleRegistry.typeMappings.getType(textureMatch[0]) ?: "default"
|
||||
val generated = GeneratedLeaf(textureMatch[0], leafType)
|
||||
.register(Client.asyncPack)
|
||||
.register(Client.generatedPack)
|
||||
.apply { sprites.add(this) }
|
||||
|
||||
detailLogger.log(INFO, " particle $leafType")
|
||||
@@ -72,15 +72,15 @@ fun TextureAtlasSprite.getColorOverride(threshold: Double) = averageColor.let {
|
||||
data class StandardLeafKey(
|
||||
val roundLeafTexture: ResourceLocation,
|
||||
val leafType: String
|
||||
) : HalfBakedWrapKey() {
|
||||
override fun replace(wrapped: ISpecialRenderModel): ISpecialRenderModel {
|
||||
) : HalfBakedWrapperKey() {
|
||||
override fun replace(wrapped: SpecialRenderModel): SpecialRenderModel {
|
||||
Atlas.BLOCKS[roundLeafTexture].logColorOverride(BetterFoliageMod.detailLogger(this), 0.1)
|
||||
return StandardLeafModel(wrapped, this)
|
||||
}
|
||||
}
|
||||
|
||||
class StandardLeafModel(
|
||||
model: ISpecialRenderModel,
|
||||
model: SpecialRenderModel,
|
||||
key: StandardLeafKey
|
||||
) : HalfBakedSpecialWrapper(model) {
|
||||
val leafNormal by leafModelsNormal.delegate(key)
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package mods.betterfoliage.render.block.vanilla
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.model.Color
|
||||
import mods.betterfoliage.model.HalfBakedSpecialWrapper
|
||||
import mods.betterfoliage.model.HalfBakedWrapperKey
|
||||
import mods.betterfoliage.model.SpecialRenderModel
|
||||
import mods.betterfoliage.model.SpriteSetDelegate
|
||||
import mods.betterfoliage.model.buildTufts
|
||||
import mods.betterfoliage.model.tuftModelSet
|
||||
import mods.betterfoliage.model.tuftShapeSet
|
||||
import mods.betterfoliage.render.lighting.LightingPreferredFace
|
||||
import mods.betterfoliage.render.pipeline.RenderCtxBase
|
||||
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingKey
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.LazyInvalidatable
|
||||
import mods.betterfoliage.util.get
|
||||
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.client.renderer.model.BlockModel
|
||||
import net.minecraft.client.renderer.model.ModelBakery
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.ResourceLocation
|
||||
|
||||
object StandardMyceliumDiscovery : AbstractModelDiscovery() {
|
||||
val MYCELIUM_BLOCKS = listOf(Blocks.MYCELIUM)
|
||||
|
||||
override fun processModel(
|
||||
bakery: ModelBakery,
|
||||
state: BlockState,
|
||||
location: ResourceLocation,
|
||||
sprites: MutableSet<ResourceLocation>,
|
||||
replacements: MutableMap<ResourceLocation, ModelBakingKey>
|
||||
): Boolean {
|
||||
val model = bakery.getUnbakedModel(location)
|
||||
if (model is BlockModel && state.block in MYCELIUM_BLOCKS) {
|
||||
replacements[location] = StandardMyceliumKey
|
||||
RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutout())
|
||||
return true
|
||||
}
|
||||
return super.processModel(bakery, state, location, sprites, replacements)
|
||||
}
|
||||
}
|
||||
|
||||
object StandardMyceliumKey : HalfBakedWrapperKey() {
|
||||
override fun replace(wrapped: SpecialRenderModel) = StandardMyceliumModel(wrapped)
|
||||
}
|
||||
|
||||
class StandardMyceliumModel(
|
||||
wrapped: SpecialRenderModel
|
||||
) : HalfBakedSpecialWrapper(wrapped) {
|
||||
|
||||
val tuftLighting = LightingPreferredFace(Direction.UP)
|
||||
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
super.render(ctx, noDecorations)
|
||||
|
||||
if (Config.shortGrass.enabled &&
|
||||
Config.shortGrass.myceliumEnabled &&
|
||||
Config.shortGrass.enabled(ctx.random) &&
|
||||
ctx.state(Direction.UP).isAir(ctx.world, ctx.pos)
|
||||
) {
|
||||
ctx.vertexLighter = tuftLighting
|
||||
ctx.render(myceliumTuftModels[ctx.random])
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val myceliumTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_mycel_$idx")
|
||||
}
|
||||
val myceliumTuftModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
val shapes = Config.shortGrass.let { tuftShapeSet(it.size, it.heightMin, it.heightMax, it.hOffset) }
|
||||
tuftModelSet(shapes, Color.white) { idx -> myceliumTuftSprites[randomI()] }.buildTufts()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package mods.betterfoliage.render.block.vanilla
|
||||
|
||||
import mods.betterfoliage.render.column.ColumnBlockKey
|
||||
import mods.betterfoliage.resource.discovery.ModelBakeKey
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingKey
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.ResourceLocation
|
||||
|
||||
@@ -9,5 +9,5 @@ data class RoundLogKey(
|
||||
override val axis: Direction.Axis?,
|
||||
val barkSprite: ResourceLocation,
|
||||
val endSprite: ResourceLocation
|
||||
) : ColumnBlockKey, ModelBakeKey {
|
||||
) : ColumnBlockKey, ModelBakingKey {
|
||||
}
|
||||
118
src/main/kotlin/mods/betterfoliage/render/block/vanilla/Sand.kt
Normal file
118
src/main/kotlin/mods/betterfoliage/render/block/vanilla/Sand.kt
Normal file
@@ -0,0 +1,118 @@
|
||||
package mods.betterfoliage.render.block.vanilla
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.Client
|
||||
import mods.betterfoliage.config.Config
|
||||
import mods.betterfoliage.model.Color
|
||||
import mods.betterfoliage.model.HalfBakedSpecialWrapper
|
||||
import mods.betterfoliage.model.HalfBakedWrapperKey
|
||||
import mods.betterfoliage.model.Quad
|
||||
import mods.betterfoliage.model.SpecialRenderModel
|
||||
import mods.betterfoliage.model.SpriteSetDelegate
|
||||
import mods.betterfoliage.model.bake
|
||||
import mods.betterfoliage.model.buildTufts
|
||||
import mods.betterfoliage.model.transform
|
||||
import mods.betterfoliage.model.tuftModelSet
|
||||
import mods.betterfoliage.model.tuftShapeSet
|
||||
import mods.betterfoliage.render.block.vanilla.StandardDirtModel.Companion.SALTWATER_BIOMES
|
||||
import mods.betterfoliage.render.lighting.LightingPreferredFace
|
||||
import mods.betterfoliage.render.pipeline.RenderCtxBase
|
||||
import mods.betterfoliage.resource.discovery.AbstractModelDiscovery
|
||||
import mods.betterfoliage.resource.discovery.BakeWrapperManager
|
||||
import mods.betterfoliage.resource.discovery.ModelBakingKey
|
||||
import mods.betterfoliage.util.Atlas
|
||||
import mods.betterfoliage.util.LazyInvalidatable
|
||||
import mods.betterfoliage.util.Rotation
|
||||
import mods.betterfoliage.util.allDirections
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.mapArray
|
||||
import mods.betterfoliage.util.randomB
|
||||
import mods.betterfoliage.util.randomD
|
||||
import mods.betterfoliage.util.randomI
|
||||
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.client.renderer.model.BlockModel
|
||||
import net.minecraft.client.renderer.model.ModelBakery
|
||||
import net.minecraft.util.Direction
|
||||
import net.minecraft.util.Direction.UP
|
||||
import net.minecraft.util.ResourceLocation
|
||||
|
||||
object StandardSandDiscovery : AbstractModelDiscovery() {
|
||||
val SAND_BLOCKS = listOf(Blocks.SAND, Blocks.RED_SAND)
|
||||
override fun processModel(
|
||||
bakery: ModelBakery,
|
||||
state: BlockState,
|
||||
location: ResourceLocation,
|
||||
sprites: MutableSet<ResourceLocation>,
|
||||
replacements: MutableMap<ResourceLocation, ModelBakingKey>
|
||||
): Boolean {
|
||||
val model = bakery.getUnbakedModel(location)
|
||||
if (model is BlockModel && state.block in SAND_BLOCKS) {
|
||||
Client.blockTypes.dirt.add(state)
|
||||
replacements[location] = StandardSandKey
|
||||
RenderTypeLookup.setRenderLayer(state.block, RenderType.getCutoutMipped())
|
||||
return true
|
||||
}
|
||||
return super.processModel(bakery, state, location, sprites, replacements)
|
||||
}
|
||||
}
|
||||
|
||||
object StandardSandKey : HalfBakedWrapperKey() {
|
||||
override fun replace(wrapped: SpecialRenderModel) = StandardSandModel(wrapped)
|
||||
}
|
||||
|
||||
class StandardSandModel(
|
||||
wrapped: SpecialRenderModel
|
||||
) : HalfBakedSpecialWrapper(wrapped) {
|
||||
val coralLighting = Direction.values().mapArray { LightingPreferredFace(it) }
|
||||
|
||||
override fun render(ctx: RenderCtxBase, noDecorations: Boolean) {
|
||||
super.render(ctx, noDecorations)
|
||||
if (noDecorations || !Config.enabled || !Config.coral.enabled(ctx.random)) return
|
||||
if (ctx.biome?.category !in SALTWATER_BIOMES) return
|
||||
|
||||
allDirections.filter { ctx.random.nextInt(64) < Config.coral.chance }.forEach { face ->
|
||||
val isWater = ctx.state(face).material == Material.WATER
|
||||
val isDeepWater = isWater && ctx.offset(face).state(UP).material == Material.WATER
|
||||
if (isDeepWater) {
|
||||
ctx.vertexLighter = coralLighting[face]
|
||||
ctx.render(coralCrustModels[face][ctx.random])
|
||||
ctx.render(coralTuftModels[face][ctx.random])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val coralTuftSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_coral_$idx")
|
||||
}
|
||||
val coralCrustSprites by SpriteSetDelegate(Atlas.BLOCKS) { idx ->
|
||||
ResourceLocation(BetterFoliageMod.MOD_ID, "blocks/better_crust_$idx")
|
||||
}
|
||||
val coralTuftModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
val shapes = Config.coral.let { tuftShapeSet(it.size, 1.0, 1.0, it.hOffset) }
|
||||
allDirections.mapArray { face ->
|
||||
tuftModelSet(shapes, Color.white) { coralTuftSprites[randomI()] }
|
||||
.transform { rotate(Rotation.fromUp[face]) }
|
||||
.buildTufts()
|
||||
}
|
||||
}
|
||||
val coralCrustModels by LazyInvalidatable(BakeWrapperManager) {
|
||||
allDirections.map { face ->
|
||||
Array(64) { idx ->
|
||||
listOf(
|
||||
Quad.horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0)
|
||||
.scale(Config.coral.crustSize)
|
||||
.move(0.5 + randomD(0.01, Config.coral.vOffset) to UP)
|
||||
.rotate(Rotation.fromUp[face])
|
||||
.mirrorUV(randomB(), randomB()).rotateUV(randomI(max = 4))
|
||||
.sprite(coralCrustSprites[idx]).colorAndIndex(null)
|
||||
).bake(applyDiffuseLighting = false)
|
||||
}
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,27 +7,4 @@ interface ForgeVertexLighterAccess {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
import mods.betterfoliage.render.old.HalfBakedQuad
|
||||
import mods.betterfoliage.model.HalfBakedQuad
|
||||
import mods.betterfoliage.util.Double3
|
||||
import mods.betterfoliage.util.EPSILON
|
||||
import mods.betterfoliage.util.minBy
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
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() }
|
||||
}
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
package mods.betterfoliage.render.old
|
||||
|
||||
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
|
||||
*
|
||||
* Zero-centered: coordinates fall between (-0.5, 0.5) (inclusive)
|
||||
*/
|
||||
data class UV(val u: Double, val v: Double) {
|
||||
companion object {
|
||||
val topLeft = UV(-0.5, -0.5)
|
||||
val topRight = UV(0.5, -0.5)
|
||||
val bottomLeft = UV(-0.5, 0.5)
|
||||
val bottomRight = UV(0.5, 0.5)
|
||||
}
|
||||
|
||||
val rotate: UV get() = UV(v, -u)
|
||||
|
||||
fun rotate(n: Int) = when (n % 4) {
|
||||
0 -> copy()
|
||||
1 -> UV(v, -u)
|
||||
2 -> UV(-u, -v)
|
||||
else -> UV(-v, u)
|
||||
}
|
||||
|
||||
fun clamp(minU: Double = -0.5, maxU: Double = 0.5, minV: Double = -0.5, maxV: Double = 0.5) =
|
||||
UV(u.minmax(minU, maxU), v.minmax(minV, maxV))
|
||||
|
||||
fun mirror(mirrorU: Boolean, mirrorV: Boolean) = UV(if (mirrorU) -u else u, if (mirrorV) -v else v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Model vertex
|
||||
*
|
||||
* @param[xyz] x, y, z coordinates
|
||||
* @param[uv] u, v coordinates
|
||||
* @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 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
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 = 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 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 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 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()
|
||||
}
|
||||
|
||||
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 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(
|
||||
Vertex(base + top + left, UV.topLeft),
|
||||
Vertex(base - top + left, UV.bottomLeft),
|
||||
Vertex(base - top - left, UV.bottomRight),
|
||||
Vertex(base + top - left, UV.topRight)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package mods.betterfoliage.render.old
|
||||
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.IBlockReader
|
||||
import net.minecraft.world.ILightReader
|
||||
|
||||
/**
|
||||
* Delegating [IBlockAccess] that fakes a _modified_ location to return values from a _target_ location.
|
||||
* All other locations are handled normally.
|
||||
*
|
||||
* @param[original] the [IBlockAccess] that is delegated to
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType")
|
||||
open class OffsetBlockReader(open val original: IBlockReader, val modded: BlockPos, val target: BlockPos) : IBlockReader {
|
||||
inline fun actualPos(pos: BlockPos) = if (pos != null && pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos
|
||||
|
||||
override fun getBlockState(pos: BlockPos) = original.getBlockState(actualPos(pos))
|
||||
override fun getTileEntity(pos: BlockPos) = original.getTileEntity(actualPos(pos))
|
||||
override fun getFluidState(pos: BlockPos) = original.getFluidState(actualPos(pos))
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType")
|
||||
class OffsetEnvBlockReader(val original: ILightReader, val modded: BlockPos, val target: BlockPos) : ILightReader by original {
|
||||
inline fun actualPos(pos: BlockPos) = if (pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos
|
||||
|
||||
override fun getBlockState(pos: BlockPos) = original.getBlockState(actualPos(pos))
|
||||
override fun getTileEntity(pos: BlockPos) = original.getTileEntity(actualPos(pos))
|
||||
override fun getFluidState(pos: BlockPos) = original.getFluidState(actualPos(pos))
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily replaces the [IBlockReader] used by this [BlockContext] and the corresponding [ExtendedRenderBlocks]
|
||||
* to use an [OffsetEnvBlockReader] while executing this lambda.
|
||||
*
|
||||
* @param[modded] the _modified_ location
|
||||
* @param[target] the _target_ location
|
||||
* @param[func] the lambda to execute
|
||||
*/
|
||||
//inline fun <reified T> BlockContext.withOffset(modded: Int3, target: Int3, func: () -> T): T {
|
||||
// val original = reader!!
|
||||
// reader = OffsetEnvBlockReader(original, pos + modded, pos + target)
|
||||
// val result = func()
|
||||
// reader = original
|
||||
// return result
|
||||
//}
|
||||
@@ -3,17 +3,27 @@ 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.model.SpecialRenderModel
|
||||
import mods.betterfoliage.render.lighting.VanillaFullBlockLighting
|
||||
import mods.betterfoliage.render.lighting.VanillaQuadLighting
|
||||
import mods.betterfoliage.render.lighting.VanillaVertexLighter
|
||||
import mods.betterfoliage.model.HalfBakedQuad
|
||||
import mods.betterfoliage.util.Int3
|
||||
import mods.betterfoliage.util.plus
|
||||
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
|
||||
|
||||
/**
|
||||
* Rendering context for drawing [SpecialRenderModel] models.
|
||||
*
|
||||
* This class (and others in its constellation) basically form a replacement, highly customizable,
|
||||
* push-based partial rendering pipeline for [SpecialRenderModel] instances.
|
||||
*/
|
||||
abstract class RenderCtxBase(
|
||||
world: ILightReader,
|
||||
pos: BlockPos,
|
||||
@@ -23,12 +33,17 @@ abstract class RenderCtxBase(
|
||||
val modelData: IModelData
|
||||
) : BlockCtx by BasicBlockCtx(world, pos) {
|
||||
|
||||
abstract fun renderQuad(quad: HalfBakedQuad)
|
||||
|
||||
var hasRendered = false
|
||||
val blockModelShapes = Minecraft.getInstance().blockRendererDispatcher.blockModelShapes
|
||||
inline fun Direction?.shouldRender() = this == null || !checkSides || Block.shouldSideBeRendered(state, world, pos, this)
|
||||
var vertexLighter: VanillaVertexLighter = VanillaFullBlockLighting
|
||||
protected val lightingData = RenderCtxBase.lightingData.get().apply {
|
||||
calc.reset(this@RenderCtxBase)
|
||||
blockColors = Minecraft.getInstance().blockColors
|
||||
}
|
||||
|
||||
protected abstract fun renderQuad(quad: HalfBakedQuad)
|
||||
abstract fun renderFallback(model: IBakedModel)
|
||||
inline fun Direction?.shouldRender() = this == null || !checkSides || Block.shouldSideBeRendered(state, world, pos, this)
|
||||
|
||||
fun render(quads: Iterable<HalfBakedQuad>) {
|
||||
quads.forEach { quad ->
|
||||
@@ -39,5 +54,13 @@ abstract class RenderCtxBase(
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun renderMasquerade(offset: Int3, func: ()->Unit)
|
||||
fun renderMasquerade(offset: Int3, func: () -> Unit) {
|
||||
lightingData.calc.blockPos += offset
|
||||
func()
|
||||
lightingData.calc.blockPos = pos
|
||||
}
|
||||
|
||||
companion object {
|
||||
val lightingData = ThreadLocal.withInitial { VanillaQuadLighting() }
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,12 @@
|
||||
package mods.betterfoliage.render.pipeline
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack
|
||||
import mods.betterfoliage.render.ISpecialRenderModel
|
||||
import mods.betterfoliage.model.SpecialRenderModel
|
||||
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 mods.betterfoliage.model.HalfBakedQuad
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.renderer.model.IBakedModel
|
||||
import net.minecraft.client.renderer.LightTexture
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.ILightReader
|
||||
import net.minecraftforge.client.model.data.IModelData
|
||||
@@ -25,28 +21,28 @@ class RenderCtxForge(
|
||||
checkSides: Boolean,
|
||||
random: Random,
|
||||
modelData: IModelData
|
||||
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData), ForgeVertexLighterAccess {
|
||||
): RenderCtxBase(world, pos, matrixStack, checkSides, random, modelData), ForgeVertexLighter {
|
||||
|
||||
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) {
|
||||
// set Forge lighter AO calculator to us
|
||||
vertexLighter.updateLightmapAndColor(quad, lightingData)
|
||||
quad.baked.pipe(lighter)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
// somewhat ugly hack to pipe lighting values into the Forge pipeline
|
||||
var vIdx = 0
|
||||
override fun updateVertexLightmap(normal: FloatArray, lightmap: FloatArray, x: Float, y: Float, z: Float) {
|
||||
lightingData.packedLight[vIdx].let { packedLight ->
|
||||
lightmap[0] = LightTexture.getLightBlock(packedLight) / 0xF.toFloat()
|
||||
lightmap[1] = LightTexture.getLightSky(packedLight) / 0xF.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
override fun renderMasquerade(offset: Int3, func: () -> Unit) {
|
||||
TODO("Not yet implemented")
|
||||
override fun updateVertexColor(normal: FloatArray, color: FloatArray, x: Float, y: Float, z: Float, tint: Float, multiplier: Int) {
|
||||
color[0] = lightingData.tint[0] * lightingData.colorMultiplier[vIdx]
|
||||
color[1] = lightingData.tint[1] * lightingData.colorMultiplier[vIdx]
|
||||
color[2] = lightingData.tint[2] * lightingData.colorMultiplier[vIdx]
|
||||
vIdx++
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -54,7 +50,7 @@ class RenderCtxForge(
|
||||
fun render(
|
||||
lighter: VertexLighterFlat,
|
||||
world: ILightReader,
|
||||
model: ISpecialRenderModel,
|
||||
model: SpecialRenderModel,
|
||||
state: BlockState,
|
||||
pos: BlockPos,
|
||||
matrixStack: MatrixStack,
|
||||
@@ -68,6 +64,7 @@ class RenderCtxForge(
|
||||
rand.setSeed(seed)
|
||||
lighter.updateBlockInfo()
|
||||
return RenderCtxForge(world, pos, lighter, matrixStack, checkSides, rand, modelData).let {
|
||||
(lighter as ForgeVertexLighterAccess).vertexLighter = it
|
||||
model.render(it, false)
|
||||
lighter.resetBlockInfo()
|
||||
it.hasRendered
|
||||
|
||||
@@ -2,17 +2,10 @@ 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 mods.betterfoliage.model.SpecialRenderModel
|
||||
import mods.betterfoliage.model.HalfBakedQuad
|
||||
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
|
||||
@@ -32,30 +25,14 @@ class RenderCtxVanilla(
|
||||
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
|
||||
vertexLighter.updateLightmapAndColor(quad, lightingData)
|
||||
buffer.addQuad(
|
||||
matrixStack.last, quad.baked,
|
||||
lightingData.colorMultiplier,
|
||||
lightingData.tint[0], lightingData.tint[1], lightingData.tint[2],
|
||||
lightingData.packedLight, combinedOverlay, true
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -63,7 +40,7 @@ class RenderCtxVanilla(
|
||||
fun render(
|
||||
renderer: BlockModelRenderer,
|
||||
world: ILightReader,
|
||||
model: ISpecialRenderModel,
|
||||
model: SpecialRenderModel,
|
||||
state: BlockState,
|
||||
pos: BlockPos,
|
||||
matrixStack: MatrixStack,
|
||||
@@ -78,13 +55,12 @@ class RenderCtxVanilla(
|
||||
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() }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user