|
| 1 | +# Slicing a mesh by planes |
| 2 | + |
| 3 | +Alejandro Morales |
| 4 | + |
| 5 | +Centre for Crop Systems Analysis - Wageningen University |
| 6 | + |
| 7 | +In this example we will learn how to use the slicer functionality to modify existing meshes |
| 8 | +according to specific slicing planes. The idea of the slicer is to cut a mesh by one or |
| 9 | +more planes, generating a new version of the mesh such that none the triangles intersect |
| 10 | +with the planes. For example, if we use multiple horizontal planes (to define layers in the |
| 11 | +soil or aboveground) this wil result in any triangle in the new mesh belonging to only one |
| 12 | +layer. The slicer can be used when modeling plant-environment interactions, since the physics |
| 13 | +of the environment will often be simulated in layers or voxels, though the user is free to |
| 14 | +apply in other contexts. |
| 15 | + |
| 16 | +To introduce the use of the slicer, we will first create a simple mesh and then slice it |
| 17 | +using a single plane. A more detailed example will be provided in the next section. |
| 18 | + |
| 19 | +## Slicing a rectangle with a single plane |
| 20 | + |
| 21 | +Let's start by creating a simple rectangle. |
| 22 | + |
| 23 | +```julia |
| 24 | +using VirtualPlantLab |
| 25 | +r = Rectangle(length = 1.0, width = 1.0) |
| 26 | +``` |
| 27 | + |
| 28 | +We can check that the rectangle is implemented as two triangles: |
| 29 | + |
| 30 | +```julia |
| 31 | +ntriangles(r) |
| 32 | +``` |
| 33 | + |
| 34 | +Now we will slice it by a horizontal plane at 0.5 units above the origin. We will use the |
| 35 | +function `slice!` and specify the plane as `Z = 0.5`: |
| 36 | + |
| 37 | +```julia |
| 38 | +slice!(r, Z = 0.5) |
| 39 | +``` |
| 40 | + |
| 41 | +We can check that the rectangle has been sliced into six triangles (each of the original |
| 42 | +triangles has been split into three): |
| 43 | + |
| 44 | +```julia |
| 45 | +ntriangles(r) |
| 46 | +``` |
| 47 | + |
| 48 | +The slicer adds a property `slices` to the mesh, which indicates to which layer or voxel |
| 49 | +each triangle belongs to: |
| 50 | + |
| 51 | +```julia |
| 52 | +properties(r)[:slices] |
| 53 | +``` |
| 54 | + |
| 55 | +As you can see, each triangle has been assigned three numbers, corresponding to the their |
| 56 | +position relative to slicing planes perpendicular to the X, Y and Z axis respectively. A |
| 57 | +`0` implies no actual slicing in a particular dimension (in this case both `X` and `Y`). |
| 58 | +For `Z` axis, we have a `1` for the triangles below the slicing plane (first layer) and a |
| 59 | +`2` for the triangles above the plane (second layer). |
| 60 | + |
| 61 | +To visualize the results, let's color each triangle according to the layer it belongs to: |
| 62 | + |
| 63 | +```julia |
| 64 | +import ColorTypes: RGB |
| 65 | +colors = [RGB(1, 0, 0), RGB(0, 1, 0)] |
| 66 | +all_colors = RGB{Float64}[] |
| 67 | +for slice in properties(r)[:slices] |
| 68 | + push!(all_colors, colors[slice[3]]) |
| 69 | +end |
| 70 | +add_property!(r, :colors, all_colors) |
| 71 | +``` |
| 72 | + |
| 73 | +And now we can render the results: |
| 74 | + |
| 75 | +```julia |
| 76 | +import GLMakie |
| 77 | +render(r) |
| 78 | +``` |
| 79 | + |
| 80 | +## Slicing a rectangle with multiple planes |
| 81 | + |
| 82 | +Let's repeat the previous example but now we slice the rectangly with multiple planes along |
| 83 | +the `Y` and `Z` axes. Note how we can pass an array of values to the `slice!` function: |
| 84 | + |
| 85 | +```julia |
| 86 | +r = Rectangle(length = 1.0, width = 1.0) |
| 87 | +Ycuts = collect(-0.25:0.25:0.5) |
| 88 | +ny = length(Ycuts) |
| 89 | +Zcuts = collect(0.25:0.25:1) |
| 90 | +nz = length(Zcuts) |
| 91 | +slice!(r, Y = Ycuts, Z = Zcuts); |
| 92 | +``` |
| 93 | + |
| 94 | +We now have a large number of triangles: |
| 95 | + |
| 96 | +```julia |
| 97 | +ntriangles(r) |
| 98 | +``` |
| 99 | + |
| 100 | +We can visualize the results as before, assigning a random color to each pixel that results |
| 101 | +from the intersection of the slicing planes. Notice that now we have to use the second and |
| 102 | +third elements of the `slice` array to identify the exact location of the triangle in the |
| 103 | +grid of pixels: |
| 104 | + |
| 105 | + |
| 106 | +```julia |
| 107 | +colors = rand(RGB, ny, nz) |
| 108 | +all_colors = RGB{Float64}[] |
| 109 | +for slice in properties(r)[:slices] |
| 110 | + push!(all_colors, colors[slice[2], slice[3]]) |
| 111 | +end |
| 112 | +add_property!(r, :colors, all_colors) |
| 113 | +render(r) |
| 114 | +``` |
| 115 | + |
| 116 | +## Slicing geometry inside the `feed!` method |
| 117 | + |
| 118 | +Using the slicer inside a `feed!` method is straightforward, though it requires some |
| 119 | +adjustments. Firstly, we do not know a priori the number of triangles that will result from |
| 120 | +the slicing, so we need to adjust the way we store colors or optical properties. Secondly, |
| 121 | +we cannot add a geometric primitive directly to the turtle, as that will prevents use from |
| 122 | +applying the slicer to it. Instead, we need to create a local version of the primitive, |
| 123 | +slice it, add any properties we want to store (adjusting to the actual number of triangles) |
| 124 | +and then feed it to the turtle using the more generic `Mesh!()`. |
| 125 | + |
| 126 | +Let's illustrate this with an example reusing the rectangle we created before. This time |
| 127 | +the rectangle represents the geometry of a node in a graph (e.g. a leaf of a plant or a soil |
| 128 | +tile). |
| 129 | + |
| 130 | +As usual, we create a module to contain the data structure and associated methods: |
| 131 | + |
| 132 | +```julia |
| 133 | +module MyRectangle |
| 134 | + using VirtualPlantLab |
| 135 | + import ColorTypes: RGB |
| 136 | + |
| 137 | + # Object that will be rendered as rectangle |
| 138 | + struct Tile <: Node |
| 139 | + length::Float64 |
| 140 | + width::Float64 |
| 141 | + colors::Matrix{RGB{Float64}} |
| 142 | + end |
| 143 | + |
| 144 | + function VirtualPlantLab.feed!(turtle::Turtle, t::Tile, data) |
| 145 | + # Create the rectangle |
| 146 | + r = Rectangle(turtle, length = t.length, width = t.width) |
| 147 | + # Slice it (YCUTS and XCUTS are global variables, could be in data too) |
| 148 | + slice!(r, Y = YCUTS, Z = ZCUTS) |
| 149 | + # Add the colors (the colors per pixel are stored in the node) |
| 150 | + all_colors = RGB{Float64}[] |
| 151 | + for slice in properties(r)[:slices] |
| 152 | + push!(all_colors, t.colors[slice[2], slice[3]]) |
| 153 | + end |
| 154 | + # Add the mesh to the turtle while defining the colors as properties |
| 155 | + Mesh!(turtle, r, colors = all_colors) |
| 156 | + return nothing |
| 157 | + end |
| 158 | + |
| 159 | + # Define the cuts and functions to modify them |
| 160 | + YCUTS::Vector{Float64} = collect(-0.25:0.25:0.5) |
| 161 | + set_ycuts!(cuts) = (global YCUTS = cuts) |
| 162 | + ZCUTS::Vector{Float64} = collect(0.25:0.25:1) |
| 163 | + set_zcuts!(cuts) = (global ZCUTS = cuts) |
| 164 | +end |
| 165 | +``` |
| 166 | + |
| 167 | +Let's now create a simple static graph and make sure that we can generate the rectangular |
| 168 | +tile: |
| 169 | + |
| 170 | +```julia |
| 171 | +import .MyRectangle as MR |
| 172 | +nz = length(MR.ZCUTS) |
| 173 | +ny = length(MR.YCUTS) |
| 174 | +graph = Graph(axiom = MR.Tile(1.0, 1.0, rand(RGB, ny, nz))) |
| 175 | +render(Mesh(graph)) |
| 176 | +``` |
0 commit comments