first Kotlin version

This commit is contained in:
octarine-noise
2015-12-28 11:49:46 +01:00
parent 554e06176b
commit f44043bb0b
143 changed files with 5469 additions and 0 deletions

View File

@@ -0,0 +1,301 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.client.render.AbstractRenderColumn.BlockType.*
import mods.betterfoliage.client.render.AbstractRenderColumn.QuadrantType.*
import mods.octarinecore.client.render.*
import net.minecraft.block.Block
import net.minecraft.client.renderer.RenderBlocks
import net.minecraftforge.common.util.ForgeDirection.*
/** 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
@Suppress("NOTHING_TO_INLINE")
abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandler(modId) {
enum class BlockType { SOLID, NONSOLID, PARALLEL, PERPENDICULAR }
enum class QuadrantType { SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE }
/** 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 radiusSmall: Double
abstract val radiusLarge: Double
abstract val surroundPredicate: (Block) -> Boolean
abstract val connectPerpendicular: Boolean
abstract val connectSolids: Boolean
abstract val lenientConnect: Boolean
// ============================
// 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)) } }
val bottomRoundSmall = model { columnLid(radiusSmall) { it.rotate(rot(EAST) * 2 + rot(UP)) } }
val bottomRoundLarge = model { columnLid(radiusLarge) { it.rotate(rot(EAST) * 2 + rot(UP)) } }
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 } }
val sideTexture = { ctx: ShadingContext, qi: Int, q: Quad -> if ((qi and 1) == 0) ctx.icon(SOUTH) else ctx.icon(EAST) }
val upTexture = { ctx: ShadingContext, qi: Int, q: Quad -> ctx.icon(UP) }
val downTexture = { ctx: ShadingContext, qi: Int, q: Quad -> ctx.icon(DOWN) }
inline fun continous(q1: QuadrantType, q2: QuadrantType) =
q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE))
abstract val axisFunc: (Block, Int)->Axis
abstract val blockPredicate: (Block, Int)->Boolean
@Suppress("NON_EXHAUSTIVE_WHEN")
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
if (ctx.isSurroundedBy(surroundPredicate) ) return false
// get AO data
if (renderWorldBlockBase(parent, face = neverRender)) return true
// check log neighborhood
val logAxis = ctx.blockAxis
val baseRotation = rotationFromUp[(logAxis to Dir.P).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)
quadrantRotations.forEachIndexed { idx, quadrantRotation ->
// set rotation for the current quadrant
val rotation = baseRotation + quadrantRotation
// disallow sharp discontinuities in the chamfer radius
if (quadrants[idx] == LARGE_RADIUS &&
upType == PARALLEL && quadrantsTop[idx] == SMALL_RADIUS &&
downType == PARALLEL && quadrantsBottom[idx] == SMALL_RADIUS) {
quadrants[idx] = SMALL_RADIUS
}
// render side of current quadrant
val sideModel = when (quadrants[idx]) {
SMALL_RADIUS -> sideRoundSmall.model
LARGE_RADIUS -> if (upType == PARALLEL && quadrantsTop[idx] == SMALL_RADIUS) transitionTop.model
else if (downType == PARALLEL && quadrantsBottom[idx] == SMALL_RADIUS) transitionBottom.model
else sideRoundLarge.model
SQUARE -> sideSquare.model
else -> null
}
if (sideModel != null) modelRenderer.render(
sideModel,
rotation,
blockContext.blockCenter,
icon = sideTexture,
rotateUV = { 0 },
postProcess = noPost
)
// render top and bottom end of current quadrant
var upModel: Model? = null
var downModel: Model? = null
var upIcon = upTexture
var downIcon = downTexture
when (upType) {
NONSOLID -> upModel = flatTop(quadrants[idx])
PERPENDICULAR -> {
if (!connectPerpendicular) {
upModel = flatTop(quadrants[idx])
} else {
upIcon = sideTexture
upModel = extendTop(quadrants[idx])
}
}
PARALLEL -> {
if (!continous(quadrants[idx], quadrantsTop[idx])) {
if (quadrants[idx] == SQUARE || quadrants[idx] == INVISIBLE) {
upModel = topSquare.model
}
}
}
}
when (downType) {
NONSOLID -> downModel = flatBottom(quadrants[idx])
PERPENDICULAR -> {
if (!connectPerpendicular) {
downModel = flatBottom(quadrants[idx])
} else {
downIcon = sideTexture
downModel = extendBottom(quadrants[idx])
}
}
PARALLEL -> {
if (!continous(quadrants[idx], quadrantsBottom[idx]) &&
(quadrants[idx] == SQUARE || quadrants[idx] == INVISIBLE)) {
downModel = bottomSquare.model
}
}
}
if (upModel != null) modelRenderer.render(
upModel,
rotation,
blockContext.blockCenter,
icon = upIcon,
rotateUV = { 0 },
postProcess = noPost
)
if (downModel != null) modelRenderer.render(
downModel,
rotation,
blockContext.blockCenter,
icon = downIcon,
rotateUV = { 0 },
postProcess = noPost
)
}
return true
}
/** 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: BlockContext, 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 axis of the block */
val BlockContext.blockAxis: Axis get() = axisFunc(block(Int3.zero), meta(Int3.zero))
/**
* Get the type of the block at the given offset in a rotated reference frame.
*/
fun BlockContext.blockType(rotation: Rotation, axis: Axis, offset: Int3): BlockType {
val offsetRot = offset.rotate(rotation)
val logBlock = block(offsetRot)
val logMeta = meta(offsetRot)
return if (!blockPredicate(logBlock, logMeta)) {
if (logBlock.isOpaqueCube) SOLID else NONSOLID
} else {
if (axisFunc(logBlock, logMeta) == axis) PARALLEL else PERPENDICULAR
}
}
}

