Modding:Trees

From Vintage Story Wiki

Overview

The overall steps for tree placement is that it first generates the tree, then generates the tree floor. Within tree generation, each trunk is treated as a branch. As the branch grows, it starts off placing wood blocks. Once the branch gets too "narrow", then it switches to branch leaf blocks, then leaf blocks. Along the way it may generate new sub-branches.

Tree generation options

The tree generation options are set through the worldgen/treegen asset files. Each json file contains a TreeGenConfig. Inside the TreeGenConfig are TreeGenTrunks and TreeGenBranches.

Tree generation commands

Both the wgen and WorldEdit commands can place trees. wgen reloads the tree generators before placing the tree. WorldEdit reloads the tree generators when the tree variety is selected. WorldEdit is especially helpful for testing changes to the tree generators, with the following sequence:

  • /we tool TreeGen - select the tree generator
  • /we tv walnut - select the tree variety
  • right click - to place the tree
  • /we undo - clean up the test tree

Height

The suggested size is passed into the tree generation. A value of 1.0 can be thought of as a normal sized tree for that variety. BESapling uses a randomly selected float in the [0.6, 1.1] range.

The suggested size is converted into the adjusted size with the following formula, where sizeMultiplier and sizeVar are settings in the TreeGenConfig. Since sizeMultiplier defaults to 0, every tree sets it, typically to a value around 1. sizeVar is a way to add extra variance to the tree height. It defaults to 0, which most trees use. The adjusted size is typically in the [0.18, 2.2] range.

adjusted size = suggested size * sizeMultiplier + sizeVar

Internally the term "width" is used to describe something like the length of a branch. Do not confuse it with the literal width of a branch. The real width of every branch is 1 block (2x2 tree trunks are actually 4 touching trunks). The adjusted size is converted into a trunk "width" with the following formula. The trunk widthMultiplier is redundant with the above parameters. So most trees let it default to 1.

trunk width = adjusted size * trunk widthMultiplier

After each trunk block is placed, its "width" gets narrower, by the widthLoss. That is how the width turns into a length. The initial width loss can either be specified by as a constant through the widthLoss field, or as a random variable through the randomWidthLoss field. Most trees use the constant widthLoss field.

From there, the trunk grows as a branch. The tree height is the length of the trunk as it grows as a branch, plus maybe extra height from the branches that come off the trunk. So the tree height is roughly the trunk width divided by the trunk's widthLoss.

Branch angles

Each branch has a horizontal and vertical angle. The angles are measured in radians. The vertical angle is relative to the Y axis. So a 0 radian vertical angle is a branch that's pointing completely up. A pi/2 vertical angle is a branch that's pointing completely horizontal. When the horizontal angle is 0, then the horizontal component of the branch points east. When the horizontal angle is pi/2, then it points south.

The trunk's angle comes from the angleVert and angleHori options.

At each step, the angle changes using the angleVertEvolve and angleHoriEvolve options, if they are set (by default they do not change the angle).

The gravityDrag option can have an effect somewhat like changing the angle. The effect of gravityDrag varies depending on how horizontal the branch is. Completely horizontal branches are vertically shifted by the whole gravityDrag at every step. Whereas completely vertical branches are not affected by gravityDrag at all.

Child branches

The simple length of a branch (called totaldistance in the code) is calculated by dividing the "width" by the widthLoss. For example, if the width is 1.0 and the widthLoss is 0.1, then the simple length would be 10 blocks. The simple length is not necessarily the real length of the branch. There are two ways it can differ: 1. The simple length calculation assumes the "width" is reduced all the way to 0. However, the dieAt option specifies what "width" the branch should stop growing at. So for example, if dieAt was set to 0.2, then the branch would stop before reaching its simple length. 2. If the widthLossCurve curve is set to less than 1 (default is 1), then the width loss will get lower (less loss) on each time a block is placed for the branch. So this way the branch could end up longer than calculated by the simple length.

The simple length and the distance traveled so far by the branch are used in the relative distance calculation.

relative distance = simple length / distance traveled

The branchStart option is specified as a relative distance. So assuming the parent branch's real length matched its simple length, then a branchStart value of 0.3 would mean that its first child branch would start 30% out from the root of the parent branch. Child branches can be disabled by setting the branchStart to a value of 1.0 or greater (assuming that the real length is less than or equal to the simple length).

The branchSpacing option is specified as a relative distance of the remaining simple length, compared to the position of the previous branch. So let's say the parent branch is 20 blocks long (and its simple length matches its real length), its first child branch was at 4 blocks from the root, and its branchSpacing is 0.5. After the first branch is placed, the parent has 16 blocks remaining. 50% of that is 8, so the next branch would be started 12 blocks from the root. Now it only has 8 blocks remaining, and 50% of that is 4. So the next branch would start 16 blocks from the root, and so forth.

Every time the parent branch decides to create child branches, it uses the branchQuantity option to control how many are created. This is used to make the branches look more evenly balanced instead of just individual branches popping out at random intervals.

