Merge branch 'kotlin-1.12' into forge-1.14

This commit is contained in:
octarine-noise
2020-01-01 17:15:13 +01:00
12 changed files with 57 additions and 93 deletions

View File

@@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx2G
group = com.github.octarine-noise group = com.github.octarine-noise
jarName = BetterFoliage-Forge jarName = BetterFoliage-Forge
version = 2.3.0 version = 2.3.1
mc_version = 1.14.4 mc_version = 1.14.4
forge_version = 28.1.109 forge_version = 28.1.109

View File

@@ -109,14 +109,16 @@ object ChunkOverlayManager {
} }
class ChunkOverlayData(layers: List<ChunkOverlayLayer<*>>) { class ChunkOverlayData(layers: List<ChunkOverlayLayer<*>>) {
val BlockPos.isValid: Boolean get() = y in validYRange
val rawData = layers.associateWith { emptyOverlay() } val rawData = layers.associateWith { emptyOverlay() }
fun <T> get(layer: ChunkOverlayLayer<T>, pos: BlockPos): T? = rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.get(pos.y) as T? fun <T> get(layer: ChunkOverlayLayer<T>, pos: BlockPos): T? = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.get(pos.y) as T? else null
fun <T> set(layer: ChunkOverlayLayer<T>, pos: BlockPos, data: T) = rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, data) fun <T> set(layer: ChunkOverlayLayer<T>, pos: BlockPos, data: T) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, data) else null
fun <T> clear(layer: ChunkOverlayLayer<T>, pos: BlockPos) = rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, UNCALCULATED) fun <T> clear(layer: ChunkOverlayLayer<T>, pos: BlockPos) = if (pos.isValid) rawData[layer]?.get(pos.x and 15)?.get(pos.z and 15)?.set(pos.y, UNCALCULATED) else null
companion object { companion object {
val UNCALCULATED = object {} val UNCALCULATED = object {}
fun emptyOverlay() = Array(16) { Array(16) { Array<Any?>(256) { UNCALCULATED }}} fun emptyOverlay() = Array(16) { Array(16) { Array<Any?>(256) { UNCALCULATED }}}
val validYRange = 0 until 256
} }
} }

View File

