CSP 3D Trees - John5i4/Shutoko-Revival-Project GitHub Wiki

How to use 3D trees in your tracks via CSP (0.79 and above)

First things first

You need to understand how the system works. The trees are not part of the track model, they are their own 3D models that get loaded in coordinates we define in a file called the tree list.

What you need

  • A 3D tree model. Ideally, more than one. The tree models are set up in a specific way that will be described below. It is OK if it has multiple materials.
  • A way to get coordinates of spots on your track where you want to have a tree. Personally, I made a Blender script that exports the coordinates of selected objects and writes them into a file. You can find it below.

Setting up the tree models

Trees need to be exported from your 3D modelling software of choice and then converted through Content Manager. For this to work:

  • Place the tree and all its LODs on the center of the world.
  • Assign a diffuse and a normal texture to the tree's materials. Its important that a normal map exists.
  • Place the tree's origin on the bottom, where it touches the ground. Make sure the LODs are aligned too.
  • Export as FBX as usual.
  • Open CM, go to Content > Tools > Tree Model Converter. Launch it, click "Convert Tree Model", open the FBX, it will convert, save as a .bin file as prompted. Tree model and textures will be packed in this .bin file. LODs will be assigned automatically based on triangle count. The name of the .bin file is important. You will be loading tree models using this name.
  • Once you are done converting your trees, place all the .bin files in /extension/trees.

Now you have at least one tree model that can be used.

Placing the trees in 3D space

Apparently x4fab will be making an app/tool for placing trees but until then, getting their coordinates from your 3D app is also valid. (You can also get the coordinates of a spot to place a tree on through Object Inspector, this is OK for placing a few trees at most.)

Once you have your coordinates, you need to put it in a .txt file called the tree list. A track can have multiple tree lists, which helps with large tracks like SRP. The format for the file is as follows:

