Skip to content

Commit 77c867a

Browse files
committed
Add how to guide on messages in scenes
1 parent cbfa3b0 commit 77c867a

2 files changed

Lines changed: 179 additions & 1 deletion

File tree

docs/make.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ makedocs(;
4040
"Relational queries" => "tutorials/more_rules_queries/relationalqueries.md"]
4141
],
4242
"How-to guides" => [
43-
"Setting up a grid cloner" => "howto/GridCloner.md"
43+
"Setting up a grid cloner" => "howto/GridCloner.md",
44+
"Messages in scenes" => "howto/Message.md"
4445
],
4546
"API" => [
4647
"Graphs" => "api/graphs.md",

docs/src/howto/Message.md

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# Messages in scene
2+
3+
In VPL it is possible to generate different scenes from the same graph or collection of
4+
graphs. This is achieved by passing a message to the `feed!` methods when creating the
5+
`Scene` object. A message is any object (of any type) assigned to the keyword argument
6+
`message` inside any `Scene()` method. Within a `feed!` method, the message can be from the
7+
turtle object (first argument of the method call) as `turtle.message` (assuming the object
8+
is named `turtle`). Examples of cases when messages can be used include:
9+
10+
* Differentiating between geometry needed for visualization and for ray tracing (e.g. we may want to include roots in the visualization but not in the ray tracing).
11+
12+
* Changing the color associated to some geometry (e.g. we may want to color the leaves of a plant based on the amount of light they receive).
13+
14+
* Changing the geometry based on the message (e.g. we may want a higher level of realism for visualization that for ray tracing to reduce computational costs).
15+
16+
Let's illustrate how to use messages with a simple example. We will modify the Tree tutorial
17+
to allow for visualizing. Below is all the code for the tree model excluding the `feed!`
18+
methods
19+
20+
````julia
21+
using VirtualPlantLab
22+
using ColorTypes
23+
import GLMakie
24+
25+
# Data types
26+
module TreeTypes
27+
import VirtualPlantLab
28+
# Meristem
29+
struct Meristem <: VirtualPlantLab.Node end
30+
# Bud
31+
struct Bud <: VirtualPlantLab.Node end
32+
# Node
33+
struct Node <: VirtualPlantLab.Node end
34+
# BudNode
35+
struct BudNode <: VirtualPlantLab.Node end
36+
# Internode (needs to be mutable to allow for changes over time)
37+
Base.@kwdef mutable struct Internode <: VirtualPlantLab.Node
38+
length::Float64 = 0.10 ## Internodes start at 10 cm
39+
end
40+
# Leaf
41+
Base.@kwdef struct Leaf <: VirtualPlantLab.Node
42+
length::Float64 = 0.30 ## Leaves are 20 cm long
43+
width::Float64 = 0.2 ## Leaves are 10 cm wide
44+
end
45+
# Graph-level variables
46+
Base.@kwdef struct treeparams
47+
growth::Float64 = 0.1
48+
budbreak::Float64 = 0.25
49+
phyllotaxis::Float64 = 140.0
50+
leaf_angle::Float64 = 30.0
51+
branch_angle::Float64 = 45.0
52+
end
53+
end
54+
55+
# Rules
56+
meristem_rule = Rule(TreeTypes.Meristem, rhs = mer -> TreeTypes.Node() +
57+
(TreeTypes.Bud(), TreeTypes.Leaf()) +
58+
TreeTypes.Internode() + TreeTypes.Meristem())
59+
60+
function prob_break(bud)
61+
# We move to parent node in the branch where the bud was created
62+
node = parent(bud)
63+
# We count the number of internodes between node and the first Meristem
64+
# moving down the graph
65+
check, steps = has_descendant(node, condition = n -> data(n) isa TreeTypes.Meristem)
66+
steps = Int(ceil(steps/2)) ## Because it will count both the nodes and the internodes
67+
# Compute probability of bud break and determine whether it happens
68+
if check
69+
prob = min(1.0, steps*graph_data(bud).budbreak)
70+
return rand() < prob
71+
# If there is no meristem, an error happened since the model does not allow
72+
# for this
73+
else
74+
error("No meristem found in branch")
75+
end
76+
end
77+
branch_rule = Rule(TreeTypes.Bud,
78+
lhs = prob_break,
79+
rhs = bud -> TreeTypes.BudNode() + TreeTypes.Internode() + TreeTypes.Meristem())
80+
81+
# Graph
82+
axiom = TreeTypes.Internode() + TreeTypes.Meristem()
83+
tree = Graph(axiom = axiom, rules = (meristem_rule, branch_rule), data = TreeTypes.treeparams())
84+
85+
# Growth functions
86+
getInternode = Query(TreeTypes.Internode)
87+
function elongate!(tree, query)
88+
for x in apply(tree, query)
89+
x.length = x.length*(1.0 + data(tree).growth)
90+
end
91+
end
92+
function growth!(tree, query)
93+
elongate!(tree, query)
94+
rewrite!(tree)
95+
end
96+
97+
# Simulation
98+
function simulate(tree, query, nsteps)
99+
new_tree = deepcopy(tree)
100+
for i in 1:nsteps
101+
growth!(new_tree, query)
102+
end
103+
return new_tree
104+
end
105+
````
106+
107+
There are three types of nodes that require geometry: `Leaf`, `Internode`, and `BudNode`,
108+
though the latter only adds the insertion angle for the branches. In the example below we
109+
will use the message to select whether to visualize the leaves or not. This would be useful
110+
if we want to visualize the branching structure of a tree with a dense canopy (to make it
111+
more meaningful we make the leaves bigger than in the original example). Note how, even
112+
when we don't generate internodes we still need to modify the state of the turtle to ensure
113+
the correct positioning of the leaves.
114+
115+
````julia
116+
# Insertion angle for the bud nodes
117+
function VirtualPlantLab.feed!(turtle::Turtle, b::TreeTypes.BudNode, vars)
118+
# Rotate turtle around the arm for insertion angle
119+
ra!(turtle, -vars.branch_angle)
120+
end
121+
122+
# Create geometry + color for the internodes
123+
function VirtualPlantLab.feed!(turtle::Turtle, i::TreeTypes.Internode, vars)
124+
# Rotate turtle around the head to implement elliptical phyllotaxis
125+
rh!(turtle, vars.phyllotaxis)
126+
# Generate the internode or move the turtle forward
127+
if turtle.message == "leaves"
128+
f!(turtle, i.length)
129+
else
130+
HollowCylinder!(turtle, length = i.length, height = i.length/15, width = i.length/15,
131+
move = true, color = RGB(0.5,0.4,0.0))
132+
end
133+
return nothing
134+
end
135+
136+
# Create geometry + color for the leaves
137+
function VirtualPlantLab.feed!(turtle::Turtle, l::TreeTypes.Leaf, vars)
138+
# Bypass geometry if only internodes are being rendered
139+
turtle.message == "internodes" && return nothing
140+
# Rotate turtle around the arm for insertion angle
141+
ra!(turtle, -vars.leaf_angle)
142+
# Generate the leaf
143+
Ellipse!(turtle, length = l.length, width = l.width, move = false,
144+
color = RGB(0.2,0.6,0.2))
145+
# Rotate turtle back to original direction
146+
ra!(turtle, vars.leaf_angle)
147+
return nothing
148+
end
149+
````
150+
151+
We can now generate a simulation:
152+
153+
````julia
154+
newtree = simulate(tree, getInternode, 15)
155+
````
156+
157+
For a visualization of both leaves and internodes we can actually leave the message empty
158+
given how the code above is structured:
159+
160+
````julia
161+
scene = Scene(newtree);
162+
render(scene, axes = false)
163+
````
164+
165+
For only leaves:
166+
167+
````julia
168+
scene = Scene(newtree, message = "leaves");
169+
render(scene, axes = false)
170+
````
171+
172+
And for only internodes:
173+
174+
````julia
175+
scene = Scene(newtree, message = "internodes");
176+
render(scene, axes = false)
177+
````

0 commit comments

Comments
 (0)