[WIP] start 1.15 port
reorganize packages to match Fabric version use util classes from Fabric version
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
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.util.Double3
|
||||
import mods.betterfoliage.util.PI2
|
||||
import mods.betterfoliage.util.minmax
|
||||
import mods.betterfoliage.util.randomD
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.renderer.BufferBuilder
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.util.math.MathHelper
|
||||
import net.minecraft.world.World
|
||||
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 kotlin.math.abs
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
const val rotationFactor = PI2.toFloat() / 64.0f
|
||||
|
||||
class EntityFallingLeavesFX(
|
||||
world: World, pos: BlockPos
|
||||
) : AbstractEntityFX(
|
||||
world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic val biomeBrightnessMultiplier = 0.5f
|
||||
}
|
||||
|
||||
var particleRot = rand.nextInt(64)
|
||||
var rotPositive = true
|
||||
val isMirrored = (rand.nextInt() and 1) == 1
|
||||
var wasCollided = false
|
||||
|
||||
init {
|
||||
maxAge = MathHelper.floor(randomD(0.6, 1.0) * Config.fallingLeaves.lifetime * 20.0)
|
||||
motionY = -Config.fallingLeaves.speed
|
||||
|
||||
particleScale = Config.fallingLeaves.size.toFloat() * 0.1f
|
||||
|
||||
val state = world.getBlockState(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)
|
||||
}
|
||||
}
|
||||
|
||||
override val isValid: Boolean get() = (sprite != null)
|
||||
|
||||
override fun update() {
|
||||
if (rand.nextFloat() > 0.95f) rotPositive = !rotPositive
|
||||
if (age > maxAge - 20) particleAlpha = 0.05f * (maxAge - age)
|
||||
|
||||
if (onGround || wasCollided) {
|
||||
velocity.setTo(0.0, 0.0, 0.0)
|
||||
if (!wasCollided) {
|
||||
age = age.coerceAtLeast(maxAge - 20)
|
||||
wasCollided = true
|
||||
}
|
||||
} else {
|
||||
velocity.setTo(cos[particleRot], 0.0, sin[particleRot]).mul(Config.fallingLeaves.perturb)
|
||||
.add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(Config.fallingLeaves.speed)
|
||||
particleRot = (particleRot + (if (rotPositive) 1 else -1)) and 63
|
||||
particleAngle = rotationFactor * particleRot.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) {
|
||||
// if (Config.fallingLeaves.opacityHack) GL11.glDepthMask(true)
|
||||
// renderParticleQuad(worldRenderer, partialTickTime, rotation = particleRot, isMirrored = isMirrored)
|
||||
}
|
||||
|
||||
fun calculateParticleColor(textureAvgColor: Int, blockColor: Int) {
|
||||
val texture = HSB.fromColor(textureAvgColor)
|
||||
val block = HSB.fromColor(blockColor)
|
||||
|
||||
val weightTex = texture.saturation / (texture.saturation + block.saturation)
|
||||
val weightBlock = 1.0f - weightTex
|
||||
|
||||
// avoid circular average for hue for performance reasons
|
||||
// one of the color components should dominate anyway
|
||||
val particle = HSB(
|
||||
weightTex * texture.hue + weightBlock * block.hue,
|
||||
weightTex * texture.saturation + weightBlock * block.saturation,
|
||||
weightTex * texture.brightness + weightBlock * block.brightness * biomeBrightnessMultiplier
|
||||
)
|
||||
setColor(particle.asColor)
|
||||
}
|
||||
}
|
||||
|
||||
object LeafWindTracker {
|
||||
var random = Random()
|
||||
val target = Double3.zero
|
||||
val current = Double3.zero
|
||||
var nextChange: Long = 0
|
||||
|
||||
init {
|
||||
MinecraftForge.EVENT_BUS.register(this)
|
||||
}
|
||||
|
||||
fun changeWind(world: World) {
|
||||
nextChange = world.worldInfo.gameTime + 120 + random.nextInt(80)
|
||||
val direction = PI2 * random.nextDouble()
|
||||
val speed = abs(random.nextGaussian()) * Config.fallingLeaves.windStrength +
|
||||
(if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * Config.fallingLeaves.stormStrength)
|
||||
target.setTo(cos(direction) * speed, 0.0, sin(direction) * speed)
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
fun handleWorldTick(event: TickEvent.ClientTickEvent) {
|
||||
if (event.phase == TickEvent.Phase.START) Minecraft.getInstance().world?.let { world ->
|
||||
// change target wind speed
|
||||
if (world.worldInfo.dayTime >= nextChange) changeWind(world)
|
||||
|
||||
// change current wind speed
|
||||
val changeRate = if (world.isRaining) 0.015 else 0.005
|
||||
current.add(
|
||||
(target.x - current.x).minmax(-changeRate, changeRate),
|
||||
0.0,
|
||||
(target.z - current.z).minmax(-changeRate, changeRate)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
fun handleWorldLoad(event: WorldEvent.Load) { if (event.world.isRemote) changeWind(event.world.world) }
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
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
|
||||
import java.util.*
|
||||
|
||||
class EntityRisingSoulFX(world: World, pos: BlockPos) :
|
||||
AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5) {
|
||||
|
||||
val particleTrail: Deque<Double3> = LinkedList<Double3>()
|
||||
val initialPhase = rand.nextInt(64)
|
||||
|
||||
init {
|
||||
motionY = 0.1
|
||||
particleGravity = 0.0f
|
||||
sprite = RisingSoulTextures.headIcons[rand.nextInt(256)]
|
||||
maxAge = MathHelper.floor((0.6 + 0.4 * rand.nextDouble()) * Config.risingSoul.lifetime * 20.0)
|
||||
}
|
||||
|
||||
override val isValid: Boolean get() = true
|
||||
|
||||
override fun update() {
|
||||
val phase = (initialPhase + age) % 64
|
||||
velocity.setTo(cos[phase] * Config.risingSoul.perturb, 0.1, sin[phase] * Config.risingSoul.perturb)
|
||||
|
||||
particleTrail.addFirst(currentPos.copy())
|
||||
while (particleTrail.size > Config.risingSoul.trailLength) particleTrail.removeLast()
|
||||
|
||||
if (!Config.enabled) setExpired()
|
||||
}
|
||||
|
||||
override fun render(worldRenderer: BufferBuilder, partialTickTime: Float) {
|
||||
// var alpha = Config.risingSoul.opacity.toFloat()
|
||||
// if (age > maxAge - 40) alpha *= (maxAge - age) / 40.0f
|
||||
//
|
||||
// renderParticleQuad(worldRenderer, partialTickTime,
|
||||
// size = Config.risingSoul.headSize * 0.25,
|
||||
// alpha = alpha
|
||||
// )
|
||||
//
|
||||
// var scale = Config.risingSoul.trailSize * 0.25
|
||||
// particleTrail.forEachPairIndexed { idx, current, previous ->
|
||||
// scale *= Config.risingSoul.sizeDecay
|
||||
// alpha *= Config.risingSoul.opacityDecay.toFloat()
|
||||
// if (idx % Config.risingSoul.trailDensity == 0) renderParticleQuad(worldRenderer, partialTickTime,
|
||||
// currentPos = current,
|
||||
// prevPos = previous,
|
||||
// size = scale,
|
||||
// alpha = alpha,
|
||||
// icon = RisingSoulTextures.trackIcon
|
||||
// )
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
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"))
|
||||
}
|
||||
158
src/main/kotlin/mods/betterfoliage/render/ModelColumn.kt
Normal file
158
src/main/kotlin/mods/betterfoliage/render/ModelColumn.kt
Normal file
@@ -0,0 +1,158 @@
|
||||
@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)
|
||||
}
|
||||
}
|
||||
78
src/main/kotlin/mods/betterfoliage/render/Utils.kt
Normal file
78
src/main/kotlin/mods/betterfoliage/render/Utils.kt
Normal file
@@ -0,0 +1,78 @@
|
||||
@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())
|
||||
@@ -0,0 +1,42 @@
|
||||
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]] }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
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)] }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
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)] }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
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]] }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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]] }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
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] }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
183
src/main/kotlin/mods/betterfoliage/render/column/OverlayLayer.kt
Normal file
183
src/main/kotlin/mods/betterfoliage/render/column/OverlayLayer.kt
Normal file
@@ -0,0 +1,183 @@
|
||||
package mods.betterfoliage.render.column
|
||||
|
||||
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.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.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.Axis
|
||||
import net.minecraft.util.Direction.AxisDirection
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.ILightReader
|
||||
|
||||
/** Index of SOUTH-EAST quadrant. */
|
||||
const val SE = 0
|
||||
/** Index of NORTH-EAST quadrant. */
|
||||
const val NE = 1
|
||||
/** Index of NORTH-WEST quadrant. */
|
||||
const val NW = 2
|
||||
/** Index of SOUTH-WEST quadrant. */
|
||||
const val SW = 3
|
||||
|
||||
/**
|
||||
* Sealed class hierarchy for all possible render outcomes
|
||||
*/
|
||||
sealed class ColumnLayerData {
|
||||
/**
|
||||
* Data structure to cache texture and world neighborhood data relevant to column rendering
|
||||
*/
|
||||
@Suppress("ArrayInDataClass") // not used in comparisons anywhere
|
||||
data class SpecialRender(
|
||||
val column: ColumnTextureInfo,
|
||||
val upType: BlockType,
|
||||
val downType: BlockType,
|
||||
val quadrants: Array<QuadrantType>,
|
||||
val quadrantsTop: Array<QuadrantType>,
|
||||
val quadrantsBottom: Array<QuadrantType>
|
||||
) : ColumnLayerData() {
|
||||
enum class BlockType { SOLID, NONSOLID, PARALLEL, PERPENDICULAR }
|
||||
enum class QuadrantType { SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE }
|
||||
}
|
||||
|
||||
/** Column block should not be rendered at all */
|
||||
object SkipRender : ColumnLayerData()
|
||||
|
||||
/** Column block must be rendered normally */
|
||||
object NormalRender : ColumnLayerData()
|
||||
|
||||
/** Error while resolving render data, column block must be rendered normally */
|
||||
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
|
||||
|
||||
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) {
|
||||
allNeighborOffsets.forEach { offset -> ChunkOverlayManager.clear(world.dimType, this, pos + offset) }
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// 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 upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0))
|
||||
val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0))
|
||||
|
||||
val quadrants = Array(4) { SMALL_RADIUS }.checkNeighbors(ctx, baseRotation, logAxis, 0)
|
||||
val quadrantsTop = Array(4) { SMALL_RADIUS }
|
||||
if (upType == PARALLEL) quadrantsTop.checkNeighbors(ctx, baseRotation, logAxis, 1)
|
||||
val quadrantsBottom = Array(4) { SMALL_RADIUS }
|
||||
if (downType == PARALLEL) quadrantsBottom.checkNeighbors(ctx, baseRotation, logAxis, -1)
|
||||
return ColumnLayerData.SpecialRender(columnTextures, upType, downType, quadrants, quadrantsTop, quadrantsBottom)
|
||||
}
|
||||
|
||||
/** Sets the type of the given quadrant only if the new value is "stronger" (larger ordinal). */
|
||||
inline fun Array<QuadrantType>.upgrade(idx: Int, value: QuadrantType) {
|
||||
if (this[idx].ordinal < value.ordinal) this[idx] = value
|
||||
}
|
||||
|
||||
/** Fill the array of [QuadrantType]s based on the blocks to the sides of this one. */
|
||||
fun Array<QuadrantType>.checkNeighbors(ctx: BlockCtx, rotation: Rotation, logAxis: Axis, yOff: Int): Array<QuadrantType> {
|
||||
val blkS = ctx.blockType(rotation, logAxis, Int3(0, yOff, 1))
|
||||
val blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0))
|
||||
val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1))
|
||||
val blkW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, 0))
|
||||
|
||||
// a solid block on one side will make the 2 neighboring quadrants SQUARE
|
||||
// if there are solid blocks to both sides of a quadrant, it is INVISIBLE
|
||||
if (connectSolids) {
|
||||
if (blkS == SOLID) {
|
||||
upgrade(SW, SQUARE); upgrade(SE, SQUARE)
|
||||
}
|
||||
if (blkE == SOLID) {
|
||||
upgrade(SE, SQUARE); upgrade(NE, SQUARE)
|
||||
}
|
||||
if (blkN == SOLID) {
|
||||
upgrade(NE, SQUARE); upgrade(NW, SQUARE)
|
||||
}
|
||||
if (blkW == SOLID) {
|
||||
upgrade(NW, SQUARE); upgrade(SW, SQUARE)
|
||||
}
|
||||
if (blkS == SOLID && blkE == SOLID) upgrade(SE, INVISIBLE)
|
||||
if (blkN == SOLID && blkE == SOLID) upgrade(NE, INVISIBLE)
|
||||
if (blkN == SOLID && blkW == SOLID) upgrade(NW, INVISIBLE)
|
||||
if (blkS == SOLID && blkW == SOLID) upgrade(SW, INVISIBLE)
|
||||
}
|
||||
val blkSE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 1))
|
||||
val blkNE = ctx.blockType(rotation, logAxis, Int3(1, yOff, -1))
|
||||
val blkNW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, -1))
|
||||
val blkSW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, 1))
|
||||
|
||||
if (lenientConnect) {
|
||||
// if the block forms the tip of an L-shape, connect to its neighbor with SQUARE quadrants
|
||||
if (blkE == PARALLEL && (blkSE == PARALLEL || blkNE == PARALLEL)) {
|
||||
upgrade(SE, SQUARE); upgrade(NE, SQUARE)
|
||||
}
|
||||
if (blkN == PARALLEL && (blkNE == PARALLEL || blkNW == PARALLEL)) {
|
||||
upgrade(NE, SQUARE); upgrade(NW, SQUARE)
|
||||
}
|
||||
if (blkW == PARALLEL && (blkNW == PARALLEL || blkSW == PARALLEL)) {
|
||||
upgrade(NW, SQUARE); upgrade(SW, SQUARE)
|
||||
}
|
||||
if (blkS == PARALLEL && (blkSE == PARALLEL || blkSW == PARALLEL)) {
|
||||
upgrade(SW, SQUARE); upgrade(SE, SQUARE)
|
||||
}
|
||||
}
|
||||
|
||||
// if the block forms the middle of an L-shape, or is part of a 2x2 configuration,
|
||||
// connect to its neighbors with SQUARE quadrants, INVISIBLE on the inner corner, and LARGE_RADIUS on the outer corner
|
||||
if (blkN == PARALLEL && blkW == PARALLEL && (lenientConnect || blkNW == PARALLEL)) {
|
||||
upgrade(SE, LARGE_RADIUS); upgrade(NE, SQUARE); upgrade(SW, SQUARE); upgrade(NW, INVISIBLE)
|
||||
}
|
||||
if (blkS == PARALLEL && blkW == PARALLEL && (lenientConnect || blkSW == PARALLEL)) {
|
||||
upgrade(NE, LARGE_RADIUS); upgrade(SE, SQUARE); upgrade(NW, SQUARE); upgrade(SW, INVISIBLE)
|
||||
}
|
||||
if (blkS == PARALLEL && blkE == PARALLEL && (lenientConnect || blkSE == PARALLEL)) {
|
||||
upgrade(NW, LARGE_RADIUS); upgrade(NE, SQUARE); upgrade(SW, SQUARE); upgrade(SE, INVISIBLE)
|
||||
}
|
||||
if (blkN == PARALLEL && blkE == PARALLEL && (lenientConnect || blkNE == PARALLEL)) {
|
||||
upgrade(SW, LARGE_RADIUS); upgrade(SE, SQUARE); upgrade(NW, SQUARE); upgrade(NE, INVISIBLE)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of the block at the given offset in a rotated reference frame.
|
||||
*/
|
||||
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)) {
|
||||
if (offset(offsetRot).isNormalCube) SOLID else NONSOLID
|
||||
} else {
|
||||
(registry[state, world, pos + offsetRot]?.axis ?: if (Config.roundLogs.defaultY) Axis.Y else null)?.let {
|
||||
if (it == axis) PARALLEL else PERPENDICULAR
|
||||
} ?: SOLID
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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]
|
||||
}
|
||||
}
|
||||
158
src/main/kotlin/mods/betterfoliage/render/lighting/Lighting.kt
Normal file
158
src/main/kotlin/mods/betterfoliage/render/lighting/Lighting.kt
Normal file
@@ -0,0 +1,158 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
@file:JvmName("PixelFormat")
|
||||
package mods.betterfoliage.render.lighting
|
||||
|
||||
146
src/main/kotlin/mods/betterfoliage/render/lighting/Vertex.kt
Normal file
146
src/main/kotlin/mods/betterfoliage/render/lighting/Vertex.kt
Normal file
@@ -0,0 +1,146 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package mods.betterfoliage.render.old
|
||||
|
||||
import mods.betterfoliage.util.Double3
|
||||
import mods.betterfoliage.util.PI2
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.particle.IParticleRenderType
|
||||
import net.minecraft.client.particle.SpriteTexturedParticle
|
||||
import net.minecraft.client.renderer.BufferBuilder
|
||||
import net.minecraft.world.World
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) : SpriteTexturedParticle(world, x, y, z) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic val sin = Array(64) { idx -> sin(PI2 / 64.0 * idx) }
|
||||
@JvmStatic val cos = Array(64) { idx -> cos(PI2 / 64.0 * idx) }
|
||||
}
|
||||
|
||||
val billboardRot = Pair(Double3.zero, Double3.zero)
|
||||
val currentPos = Double3.zero
|
||||
val prevPos = Double3.zero
|
||||
val velocity = Double3.zero
|
||||
|
||||
override fun tick() {
|
||||
super.tick()
|
||||
currentPos.setTo(posX, posY, posZ)
|
||||
prevPos.setTo(prevPosX, prevPosY, prevPosZ)
|
||||
velocity.setTo(motionX, motionY, motionZ)
|
||||
update()
|
||||
posX = currentPos.x; posY = currentPos.y; posZ = currentPos.z;
|
||||
motionX = velocity.x; motionY = velocity.y; motionZ = velocity.z;
|
||||
}
|
||||
|
||||
/** Render the particle. */
|
||||
abstract fun render(worldRenderer: BufferBuilder, partialTickTime: Float)
|
||||
|
||||
/** Update particle on world tick. */
|
||||
abstract fun update()
|
||||
|
||||
/** True if the particle is renderable. */
|
||||
abstract val isValid: Boolean
|
||||
|
||||
/** Add the particle to the effect renderer if it is valid. */
|
||||
fun addIfValid() { if (isValid) Minecraft.getInstance().particles.addEffect(this) }
|
||||
|
||||
// override fun renderParticle(buffer: BufferBuilder, entity: ActiveRenderInfo, partialTicks: Float, rotX: Float, rotZ: Float, rotYZ: Float, rotXY: Float, rotXZ: Float) {
|
||||
// billboardRot.first.setTo(rotX + rotXY, rotZ, rotYZ + rotXZ)
|
||||
// billboardRot.second.setTo(rotX - rotXY, -rotZ, rotYZ - rotXZ)
|
||||
// render(buffer, partialTicks)
|
||||
// }
|
||||
/**
|
||||
* Render a particle quad.
|
||||
*
|
||||
* @param[tessellator] the [Tessellator] instance to use
|
||||
* @param[partialTickTime] partial tick time
|
||||
* @param[currentPos] render position
|
||||
* @param[prevPos] previous tick position for interpolation
|
||||
* @param[size] particle size
|
||||
* @param[rotation] viewpoint-dependent particle rotation (64 steps)
|
||||
* @param[icon] particle texture
|
||||
* @param[isMirrored] mirror particle texture along V-axis
|
||||
* @param[alpha] aplha blending
|
||||
*/
|
||||
// fun renderParticleQuad(worldRenderer: BufferBuilder,
|
||||
// partialTickTime: Float,
|
||||
// currentPos: Double3 = this.currentPos,
|
||||
// prevPos: Double3 = this.prevPos,
|
||||
// size: Double = particleScale.toDouble(),
|
||||
// rotation: Int = 0,
|
||||
// icon: TextureAtlasSprite = sprite,
|
||||
// isMirrored: Boolean = false,
|
||||
// alpha: Float = this.particleAlpha) {
|
||||
//
|
||||
// val minU = (if (isMirrored) icon.minU else icon.maxU).toDouble()
|
||||
// val maxU = (if (isMirrored) icon.maxU else icon.minU).toDouble()
|
||||
// val minV = icon.minV.toDouble()
|
||||
// val maxV = icon.maxV.toDouble()
|
||||
//
|
||||
// val center = currentPos.copy().sub(prevPos).mul(partialTickTime.toDouble()).add(prevPos).sub(interpPosX, interpPosY, interpPosZ)
|
||||
// val v1 = if (rotation == 0) billboardRot.first * size else
|
||||
// Double3.weight(billboardRot.first, cos[rotation and 63] * size, billboardRot.second, sin[rotation and 63] * size)
|
||||
// val v2 = if (rotation == 0) billboardRot.second * size else
|
||||
// Double3.weight(billboardRot.first, -sin[rotation and 63] * size, billboardRot.second, cos[rotation and 63] * size)
|
||||
//
|
||||
// val renderBrightness = this.getBrightnessForRender(partialTickTime)
|
||||
// val brLow = renderBrightness shr 16 and 65535
|
||||
// val brHigh = renderBrightness and 65535
|
||||
//
|
||||
// worldRenderer
|
||||
// .pos(center.x - v1.x, center.y - v1.y, center.z - v1.z)
|
||||
// .tex(maxU, maxV)
|
||||
// .color(particleRed, particleGreen, particleBlue, alpha)
|
||||
// .lightmap(brLow, brHigh)
|
||||
// .endVertex()
|
||||
//
|
||||
// worldRenderer
|
||||
// .pos(center.x - v2.x, center.y - v2.y, center.z - v2.z)
|
||||
// .tex(maxU, minV)
|
||||
// .color(particleRed, particleGreen, particleBlue, alpha)
|
||||
// .lightmap(brLow, brHigh)
|
||||
// .endVertex()
|
||||
//
|
||||
// worldRenderer
|
||||
// .pos(center.x + v1.x, center.y + v1.y, center.z + v1.z)
|
||||
// .tex(minU, minV)
|
||||
// .color(particleRed, particleGreen, particleBlue, alpha)
|
||||
// .lightmap(brLow, brHigh)
|
||||
// .endVertex()
|
||||
//
|
||||
// worldRenderer
|
||||
// .pos(center.x + v2.x, center.y + v2.y, center.z + v2.z)
|
||||
// .tex(minU, maxV)
|
||||
// .color(particleRed, particleGreen, particleBlue, alpha)
|
||||
// .lightmap(brLow, brHigh)
|
||||
// .endVertex()
|
||||
// }
|
||||
|
||||
// override fun getFXLayer() = 1
|
||||
override fun getRenderType(): IParticleRenderType = IParticleRenderType.PARTICLE_SHEET_TRANSLUCENT
|
||||
|
||||
fun setColor(color: Int) {
|
||||
particleBlue = (color and 255) / 256.0f
|
||||
particleGreen = ((color shr 8) and 255) / 256.0f
|
||||
particleRed = ((color shr 16) and 255) / 256.0f
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
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)
|
||||
}
|
||||
|
||||
116
src/main/kotlin/mods/betterfoliage/render/old/CombinedContext.kt
Normal file
116
src/main/kotlin/mods/betterfoliage/render/old/CombinedContext.kt
Normal file
@@ -0,0 +1,116 @@
|
||||
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
|
||||
}
|
||||
159
src/main/kotlin/mods/betterfoliage/render/old/Model.kt
Normal file
159
src/main/kotlin/mods/betterfoliage/render/old/Model.kt
Normal file
@@ -0,0 +1,159 @@
|
||||
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.replace
|
||||
import mods.betterfoliage.util.times
|
||||
import mods.betterfoliage.util.vec
|
||||
import net.minecraft.util.Direction
|
||||
import java.lang.Math.max
|
||||
import java.lang.Math.min
|
||||
|
||||
/**
|
||||
* 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 aoShader: ModelLighter = NoLighting,
|
||||
val flatShader: ModelLighter = NoLighting
|
||||
)
|
||||
|
||||
/**
|
||||
* Model quad
|
||||
*/
|
||||
data class Quad(val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex) {
|
||||
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))
|
||||
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 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 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>()
|
||||
|
||||
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(
|
||||
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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val fullCube = Model().apply {
|
||||
allDirections.forEach {
|
||||
faceQuad(it)
|
||||
.setAoShader(faceOrientedAuto(corner = cornerAo(it.axis), edge = null))
|
||||
.setFlatShader(faceOrientedAuto(corner = cornerFlat, edge = null))
|
||||
.add()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
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
|
||||
//}
|
||||
@@ -0,0 +1,23 @@
|
||||
@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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user