“Also, I need to figure out how to sort through the 360º search results. I would like to wind up with an array of steervals sorted in decreasing magnitude order, with a companion array of headings so Hdg[i] <–> Steerval[i]. Not sure how to do this yet, but it sounds promising!“
After some research on array sorts in C++ I came across this post with a nice example of a quick sort program, which I shamelessly copied. After some fumbling around (including writing my own ‘swap’ routine to allow future porting to Arduino code) I got it to work in my Visual Studio 2022 Community Edition setup with a single int[] array as shown in the following image:
Now the challenge was to extend this algorithm to sort multiple same-sized companion arrays. Looking at the QuickSort code, it appeared all I had to do was duplicate the ‘swap’ operation on all the companion arrays using the same swap indices determined for the ‘master’ array. One additional fly in the ointment was the requirement to handle both int[] and float[] arrays.
First I modified my ‘swap’ routine to be a generic template function as shown below:
1
2
3
4
5
6
7
template<classT>
voidmySwap(T&rightval,T&leftval)
{
Ttempright=rightval;
rightval=leftval;
leftval=tempright;
}
Then I renamed the ‘arr’ array from the example to ‘FrontD’ and defined a second ‘Hdg[]’ array of float values with the same length as the original example array as shown below:
1
2
intFrontD[]={9,3,4,2,1,8};
floatHdg[]={19,13,14,12,11,18};
Then, for each occurrence of a call to ‘mySwap’ for the ‘FrontD’ array, I added a second call for the ‘Hdg’ array as shown below:
1
2
mySwap(FrontD[pivotIndex],FrontD[start]);
mySwap(Hdg[pivotIndex],Hdg[start]);
When I ran this code, it *almost* worked right off the bat. Unfortunately the ‘Hdg’ slave array wound up being sorted slightly differently than the ‘FrontD’ master array. After closely examining the code, I finally found the problem. In one place the original programmer used the indexing syntax ‘[i++]’ and ‘[i–]’ as input to the ‘mySwap’ function. This worked fine for the single master array, but failed with the second array because on the second call to ‘mySwap’ the indices had been changed – oops! Here is the original and revised syntax:
1
2
3
4
5
6
7
8
9
//10/06/23 can't use '++' or '--' here because then second mySwap has wrong indices
//mySwap(FrontD[i++], FrontD[j--]);
//mySwap(Hdg[i++], Hdg[j--]);
mySwap(FrontD[i],FrontD[j]);
mySwap(Hdg[i],Hdg[j]);
i++;j--;
Now both calls to mySwap() use the same index values, and life is good. Here’s a debug printout from VS2022 showing a successful program run with a master (int Frontd[]) array and one slave (float Hdg[]) array:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
.
FrontD contents before sort
934218
Hdg contents before sort
191314121118
Inpartition just before swap:pivotIndex=5-->>8
FrontD contents before swap(8,9)
934218
FrontD contents after swap(9,8)
834219
Hdg contents after swap(19,18)
181314121119
Inpartition just before swap:pivotIndex=4-->>1
FrontD contents before swap(1,8)
834219
FrontD contents after swap(8,1)
134289
Hdg contents after swap(18,11)
111314121819
Inpartition just before swap:pivotIndex=0-->>1
FrontD contents before swap(1,1)
134289
FrontD contents after swap(1,1)
134289
Hdg contents after swap(11,11)
111314121819
Inpartition just before swap:pivotIndex=2-->>4
FrontD contents before swap(4,3)
134289
FrontD contents after swap(3,4)
143289
Hdg contents after swap(13,14)
111413121819
Inpartition just before swap(3,3)
FrontD contents before swap(3,3)
143289
Hdg contents before swap(13,13)
111413121819
Inpartition just after swap(3,3)
FrontD contents after swap(3,3)
123489
Hdg contents after swap(13,13)
111213141819
FrontD contents after sort
123489
Hdg contents after sort
111213141819
C:\Users\Frank\Documents\Visual Studio2022\Projects\QuickSortV3\x64\Debug\QuickSortV3.exe(process25900)exited with code0.
Toautomatically close the console when debugging stops,enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key toclose thiswindow...
And here is the complete code that produced the above output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// QuickSortV3.cpp : This file contains the 'main' function. Program execution begins and ends there.
cout<<"In partition just before swap: pivotIndex = "<<pivotIndex<<"-->> "<<FrontD[pivotIndex]<<"\n";
cout<<"FrontD contents before swap("<<FrontD[pivotIndex]<<", "<<FrontD[start]<<")\n";
for(inti=0;i<6;i++)
{
cout<<FrontD[i]<<" ";
}
cout<<"\n";
mySwap(FrontD[pivotIndex],FrontD[start]);
mySwap(Hdg[pivotIndex],Hdg[start]);
cout<<"FrontD contents after swap("<<FrontD[pivotIndex]<<", "<<FrontD[start]<<")\n";
for(inti=0;i<6;i++)
{
cout<<FrontD[i]<<" ";
}
cout<<"\n";
cout<<"Hdg contents after swap("<<Hdg[pivotIndex]<<", "<<Hdg[start]<<")\n";
for(inti=0;i<6;i++)
{
cout<<Hdg[i]<<" ";
}
cout<<"\n";
// Sorting left and right parts of the pivot element
inti=start,j=end;
while(i<pivotIndex&&j>pivotIndex){
while(FrontD[i]<=pivot){
i++;
}
while(FrontD[j]>pivot){
j--;
}
if(i<pivotIndex&&j>pivotIndex)
{
cout<<"In partition just before swap("<<FrontD[i+1]<<", "<<FrontD[j-1]<<")\n";
cout<<"FrontD contents before swap("<<FrontD[i+1]<<", "<<FrontD[j-1]<<")\n";
for(inti=0;i<6;i++)
{
cout<<FrontD[i]<<" ";
}
cout<<"\n";
cout<<"Hdg contents before swap("<<Hdg[i+1]<<", "<<Hdg[j-1]<<")\n";
for(inti=0;i<6;i++)
{
cout<<Hdg[i]<<" ";
}
cout<<"\n";
//10/06/23 can't use '++' or '--' here because then second mySwap has wrong indices
//mySwap(FrontD[i++], FrontD[j--]);
//mySwap(Hdg[i++], Hdg[j--]);
mySwap(FrontD[i],FrontD[j]);
mySwap(Hdg[i],Hdg[j]);
i++;j--;
cout<<"In partition just after swap("<<FrontD[i]<<", "<<FrontD[j]<<")\n";
cout<<"FrontD contents after swap("<<FrontD[i]<<", "<<FrontD[j]<<")\n";
for(inti=0;i<6;i++)
{
cout<<FrontD[i]<<" ";
}
cout<<"\n";
cout<<"Hdg contents after swap("<<Hdg[i]<<", "<<Hdg[j]<<")\n";
for(inti=0;i<6;i++)
{
cout<<Hdg[i]<<" ";
}
cout<<"\n";
}
}
returnpivotIndex;
}
voidquickSort(intFrontD[],intstart,intend)
{
//cout << "In quickSort(" << "start = " << start << ", end = " << end << ")\n";
// base case
if(start>=end)
return;
// partitioning the FrontD array
intp=partition(FrontD,start,end);
// Sorting the left part
quickSort(FrontD,start,p-1);
// Sorting the right part
quickSort(FrontD,p+1,end);
}
intmain()
{
//std::cout << "Hello World!\n";
//int FrontD[] = { 9, 3, 4, 2, 1, 8 };
//float Hdg[] = { 19, 3, 11, 15, 4, 8 };
//int n = 6;
cout<<"FrontD contents before sort\n";
for(inti=0;i<n;i++){
cout<<FrontD[i]<<" ";
}
cout<<"\n";
cout<<"Hdg contents before sort\n";
for(inti=0;i<n;i++){
cout<<Hdg[i]<<" ";
}
cout<<"\n";
quickSort(FrontD,0,n-1);
cout<<"FrontD contents after sort\n";
for(inti=0;i<n;i++){
cout<<FrontD[i]<<" ";
}
cout<<"\n";
cout<<"Hdg contents after sort\n";
for(inti=0;i<n;i++){
cout<<Hdg[i]<<" ";
}
cout<<"\n";
return0;
}
09 October 2023 Update:
After convincing myself that this scheme for synchronizing the sorting of ‘master’ and ‘slave’ arrays, I decided to port the capability into my robot code. I created a new WallE3 program called ‘WallE3_Quicksort_V3’ as a copy of ‘WallE3_Complete_V5’, and then added the ‘quickSort’, ‘partition’, and ‘mySwap’ functions from ‘Quicksort_V3’.
Then I set up a test block in setup() as shown below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#pragma region QUICKSORT_TEST
//initialize Frontd & Hdg arrays
for(size_ti=0;i<QUICKSORT_ARRAY_SIZE;i++)
{
FrontD[i]=QUICKSORT_ARRAY_SIZE-i;//should be 36,35,34....1
In this test, the FrontD[] is the ‘master’ and the ‘Hdg[] is the ‘slave’, and the algorithm is set up to sort the array in increasing order.As can be seen from the above output, the FrontD[] array after Quicksort is indeed sorted from smallest to largest value, and the Hdg[] array elements after Quicksort are ordered in such a way as to correspond to their original relationship to FrontD[].
In my application I want to sort the master array in descending order rather than ascending, so after some googling I found that making the following change:
1
2
//if (FrontD[i] <= pivot)
if(FrontD[i]>pivot)//sort in reverse order
causes the sort to run in the other direction, giving the following output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
FrontD Hdg
136.0
235.0
334.0
433.0
532.0
631.0
730.0
829.0
928.0
1027.0
1126.0
1225.0
1324.0
1423.0
1522.0
1621.0
1720.0
1819.0
1918.0
2017.0
2116.0
2215.0
2314.0
2413.0
2512.0
2611.0
2710.0
289.0
298.0
307.0
316.0
325.0
334.0
343.0
352.0
361.0
After sort,FrontD&Hdg arrays are
FrontD Hdg
361.0
352.0
343.0
334.0
325.0
316.0
307.0
298.0
289.0
2710.0
2611.0
2512.0
2413.0
2314.0
2215.0
2116.0
2017.0
1918.0
1819.0
1720.0
1621.0
1522.0
1423.0
1324.0
1225.0
1126.0
1027.0
928.0
829.0
730.0
631.0
532.0
433.0
334.0
235.0
136.0
As desired, the ‘FrontD’ master array is sorted in descending order, and the ‘Hdg’ slave array elements are still synchronized with their original FrontD companion elements.
So I changed the test to use real data using the initialization code below:
1
2
3
4
5
6
7
8
9
10
11
//10/09/23 now let's try this again with real data
for(size_ti=0;i<QUICKSORT_ARRAY_SIZE;i++)
{
//get current environmental variables
UpdateAllEnvironmentParameters();
Hdg[i]=IMUHdgValDeg;
FrontD[i]=gl_FrontCm;
SpinTurn(false,10);
}
and got the following output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
After init,FrontD&Hdg arrays are
FrontD Hdg
510.1
599.6
7520.0
7330.7
9041.2
18251.5
23962.7
39573.3
29683.9
33393.9
798105.0
180114.6
120124.4
105135.1
106144.6
48154.6
160164.3
86174.0
76-176.4
151-166.7
88-157.1
42-147.5
48-138.1
89-128.6
88-118.0
85-107.3
83-96.8
98-86.3
63-76.7
52-65.6
47-55.8
44-46.0
43-36.1
43-26.6
44-16.9
49-7.3
After sort,FrontD&Hdg arrays are
FrontD Hdg
44-16.9
43-26.6
48154.6
39573.3
42-147.5
44-46.0
47-55.8
43-36.1
29683.9
48-138.1
798105.0
120124.4
180114.6
106144.6
160164.3
33393.9
105135.1
49-7.3
151-166.7
88-157.1
86174.0
88-118.0
23962.7
89-128.6
85-107.3
76-176.4
510.1
63-76.7
98-86.3
7520.0
599.6
83-96.8
18251.5
9041.2
7330.7
52-65.6
Gee, that went well — The FrontD distance array isn’t ordered at all – yuk!
OK, so back to basic troubleshooting. The first thing I did was to replace the FrontD[6] test array in my QuickSortV3 C++ program with the FrontD[36] array of actual front distance values (edited in Notepad++ to be a single line of comma-separated values) to see if I could establish a working baseline – an absolute necessity for efficient troubleshooting.
I had to edit the QuickSortV3 program to remove the references to the second Hdg[](slave) array, as I didn’t want to complicate things, but after I did this, the program sorted FrontD[36] properly in both the forward and reverse direction. To get the reverse sort, I had to flip ‘<=’ to ‘>’ in two places, and ‘>’ to ‘<=’ in one place, as shown below:
The following Excel plot shows the result for both the forward and reverse sorts
Now that I have a working baseline with my QuickSortV3 C++ program, it became easy to see the problem with my WallE3_Quicksort_V3 Arduino program; there are three places that need to be changed for reverse sorts, and I only changed one – ooops! After fixing these problems, I got the following output:
And now, the slave sort seems to be working as well, as shown by the following Excel plot:
The above plot looks very confusing at first, but it seems to be accurate; the ‘before’ picture is straightforward – the robot rotated in 10º steps, so the smooth blue line is expected. The ‘after’ plot looks crazy, but remember it is synchronized with the reverse sorted front distance array, so there is no organizing principal. The relationship of heading values with front distance values after the reverse sort is easier to see with the text output from the program, shown below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
After init,FrontD&Hdg arrays are
FrontD Hdg
720.1
979.9
20019.5
22629.8
27739.6
32350.2
41060.7
44471.4
50782.1
45592.1
206102.4
53112.6
59121.9
97131.4
181141.9
164152.3
133163.0
88173.2
137-176.2
129-165.7
61-156.5
63-147.1
49-137.4
41-126.6
30-117.0
24-106.1
33-95.2
32-84.6
36-74.3
38-63.9
43-53.8
54-44.5
73-35.0
80-25.6
75-15.6
73-6.1
After sort,FrontD&Hdg arrays are
FrontD Hdg
50782.1
45592.1
44471.4
41060.7
32350.2
27739.6
22629.8
206102.4
20019.5
181141.9
164152.3
137-176.2
133163.0
129-165.7
979.9
97131.4
88173.2
80-25.6
75-15.6
73-6.1
73-35.0
720.1
63-147.1
61-156.5
59121.9
54-44.5
53112.6
49-137.4
43-53.8
41-126.6
38-63.9
36-74.3
33-95.2
32-84.6
30-117.0
24-106.1
For instance, the largest front distance shown is 507cm (row 9 in the original listing). In the unsorted data, the heading associated with 507cm is 82.1º. In the reverse sorted listing, 507cm is of course on the first row, and so is 82.1º. The lowest front distance (last row of the reverse sorted list) is 24cm, and the heading on the same line is -106.1º. Looking through the original (unsorted) list, 24cm is found on line 26, and the heading on that line is -106.1º as expected.
At this point, it is clear that my plan to have WallE3 turn a full circle while recording front distances and associated headings, then reverse sort the distance data array as the ‘master’ array while maintaining each distance value’s associated heading (the ‘slave’ array) is going to work. Then I should be able to easily find the heading associated with the median front distance of the first(only) group of front distances greater than some threshold – say 1.5m. Looking at the reverse-sorted front distance data above, I see there are about 10 distance measurements above 1.5m as shown below:
1
2
3
4
5
6
7
8
9
10
11
12
FrontD Hdg
50782.1
45592.1
44471.4
41060.7
32350.2
27739.6
22629.8
206102.4
20019.5
181141.9
164152.3
The median heading value for this group of 11 distances is 71.4º, which is associated with the front distance value of 444cm.
11 October 2023 Update:
After figuring out how to change my quickSort() function from a forward (increasing values) to a reverse (decreasing values) sort, I decided that I should make it capable of performing either sort (fwd or rev), by adding a boolean parameter to the function signature. I started by going back to my C++ program and making the mods there, and I was able to make it work fairly quickly, as the following output shows:
After getting this to work in my C++ project, I ported it over to WallE3_QuickSort_V3 and got it working there. Thinking about the overall ‘Guest Bedroom Problem’, it is clear to me that I will need six synchronized arrays – FrontD, Hdg, L/R Dist, L/R Steer. At first I thought I could do this with five calls to ‘QuickSort() – one each for (FrontD, Hdg) and then one each for the remaining four, each using FrontD as the ‘master’ array. However, when I tried this, it failed miserably – only the first sort (FrontD, Hdg) worked, and the remaining four calls did nothing. After thinking about this for a while, I eventually figured out that the first call – (FrontD, Hdg) worked because each time two FrontD array items got swapped in mySwap(), the Hdg array got swapped in the same way – preserving synchrony. However, when the sorted FrontD array was used in the second and subsequent calls, mySwap() never gets called because all FrontD items are already in order. This meant that the second and subsequent ‘slave’ arrays stayed in their original unsorted state – oops!
So the answer to this problem is either keep replacing the ‘master’ parameter to QuickSort() with the unsorted version of FontD[] so that it will get sorted again (causing the required mySwap() calls to the ‘slave’ array, or modify QuickSort to take all five ‘slave’ arrays as parameters. Either way is a real PITA, but I think the ‘all at once’ strategy is more straightforward.
After implementing the ‘all at once’ strategy, I got the following output from my test program:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
After init,arrays are
FrontD Hdg LeftD LSteer RightD RSteer
230.020.20.174.61.0
259.622.70.368.90.1
2519.126.20.265.80.5
24028.827.90.357.80.0
28239.432.40.463.3-1.0
33250.135.00.3162.5-0.6
43361.046.70.9162.5-0.2
46871.546.8-1.0141.2-0.0
51582.030.2-0.2105.41.0
44992.828.80.1122.71.0
63101.730.70.649.81.0
56111.334.81.029.70.1
56121.5235.11.051.3-1.0
203132.2271.20.441.50.8
178142.1309.5-0.235.30.5
162152.3371.6-0.931.80.5
132162.5355.71.028.20.6
74172.3378.51.024.80.6
13822.6245.6-0.923.0-0.4
68-167.964.9-0.028.8-0.4
54-158.565.6-0.330.2-0.3
54-148.358.4-0.035.3-0.3
42-138.264.60.937.8-0.6
35-127.7160.61.043.6-0.6
28-117.4155.50.343.71.0
29-107.0143.4-0.326.10.2
20-96.4116.10.323.4-0.1
30-85.9129.90.228.6-0.9
31-76.273.2-1.041.9-1.0
35-65.845.5-1.0225.6-0.8
36-55.546.60.4264.2-0.4
45-45.449.0-0.3296.4-0.5
53-35.843.4-0.3359.7-0.6
27-26.440.2-0.3378.61.0
26-16.136.1-0.3400.0-0.1
26-6.734.4-0.2335.1-1.0
After FWD sort,arrays are
FrontD Hdg LeftD LSteer RightD RSteer
20-96.4116.10.323.4-0.1
230.020.20.174.61.0
259.622.70.368.90.1
2519.126.20.265.80.5
26-6.734.4-0.2335.1-1.0
26-16.136.1-0.3400.0-0.1
27-26.440.2-0.3378.61.0
28-117.4155.50.343.71.0
29-107.0143.4-0.326.10.2
30-85.9129.90.228.6-0.9
31-76.273.2-1.041.9-1.0
35-127.7160.61.043.6-0.6
35-65.845.5-1.0225.6-0.8
36-55.546.60.4264.2-0.4
42-138.264.60.937.8-0.6
45-45.449.0-0.3296.4-0.5
53-35.843.4-0.3359.7-0.6
54-148.358.4-0.035.3-0.3
54-158.565.6-0.330.2-0.3
56121.5235.11.051.3-1.0
56111.334.81.029.70.1
63101.730.70.649.81.0
68-167.964.9-0.028.8-0.4
74172.3378.51.024.80.6
132162.5355.71.028.20.6
13822.6245.6-0.923.0-0.4
162152.3371.6-0.931.80.5
178142.1309.5-0.235.30.5
203132.2271.20.441.50.8
24028.827.90.357.80.0
28239.432.40.463.3-1.0
33250.135.00.3162.5-0.6
43361.046.70.9162.5-0.2
44992.828.80.1122.71.0
46871.546.8-1.0141.2-0.0
51582.030.2-0.2105.41.0
After REV sort,arrays are
FrontD Hdg LeftD LSteer RightD RSteer
51582.030.2-0.2105.41.0
46871.546.8-1.0141.2-0.0
44992.828.80.1122.71.0
43361.046.70.9162.5-0.2
33250.135.00.3162.5-0.6
28239.432.40.463.3-1.0
24028.827.90.357.80.0
203132.2271.20.441.50.8
178142.1309.5-0.235.30.5
162152.3371.6-0.931.80.5
13822.6245.6-0.923.0-0.4
132162.5355.71.028.20.6
74172.3378.51.024.80.6
68-167.964.9-0.028.8-0.4
63101.730.70.649.81.0
56111.334.81.029.70.1
56121.5235.11.051.3-1.0
54-148.358.4-0.035.3-0.3
54-158.565.6-0.330.2-0.3
53-35.843.4-0.3359.7-0.6
45-45.449.0-0.3296.4-0.5
42-138.264.60.937.8-0.6
36-55.546.60.4264.2-0.4
35-127.7160.61.043.6-0.6
35-65.845.5-1.0225.6-0.8
31-76.273.2-1.041.9-1.0
30-85.9129.90.228.6-0.9
29-107.0143.4-0.326.10.2
28-117.4155.50.343.71.0
27-26.440.2-0.3378.61.0
26-6.734.4-0.2335.1-1.0
26-16.136.1-0.3400.0-0.1
259.622.70.368.90.1
2519.126.20.265.80.5
230.020.20.174.61.0
20-96.4116.10.323.4-0.1
It appears that both the FWD & REV sorts succeeded (at least with respect to the front distance values). Spot checking the other arrays, we see for front distance values of 23 & 449:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
After init,arrays are
FrontD Hdg LeftD LSteer RightD RSteer
230.020.20.174.61.0
44992.828.80.1122.71.0
After FWD sort,arrays are
FrontD Hdg LeftD LSteer RightD RSteer
230.020.20.174.61.0
44992.828.80.1122.71.0
After REV sort,arrays are
FrontD Hdg LeftD LSteer RightD RSteer
230.020.20.174.61.0
44992.828.80.1122.71.0
So it is clear that all six arrays are synchronized through both FWD & REV sorts – Yay!
Looking at the reverse sorted and synched data for FrontD values above 1.5m we see a number options for travel directions, as detailed by the following lines from the reverse sort output:
1
2
3
4
5
6
7
8
FrontD Hdg LeftD LSteer RightD RSteer
51582.030.2-0.2105.41.0
44992.828.80.1122.71.0
33250.135.00.3162.5-0.6
28239.432.40.463.3-1.0
24028.827.90.357.80.0
178142.1309.5-0.235.30.5
162152.3371.6-0.931.80.5
The first four lines above are all ‘left-side’ tracking options. The fifth line above (at FrontD = 240) could actually utilize either the left or right walls for tracking, and the last two are ‘right-side’ options.
The option with the largest ‘head room’ (515cm) is shown in the first line above; on a relative heading of 82.0º, there is 515cm of travel distance available, and the robot is 30.2cm away from the left wall and is oriented almost parallel to it (left steerval is -0.2).
So it looks like this ‘Run To Daylight’ scheme might actually work, but there were a LOT more options than I thought I would have for tracking side and tracking direction. This may have been caused by the fact that I was doing the testing on my lab bench, with lots of ‘clutter’ around. It may be that in a real situation there are very few (or even no) options – we’ll see!
I modified my test program to choose the first acceptable parameter set from the reverse-sorted data, then turn WallE3 to that heading and refresh all parameters. The following short video and the resulting telemetry output are shown below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
After REV sort,arrays are
FrontD Hdg LeftD LSteer RightD RSteer
50382.878.2-0.0111.41.0
45272.553.81.0141.40.4
43293.277.7-0.198.21.0
41961.641.10.775.9-0.5
395103.978.50.236.21.0
31950.833.20.567.5-0.2
27140.532.00.269.90.4
24030.127.60.3119.11.0
213-15.224.90.1123.10.9
164153.6371.21.028.80.9
150-176.0355.01.026.6-0.3
144164.0324.4-1.025.30.8
117114.096.31.038.4-1.0
79174.5289.91.021.8-0.2
76-14.632.3-0.4351.71.0
76-24.737.5-0.5386.21.0
759.223.10.5354.2-1.0
75-5.230.8-0.1400.01.0
73-0.020.50.5385.90.2
68-165.6355.41.027.3-0.2
67124.5238.4-0.850.60.7
67144.2313.1-0.734.90.6
66134.0275.4-0.540.80.7
64-155.4124.90.330.5-0.3
50-145.0113.6-0.831.6-0.0
49-34.842.7-0.4352.01.0
41-45.049.8-0.5284.40.4
39-134.670.3-0.133.4-0.9
34-54.651.60.0249.6-0.1
31-64.045.50.5218.5-0.0
30-73.654.7-1.084.2-1.0
27-124.671.6-0.142.1-0.8
26-94.5117.20.675.3-0.0
24-84.3127.6-0.275.2-0.2
23-114.279.41.059.1-1.0
20-104.0137.6-0.477.0-0.2
FrontD Hdg LeftD LSteer RightD RSteer
Trackable Wall found at index=0
FrontD Hdg LeftD LSteer RightD RSteer
50382.878.2-0.0111.41.0
Turning toheading=82.8
Current environmental parameters
36785.077.6-0.2107.30.3
As can be seen from the above, the first set of parameters in the synchronized arrays met the criteria, and was chosen. When WallE3 turned to the selected heading and refreshed parameters, everything except the front distance matched very well. I believe the difference in front distances was due to a very slight change in heading which resulted in the distance to a desk chair being measured instead of the distance to the far wall.
Next I tried a test in my office with a simulated corner situation, to see if WallE3 could used his new superpowers for good. I removed the infinite loop at the end of the test program and let loop() run as normal, after setting gl_LastAnomalyCode to ANOMALY_NONE. The following short video and telemetry readout shows the action:
Test of WallE3’s new ‘Run To Daylight’ capability
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
After REV sort,arrays are
FrontD Hdg LeftD LSteer RightD RSteer
1000-176.231.50.722.20.7
617141.420.5-0.0162.1-0.4
402129.920.6-0.2113.0-0.3
325172.625.70.438.11.0
322151.621.00.2177.80.3
305162.122.40.358.21.0
196-166.938.11.018.20.5
177-118.9149.31.014.7-0.2
151-157.650.40.917.30.5
150-128.6164.31.014.4-0.0
130118.922.4-0.3137.00.3
111-148.373.60.915.10.4
75-108.3266.30.215.5-0.3
68-138.2105.81.014.20.2
50108.326.3-0.4174.7-0.0
42-98.8286.60.419.0-0.5
3498.526.70.3235.90.0
2788.923.30.6298.41.0
260.133.2-1.027.2-0.8
23-88.3232.20.422.7-0.3
239.728.7-0.831.7-0.7
2279.521.20.4316.30.9
1969.918.80.3300.5-0.3
19-78.7175.01.025.50.0
1960.418.30.1242.81.0
1919.023.6-0.342.2-1.0
1850.118.3-0.1355.4-1.0
1829.620.0-0.268.1-1.0
1839.719.2-0.2144.21.0
18-8.743.5-1.025.3-0.6
16-69.3148.81.021.90.5
16-18.255.8-1.022.2-0.4
14-59.5119.50.519.20.2
14-28.8171.7-0.220.0-0.2
13-48.8104.70.218.70.2
13-38.2153.9-0.621.1-0.2
FrontD Hdg LeftD LSteer RightD RSteer
Trackable Wall found at index=1
FrontD Hdg LeftD LSteer RightD RSteer
617141.420.5-0.0162.1-0.4
Turning toheading=141.4
29977:Endof setup():Elapsed run time set to0
0.0:Top of loop()-calling UpdateAllEnvironmentParameters()
Battery Voltage=7.94
Just after first UpdateAllEnvironmentParameters()&200mSecdelay at top of loop()0.5:gl_Left/RightCenterCm=20.3/174.0,Left/RightSteerVal=0.13/0.01
Just before call toHandleAnomalousConditions()
InHandleAnomalousConditions(NEITHER)with last anomaly code=NONE
InHandleAnomalousConditions(NEITHER)ANOMALY_NONE CASE
Sec LCen RCen Deg LF LR LStr Front Rear FVar RVar LSpd RSpd ACODE TRKDIR
0.720.7175.1143.320.619.30.1336891265471516910445NONE LEFT
0.820.8175.0145.420.519.30.1237091161021588310347NONE LEFT
0.921.1176.5148.521.819.30.2532712103033165856782NONE LEFT
1.023.9176.1150.624.520.70.2929714775951757743106NONE LEFT
1.124.9176.1149.126.223.90.2330121563221818749100NONE LEFT
1.225.9175.4146.426.725.10.163222437884187436090NONE LEFT
1.326.6166.7143.626.925.90.103552837935192466782NONE LEFT
1.427.5122.4142.228.027.50.053483237777194677772NONE LEFT
1.528.2107.8141.428.628.30.033293537414196707871NONE LEFT
1.629.0106.4141.029.828.30.1544040377291985038111NONE LEFT
1.730.3105.2138.529.629.50.015634740731201587079NONE LEFT
1.830.9104.9137.130.729.90.0857450419382028548101NONE LEFT
1.931.2107.1134.530.330.9-0.065115643196204808762NONE LEFT
2.031.1110.3133.730.530.6-0.014225842468205517277NONE LEFT
2.131.8115.1133.131.430.70.0739666409772065646103NONE LEFT
2.233.0122.0132.031.131.5-0.043907139315206026683NONE LEFT
2.332.4134.4130.531.231.9-0.073767629566203698465NONE LEFT
2.432.6139.8129.331.731.8-0.013538128306199476485NONE LEFT
2.532.9139.5127.532.332.3-0.1334987266111931911336NONE LEFT
2.632.7152.1127.930.931.8-0.093379223722187818960NONE LEFT
2.732.3157.1127.531.131.5-0.0433410019934181357574NONE LEFT
2.832.3160.8127.130.731.4-0.0732610515701173868564NONE LEFT
2.932.1167.1127.330.831.4-0.0632311211138165298366NONE LEFT
3.032.2172.8127.731.731.00.0732011863531556238111NONE LEFT
3.132.5175.2126.532.531.00.1531812352131448418127NONE LEFT
3.235.1176.7124.754.432.41.002991285396132920127EXCESS_STEER_VAL LEFT
Stopping Motors!Error Code is8-->EXCESS_STEER_VAL
From the telemetry output we can see that WallE3 found an acceptable tracking option at index 1 in the reverse-sorted FrontD array, at a relative heading of 141.4º from the start of rotation, with the following parameters:
1
2
FrontD Hdg LeftD LSteer RightD RSteer
617141.420.5-0.0162.1-0.4
Then the robot turned to the desired heading, and then dropped into the normal ‘top of loop’. This caused
TrackLeftWallOffset(350.0,0.0,20.0,30) to be called. WallE3 tracked the left wall nicely from 0.7 sec to 3.2 sec where it ran out of wall and detected an EXCESS_STEERVAL Anomaly. All in all, this test seemed to work perfectly.
In a previous post I described my effort to use ‘Processsing’ to graphically depict the wall-following behavior of WallE3 my autonomous wall-following robot. This worked ‘ok’ (lower case ‘OK’), but with some significant issues that prompted me to try again using C#.Net. I have done quite a bit of work in C#, so I was pretty sure I could make something useful. However, I almost immediately ran into a problem that turned out to be non-trivial (at least to me) to solve.
The problem was that I wanted to use a traditional engineering/scientific coordinate system, with the origin at the lower left-hand corner of the viewing area, with x increasing to the right and y increasing upwards. Unfortunately, the default system in Windows has the origin at the top left-hand corner with x increasing to the right and y increasing downwards. Should be a piece of cake, right?
Well, it is, and it isn’t. Flipping the y-increase direction and moving the origin to bottom-left wasn’t that bad, but then I discovered that if you wish to draw some text (like ‘x’ and ‘y’ at the ends of coordinate axis marker lines), the ‘y’ shows up flipped vertically (the ‘x’ is also vertically flipped, but a vertically flipped ‘x’ is….. ‘x’ ).
So, I bumbled around in Google-land for a while and ran across a post where someone else (Andrew Norton, I think) was having (and had ‘solved’) the same issue. Here is his solution:
So I fired up my VS2022 Community edition IDE and played with this for a while, and it worked – sort of. However, it seemed the text sizing and placement was ‘off’, and I couldn’t figure out why. After lots of playing around, I finally worked out what was happening, and was able to boil it down to what I thought was the simplest possible example. I put all the code into the ‘Paint’ event handler for a Windows Form project, as shown below:
//Step4: re-flip the drawing transform back to y-down increasing using
// g.ResetTransform(). This puts the origing back to the upper left-hand corner of the window
//Step5: Draw the text as required, noting the requirement to take into account that Y=0 is at the top of the window, and
// positive y goes down
//Step6: (optional??) restore the x-right, y-up origin at bottom left transform with
// g.RestoreTransform();
//Step7: Get drunk on your success!
//Step1: Change from Y-down to Y-up increasing using
Graphicsg=e.Graphics;
g.ScaleTransform(1.0f,-1.0f);//flip y
g.TranslateTransform(0,-this.ClientRectangle.Height);//move origin to bottom-left corner
g.TranslateTransform(100,100);//move origin to right 100 and up 100 pix
//draw coordinate system lines in new coordinate system
//Step2: Draw the required geometry elements
g.DrawLine(newPen(Color.Black,3),0,0,100,0);
g.DrawLine(newPen(Color.Black,3),0,0,0,100);
//Step3: Save the transforms with g.Save()
GraphicsState transState=g.Save();//do this so can restore later
// Create string to draw.
StringdrawString="Sample Text";
// Create font and brush.
Font drawFont=newFont("Arial",16);
SolidBrush drawBrush=newSolidBrush(Color.Black);
// Create point for upper-left corner of string (in y-up system).
floatx=150.0F;
floaty=50.0F;
// Set format of string.
StringFormat drawFormat=newStringFormat();
drawFormat.Alignment=StringAlignment.Center;
drawFormat.LineAlignment=StringAlignment.Far;
// Draw string to screen.
e.Graphics.DrawString(drawString,drawFont,drawBrush,x,y,drawFormat);//this is the 'inverted/reversed' text
//Step4: re-flip the drawing transform back to y-down increasing
// note here that origin is still at (100, 100) measured x right, y up from bottom lh corner,
// so text placement has to take that into account
g.ScaleTransform(1.0f,-1.0f);
//Step5: Draw the text as required, noting the requirement to take into account that Y=0 is 100 units up from the bottome of the window,
//and X = 0 is 100 units right of the left edge of the window, and positive y goes down
e.Graphics.DrawString(drawString,drawFont,drawBrush,0,-100,drawFormat);//the vertical coordinate line goes 100 units up from origin
//Step6: (optional??) restore the x-right, y-up origin at bottom left transform with
g.Restore(transState);
//Step7: Get drunk on your success!
}
}
When run, this produces the following output:
Original and flipped/aligned “Sample Text” drawings
In the above figure, the vertically flipped “Sample Text” was drawn after applying the transforms that flipped the y direction and moved the origin to 100,100 with respect to the bottom left-hand corner. The second correctly placed and oriented rendition of “Sample Text” was obtained after implementing steps 4-6 in the above code.
This was pretty cool, but I also wanted to be able to pull in robot telemetry data in Cm and display it in a way that makes sense. I found the ‘Graphics.PageUnit’ method, and I found a small example to show a rectangle drawn with the default ‘pixels’ setting and also with the ‘Point’ setting. I modified this to add the line ‘e.Graphics.PageUnit = GraphicsUnit.Millimeter;’ and got the following:
50w x 100h unit rectangle with top left corner at (20,20) units in pix, points, and mm
According to my trusty digital calipers, the orange ‘mm’ rectangle was very close to 50 x 100 mm (at least on my screen).
So, I *should* be able to combine these two effects and get what I’m after – a screen with the origin at the bottom, left-hand corner and calibrated in mm. My data is actually in cm, but the inherent 10:1 scale factor should work out pretty well, given that I’m working with distances from a few cm to as much as 10m.
03 April 2023 Update:
After a lot of fits and starts, I think I have finally arrived at a drawing algorithm that allows me to use a x-right, y-up coordinate system in which I can draw text correctly (i.e. it doesn’t display upside-down). I posted this in the Stack Overflow thread from a few years ago that gave me my first big clue about how to solve this problem, so hopefully it will help some other poor soul, and I’m also including it below. To use this example, create a Windows .NET form application and enable the ‘Paint’ and ‘ResizeEnd’ handlers (the ‘ResizeEnd’ handler isn’t strictly required, but it allows the user to re-run the example by just resizing the screen slightly). Then replace the contents of the Paint and Resize handlers with the example code, and also paste in the two helper functions in their entirety.
//Purpose: convert a Rectangle or RectangleF object into a 2-element array of PointF objects
//Inputs:
// rectF = Rectangle or RectangleF object
//Outputs:
// returns a 2-element array of PointF objects
// points[0] = PointF(rectF.X, rectF.Y)
// points[1] = PointF(rectF.Width,rectF.Height)
PointF[]points=newPointF[2];
points[0]=newPointF(rectF.X,rectF.Y);
points[1]=newPointF(rectF.Width,rectF.Height);
returnpoints;
}
Here are a couple of screenshots of my form after running the example code. The first image shows the default Windows form size, with the top portion (and the ‘Y’ label) cut off. The second image shows the situation after resizing the form down a bit, allowing the ‘ResizeEnd’ handler to force the program to re-run and re-draw.
Default Form1 size doesn’t show top part of ‘Y’ coordinate lineScreenshot after dragging the bottom of the form down