[Mod] Living (dynamic) trees [living_trees] [WIP]

Post Reply
RichardTry
New member
Posts: 6
Joined: Fri Oct 16, 2020 13:26
GitHub: RichardTry
In-game: RichardTry

[Mod] Living (dynamic) trees [living_trees] [WIP]

by RichardTry » Post

Hi everybody, this is my first mod.
It adds roots, branches and smooth tree growth:
https://www.youtube.com/watch?v=3JJ-5R2PjPo
(Growth is fast just for demonstration!)

Screenshots:
branches.png
branches.png (975.84 KiB) Viewed 1171 times
tree.png
tree.png (727.67 KiB) Viewed 1171 times
pine.png
pine.png (742.23 KiB) Viewed 1171 times

GitHub: https://github.com/RichardTry/living_trees

How it works?
For every tree type (apple, pine, etc.) there is a node of roots (can be several: pine_roots_in_dirt, pine_roots_in_sand, etc.) During mapgen (or planting a sapling), every root gets unique L-system string, which generates from axiom for it's tree type. ABM, which runs for every root node, parses its string, and update corresponding node, if it is smaller (or air/leaves), than branch at string (branch getting blocked by other nodes).
Breaking a branch with axe/piston/TNT will recursively break its childs. Leaves will disappear with standart Minetest Game ABM.

Warning: Mapgen and l-string generation parts are not implemented yet! T — Draw trunk and go forward
Q — Draw trunk, turning to large branch and go forward
W — Draw large, turning to medium branch and go forward
E — Draw medium, turning to small branch and go forward
R — Draw small, turning to smallest branch and go forward
1 — Draw large branch and go forward
2 — Draw medium branch and go forward
3 — Draw small branch and go forward
4 — Draw smallest branch and go forward
+ — Yaw the turtle right
- — Yaw the turtle left
& — Pitch the turtle down
^ — Pitch the turtle up
/ — Roll the turtle to the right
* — Roll the turtle to the left
[ — Push/save current state into stack
] — Pop/recover current state from stack
A — Replace with rule set A
B — Replace with rule set B
C — Replace with rule set C
D — Replace with rule set D

You don't need to care about branches, which changes it's size (Q, W, E, R). During string generation they will automatically appear from 1, 2, 3, 4.

Characters for rules (A, B, C, D, ...) is not implemented yet, because string generation is not.
API:
To register a living tree run:

Code: Select all

living_trees.register_tree({name, roots, texture, trunk, leaves})
where:
name — tree's name.
roots — table of node names on which the tree grows.
texture — trunk and branches texture.
leaves — name of leaves node.
Example:

Code: Select all

living_trees.register_tree({
	name = "Apple",
	roots = {"living_trees:apple_roots"},
	texture = "default_tree.png",
	trunk = "default:tree",
	leaves = "default:leaves"
})
Will be implemented:
Mapgen,
L-string generation,
Branch adaptation (growing around obstacles).

User avatar
Krock
Developer
Posts: 4649
Joined: Thu Oct 03, 2013 07:48
GitHub: SmallJoker
Location: Switzerland
Contact:

Re: [Mod] Living (dynamic) trees [living_trees] [WIP]

by Krock » Post

This growth mechanism already looks very good. Keep up the good work!
It would be possible to use faster node timers on the next lower trunk node to spawn new branches. ABMs are quite slow and do not scale well when there's neighbour nodes to check.

PS: There's an L-tree/system implementation in Minetest, but it might not contain enough features for your needs.
Look, I programmed a bug for you. >> Mod Search Engine << - Mods by Krock - DuckDuckGo mod search bang: !mtmod <keyword here>

cuthbertdoublebarrel
Member
Posts: 348
Joined: Tue Apr 14, 2020 16:03
GitHub: cuthbert

Re: [Mod] Living (dynamic) trees [living_trees] [WIP]

by cuthbertdoublebarrel » Post

nice , really like the idea of the trees taking over the world and changing the terrain into woodland and forest . could you do the same with brambles and creeping vines or root systems ? so that they obstruct paths and passages and smother buildings .
Project BrutalTest...hide your Petz