View File

@@ -0,0 +1,131 @@
package mods.betterfoliage.client.render
import cpw.mods.fml.common.FMLCommonHandler
import cpw.mods.fml.common.eventhandler.SubscribeEvent
import cpw.mods.fml.common.gameevent.TickEvent
import cpw.mods.fml.relauncher.Side
import cpw.mods.fml.relauncher.SideOnly
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.texture.LeafRegistry
import mods.octarinecore.PI2
import mods.octarinecore.client.render.AbstractEntityFX
import mods.octarinecore.client.render.Double3
import mods.octarinecore.client.render.HSB
import mods.octarinecore.minmax
import mods.octarinecore.random
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.Tessellator
import net.minecraft.util.MathHelper
import net.minecraft.world.World
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.common.util.ForgeDirection.DOWN
import net.minecraftforge.event.world.WorldEvent
import org.lwjgl.opengl.GL11
import java.lang.Math.*
import java.util.*
class EntityFallingLeavesFX(world: World, x: Int, y: Int, z: Int) :
AbstractEntityFX(world, x.toDouble() + 0.5, y.toDouble(), 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 wasOnGround = false
init {
particleMaxAge = MathHelper.floor_double(random(0.6, 1.0) * Config.fallingLeaves.lifetime * 20.0)
motionY = -Config.fallingLeaves.speed
particleScale = Config.fallingLeaves.size.toFloat() * 0.1f
val block = world.getBlock(x, y, z)
LeafRegistry.leaves[block.getIcon(world, x, y, z, DOWN.ordinal)]?.let {
particleIcon = it.particleTextures[rand.nextInt(1024)]
calculateParticleColor(it.averageColor, block.colorMultiplier(world, x, y, z))
}
}
override val isValid: Boolean get() = (particleIcon != null)
override fun update() {
if (rand.nextFloat() > 0.95f) rotPositive = !rotPositive
if (particleAge > particleMaxAge - 20) particleAlpha = 0.05f * (particleMaxAge - particleAge)
if (onGround || wasOnGround) {
velocity.setTo(0.0, 0.0, 0.0)
if (!wasOnGround) {
particleAge = Math.max(particleAge, particleMaxAge - 20)
wasOnGround = 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
}
}
override fun render(tessellator: Tessellator, partialTickTime: Float) {
if (Config.fallingLeaves.opacityHack) GL11.glDepthMask(true)
renderParticleQuad(tessellator, 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)
}
}
@SideOnly(Side.CLIENT)
object LeafWindTracker {
var random = Random()
val target = Double3.zero
val current = Double3.zero
var nextChange: Long = 0
init {
MinecraftForge.EVENT_BUS.register(this)
FMLCommonHandler.instance().bus().register(this)
}
fun changeWind(world: World) {
nextChange = world.worldInfo.worldTime + 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.getMinecraft().theWorld?.let { world ->
// change target wind speed
if (world.worldInfo.worldTime >= 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) }
}

View File

@@ -0,0 +1,75 @@
package mods.betterfoliage.client.render
import cpw.mods.fml.relauncher.Side
import cpw.mods.fml.relauncher.SideOnly
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.AbstractEntityFX
import mods.octarinecore.client.render.Double3
import mods.octarinecore.client.resource.ResourceHandler
import mods.octarinecore.forEachPairIndexed
import net.minecraft.client.renderer.Tessellator
import net.minecraft.util.MathHelper
import net.minecraft.world.World
import org.apache.logging.log4j.Level.*
import java.util.*
class EntityRisingSoulFX(world: World, x: Int, y: Int, z: Int) :
AbstractEntityFX(world, x.toDouble() + 0.5, y.toDouble() + 1.0, z.toDouble() + 0.5) {
val particleTrail: Deque<Double3> = linkedListOf()
val initialPhase = rand.nextInt(64)
init {
motionY = 0.1
particleGravity = 0.0f
particleIcon = RisingSoulTextures.headIcons[rand.nextInt(256)]
particleMaxAge = MathHelper.floor_double((0.6 + 0.4 * rand.nextDouble()) * Config.risingSoul.lifetime * 20.0)
}
override val isValid: Boolean get() = true
override fun update() {
val phase = (initialPhase + particleAge) % 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) setDead()
}
override fun render(tessellator: Tessellator, partialTickTime: Float) {
var alpha = Config.risingSoul.opacity
if (particleAge > particleMaxAge - 40) alpha *= (particleMaxAge - particleAge) / 40.0f
renderParticleQuad(tessellator, 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
if (idx % Config.risingSoul.trailDensity == 0) renderParticleQuad(tessellator, partialTickTime,
currentPos = current,
prevPos = previous,
size = scale,
alpha = alpha,
icon = RisingSoulTextures.trackIcon.icon!!
)
}
}
}
@SideOnly(Side.CLIENT)
object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID) {
val headIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "rising_soul_%d")
val trackIcon = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "soul_track")
override fun afterStitch() {
Client.log(INFO, "Registered ${headIcons.num} soul particle textures")
}
}