@@ -1,28 +1,13 @@
package mods.betterfoliage.client.config package mods.betterfoliage.client.config
import mods.betterfoliage.BetterFoliage import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.octarinecore.client.resource.LoadModelDataEvent import mods.octarinecore.client.resource.LoadModelDataEvent
import mods.octarinecore.common.config.* import mods.octarinecore.common.config.*
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.eventbus.api.SubscribeEvent import net.minecraftforge.eventbus.api.SubscribeEvent
// BetterFoliage-specific property delegates
//private val OBSOLETE = ObsoleteConfigProperty()
private fun featureEnable() = boolean(true).lang("enabled") private fun featureEnable() = boolean(true).lang("enabled")
/*
fun biomeList(defaults: (Biome) -> Boolean) = intList {
Biome.REGISTRY
.filter { it != null && defaults(it) }
.map { Biome.REGISTRY.getIDForObject(it) }
.toTypedArray()
}.apply { guiClass = BiomeListConfigEntry::class.java }
// Biome filter methods
private fun Biome.filterTemp(min: Float?, max: Float?) = (min == null || min <= defaultTemperature) && (max == null || max >= defaultTemperature)
private fun Biome.filterRain(min: Float?, max: Float?) = (min == null || min <= rainfall) && (max == null || max >= rainfall)
private fun Biome.filterClass(vararg name: String) = name.any { it in this.javaClass.name.toLowerCase() }
*/
// Config singleton // Config singleton
object Config : DelegatingConfig(BetterFoliage.MOD_ID, BetterFoliage.MOD_ID) { object Config : DelegatingConfig(BetterFoliage.MOD_ID, BetterFoliage.MOD_ID) {
@@ -30,30 +15,10 @@ object Config : DelegatingConfig(BetterFoliage.MOD_ID, BetterFoliage.MOD_ID) {
val enabled by boolean(true) val enabled by boolean(true)
val nVidia by boolean(false) val nVidia by boolean(false)
/* object shaders {
object blocks { val leavesId by long(min = 1, max = 65535, default = ShadersModIntegration.leavesDefaultBlockId)
val leavesClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "leaves_blocks_default.cfg") val grassId by long(min = 1, max = 65535, default = ShadersModIntegration.grassDefaultBlockId)
val leavesModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "leaves_models_default.cfg", 1)
val grassClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "grass_blocks_default.cfg")
val grassModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "grass_models_default.cfg", 1)
val mycelium = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "mycelium_blocks_default.cfg")
val dirt = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "dirt_default.cfg")
val crops = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "crop_default.cfg")
val logClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "log_blocks_default.cfg")
val logModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "log_models_default.cfg", 3)
val sand = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "sand_default.cfg")
val lilypad = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "lilypad_default.cfg")
val cactus = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "cactus_default.cfg")
val netherrack = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "netherrack_blocks_default.cfg")
val leavesWhitelist = OBSOLETE
val leavesBlacklist = OBSOLETE
val grassWhitelist = OBSOLETE
val grassBlacklist = OBSOLETE
val logsWhitelist = OBSOLETE
val logsBlacklist = OBSOLETE
} }
*/
object leaves : ConfigCategory() { object leaves : ConfigCategory() {
val enabled by featureEnable() val enabled by featureEnable()
@@ -178,22 +143,6 @@ object Config : DelegatingConfig(BetterFoliage.MOD_ID, BetterFoliage.MOD_ID) {
val trailLength by int(min=2, max=128, default=48) val trailLength by int(min=2, max=128, default=48)
val trailDensity by int(min=1, max=16, default=3) val trailDensity by int(min=1, max=16, default=3)
} }
/*
val forceReloadOptions = listOf(
blocks.leavesClasses,
blocks.leavesModels,
blocks.grassClasses,
blocks.grassModels,
shortGrass["saturationThreshold"]!!
)
override fun onChange(event: ConfigChangedEvent.PostConfigChangedEvent) {
if (hasChanged(forceReloadOptions))
Minecraft.getInstance().refreshResources()
else
Minecraft.getInstance().renderGlobal.loadRenderers()
}
*/
} }
object BlockConfig { object BlockConfig {

View File

@@ -35,7 +35,7 @@ object OptifineCustomColors {
fun getBlockColor(ctx: BlockContext): Int { fun getBlockColor(ctx: BlockContext): Int {
val ofColor = if (isColorAvailable && Minecraft.getInstance().gameSettings.reflectField<Boolean>("ofCustomColors") == true) { val ofColor = if (isColorAvailable && Minecraft.getInstance().gameSettings.reflectField<Boolean>("ofCustomColors") == true) {
renderEnv.reset(ctx.reader!!, ctx.blockState(Int3.zero), ctx.pos) renderEnv.reset(ctx.blockState(Int3.zero), ctx.pos)
Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.blockState(Int3.zero), ctx.reader!!, ctx.pos, renderEnv.wrapped) as? Int Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.blockState(Int3.zero), ctx.reader!!, ctx.pos, renderEnv.wrapped) as? Int
} else null } else null
return if (ofColor == null || ofColor == -1) ctx.blockData(Int3.zero).color else ofColor return if (ofColor == null || ofColor == -1) ctx.blockData(Int3.zero).color else ofColor
@@ -44,13 +44,13 @@ object OptifineCustomColors {
class OptifineRenderEnv { class OptifineRenderEnv {
val wrapped: Any = Refs.RenderEnv.element!!.getDeclaredConstructor( val wrapped: Any = Refs.RenderEnv.element!!.getDeclaredConstructor(
Refs.IBlockReader.element, Refs.BlockState.element, Refs.BlockPos.element Refs.BlockState.element, Refs.BlockPos.element
).let { ).let {
it.isAccessible = true it.isAccessible = true
it.newInstance(null, null, null) it.newInstance(null, null)
} }
fun reset(blockAccess: IBlockReader, state: BlockState, pos: BlockPos) { fun reset(state: BlockState, pos: BlockPos) {
Refs.RenderEnv_reset.invoke(wrapped, blockAccess, state, pos) Refs.RenderEnv_reset.invoke(wrapped, state, pos)
} }
} }

View File

@@ -2,11 +2,16 @@ package mods.betterfoliage.client.integration
import mods.betterfoliage.client.Client import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.BlockConfig import mods.betterfoliage.client.config.BlockConfig
import mods.betterfoliage.client.config.Config
import mods.betterfoliage.loader.Refs import mods.betterfoliage.loader.Refs
import mods.octarinecore.metaprog.allAvailable import mods.octarinecore.metaprog.allAvailable
import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockRenderType.MODEL
import net.minecraft.block.BlockState import net.minecraft.block.BlockState
import net.minecraft.block.Blocks import net.minecraft.block.Blocks
import net.minecraft.block.TallGrassBlock
import net.minecraft.client.renderer.BufferBuilder import net.minecraft.client.renderer.BufferBuilder
import net.minecraftforge.registries.ForgeRegistries
import org.apache.logging.log4j.Level.INFO import org.apache.logging.log4j.Level.INFO
/** /**
@@ -14,23 +19,18 @@ import org.apache.logging.log4j.Level.INFO
*/ */
object ShadersModIntegration { object ShadersModIntegration {
@JvmStatic var isAvailable = allAvailable(Refs.sVertexBuilder, Refs.pushEntity_state, Refs.pushEntity_num, Refs.popEntity) @JvmStatic val isAvailable = allAvailable(Refs.sVertexBuilder, Refs.pushEntity_state, Refs.pushEntity_num, Refs.popEntity)
@JvmStatic val tallGrassEntityData = entityDataFor(Blocks.TALL_GRASS.defaultState)
@JvmStatic val leavesEntityData = entityDataFor(Blocks.OAK_LEAVES.defaultState)
fun entityDataFor(blockState: BlockState) = 0L
// (ForgeRegistries.BLOCKS.getIDForObject(blockState.block).toLong() and 65535) or
// ((blockState.renderType.ordinal.toLong() and 65535) shl 16) or
// (blockState.block.getMetaFromState(blockState).toLong() shl 32)
val grassDefaultBlockId = 31L
val leavesDefaultBlockId = 18L
/** /**
* Called from transformed ShadersMod code. * Called from transformed ShadersMod code.
* @see mods.betterfoliage.loader.BetterFoliageTransformer * @see mods.betterfoliage.loader.BetterFoliageTransformer
*/ */
@JvmStatic fun getBlockIdOverride(original: Long, blockState: BlockState): Long { @JvmStatic fun getBlockIdOverride(original: Long, blockState: BlockState): Long {
if (BlockConfig.leafBlocks.matchesClass(blockState.block)) return leavesEntityData if (BlockConfig.leafBlocks.matchesClass(blockState.block)) return Config.shaders.leavesId
if (BlockConfig.crops.matchesClass(blockState.block)) return tallGrassEntityData if (BlockConfig.crops.matchesClass(blockState.block)) return Config.shaders.grassId
return original return original
} }
@@ -39,10 +39,11 @@ object ShadersModIntegration {
} }
/** Quads rendered inside this block will use the given block entity data in shader programs. */ /** Quads rendered inside this block will use the given block entity data in shader programs. */
inline fun renderAs(blockEntityData: Long, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) { inline fun renderAs(blockId: Long, renderType: BlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) {
val blockData = blockId or (renderType.ordinal shl 16).toLong()
if ((isAvailable && enabled)) { if ((isAvailable && enabled)) {
val vertexBuilder = Refs.sVertexBuilder.get(renderer)!! val vertexBuilder = Refs.sVertexBuilder.get(renderer)!!
Refs.pushEntity_num.invoke(vertexBuilder, blockEntityData) Refs.pushEntity_num.invoke(vertexBuilder, blockId)
func() func()
Refs.popEntity.invoke(vertexBuilder) Refs.popEntity.invoke(vertexBuilder)
} else { } else {
@@ -51,14 +52,14 @@ object ShadersModIntegration {
} }
/** Quads rendered inside this block will use the given block entity data in shader programs. */ /** Quads rendered inside this block will use the given block entity data in shader programs. */
inline fun renderAs(state: BlockState, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) = // temporarily NO-OP
renderAs(entityDataFor(state), renderer, enabled, func) inline fun renderAs(state: BlockState, renderType: BlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) = func()
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */ /** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
inline fun grass(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) = inline fun grass(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
renderAs(tallGrassEntityData, renderer, enabled, func) renderAs(Config.shaders.grassId, MODEL, renderer, enabled, func)
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */ /** Quads rendered inside this block will behave as leaf blocks in shader programs. */
inline fun leaves(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) = inline fun leaves(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
renderAs(leavesEntityData, renderer, enabled, func) renderAs(Config.shaders.leavesId, MODEL, renderer, enabled, func)
} }

View File

@@ -10,6 +10,8 @@ import mods.octarinecore.client.render.*
import mods.octarinecore.common.* import mods.octarinecore.common.*
import mods.octarinecore.random import mods.octarinecore.random
import net.minecraft.block.Block import net.minecraft.block.Block
import net.minecraft.block.BlockRenderType
import net.minecraft.block.BlockRenderType.MODEL
import net.minecraft.client.renderer.BlockRendererDispatcher import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.tags.BlockTags import net.minecraft.tags.BlockTags
@@ -77,7 +79,7 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliage.MOD_ID, BetterFo
val isVisible = forgeDirs.map { ctx.shouldSideBeRendered(it) } val isVisible = forgeDirs.map { ctx.shouldSideBeRendered(it) }
// render full grass block // render full grass block
ShadersModIntegration.renderAs(ctx.blockState(Int3.zero), renderer) { ShadersModIntegration.renderAs(ctx.blockState(Int3.zero), MODEL, renderer) {
modelRenderer.render( modelRenderer.render(
renderer, renderer,
fullCube, fullCube,

View File

@@ -12,10 +12,9 @@ import mods.octarinecore.common.Int3
import mods.octarinecore.common.Rotation import mods.octarinecore.common.Rotation
import mods.octarinecore.common.face import mods.octarinecore.common.face
import mods.octarinecore.common.rot import mods.octarinecore.common.rot
import net.minecraft.block.BlockRenderType.MODEL
import net.minecraft.client.renderer.BlockRendererDispatcher import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.BufferBuilder import net.minecraft.client.renderer.BufferBuilder
import net.minecraft.client.renderer.chunk.ChunkRenderCache
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.BlockRenderLayer import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.Direction.* import net.minecraft.util.Direction.*
import net.minecraftforge.client.model.data.IModelData import net.minecraftforge.client.model.data.IModelData
@@ -115,7 +114,7 @@ abstract class AbstractRenderColumn(modId: String, modBus: IEventBus) : Abstract
modelRenderer.updateShading(Int3.zero, allFaces) modelRenderer.updateShading(Int3.zero, allFaces)
val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal] val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal]
renderAs(ctx.blockState(Int3.zero), renderer) { renderAs(ctx.blockState(Int3.zero), MODEL, renderer) {
quadrantRotations.forEachIndexed { idx, quadrantRotation -> quadrantRotations.forEachIndexed { idx, quadrantRotation ->
// set rotation for the current quadrant // set rotation for the current quadrant
val rotation = baseRotation + quadrantRotation val rotation = baseRotation + quadrantRotation

View File

@@ -91,7 +91,7 @@ class BetterFoliageTransformer : Transformer() {
} }
if (isOptifinePresent) { if (isOptifinePresent) {
find(varinsn(ISTORE, 23))?.insertAfter { find(varinsn(ISTORE, 23))?.insertAfter {
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override") log.info("[BetterFoliageLoader] Applying RenderChunk block layer override (Optifine)")
varinsn(ALOAD, 19) varinsn(ALOAD, 19)
varinsn(ALOAD, 18) varinsn(ALOAD, 18)
varinsn(ALOAD, 22) varinsn(ALOAD, 22)
@@ -100,7 +100,7 @@ class BetterFoliageTransformer : Transformer() {
} }
} else { } else {
find(invokeRef(Refs.canRenderInLayer))?.replace { find(invokeRef(Refs.canRenderInLayer))?.replace {
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override") log.info("[BetterFoliageLoader] Applying RenderChunk block layer override (non-Optifine)")
invokeStatic(Refs.canRenderBlockInLayer) invokeStatic(Refs.canRenderBlockInLayer)
} }
} }

View File

@@ -37,7 +37,7 @@ object Refs {
// Optifine // Optifine
val RenderEnv = ClassRef("net.optifine.render.RenderEnv") val RenderEnv = ClassRef("net.optifine.render.RenderEnv")
val RenderEnv_reset = MethodRef(RenderEnv, "reset", ClassRef.void, IBlockReader, BlockState, BlockPos) val RenderEnv_reset = MethodRef(RenderEnv, "reset", ClassRef.void, BlockState, BlockPos)
val quadSprite = FieldRef(BufferBuilder, "quadSprite", TextureAtlasSprite) val quadSprite = FieldRef(BufferBuilder, "quadSprite", TextureAtlasSprite)
val BlockPosM = ClassRef("net.optifine.BlockPosM") val BlockPosM = ClassRef("net.optifine.BlockPosM")
val IColorizer = ClassRef("net.optifine.CustomColors\$IColorizer") val IColorizer = ClassRef("net.optifine.CustomColors\$IColorizer")

View File

@@ -1,15 +1,10 @@
package mods.octarinecore.client.render package mods.octarinecore.client.render
import mods.betterfoliage.BetterFoliage
import mods.betterfoliage.loader.Refs
import mods.octarinecore.common.* import mods.octarinecore.common.*
import mods.octarinecore.metaprog.allAvailable
import net.minecraft.client.Minecraft import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.BlockModelRenderer import net.minecraft.client.renderer.BlockModelRenderer
import net.minecraft.client.renderer.BlockModelRenderer.AmbientOcclusionFace
import net.minecraft.util.Direction import net.minecraft.util.Direction
import net.minecraft.util.Direction.* import net.minecraft.util.Direction.*
import org.apache.logging.log4j.Level
import java.lang.Math.min import java.lang.Math.min
import java.util.* import java.util.*

View File

@@ -64,6 +64,14 @@ class DelegatingIntValue(
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue) override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue)
} }
class DelegatingLongValue(
val minValue: Long = 0,
val maxValue: Long = 1,
val defaultValue: Long = 0
) : ConfigDelegate<Long>() {
override fun getConfigValue(name: String, builder: ForgeConfigSpec.Builder) = builder.defineInRange(name, defaultValue, minValue, maxValue)
}
class DelegatingDoubleValue( class DelegatingDoubleValue(
val minValue: Double = 0.0, val minValue: Double = 0.0,
val maxValue: Double = 1.0, val maxValue: Double = 1.0,
@@ -77,4 +85,5 @@ class DelegatingDoubleValue(
// ============================ // ============================
fun double(min: Double = 0.0, max: Double = 1.0, default: Double) = DelegatingDoubleValue(min, max, default) fun double(min: Double = 0.0, max: Double = 1.0, default: Double) = DelegatingDoubleValue(min, max, default)
fun int(min: Int = 0, max: Int, default: Int) = DelegatingIntValue(min, max, default) fun int(min: Int = 0, max: Int, default: Int) = DelegatingIntValue(min, max, default)
fun boolean(default: Boolean) = DelegatingBooleanValue(default) fun long(min: Long = 0, max: Long, default: Long) = DelegatingLongValue(min, max, default)
fun boolean(default: Boolean) = DelegatingBooleanValue(default)

View File

@@ -108,6 +108,13 @@ betterfoliage.blocks.netherrackBlacklist.arrayEntry=%d entries
betterfoliage.blocks.netherrackWhitelist.tooltip=Blocks recognized as Netherrack. Has an impact on Netherrack Vines betterfoliage.blocks.netherrackWhitelist.tooltip=Blocks recognized as Netherrack. Has an impact on Netherrack Vines
betterfoliage.blocks.netherrackBlacklist.tooltip=Blocks never accepted Netherrack. Has an impact on Netherrack Vines betterfoliage.blocks.netherrackBlacklist.tooltip=Blocks never accepted Netherrack. Has an impact on Netherrack Vines
betterfoliage.shaders=Shader configuration
betterfoliage.shaders.tooltip=Configure integration with shaders
betterfoliage.shaders.leavesId=Leaves ID
betterfoliage.shaders.leavesId.tooltip=Block ID reported to shader programs for all kinds of leaves. If your shader uses a §6block.properties§e file, you'll probably need to change this to match the shader's mappings.
betterfoliage.shaders.grassId=Grass ID
betterfoliage.shaders.grassId.tooltip=Block ID reported to shader programs for all grasses and crops. If your shader uses a §6block.properties§e file, you'll probably need to change this to match the shader's mappings.
betterfoliage.leaves=Extra Leaves betterfoliage.leaves=Extra Leaves
betterfoliage.leaves.tooltip=Extra round leaves on leaf blocks betterfoliage.leaves.tooltip=Extra round leaves on leaf blocks
betterfoliage.leaves.dense=Dense mode betterfoliage.leaves.dense=Dense mode