User avatar
philipbenr
Member
Posts: 1897
Joined: Fri Jun 14, 2013 01:56
GitHub: philipbenr
IRC: philipbenr
In-game: robinspi
Location: United States

Re: [Mod] Living (dynamic) trees [living_trees] [WIP]

by philipbenr » Post

Very interested to see how this progresses. I like the idea of a dynamic environment, and even just the branches is a nice addition.

User avatar
v-rob
Developer
Posts: 970
Joined: Thu Mar 24, 2016 03:19
GitHub: v-rob
IRC: v-rob
Location: Right behind you.

Re: [Mod] Living (dynamic) trees [living_trees] [WIP]

by v-rob » Post

This is very nice! I really like the idea of real branches, but I've never been able to think of a proper way to make them look good. Yours are better than I imagined.

(I wish my first mod was half as cool as this one...)
Core Developer | My Best Mods: Bridger - Slats - Stained Glass

RichardTry
New member
Posts: 6
Joined: Fri Oct 16, 2020 13:26
GitHub: RichardTry
In-game: RichardTry

Re: [Mod] Living (dynamic) trees [living_trees] [WIP]

by RichardTry » Post

Hi everybody again. Thanks for the nice reviews. I'm sorry for so looong delay. I'm going back to work on the mod and minetest in general.
Krock wrote:
Mon Feb 15, 2021 13:54
It would be possible to use faster node timers on the next lower trunk node to spawn new branches. ABMs are quite slow and do not scale well when there's neighbour nodes to check.
I'm not sure its better to have thousands of node timers for each tree (for example in forest) instead of one abm, if the timers are a few game days long.
Krock wrote:
Mon Feb 15, 2021 13:54
PS: There's an L-tree/system implementation in Minetest, but it might not contain enough features for your needs.
Unfortunately there are only builtin characters. I've almost done with my own implementation.
cuthbertdoublebarrel wrote:
Mon Feb 15, 2021 14:03
could you do the same with brambles and creeping vines or root systems ? so that they obstruct paths and passages and smother buildings .
It is not difficult, but it's not the same, cos such things do not have a clear structure and you can simply implement random growth along objects.

User avatar
Festus1965
Member
Posts: 4181
Joined: Sun Jan 03, 2016 11:58
GitHub: Festus1965
In-game: Festus1965 Thomas Thailand Explorer
Location: Thailand ChiangMai
Contact:

Re: [Mod] Living (dynamic) trees [living_trees] [WIP]

by Festus1965 » Post

as long your basic on ABM and not LBM ... no way for me
I already closed vines and roots at several plant mods ... to keep the lag down
also all this trunk sticking nodes are not enabled also ...

yes, nice idea ... belong together have good root and get green, and might die down ... but please without lag from ABM
Human has no future (climate change)
If urgend, you find me in Roblox (as CNXThomas)

User avatar
PolySaken
Member
Posts: 817
Joined: Thu Nov 09, 2017 05:18
GitHub: PolySaken-I-Am
In-game: PolySaken
Location: Wānaka, Aotearoa
Contact:

Re: [Mod] Living (dynamic) trees [living_trees] [WIP]

by PolySaken » Post

Festus1965 wrote:
Fri Jun 18, 2021 03:06
as long your basic on ABM and not LBM ... no way for me
I already closed vines and roots at several plant mods ... to keep the lag down
also all this trunk sticking nodes are not enabled also ...

yes, nice idea ... belong together have good root and get green, and might die down ... but please without lag from ABM
There isn't a way to implement something like this without 'lag', as every tree needs to update at some point. The method used won't make a difference, LBMs will just concentrate all the lag to whenever a tree loads in rather than distributing it over the entire time it's loaded.
ABMs are the best way here because something like this is exactly what ABMs are for, while LBMs are for generation and nodetimers are more for single high-performance nodes like machines, etc.
Guidebook Lib, for in-game docs | Poly Decor, some cool blocks | Vision Lib, an all-purpose library.

User avatar
Eris
Member
Posts: 175
Joined: Thu Nov 19, 2020 23:12
IRC: definitelya Ovalo
In-game: Eris_still_crafts