View File

@@ -0,0 +1,140 @@
@file:JvmName("ModelColumn")
package mods.betterfoliage.client.render
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.*
import mods.octarinecore.exchange
import net.minecraftforge.common.util.ForgeDirection.*
/** 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)),
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))
)
.setAoShader(
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming)),
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)))
.setAoShader(
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming)),
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))
).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 == 1 || vi == 2})
).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
})}
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
})}
listOf(q1, q2).forEach { transform(it.setFlatShader(FaceFlat(UP))).add() }
}
/**
* Create a model of the top lid of a square column quadrant.
*
* @param[transform] transformation to apply to the model
*/
fun Model.columnLidSquare(transform: (Quad)-> Quad = { it }) {
transform(
horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = 0.5)
.transformVI { vertex, idx -> vertex.copy(uv = UV(vertex.xyz.x, vertex.xyz.z), aoShader = when(idx) {
0 -> FaceCenter(UP)
1 -> EdgeInterpolateFallback(UP, SOUTH, 0.0)
2 -> CornerSingleFallback(UP, SOUTH, EAST, UP)
else -> EdgeInterpolateFallback(UP, EAST, 0.0)
}) }
.setFlatShader(FaceFlat(UP))
).add()
}
/**
* Transform a chamfered side quadrant model of a column that extends from the top of the block.
* (clamp UV coordinates, apply some scaling to avoid Z-fighting).
*
* @param[size] amount that the model extends from the top
*/
fun topExtension(size: Double) = { q: Quad ->
q.clampUV(minV = 0.5 - size).transformVI { vertex, idx ->
if (idx < 2) vertex else vertex.copy(xyz = vertex.xyz * zProtectionScale)
}
}
/**
* Transform a chamfered side quadrant model of a column that extends from the bottom of the block.
* (clamp UV coordinates, apply some scaling to avoid Z-fighting).
*
* @param[size] amount that the model extends from the bottom
*/
fun bottomExtension(size: Double) = { q: Quad ->
q.clampUV(maxV = -0.5 + size).transformVI { vertex, idx ->
if (idx > 1) vertex else vertex.copy(xyz = vertex.xyz * zProtectionScale)
}
}

