02_user_tutorials:04_light_modelling:sensor-grids
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revision | |||
| 02_user_tutorials:04_light_modelling:sensor-grids [2025/01/24 15:51] – removed - external edit (Unknown date) 127.0.0.1 | 02_user_tutorials:04_light_modelling:sensor-grids [2025/01/24 15:51] (current) – ↷ Page moved from 02_user_tutorials:light_modelling:sensor-grids to 02_user_tutorials:04_light_modelling:sensor-grids tim2 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== Sensor Grids and Areal Sensors ====== | ||
| + | |||
| + | Light distributions, | ||
| + | |||
| + | ===== Sensor Grids ===== | ||
| + | |||
| + | Here, a sensor grid is a 3D arrangement of // | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | In order to analyse the sensed radiation, e.g., using external tools, it is most probably required to record the location (Cartesian/ | ||
| + | |||
| + | To record the position of each sensor node it is needed to have some user defined module that provides variables to store the position, e.g., three simple floats, one for each axis. In XL this can be done in the following way that defines a new module // | ||
| + | |||
| + | <code java> | ||
| + | module SensorElement extends SensorNode() { | ||
| + | float x,y,z; | ||
| + | |||
| + | public SensorElement(float xi, float yi, float zi) { | ||
| + | x = xi; | ||
| + | y = yi; | ||
| + | z = zi; | ||
| + | setRadius(0.1); | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Using the defined constructor, | ||
| + | |||
| + | <code java> | ||
| + | public void init() [ | ||
| + | Axiom ==> SensorElement(1, | ||
| + | ] | ||
| + | </ | ||
| + | |||
| + | |||
| + | Now, to construct a sensor grid, we basically just need three nested loops arranging our sensors in a three-dimensional grid. The code to do so could look like this: | ||
| + | |||
| + | <code java> | ||
| + | { | ||
| + | float xi, yi, zi; | ||
| + | } | ||
| + | for (int x=0;x<6; x++) ( | ||
| + | for (int y=0;y<6; y++) ( | ||
| + | for (int z=0;z<3; z++) ( | ||
| + | [ | ||
| + | { | ||
| + | xi =-2+0.75*x; | ||
| + | yi =-2+0.75*y; | ||
| + | zi =0.5*z; | ||
| + | } | ||
| + | Null(xi, yi, zi) SensorElement(xi, | ||
| + | ] | ||
| + | ) | ||
| + | ) | ||
| + | ) | ||
| + | </ | ||
| + | |||
| + | When integrated into a small canopy model of binary trees, the results may look like this (side and top view; Note: the Light source is not totally facing to the middle of the scene, but rather slightly sidewards to get some more interesting distribution pattern): | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | Note: The above code works fine as long as the number of sensor nodes is moderate, e.g., below 100. The drawback of the above code it that it connects all sensor nodes to one parent, what, unfortunately, | ||
| + | |||
| + | The 'large number of sensor node save code' would look like given below: | ||
| + | <code java> | ||
| + | { | ||
| + | float xi, yi, zi; | ||
| + | } | ||
| + | for (int x=0;x<6; x++) ( | ||
| + | [Node | ||
| + | for (int y=0;y<6; y++) ( | ||
| + | [Node | ||
| + | for (int z=0;z<3; z++) ( | ||
| + | [ | ||
| + | { | ||
| + | xi =-2+0.75*x; | ||
| + | yi =-2+0.75*y; | ||
| + | zi =0.5*z; | ||
| + | } | ||
| + | Null(xi, yi, zi) SensorElement(xi, | ||
| + | ] | ||
| + | ]) | ||
| + | ]) | ||
| + | ) | ||
| + | </ | ||
| + | |||
| + | |||
| + | The last thing to do in order to make really use of sensor grid is to obtain the sensed radiation for the sensor nodes and also may want to export them for further/ | ||
| + | |||
| + | <code java> | ||
| + | public void simulateLight() { | ||
| + | // perform light calculation | ||
| + | LightModel lm = new LightModel(5000000, | ||
| + | lm.compute(); | ||
| + | | ||
| + | //buffer to store the data | ||
| + | StringBuffer sensorData = new StringBuffer(); | ||
| + | |||
| + | // see how much was sensed by the sensor | ||
| + | [ | ||
| + | x: | ||
| + | sensorData.append(x.x+", | ||
| + | } | ||
| + | ] | ||
| + | | ||
| + | //write the data to a file | ||
| + | BufferedWriter bwr = new BufferedWriter(new FileWriter(new File(" | ||
| + | bwr.write(sensorData.toString()); | ||
| + | bwr.flush(); | ||
| + | bwr.close(); | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | The complete code is given below: | ||
| + | |||
| + | <code java> | ||
| + | //to writing data to a file | ||
| + | import java.io.BufferedWriter; | ||
| + | import java.io.File; | ||
| + | import java.io.FileWriter; | ||
| + | import java.io.IOException; | ||
| + | |||
| + | const String PATH = "/ | ||
| + | |||
| + | module SensorElement extends SensorNode() { | ||
| + | float x,y,z; | ||
| + | | ||
| + | public SensorElement(float xi, float yi, float zi) { | ||
| + | x = xi; | ||
| + | y = yi; | ||
| + | z = zi; | ||
| + | setRadius(0.1); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | |||
| + | module A(float len) extends Sphere(0.1).(setShader(GREEN)); | ||
| + | |||
| + | protected void init() [ | ||
| + | {setSeed(12345); | ||
| + | Axiom ==> | ||
| + | |||
| + | //create the sensor grid | ||
| + | { | ||
| + | float xi, yi, zi; | ||
| + | } | ||
| + | for (int x=0;x<6; x++) ( | ||
| + | for (int y=0;y<6; y++) ( | ||
| + | for (int z=0;z<3; z++) ( | ||
| + | [ | ||
| + | { | ||
| + | xi =-2+0.75*x; | ||
| + | yi =-2+0.75*y; | ||
| + | zi =0.5*z; | ||
| + | } | ||
| + | Null(xi, yi, zi) SensorElement(xi, | ||
| + | ] | ||
| + | ) | ||
| + | ) | ||
| + | ) | ||
| + | | ||
| + | //add a lamp | ||
| + | [ | ||
| + | Null(0,0,5) RU(170) | ||
| + | LightNode.(setLight(new SpotLight().( | ||
| + | setPower(100), | ||
| + | setRaylength(1.5) | ||
| + | ))) | ||
| + | ] | ||
| + | | ||
| + | //arrange some plants | ||
| + | for (int i=0; | ||
| + | [ | ||
| + | Null(random(-2, | ||
| + | RH(random(0, | ||
| + | Scale(0.5) A(1) | ||
| + | ] | ||
| + | ); | ||
| + | { | ||
| + | derive(); | ||
| + | for(apply(5)) run(); | ||
| + | } | ||
| + | ] | ||
| + | |||
| + | private void run () [ | ||
| + | A(x) ==> F(x) [RU(30) RH(90) A(x*0.8)] [RU(-30) RH(90) A(x*0.8)]; | ||
| + | ] | ||
| + | |||
| + | public void simulateLight() { | ||
| + | // perform light calculation | ||
| + | LightModel lm = new LightModel(5000000, | ||
| + | lm.compute(); | ||
| + | | ||
| + | //buffer to store the data | ||
| + | StringBuffer sensorData = new StringBuffer(); | ||
| + | |||
| + | // see how much was sensed by the sensors | ||
| + | [ | ||
| + | x: | ||
| + | sensorData.append(x.x+", | ||
| + | } | ||
| + | ] | ||
| + | | ||
| + | //write the data to a file | ||
| + | BufferedWriter bwr = new BufferedWriter(new FileWriter(new File(String.format(" | ||
| + | bwr.write(sensorData.toString()); | ||
| + | bwr.flush(); | ||
| + | bwr.close(); | ||
| + | | ||
| + | //save a snapshot | ||
| + | makeSnapshot(String.format(" | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | The output of the model will be a database (CSV-file), containing the x, y, and z location and the sensed radiation for each of them. | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | And when loaded and 3d interpolated using external tools, we nicely can visualize the the 3d light distribution within the canopy. | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | |||
| + | ===== Areal Sensors ===== | ||
| + | |||
| + | In a very similar way, areal sensors can be defined. Instead of // | ||
| + | |||
| + | In the following, a // | ||
| + | |||
| + | <code java> | ||
| + | module SensorElement(float x, float y) extends Box(0.001, | ||
| + | </ | ||
| + | |||
| + | The sensor elements are arranged in 2d to a closed surface using two nested loops. To make things more general, the dimension (x and y) of the grid as well as the resolution of the singles sensor elements can be defined. | ||
| + | |||
| + | |||
| + | <code java> | ||
| + | //dimension of wall elements | ||
| + | const float WIDTH_X = 1.0; | ||
| + | const float WIDTH_Y = 1.0; | ||
| + | |||
| + | //dimension of sensor elements | ||
| + | const float dX = 0.05; | ||
| + | const float dY = 0.05; | ||
| + | //number sensor elements | ||
| + | const int nX_SE = (int)Math.round(WIDTH_X/ | ||
| + | const int nY_SE = (int)Math.round(WIDTH_Y/ | ||
| + | |||
| + | |||
| + | protected void init()[ | ||
| + | Axiom ==> | ||
| + | //area sensor on the ground | ||
| + | { | ||
| + | float x,y; | ||
| + | } | ||
| + | [ | ||
| + | for(int i: | ||
| + | [ | ||
| + | Null(0,0,0) //dummy node to prevent having too many nodes on one parent node | ||
| + | { | ||
| + | y = 0.5*WIDTH_Y-i*dY; | ||
| + | } | ||
| + | for(int p: | ||
| + | [ | ||
| + | { | ||
| + | x = 0.5*WIDTH_X -p*dX; | ||
| + | } | ||
| + | Null(x, y, 0) //move to position | ||
| + | SensorElement(x, | ||
| + | ] | ||
| + | ) | ||
| + | ] | ||
| + | ) | ||
| + | ]; | ||
| + | ] | ||
| + | </ | ||
| + | |||
| + | Depending on the resolution, the numberer of single sensor elements that build the areal sensor, the resulting absorption pattern will change. Obviously, with decreasing element size, the resolution increases and the absorbed pattern will become sharper. The image below uses from left to right sensor element dimension of 0.2x0.2, 0.1x0.1, 0.05x0.05, and 0.005x0.005 meter. Taking the one by one meter areal sensor size, this leads to 36 (1m/0.2m=5; (5+1)*(5+1)=25) up to 40401 (1m/ | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | Queering the // | ||
| + | |||
| + | Additionally, | ||
| + | |||
| + | <code java> | ||
| + | //the colour map for the nice colour gradient | ||
| + | const ColorGradient colorMap = new ColorGradient(" | ||
| + | |||
| + | public void simulateLight() { | ||
| + | ... | ||
| + | |||
| + | // see how much was sensed by the sensors | ||
| + | float sumAbsorbedPower = 0; | ||
| + | float min = 10000; | ||
| + | float max = -10000; | ||
| + | [ | ||
| + | x: | ||
| + | float absPower = lm.getAbsorbedPower(x).integrate(); | ||
| + | if (absPower< | ||
| + | if (absPower> | ||
| + | sumAbsorbedPower += absPower; | ||
| + | x.setShader(new RGBAShader(colorMap.getColor(absPower))); | ||
| + | } | ||
| + | ] | ||
| + | println(" | ||
| + | ] | ||
| + | </ | ||
| + | |||
| + | Further, a ColorBar is inserted to provide some visual reference. It used the defined colour map as input and visualizes it. | ||
| + | |||
| + | <code java> | ||
| + | protected void init() [ | ||
| + | ... | ||
| + | |||
| + | Axiom ==> | ||
| + | ... | ||
| + | // the colour bar as reference | ||
| + | [ | ||
| + | Null(-0.75, | ||
| + | ] | ||
| + | ... | ||
| + | ; | ||
| + | ] | ||
| + | </ | ||
| + | |||
| + | |||
| + | The full code is given below: | ||
| + | |||
| + | <code java> | ||
| + | import java.awt.Color; | ||
| + | |||
| + | //for writing data to a file | ||
| + | import java.io.BufferedWriter; | ||
| + | import java.io.File; | ||
| + | import java.io.FileWriter; | ||
| + | import java.io.IOException; | ||
| + | |||
| + | |||
| + | //dimension of wall elements | ||
| + | const float WIDTH_X = 1.0; | ||
| + | const float WIDTH_Y = 1.0; | ||
| + | |||
| + | //dimension of sensor elements | ||
| + | const float dX = 0.05; | ||
| + | const float dY = 0.05; | ||
| + | //number sensor elements | ||
| + | const int nX_SE = (int)Math.round(WIDTH_X/ | ||
| + | const int nY_SE = (int)Math.round(WIDTH_Y/ | ||
| + | |||
| + | const String PATH = "/ | ||
| + | |||
| + | //the colour map for the nice colour gradient | ||
| + | const ColorGradient colorMap = new ColorGradient(" | ||
| + | |||
| + | //a single sensor element | ||
| + | module SensorElement(float x, float y) extends Box(0.001, | ||
| + | |||
| + | |||
| + | //////////////////////////////////////////////////////////////////////////////// | ||
| + | module A(float len) extends Sphere(0.025).(setShader(GREEN)); | ||
| + | |||
| + | protected void init() [ | ||
| + | { | ||
| + | clearConsole(); | ||
| + | println(" | ||
| + | } | ||
| + | | ||
| + | Axiom ==> | ||
| + | //area sensor on the ground | ||
| + | { | ||
| + | float x,y; | ||
| + | } | ||
| + | [ | ||
| + | for(int i: | ||
| + | [ | ||
| + | Null(0,0,0) | ||
| + | { | ||
| + | y = 0.5*WIDTH_Y-i*dY; | ||
| + | } | ||
| + | for(int p: | ||
| + | [ | ||
| + | { | ||
| + | x = 0.5*WIDTH_X -p*dX; | ||
| + | } | ||
| + | Null(x, y, 0) //move to position | ||
| + | SensorElement(x, | ||
| + | ] | ||
| + | ) | ||
| + | ] | ||
| + | ) | ||
| + | ] | ||
| + | | ||
| + | //just a plane on the ground | ||
| + | [ | ||
| + | M(-0.01) Box(0.001, | ||
| + | ] | ||
| + | | ||
| + | // the colour bar as reference | ||
| + | [ | ||
| + | Null(-0.75, | ||
| + | ] | ||
| + | | ||
| + | //add a lamp | ||
| + | [ | ||
| + | Null(-0.5, | ||
| + | LightNode.(setLight(new DirectionalLight().( | ||
| + | setPowerDensity(100), | ||
| + | setRaylength(1.5) | ||
| + | ))) | ||
| + | ] | ||
| + | | ||
| + | // add the plant ' | ||
| + | A(0.25); | ||
| + | | ||
| + | { | ||
| + | derive(); | ||
| + | for(apply(5)) run(); | ||
| + | } | ||
| + | ] | ||
| + | |||
| + | private void run () [ | ||
| + | A(x) ==> F(x,0.02) [RU(30) RH(90) A(x*0.8)] [RU(-30) RH(90) A(x*0.8)]; | ||
| + | ] | ||
| + | //////////////////////////////////////////////////////////////////////////////// | ||
| + | |||
| + | |||
| + | public void simulateLight() { | ||
| + | // perform light calculation | ||
| + | LightModel lm = new LightModel(5000000, | ||
| + | lm.compute(); | ||
| + | |||
| + | //buffer to store the data | ||
| + | StringBuffer sensorData = new StringBuffer(); | ||
| + | | ||
| + | // see how much was sensed by the sensors | ||
| + | float sumAbsorbedPower = 0; | ||
| + | float min = 10000; | ||
| + | float max = -10000; | ||
| + | [ | ||
| + | x: | ||
| + | float absPower = lm.getAbsorbedPower(x).integrate(); | ||
| + | if (absPower< | ||
| + | if (absPower> | ||
| + | sumAbsorbedPower += absPower; | ||
| + | x.setShader(new RGBAShader(colorMap.getColor(absPower))); | ||
| + | sensorData.append(x.x+", | ||
| + | } | ||
| + | ] | ||
| + | println(" | ||
| + | repaintView3D(); | ||
| + | | ||
| + | //write the data to a file | ||
| + | BufferedWriter bwr = new BufferedWriter(new FileWriter(new File(String.format(" | ||
| + | bwr.write(sensorData.toString()); | ||
| + | bwr.flush(); | ||
| + | bwr.close(); | ||
| + | | ||
| + | //save a snapshot | ||
| + | makeSnapshot(String.format(" | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | Using the above code and adding two more areal sensors along the XZ- and YZ-plane, we can get visualizations as the following of an adult tomato plant: | ||
| + | |||
| + | {{ : | ||
| + | |||