Re: [Mod] Living (dynamic) trees [living_trees] [WIP]

by Eris » Post

I patched the mod so if anybody would like to use the tree generation RichardTry used, which isn't working, they can swap the files in the mod with the file contents I attached. :)

Code: Select all

living_trees = {}

local modpath = minetest.get_modpath("living_trees")
dofile(modpath.."/breaking.lua")
dofile(modpath.."/branches.lua")

function opposite_dir(dir)
	if dir % 2 == 0 then
		return dir + 1
	else
		return dir - 1
	end
end

function wallmounted_to_facedir(dir)
	if dir == 0 then
		return 0
	elseif dir == 1 then
		return 5
	elseif dir == 2 then
		return 3
	elseif dir == 3 then
		return 4
	elseif dir == 4 then
		return 1
	elseif dir == 5 then
		return 2
	end
end

function add_dir_to_pos(pos, dir)
	if dir == 0 then
		pos.y = pos.y + 1
	elseif dir == 1 then
		pos.y = pos.y - 1
	elseif dir == 2 then
		pos.x = pos.x + 1
	elseif dir == 3 then
		pos.x = pos.x - 1
	elseif dir == 4 then
		pos.z = pos.z + 1
	elseif dir == 5 then
		pos.z = pos.z - 1
	end
	return pos
end