View File

@@ -0,0 +1,49 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.octarinecore.client.render.*
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.RenderBlocks
import net.minecraft.init.Blocks
import org.apache.logging.log4j.Level.INFO
class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val noise = simplexNoise()
val algaeIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_algae_%d")
val algaeModels = modelSet(64, RenderGrass.grassTopQuads)
override fun afterStitch() {
Client.log(INFO, "Registered ${algaeIcons.num} algae textures")
}
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.algae.enabled &&
ctx.cameraDistance < Config.algae.distance &&
ctx.block(up2).material == Material.water &&
ctx.block(up1).material == Material.water &&
Config.blocks.dirt.matchesID(ctx.block) &&
ctx.biomeId in Config.algae.biomes &&
noise[ctx.x, ctx.z] < Config.algae.population
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
val rand = ctx.semiRandomArray(3)
ShadersModIntegration.grass(Config.algae.shaderWind) {
modelRenderer.render(
algaeModels[rand[2]],
Rotation.identity,
icon = { ctx, qi, q -> algaeIcons[rand[qi and 1]]!! },
rotateUV = { 0 },
postProcess = noPost
)
}
return true
}
}

View File

@@ -0,0 +1,83 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.*
import net.minecraft.client.renderer.RenderBlocks
import net.minecraftforge.common.util.ForgeDirection.*
import org.apache.logging.log4j.Level
class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val cactusStemRadius = 0.4375
val cactusArmRotation = listOf(NORTH, SOUTH, EAST, WEST).map { Rotation.rot90[it.ordinal] }
val iconCross = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "better_cactus")
val iconArm = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_cactus_arm_%d")
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 afterStitch() {
Client.log(Level.INFO, "Registered ${iconArm.num} cactus arm textures")
}
override fun isEligible(ctx: BlockContext): Boolean =
Config.enabled && Config.cactus.enabled &&
ctx.cameraDistance < Config.cactus.distance &&
Config.blocks.cactus.matchesID(ctx.block)
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
// get AO data
if (renderWorldBlockBase(parent, face = neverRender)) return true
modelRenderer.render(
modelStem.model,
Rotation.identity,
icon = { ctx, qi, q -> ctx.icon(forgeDirs[qi])},
rotateUV = { 0 },
postProcess = noPost
)
modelRenderer.render(
modelCross[ctx.random(0)],
Rotation.identity,
icon = { ctx, qi, q -> iconCross.icon!!},
rotateUV = { 0 },
postProcess = noPost
)
modelRenderer.render(
modelArm[ctx.random(1)],
cactusArmRotation[ctx.random(2) % 4],
icon = { ctx2, qi, q -> iconArm[ctx.random(3)]!!},
rotateUV = { 0 },
postProcess = noPost
)
return true
}
}