The branchHorizontalAngle option (units in radians) controls the horizontal angle of the child branch relative to the parent branch (it is added to the parent branch's horizontal angle). To reduce the changes of two children using the same horizontal angle, the tree generator tries to pick a horizontal angle that's branchHorizontalAngle.var / 5 away from the previous child. However, the formula only takes into account the previous child (not two more more children earlier), and it does not handle 360 degree rotations correctly. So it may end up placing child branches at similar angles anyway.

The vertical angle for the child branches is directly calculated from branchVerticalAngle. Unlike the horizontal angle, the parent branch's angle is not added to the child's vertical angle. So most trees use a branchVerticalAngle close to pi/2 (about 1.57) plus some variance so that the child branches are close to horizontal.

Each child branch inherits the "width" of the parent at the branch point, times a multiplier. The multiplier is initially branchWidthMultiplier. If branchWidthMultiplierEvolve is set, then it is used to change the multiplier on each depth of the tree.

The trunk uses it's options to create the first level of branches. The next level of branches takes its options from the first entry in the branches array in the json file. The next level uses the second entry in the array. When the array runs out of entries, the last entry in the array is used for the remaining depths. So for example, if the branches array is empty, then the trunk options are used for all child branches at every level.

Tree branch blocks

When the "width" is greater than or equal to 0.3, then a log block is used. This is typically the logBlockCode listed in the treeBlocks section, but there are some exceptions. If the branch is a trunk and the trunk has segment set to a value of 1 or greater, then a block from trunkSegmentBase + trunkSegmentVariants is used instead. This is used for trees like redwood that have different trunk and branch logs.

These log block exceptions only apply for trees generated by a command or worldgen (not grown by sapling):

  • The otherLogBlockCode may be used instead according to otherLogChance * treeGenParams.otherBlockChance -- this is typically a leaking resin block.
  • A moss decor may be placed on top of the wood block if mossDecorBlock and mossGrowthChance are set.
  • A vine block may be placed on top of the wood block if vinesGrowthChance and vinesBlock are set.

If the width is less then 0.3, then a leaf block is placed instead. Closer to the root of the branch, branchy leaf blocks are used. At the end of the branch, regular leaf blocks are used. The transition point depends on leafLevelFactor (defaults to 5). If the width is greater than or equal to (1 - .5)/leafLevelFactor, then a branchy leaf block is used. So by default, branch leaf blocks are used from width 0.3 to 0.1, and regular leaf blocks are used from 0.1 to 0.

By setting the dieAt option, the leaves can be reduced. Setting it to 0.1 (assuming the default leafLevelFactor) would create a tree with no regular leaf blocks. Setting it to 0.3 would create a tree with branches but no leaf blocks at all.

Setting widthlossCurve to a value less than 1.0 causes the width loss to reduce over the length of the branch. This causes the branch to generate more blocks in the leaf phase relative to the log phase.

Multiple trunk entries

There are two reasons to put multiple entries in the trunk array: 1. To create a 2x2 trunk, like a redwood. 2. To create a trunk that vertically branches off, like acacia.

In both cases, each trunk entry grows independently. It is okay for the trunks to overlap in the sense that they will keep growing when the recognize the location is already occupied by a block from the tree generation (only stops if it runs into a block not belonging to the tree).

Note that each trunk creates its child branches independently. So by adding multiple trunks, you may end up with more child branches than you expected.

2x2 trunk

Create 4 entries in the trunks array. Use the dx and dz options to place each trunk in the center of each of the base blocks ( (0.5, 0.5), (0.5, 1.5), (1.5, 1.5), and (1.5, 0.5) ). Each trunk will independently grow from from its base. Ensure the angleVert is unset so that the trunks grow completely vertical (rather than separate mid way up the tree). Give each trunk a segment index from 1 to 4. Use the trunkSegmentBase and trunkSegmentVariants fields to give the trunks different block types (to show bark only on the outer faces of the trunk sections).

Vertically branching off trunk

Leave the angleHori unset. It defaults to randomly selecting any horizontal angle. Set the angleVert and optionally angleVertEvolve on the trunks. angleVert controls the angle that the trunks start at. angleVertEvolve controls how the trunks spread as they grow.

Forest floor generation

Forest floor generation is only run when the tree is created as part of worldgen. It is skipped when the tree is grown from a sapling.

During the tree generation, the (x,z) positions of the placed blocks (trunk, branches, leaves, etc.) is tracked. A 2 block blur is applied to those positions (represents the wider area that is under the shadow of the canopy), then they are updated to forest floor blocks.

In addition to placing forest floor blocks, block patches may also be applied, which spawn things like berry bushes and mushrooms.


Content Modding
Basics Content Mods Developing a Content Mod Packaging & Release
Tutorials
Concepts Modding Concepts Modinfo Variants Domains Patching Remapping World Properties
Moddable Assets
Uncategorized
Icon Sign.png

Wondering where some links have gone?
The modding navbox is going through some changes! Check out Navigation Box Updates for more info and help finding specific pages.

Modding
Modding Introduction Getting Started Theme Pack
Content Modding Content Mods Developing a Content Mod Basic Tutorials Intermediate Tutorials Advanced Tutorials Content Mod Concepts
Code Modding Code Mods Setting up your Development Environment
Property Overview ItemEntityEntity BehaviorsBlockBlock BehaviorsBlock ClassesBlock EntitiesBlock Entity BehaviorsCollectible BehaviorsWorld properties
Workflows & Infrastructure Modding Efficiency TipsMod-engine compatibilityMod ExtensibilityVS Engine
Additional Resources Community Resources Modding API Updates Programming Languages List of server commandsList of client commandsClient startup parametersServer startup parameters
Example ModsAPI DocsGitHub Repository