function living_trees.register_tree(tree)

    tree.name = tree.name:lower()

    minetest.register_node("living_trees:" .. tree.name .. "_sapling", {
        description = tree.name .. "sapling",
        tiles = { "Sapling.png" },
        paramtype = "light",
        drawtype = "plantlike",
        paramtype2 = "meshoptions",
        sunlight_propagates = true,
        walkable = false,
        move_resistance = 2,
        waving = 1,
        param2 = 2,
        groups = { oddly_breakable_by_hand = 3, tree = 1, flammable = 5, attached_node = 1, sapling = 1 },
        on_construct = function(pos)
            minetest.get_node_timer(pos):start(math.random(5, 15))
        end,
        on_timer = function(pos, elapsed)
            minetest.set_node({ x = pos.x, y = pos.y - 1, z = pos.z }, { name = "living_trees:" .. tree.name .. "_roots", param2 = 32 })
        end,
        after_dig_node = function(pos)
            minetest.set_node({ x = pos.x, y = pos.y - 1, z = pos.z }, { name = "default:dirt" })
        end,
    })

    tree.sapling = "living_trees:" .. tree.name .. "_sapling"

    tree.roots = { "living_trees:" .. tree.name .. "_roots" }

	for _, root in ipairs(tree.roots) do
		minetest.override_item(root,
		{
			after_dig_node = function(pos, oldnode, oldmetadata, digger)
				break_childs(pos, oldnode, true)
			end
		})
	end

	local trunk_def = table.copy(minetest.registered_nodes[tree.trunk])
	trunk_def.description = tree.name.." trunk"
	trunk_def.drop = tree.trunk
	trunk_def.paramtype2 = "wallmounted"
	trunk_def.on_place = nil
	trunk_def.after_dig_node = function(pos, oldnode, oldmetadata, digger)
		break_childs(pos, oldnode)
	end

	tree.name = tree.name:lower()

	minetest.register_node("living_trees:branch_trunk_"..tree.name, trunk_def)

	local branch_trunk = "living_trees:branch_trunk_"..tree.name

	register_branches(tree.name, tree.texture)
	local branch_3_4 = "living_trees:branch_3_4_"..tree.name
	local branch_2_3 = "living_trees:branch_2_3_"..tree.name
	local branch_1_2 = "living_trees:branch_1_2_"..tree.name
	local branch_T_1 = "living_trees:branch_T_1_"..tree.name
	local branch_4 = "living_trees:branch_4_"..tree.name
	local branch_3 = "living_trees:branch_3_"..tree.name
	local branch_2 = "living_trees:branch_2_"..tree.name
	local branch_1 = "living_trees:branch_1_"..tree.name

	minetest.register_abm({
		nodenames = tree.roots,
		interval = 50.0,
		chance = 100,
		action = function(pos, node, active_object_count, active_object_count_wider)
			local curpos = pos
			local currot = {dir = 0, up = 3, left = 4}		-- In wallmounted numbers
			local meta = minetest.get_meta(pos)
			local lstr = meta:get_string("lstring")
			local stack = {}
			local skipbranch = 0
			for i = 1, #lstr do

				local c = lstr:sub(i,i)
				
				if c == "]" then
					if skipbranch > 1 then
						skipbranch = skipbranch - 1
					else
						skipbranch = 0
						currot = table.remove(stack)
						curpos = table.remove(stack)
					end
				elseif c == "[" then
					if skipbranch > 0 then
						skipbranch = skipbranch + 1
					else
						local savedpos = {}
						for k,v in pairs(curpos) do
							savedpos[k] = v
						end
						local savedrot = {}
						for k,v in pairs(currot) do
							savedrot[k] = v
						end
						table.insert(stack, savedpos)
						table.insert(stack, savedrot)
					end
				elseif skipbranch > 0 then
				elseif c == "T" then
					add_dir_to_pos(curpos, currot.dir)
					local name = minetest.get_node(curpos).name
					if name == "air" or name == tree.leaves or name == tree.sapling then
						minetest.set_node(curpos, {name=branch_3_4, param2 = opposite_dir(currot.dir)})
						skipbranch = skipbranch + 1 
					elseif name == branch_3_4 then
						minetest.set_node(curpos, {name=branch_2_3, param2 = opposite_dir(currot.dir)})
					elseif name == branch_2_3 then
						minetest.set_node(curpos, {name=branch_1_2, param2 = opposite_dir(currot.dir)})
					elseif name == branch_1_2 then
						minetest.set_node(curpos, {name=branch_T_1, param2 = opposite_dir(currot.dir)})
					elseif name == branch_T_1 then
						minetest.set_node(curpos, {name=branch_trunk, param2 = opposite_dir(currot.dir)}) --wallmounted_to_facedir(currot.dir) * 4})
					elseif name ~= branch_trunk then
						skipbranch = skipbranch + 1
					end
				elseif c == "Q" then
					add_dir_to_pos(curpos, currot.dir)
					local name = minetest.get_node(curpos).name
					if name == "air" or name == tree.leaves then
						minetest.set_node(curpos, {name=branch_3_4, param2 = opposite_dir(currot.dir)})
						skipbranch = skipbranch + 1
					elseif name == branch_3_4 then
						minetest.set_node(curpos, {name=branch_2_3, param2 = opposite_dir(currot.dir)})
					elseif name == branch_2_3 then
						minetest.set_node(curpos, {name=branch_1_2, param2 = opposite_dir(currot.dir)})
					elseif name == branch_1_2 then
						minetest.set_node(curpos, {name=branch_T_1, param2 = opposite_dir(currot.dir)})
					elseif name ~= branch_T_1 then
						skipbranch = skipbranch + 1
					end
				elseif c == "W" then
					add_dir_to_pos(curpos, currot.dir)
					local name = minetest.get_node(curpos).name
					if name == "air" or name == tree.leaves then
						minetest.set_node(curpos, {name=branch_3_4, param2 = opposite_dir(currot.dir)})
						skipbranch = skipbranch + 1
					elseif name == branch_3_4 then
						minetest.set_node(curpos, {name=branch_2_3, param2 = opposite_dir(currot.dir)})
					elseif name == branch_2_3 then
						minetest.set_node(curpos, {name=branch_1_2, param2 = opposite_dir(currot.dir)})
					elseif name ~= branch_1_2 then
						skipbranch = skipbranch + 1
					end
				elseif c == "E" then
					add_dir_to_pos(curpos, currot.dir)
					local name = minetest.get_node(curpos).name
					if name == "air" or name == tree.leaves then
						minetest.set_node(curpos, {name=branch_3_4, param2 = opposite_dir(currot.dir)})
						skipbranch = skipbranch + 1
					elseif name == branch_3_4 then
						minetest.set_node(curpos, {name=branch_2_3, param2 = opposite_dir(currot.dir)})
					elseif name ~= branch_2_3 then
						skipbranch = skipbranch + 1
					end
				elseif c == "R" then
					add_dir_to_pos(curpos, currot.dir)
					local name = minetest.get_node(curpos).name
					if name == "air" or name == tree.leaves then
						minetest.set_node(curpos, {name=branch_3_4, param2 = opposite_dir(currot.dir)})
						skipbranch = skipbranch + 1
					elseif name ~= branch_3_4 then
						skipbranch = skipbranch + 1
					end
				elseif c == "1" then
					add_dir_to_pos(curpos, currot.dir)
					local name = minetest.get_node(curpos).name
					if name == "air" or name == tree.leaves then
						minetest.set_node(curpos, {name=branch_4, param2 = opposite_dir(currot.dir)})
						skipbranch = skipbranch + 1
					elseif name == branch_4 then
						minetest.set_node(curpos, {name=branch_3, param2 = opposite_dir(currot.dir)})
					elseif name == branch_3 then
						minetest.set_node(curpos, {name=branch_2, param2 = opposite_dir(currot.dir)})
					elseif name == branch_2 then
						minetest.set_node(curpos, {name=branch_1, param2 = opposite_dir(currot.dir)})
					elseif name ~= branch_1 then
						skipbranch = skipbranch + 1
					end
				elseif c == "2" then
					add_dir_to_pos(curpos, currot.dir)
					local name = minetest.get_node(curpos).name
					if name == "air" or name == tree.leaves then
						minetest.set_node(curpos, {name=branch_4, param2 = opposite_dir(currot.dir)})
						skipbranch = skipbranch + 1
					elseif name == branch_4 then
						minetest.set_node(curpos, {name=branch_3, param2 = opposite_dir(currot.dir)})
					elseif name == branch_3 then
						minetest.set_node(curpos, {name=branch_2, param2 = opposite_dir(currot.dir)})
					elseif name ~= branch_2 then
						skipbranch = skipbranch + 1
					end
				elseif c == "3" then
					add_dir_to_pos(curpos, currot.dir)
					local name = minetest.get_node(curpos).name
					if name == "air" or name == tree.leaves then
						minetest.set_node(curpos, {name=branch_4, param2 = opposite_dir(currot.dir)})
						skipbranch = skipbranch + 1
					elseif name == branch_4 then
						minetest.set_node(curpos, {name=branch_3, param2 = opposite_dir(currot.dir)})
					elseif name ~= branch_3 then
						skipbranch = skipbranch + 1
					end
				elseif c == "4" then
					add_dir_to_pos(curpos, currot.dir)
					local name = minetest.get_node(curpos).name
					if name == "air" or name == tree.leaves then
						minetest.set_node(curpos, {name=branch_4, param2 = opposite_dir(currot.dir)})
						skipbranch = skipbranch + 1
					elseif name ~= branch_4 then
						skipbranch = skipbranch + 1
					end
				elseif c == "^" then
					local temp = opposite_dir(currot.dir)
					currot.dir = currot.up
					currot.up = temp
				elseif c == "&" then
					local temp = opposite_dir(currot.up)
					currot.up = currot.dir
					currot.dir = temp
				elseif c == "+" then
					local temp = opposite_dir(currot.left)
					currot.left = currot.dir
					currot.dir = temp
				elseif c == "-" then
					local temp = opposite_dir(currot.dir)
					currot.dir = currot.left
					currot.left = temp
				elseif c == "/" then
					local temp = opposite_dir(currot.left)
					currot.left = currot.up
					currot.up = temp
				elseif c == "*" then
					local temp = opposite_dir(currot.up)
					currot.up = currot.left
					currot.left = temp
				end
			end
		end
	})
	
	if tree.leaves then
		minetest.register_abm({
		    nodenames = {branch_3_4, branch_4},
		    interval = 30.0,
		    chance = 50,
		    action = function(pos, node, active_object_count, active_object_count_wider)
			for x = -1,1 do
				for y = -1,1 do
					for z = -1,1 do
						local curpos = {x = x, y = y, z = z}
						curpos = vector.add(pos, curpos)
						if minetest.get_node(curpos).name == "air"  and (y == 0 or x == 0 or z == 0) then
							minetest.set_node(curpos, {name = tree.leaves})
						end
					end
				end
			end
		    end
		})
	end