View File

@@ -0,0 +1,23 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.*
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.RenderBlocks
class RenderConnectedGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.connectedGrass.enabled &&
Config.blocks.dirt.matchesID(ctx.block) &&
Config.blocks.grass.matchesID(ctx.block(up1)) &&
(Config.connectedGrass.snowEnabled || !ctx.block(up2).isSnow)
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
return ctx.withOffset(Int3.zero, up1) {
ctx.withOffset(up1, up2) {
renderWorldBlockBase(parent, face = alwaysRender)
}
}
}
}

View File

@@ -0,0 +1,31 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.*
import net.minecraft.client.renderer.RenderBlocks
import net.minecraftforge.common.util.ForgeDirection.*
class RenderConnectedGrassLog : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val grassCheckDirs = listOf(EAST, WEST, NORTH, SOUTH)
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass &&
Config.blocks.dirt.matchesID(ctx.block) &&
Config.blocks.logs.matchesID(ctx.block(up1))
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
val grassDir = grassCheckDirs.find {
Config.blocks.grass.matchesID(ctx.block(it.offset))
}
return if (grassDir != null) {
ctx.withOffset(Int3.zero, grassDir.offset) {
renderWorldBlockBase(parent, face = alwaysRender)
}
} else {
renderWorldBlockBase(parent, face = alwaysRender)
}
}
}

View File

@@ -0,0 +1,65 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.*
import mods.octarinecore.random
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.RenderBlocks
import net.minecraftforge.common.util.ForgeDirection.UP
import org.apache.logging.log4j.Level.INFO
class RenderCoral : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val noise = simplexNoise()
val coralIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_coral_%d")
val crustIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_crust_%d")
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 = random(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 afterStitch() {
Client.log(INFO, "Registered ${coralIcons.num} algae textures")
}
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.coral.enabled &&
ctx.cameraDistance < Config.coral.distance &&
ctx.block(up2).material == Material.water &&
ctx.block(up1).material == Material.water &&
Config.blocks.sand.matchesID(ctx.block) &&
ctx.biomeId in Config.coral.biomes &&
noise[ctx.x, ctx.z] < Config.coral.population
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
forgeDirs.forEachIndexed { idx, face ->
if (!ctx.block(forgeDirOffsets[idx]).isOpaqueCube && blockContext.random(idx) < Config.coral.chance) {
var variation = blockContext.random(6)
modelRenderer.render(
coralModels[variation++],
rotationFromUp[idx],
icon = { ctx, qi, q -> if (qi == 4) crustIcons[variation]!! else coralIcons[variation + (qi and 1)]!!},
rotateUV = { 0 },
postProcess = noPost
)
}
}
return true
}
}

View File