configure: size variance = 0.8, 1.2 ;Optional line. It adds a random size variation to your trees (min 0.8, max 1.2 times the model's actual size).
configure: angle variance = 0, 360   ;Optional line. It adds a random rotation variation to your trees (min 0, max 360 degrees from the model's actual rotation).

tree: populus3.bin; pos=-4598.23, 34.30, -6069.9; angle = 0; size = 0.48
tree: Birch_River_Desktop_9B_Forest.bin; pos=-4558.35, 34.34, -6099.18; angle = -66; size = 0.57
tree: Birch_River_Desktop_9B_Forest.bin; pos=-4538.85, 34.07, -6171.27; angle = 128; size = 0.68
tree: Birch_River_Desktop_9_Forest.bin; pos=-4554.41, 34.37, -6140.23; angle = -227; size = 1.00
tree: Birch_River_Desktop_8_Forest.bin; pos=-4513.63, 33.48, -6187.17; angle = -201; size = 1.00
tree: Birch_River_Desktop_9B_Forest.bin; pos=-4512.43, 32.29, -6220.43; angle = 0; size = 0.70
tree: Birch_River_Desktop_9B_Forest.bin; pos=-4544.41, 34.42, -6127.2; angle = -44; size = 0.66

;etc, etc

You get the idea. Each tree gets its own "tree:....." line, which has its .bin's name, position in the world, rotation and scale.

Making CSP load the trees

Once you have this list (its name can be anything, for this example lets name it treelist.txt), place it in /extension/trees as well. Finally, add the tree list to the track's ext_config as follows:

[TREES]
LIST_0 = trees/treelist.txt
LIST_1 = trees/treelist2.txt
; ofc you can add as many as you like

There are some other ext_config parameters for trees that will be described later. For now, just confirm that your trees show in game.

Final steps

Now that your trees are working, you should run the track through the latest VAO Bakery (I don't know if its been released publicly yet). The Bakery will recognize that you are using 3D trees and do a special bake for them. It will also compile the trees into a final .bin file, which loads faster and improves performance. Obviously this is something you do at the very end. After the compilation, you can replace the tree lists in your ext_config with the compiled .bin, like so:

[TREES]
COMPILED_LIST = compiled_trees.bin

There are also these parameters that can be added to the [TREES] section:

SURFACE_MATERIALS = tarmac, Concrete2Mat ;This is a list of materials you can select which will force trees floating over or under these materials to stick to the surface. For most people, it will be used as a quick-fix of floating trees if you've placed your model's origin wrong but it has other uses. 
SEASON_AUTUMN_0 = SEASON_AUTUMN ;These are ways to change the trees during different seasons like making the snowy in winter
SEASON_WINTER_0 = SEASON_WINTER
SEASON_WINTER_1 = tree_pine?, 0

These are used in combination with seasonal adjustment entries in your track's config. For example, lets say you have the following seasonal adjustment:

[CONDITION_...]
NAME=SEASON_WINTER
INPUT=YEAR_PROGRESS
LUT=(|-1=0|0=0.75|0.075=1|0.2=0|0.8=0|0.9=0.5|1=0.75|)
LAG=0

So your condition is named "SEASON_WINTER". You add SEASON_WINTER_0 = SEASON_WINTER to your [TREES] section to tell CSP that SEASON_WINTER is the winter season. The type is as so: NAME_OF_SEASON_THAT_CSP_TREES_UNDERSTAND = NAME_OF_YOUR_CONDITION_FOR_THAT_SEASON The names that CSP understands are: SEASON_AUTUMN_0, SEASON_WINTER_0, SEASON_SPRING_0 and SEASON_SUMMER_0 Once you have added all the seasons you want, you need to add a second entry for each that will describe what materials are affected in that season. So, lets say you have this:

[TREES]
COMPILED_LIST = compiled_trees.bin
SEASON_AUTUMN_0 = SEASON_AUTUMN ;

You need to add: SEASON_AUTUMN_1 = list_of_materials_that_will_get_affected, 0

My method

Here is how I do it. I will not get into too much detail, I will just tell you how to make it work so you can use the same method:

  • Make a .blend file with all your trees. Just LOD0 is ok, you can have the rest in there too if you'd like.
  • Export a .bin file for every tree.
  • Name the LOD0 trees and the bin files the same thing, so that the tree in Blender has the same name as the .bin file it represents. For example "White_Birch1" in Blender, "White_Birch1.bin" in your extension/trees folder. Repeat for all trees.
  • Mark all LOD0 trees as Blender Assets (in the outliner, select, right click, "Mark asset".
  • Save the .blend file.

Now while editing your track, you can load the trees.blend you created as an asset library (Here's a tutorial). Make a new collection where all the trees will go in and then just drag and drop the trees you want in the places you want. You can scale and rotate them too, my script will carry over those changes to the CSP tree list. Do not apply transformations.

Check the names of the trees you are placing. They should be (original name of the tree) and if you have multiples of the same tree: a dot, then a number. Just like Blender usually names objects. Do not change the names or the script will not work. A correct collection full of trees will look like this:

Once you have all your trees placed, you need to select them all and run the following script:

import bpy
import os 
import math
import random
from pathlib import Path

home_dir = str(Path.home())
desktop = str(r"\Desktop\treelist.txt")
print(home_dir)
print(desktop)
home_dir += desktop
print(home_dir)
 
treelist=open(home_dir ,'w')

for obj in bpy.context.selected_objects:
    treetype = str(obj.name)
    sep = '.'
    treetype = treetype.split(sep, 1)[0] + '.bin'
    treelist.write('tree: ' + str(treetype) + '; ')
    treelist.write('pos=' + "{:.2f}".format(obj.location.x) + ', ' + "{:.2f}".format(obj.location.z) + ', ' + str(-1 * float("{:.2f}".format(obj.location.y))) + '; ')
    angle = str(math.degrees(obj.rotation_euler[2]))
    angle = angle.split(sep, 1)[0]
    treelist.write('angle = ' + (str(angle)+'; '))
    treelist.write('size = ' + "{:.2f}".format(obj.scale[0]))
    treelist.write('\n')

treelist.close()

Now you will have a .txt file (treelist.txt if you havent renamed it in the code) on your desktop. That list is ready to go and you can use it as described above.

Remember that the script does nothing but translate objects' origins to instructions for AC tree placement. That means that you could use any object in Blender, grab its vertices, make them into separate objects and export them as instructions for tree placement.

## Version of the script for randomly assigned tree types

If you dont care what kind of tree ends up where, you can use the version below to assign them at random. You need to type a list of the .bin files you have available in the tree_types = table.

import bpy
import os 
import math
import random
from pathlib import Path

home_dir = str(Path.home())
desktop = str(r"\Desktop\treelist.txt")
print(home_dir)
print(desktop)
home_dir += desktop
print(home_dir)
 
treelist=open(home_dir ,'w')
tree_types = ["tree02.bin", "tree354235.bin"]

treelist.write('configure: size variance = 0.8, 1.2\n')
treelist.write('configure: angle variance = 0, 360\n\n')
 
for obj in bpy.context.selected_objects:
    treetype = random.choice(tree_types)
    sep = '.'
    treetype = treetype.split(sep, 1)[0] + '.bin'
    treelist.write('tree: ' + str(treetype) + '; ')
    treelist.write('pos=' + "{:.2f}".format(obj.location.x) + ', ' + "{:.2f}".format(obj.location.z) + ', ' + str(-1 * float("{:.2f}".format(obj.location.y))) + '; ')
    angle = str(math.degrees(obj.rotation_euler[2]))
    angle = angle.split(sep, 1)[0]
    treelist.write('angle = ' + (str(angle)+'; '))
    treelist.write('size = ' + "{:.2f}".format(obj.scale[0]))
    treelist.write('\n')

treelist.close()