end

dofile(modpath.."/default_trees.lua")

Code: Select all

minetest.register_node("living_trees:apple_roots", {
	description = "Apple roots",
	tiles = {"default_dirt.png^living_trees_roots.png"},
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("lstring", "TTT[^Q[+W-E&R][-W+E&R]][&Q[+W-E^R][-W+E^R]]Q[^W&E^R][&W^E&R][+W-E+R][-W+E-R]")
	end,
	drop = { max_items = 1, items = { { rarity = 1, items = { "default:dirt" } } } },
	on_drop = function(itemstack, dropper, pos)
		return false
	end,
	after_dig_node = function(pos, oldnode, oldmetadata, digger)
		break_childs(pos, oldnode, true)
	end,
	groups = {crumbly = 2, choppy = 1}
})

minetest.register_node("living_trees:pine_roots", {
	description = "Pine roots",
	tiles = {"default_dirt.png^living_trees_roots.png"},
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("lstring", "TTTT^[QW[+E-R][-E+R]ER]+[QW[+E-R][-E+R]ER]+[QW[+E-R][-E+R]ER]+[QW[+E-R][-E+R]ER]&Q^[WE[+R][-R]R]+[WE[+R][-R]R]+[WE[+R][-R]R]+[WE[+R][-R]R]&1W^[ER]+[ER]+[ER]+[ER]&2E^[R]+[R]+[R]+[R]&3R")
	end,
	drop = { max_items = 1, items = { { rarity = 1, items = { "default:dirt" } } } },
	on_drop = function(itemstack, dropper, pos)
		return false
	end,
	after_dig_node = function(pos, oldnode, oldmetadata, digger)
		break_childs(pos, oldnode, true)
	end,
	groups = {crumbly = 2, choppy = 1}
})