@@ -0,0 +1,106 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.texture.GrassRegistry
import mods.octarinecore.client.render.*
import mods.octarinecore.random
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.RenderBlocks
import net.minecraftforge.common.util.ForgeDirection.UP
import org.apache.logging.log4j.Level.INFO
class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
companion object {
@JvmStatic val grassTopQuads: Model.(Int)->Unit = { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5,
yTop = 0.5 + random(Config.shortGrass.heightMin, Config.shortGrass.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 normalIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_grass_long_%d")
val snowedIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_grass_snowed_%d")
val normalGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:tallgrass", "snowed" to false))
val snowedGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:tallgrass", "snowed" to true))
val grassModels = modelSet(64, grassTopQuads)
override fun afterStitch() {
Client.log(INFO, "Registered ${normalIcons.num} grass textures")
Client.log(INFO, "Registered ${snowedIcons.num} snowed grass textures")
}
override fun isEligible(ctx: BlockContext) =
Config.enabled &&
ctx.cameraDistance < Config.shortGrass.distance &&
(Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) &&
Config.blocks.grass.matchesID(ctx.block)
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
val isConnected = ctx.block(down1).let { Config.blocks.dirt.matchesID(it) || Config.blocks.grass.matchesID(it) }
val isSnowed = ctx.block(up1).isSnow
val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled)
val grassInfo = GrassRegistry.grass[ctx.icon(UP)]
if (grassInfo == null) {
renderWorldBlockBase(parent, face = alwaysRender)
return true
}
val cubeTexture = if (isSnowed) ctx.icon(up1, UP) else null ?: grassInfo.grassTopTexture
val blockColor = ctx.blockColor(Int3.zero)
if (connectedGrass) {
// get AO data
if (renderWorldBlockBase(parent, face = neverRender)) return true
// render full grass block
modelRenderer.render(
fullCube,
Rotation.identity,
ctx.blockCenter,
icon = { ctx, qi, q -> cubeTexture },
rotateUV = { 2 },
postProcess = { ctx, qi, q, vi, v ->
if (isSnowed) { if(!ctx.aoEnabled) setGrey(1.4f) }
else if (qi != UP.ordinal && ctx.aoEnabled) multiplyColor(blockColor)
}
)
} else {
// render normally
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
}
if (!Config.shortGrass.grassEnabled) return true
if (isSnowed && !Config.shortGrass.snowEnabled) return true
if (ctx.block(up1).isOpaqueCube) return true
// render grass quads
val iconset = if (isSnowed) snowedIcons else normalIcons
val iconGen = if (isSnowed) snowedGenIcon else normalGenIcon
val rand = ctx.semiRandomArray(2)
ShadersModIntegration.grass(Config.shortGrass.shaderWind) {
modelRenderer.render(
grassModels[rand[0]],
Rotation.identity,
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = if (Config.shortGrass.useGenerated)
{ ctx: ShadingContext, qi: Int, q: Quad -> iconGen.icon!! }
else
{ ctx: ShadingContext, qi: Int, q: Quad -> iconset[rand[qi and 1]]!! },
rotateUV = { 0 },
postProcess = if (isSnowed) whitewash else if (grassInfo.overrideColor == null) noPost else
{ ctx, qi, q, vi, v -> multiplyColor(grassInfo.overrideColor) }
)
}
return true
}
}

View File

@@ -0,0 +1,72 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.texture.LeafRegistry
import mods.octarinecore.PI2
import mods.octarinecore.client.render.*
import mods.octarinecore.random
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.RenderBlocks
import net.minecraftforge.common.util.ForgeDirection.DOWN
import net.minecraftforge.common.util.ForgeDirection.UP
import java.lang.Math.cos
import java.lang.Math.sin
class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
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 = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_leaves_snowed_%d")
val perturbs = vectorSet(64) { idx ->
val angle = PI2 * idx / 64.0
Double3(cos(angle), 0.0, sin(angle)) * Config.leaves.hOffset +
UP.vec * random(-1.0, 1.0) * Config.leaves.vOffset
}
override fun isEligible(ctx: BlockContext) =
Config.enabled &&
Config.leaves.enabled &&
ctx.cameraDistance < Config.leaves.distance &&
Config.blocks.leaves.matchesID(ctx.block)
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
val isSnowed = ctx.block(up1).material.let {
it == Material.snow || it == Material.craftedSnow
}
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
val leafInfo = LeafRegistry.leaves[ctx.icon(DOWN)]
if (leafInfo != null) ShadersModIntegration.leaves {
val rand = ctx.semiRandomArray(2)
(if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation ->
modelRenderer.render(
leavesModel.model,
rotation,
ctx.blockCenter + perturbs[rand[0]],
icon = { ctx, qi, q -> leafInfo.roundLeafTexture },
rotateUV = { q -> rand[1] },
postProcess = noPost
)
}
if (isSnowed) modelRenderer.render(
leavesModel.model,
Rotation.identity,
ctx.blockCenter + perturbs[rand[0]],
icon = { ctx, qi, q -> snowedIcon[rand[1]]!! },
rotateUV = { 0 },
postProcess = whitewash
)
}
return true
}
}

