|
| 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