living_trees.register_tree({name = "Apple", roots = {"living_trees:apple_roots"}, texture = "default_tree.png", trunk = "default:tree", leaves = "default:leaves"})
living_trees.register_tree({name = "Pine", roots = {"living_trees:pine_roots"}, texture = "default_pine_tree.png", trunk = "default:pine_tree", leaves = "default:pine_needles"})
Last edited by Eris on Fri Oct 28, 2022 15:37, edited 1 time in total.
Jump in the caac

User avatar
StarNinjas
Member
Posts: 411
Joined: Wed Mar 14, 2018 00:32
GitHub: starninjas
IRC: StarNinjas
In-game: J1
Location: Terrarca
Contact:

Re: [Mod] Living (dynamic) trees [living_trees] [WIP]

by StarNinjas » Post

I'm glad I found this mod! I've been looking for it
Don't go to bed tonight, without knowing what would happen if you died. https://thegospelfilm.org/

User avatar
j0j0n4th4n
Member
Posts: 249
Joined: Tue Jan 26, 2021 06:45

Re: [Mod] Living (dynamic) trees [living_trees] [WIP]

by j0j0n4th4n » Post

This mod looks amazing, I can't wait to try it. =)


On a side note, after seeing the video I believe there are other mods which could be interesting for you to check it out.

The first one is the 'treecaptator' mod [ viewtopic.php?t=4772 ], in this mod after chopping a tree the leaves and fruits also fall down. I don't know if you would like that to happen in your mod but I think if is worth looking.

Other mods have try making trees with more realistic behavior are the 'Ecobots' from self organizing systems modpack [ viewtopic.php?t=17608 ]. I remember they have a really cool system for growing a forest, but the trees themselves were hard to implement because of the multiple nodes involved. There is also the 'rnd_trees' mod [ viewtopic.php?t=13451 ] which implement tree growth in another way (I think it adds some randomness as well but I'm not certain).

Hope it can be of use to yours =)
cdb_894a100ddd76

Post Reply

Who is online

Users browsing this forum: No registered users and 21 guests