View File

@@ -0,0 +1,64 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.*
import net.minecraft.client.renderer.RenderBlocks
import net.minecraftforge.common.util.ForgeDirection.*
import org.apache.logging.log4j.Level
class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
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 = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_lilypad_roots_%d")
val flowerIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_lilypad_flower_%d")
val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset }
override fun afterStitch() {
Client.log(Level.INFO, "Registered ${rootIcon.num} lilypad root textures")
Client.log(Level.INFO, "Registered ${flowerIcon.num} lilypad flower textures")
}
override fun isEligible(ctx: BlockContext): Boolean =
Config.enabled && Config.lilypad.enabled &&
ctx.cameraDistance < Config.lilypad.distance &&
Config.blocks.lilypad.matchesID(ctx.block)
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
val rand = ctx.semiRandomArray(5)
modelRenderer.render(
rootModel.model,
Rotation.identity,
ctx.blockCenter.add(perturbs[rand[2]]),
forceFlat = true,
icon = { ctx, qi, q -> rootIcon[rand[qi and 1]]!! },
rotateUV = { 0 },
postProcess = noPost
)
if (rand[3] < Config.lilypad.flowerChance) modelRenderer.render(
flowerModel.model,
Rotation.identity,
ctx.blockCenter.add(perturbs[rand[4]]),
forceFlat = true,
icon = { ctx, qi, q -> flowerIcon[rand[0]]!! },
rotateUV = { 0 },
postProcess = noPost
)
return true
}
}

View File

@@ -0,0 +1,30 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.Axis
import mods.octarinecore.client.render.BlockContext
import net.minecraft.block.Block
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.roundLogs.enabled &&
ctx.cameraDistance < Config.roundLogs.distance &&
Config.blocks.logs.matchesID(ctx.block)
override var axisFunc = { block: Block, meta: Int -> when ((meta shr 2) and 3) {
1 -> Axis.X
2 -> Axis.Z
else -> Axis.Y
} }
override val blockPredicate = { block: Block, meta: Int -> Config.blocks.logs.matchesID(block) }
override val surroundPredicate = { block: Block -> block.isOpaqueCube && !Config.blocks.logs.matchesID(block) }
override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular
override val connectSolids: Boolean get() = Config.roundLogs.connectSolids
override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect
override val radiusLarge: Double get() = Config.roundLogs.radiusLarge
override val radiusSmall: Double get() = Config.roundLogs.radiusSmall
}

View File

@@ -0,0 +1,48 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.*
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.RenderBlocks
import net.minecraft.init.Blocks
import org.apache.logging.log4j.Level.INFO
class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val myceliumIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_mycel_%d")
val myceliumModel = modelSet(64, RenderGrass.grassTopQuads)
override fun afterStitch() {
Client.log(INFO, "Registered ${myceliumIcon.num} mycelium textures")
}
override fun isEligible(ctx: BlockContext): Boolean {
if (!Config.enabled || !Config.shortGrass.myceliumEnabled) return false
return ctx.block == Blocks.mycelium &&
ctx.cameraDistance < Config.shortGrass.distance
}
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
val isSnowed = ctx.block(up1).material.let {
it == Material.snow || it == Material.craftedSnow
}
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
if (isSnowed && !Config.shortGrass.snowEnabled) return true
if (ctx.block(up1).isOpaqueCube) return true
val rand = ctx.semiRandomArray(2)
modelRenderer.render(
myceliumModel[rand[0]],
Rotation.identity,
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
icon = { ctx, qi, q -> myceliumIcon[rand[qi and 1]]!! },
rotateUV = { 0 },
postProcess = if (isSnowed) whitewash else noPost
)
return true
}
}

View File

