Tree Gen is a tool to dynamically generate a 3D model of a tree based on specifications the user inputs.
You can set the height, width, branch number and color of the tree.

- Create a base tree trunk and set it's width, height, tree color, background color, lean and how many branches the tree should have.
- You can also press "random" to randomize those settings.
- For more customization, you can also edit individual branches themselves to get the tree to look exactly as you want it to.

- Javascript for basic logic to handle input and generate trees
- three.js for 3D
- dat.gui for tree settings
The code is fundamentally built using Object Oriented Programming where I have a Trunk class, a Branch class, and a Leaf class, where the trunk has many branches and the branch has many leaves.
class Trunk {
constructor(sizing, gui, requestRender){
this.sizing = sizing;
this.gui = gui;
this.folder = gui.addFolder('Trunk');
this.branchFolder = gui.addFolder(`Branches`);
this.mesh = this.setupTrunkMesh()
this.bones = this.mesh.skeleton.bones;
this.requestRender = requestRender;
this.params = {
lean: 0.0,
twist: false,
straight: false,
width: 1,
height: 1,
basePos: 0,
color: 0x58433d,
leafColor: 0x34822d,
branchNum: 20.0,
branches : {},
random: false
};
this.branchSizing = {
width: Math.floor(sizing.width / 4),
segmentHeight: Math.floor(sizing.segmentHeight/1.5),
segmentCount: Math.floor(sizing.segmentCount/1.5),
height: Math.floor(sizing.segmentHeight/1.5) * Math.floor(sizing.segmentCount/1.5),
halfHeight: (Math.floor(sizing.segmentHeight/1.5) * Math.floor(sizing.segmentCount/1.5)) / 2,
}
this.branches = [];
this.initialBranchPos = [];
this.branchesFolders = [];
this.leaves = [];
this.setupTrunkFolder();
this.setupBranches();
this.setupLeaves();
}class Branch {
constructor(sizing, branchFolder, requestRender, color, leafColor){
this.sizing = sizing;
this.folder = [];
this.params = {
lean: 0,
twist: false,
straight: false,
width: 1,
height: 1,
basePos: 0,
color: color,
rotateY: (THREE.Math.radToDeg(1) % 360),
rotateX: (THREE.Math.radToDeg(1) % 360),
rotateZ: (THREE.Math.radToDeg(1) % 360),
leafColor: leafColor
};
this.mesh = this.setupBranchMesh();
this.bones = this.mesh.skeleton.bones;
this.requestRender = requestRender;
this.leaves = [];
this.setupLeaves()
}class Leaf {
constructor(loader, requestRender){
this.mesh = [];
this.requestRender = requestRender;
this.params = {
color: 0x34822d
}
this.loader = loader;
}To connect the branch to the trunk, once I generate the branch, I change it's positioning and rotation to coincide with the trunk's bones.
connectBranch(branch){
let anglesX = [-2.5, 2.5];
let anglesZ = [-2, 2];
let y;
let x;
let z;
let different = false;
while(!different){
y = Math.floor(Math.random() * ((this.bones.length - 3) - 1 + 1 ) + 1);
x = anglesZ[Math.floor(Math.random() * anglesZ.length)];
z = anglesX[Math.floor(Math.random() * anglesX.length)];
if (!this.initialBranchPos.some((sub) => {
return (sub[1] == y && sub[2] == z && sub[0] == x);
})){
different = true;
this.initialBranchPos.push([x,y,z]);
} else{
}
}
branch.bones[0].position.x = this.bones[y].position.x;
branch.bones[0].position.y = this.bones[y].position.y;
branch.bones[0].position.z = this.bones[y].position.z;
branch.bones[0].rotation.x = x;
branch.bones[0].rotation.z = z;
branch.bones[0].rotation.y = 0;
this.bones[y].add(branch.bones[0]);
this.branches.push(branch);
if(y === 0){
}
let rowFolder = this.branchesFolders[y-1];
if(!rowFolder){
}
branch.setupBranchRotation(x,0,z);
let folder = rowFolder.addFolder('Branch ' + y + "," + x + "," + z);
branch.setupBranchFolder(folder)
this.mesh.add(branch.mesh);
}And then, to connect the leaves to the branches, I apply a similar idea where I change the leaf's position and rotation to coincide with the branch's bones, but I allow for more divergence from those initial positions to allow for more randomization.
async setupLeaves(){
const loader = new GLTFLoader();
for(let i = 0; i < 50; i++){
let boneNum = Math.floor(Math.random() * ((this.bones.length - 1) - 1 + 1 ) + 1)
let bone = this.bones[boneNum];
let path = 'src/models/leaf-long.glb';
if(boneNum === this.bones.length - 1){
path = 'src/models/leaf.glb'
}
let file = await loader.loadAsync('src/models/leaf.glb')
let leafMesh = file.scenes[0].children[2];
leafMesh.scale.set(7,7,7);
leafMesh.material = new THREE.MeshToonMaterial();
leafMesh.material.color.set(this.params.leafColor)
let max = bone.position.y;
let min = this.bones[1].position.y;
leafMesh.position.y = boneNum === this.bones.length - 1 ? 0 : Math.random() * (9 - 1) + 1;
leafMesh.rotation.x = Math.random() * (6 - 1) + 1;
leafMesh.rotation.y = Math.random() * (6 - 1) + 1;
leafMesh.rotation.z = Math.random() * (6 - 1) + 1;
bone.add(leafMesh)
this.leaves.push(leafMesh);
this.requestRender()
}With this, I can tweak small settings and change how many branches or leaves a tree has very simply, or in the future, even allow for multiple trees to be generated at once.
- Research three.js implementation and create trunk of tree - 2 days
- Create branches - 1 day
- Create leaves - 1 day
- Have the leaves fall and regrow on the tree
- Move a premade character around the tree to see it from any angle