@@ -0,0 +1,51 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.octarinecore.client.render.*
import mods.octarinecore.random
import net.minecraft.client.renderer.RenderBlocks
import net.minecraft.init.Blocks
import net.minecraftforge.common.util.ForgeDirection.DOWN
import net.minecraftforge.common.util.ForgeDirection.UP
import org.apache.logging.log4j.Level.INFO
class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val netherrackIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_netherrack_%d")
val netherrackModel = modelSet(64) { modelIdx ->
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yTop = -0.5,
yBottom = -0.5 - random(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 afterStitch() {
Client.log(INFO, "Registered ${netherrackIcon.num} netherrack textures")
}
override fun isEligible(ctx: BlockContext): Boolean {
if (!Config.enabled || !Config.netherrack.enabled) return false
return ctx.block == Blocks.netherrack &&
ctx.cameraDistance < Config.netherrack.distance
}
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
if (ctx.block(down1).isOpaqueCube) return true
val rand = ctx.semiRandomArray(2)
modelRenderer.render(
netherrackModel[rand[0]],
Rotation.identity,
icon = { ctx, qi, q -> netherrackIcon[rand[qi and 1]]!! },
rotateUV = { 0 },
postProcess = noPost
)
return true
}
}

View File

@@ -0,0 +1,65 @@
package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.octarinecore.client.render.*
import mods.octarinecore.random
import net.minecraft.block.material.Material
import net.minecraft.client.renderer.RenderBlocks
import net.minecraftforge.common.util.ForgeDirection.UP
import org.apache.logging.log4j.Level
class RenderReeds : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
val noise = simplexNoise()
val reedIcons = iconSet(Client.genReeds.generatedResource("${BetterFoliageMod.LEGACY_DOMAIN}:better_reed_%d"))
val reedModels = modelSet(64) { modelIdx ->
val height = random(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 afterStitch() {
Client.log(Level.INFO, "Registered ${reedIcons.num} reed textures")
}
override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.reed.enabled &&
ctx.cameraDistance < Config.reed.distance &&
ctx.block(up2).material == Material.air &&
ctx.block(up1).material == Material.water &&
Config.blocks.dirt.matchesID(ctx.block) &&
ctx.biomeId in Config.reed.biomes &&
noise[ctx.x, ctx.z] < Config.reed.population
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
val iconVar = ctx.random(1)
ShadersModIntegration.grass(Config.reed.shaderWind) {
modelRenderer.render(
reedModels[ctx.random(0)],
Rotation.identity,
forceFlat = true,
icon = { ctx, qi, q -> reedIcons[iconVar]!! },
rotateUV = { 0 },
postProcess = noPost
)
}
return true
}
}

View File

@@ -0,0 +1,54 @@
@file:JvmName("Utils")
package mods.betterfoliage.client.render
import mods.octarinecore.PI2
import mods.octarinecore.client.render.*
import net.minecraft.block.Block
import net.minecraft.block.material.Material
import net.minecraft.tileentity.TileEntity
import net.minecraft.world.IBlockAccess
import net.minecraft.world.biome.BiomeGenBase
import net.minecraftforge.common.util.ForgeDirection
import net.minecraftforge.common.util.ForgeDirection.*
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: RenderVertex.(ShadingContext, Int, Quad, Int, Vertex)->Unit = { ctx, qi, q, vi, v -> setGrey(1.4f) }
val greywash: RenderVertex.(ShadingContext, Int, Quad, Int, Vertex)->Unit = { ctx, qi, q, vi, v -> setGrey(1.0f) }
val Block.isSnow: Boolean get() = material.let { it == Material.snow || it == Material.craftedSnow }
fun Quad.toCross(rotAxis: ForgeDirection, trans: (Quad)->Quad) =
(0..3).map { rotIdx ->
trans(rotate(Rotation.rot90[rotAxis.ordinal] * rotIdx).mirrorUV(rotIdx > 1, false))
}
fun Quad.toCross(rotAxis: ForgeDirection) = toCross(rotAxis) { it }
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(Math.cos(it), 0.0, Math.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()
}
}