Hey! Looks like we haven’t had an update in a while. On the mechanical side, we’ve been finishing up our inverted swerves and finalizing our robot design (more on that soon, hopefully). Meanwhile, I’m excited to share what’s been going on over on programming.
So far, many of our programming team members have been working on computer vision, but others have been working on a shooting algorithm. We decided to tackle the shooting problem in 3d, while taking into account the robot’s current velocity. I’ll spare you some of the more complicated math, but here’s the gist of it:
- Get the x and y coordinates of the robot relative to the center of the speaker using odometry or AprilTags. The z coordinate should be fixed, so no need to measure that.
- Get the x and y component of the robot’s velocity.
- Get the current shooting velocity S of your shooter (this will need experimentation, since the speed of the shooter won’t be perfectly proportional to the Note’s exit velocity)
- Do a ton of work to figure out the robot’s xy angle and the angle of the shooter above the horizontal.
For the more mathematically inclined, here’s a photo of the work that you have to do:
It turns out that solving for the angle θ(shooting angle) isn’t possible analytically while accounting for the robot’s velocity(trust me, I tried working out the trig for 6 hours), so we had to write a program to solve for this angle. The essence of the program is splitting up our possible shooting range(0 - π/2) into many different regions, then testing angles in those regions to find the one that gets the closest height. If we have more operation time, we can continue running this algorithm on the new region we’ve chosen to further refine our angle.
// this import might not work if you don't have the g++ compiler installed, in that case, just import whatever the function needs
#include <bits/stdc++.h>
using namespace std;
const double PI = 3.14159265358979323846;
// so far, assuming perfect conditions (i.e. no air resistance, lift, etc)
int main(){
double x, y, z; // x, y, and z displacement from the speaker's bottom center
cout << "Enter x, y, and z coordinates from the speaker center: "; // z should be negative
cin >> x >> y >> z;
// facing the speaker, positives for the dimensions are forward(x), right(y), and up(z)
double a = sqrt(x*x + y*y);
double v_x, v_y; cin >> v_x >> v_y;
cout << "Enter your x and y components of velocity: ";
double v = sqrt(v_x*v_x + v_y*v_y);
double t1 = (v_x == 0 ? PI / 2.0 : atan2(v_y, v_x)); // angle between velocity and x
double t2 = (x == 0 ? PI / 2.0 : atan2(y, x)); // angle between robot and speaker
double t3 = t2 - t1; // angle between velocity and line to speaker
double v_a = v*cos(t3), v_b = v*sin(t3); // a velocity in direction of the speaker, b is perpendicular
cout << "Enter your shooting velocity: ";
double s; cin >> s;
double s_az = sqrt(s*s - v_b*v_b); // the b-component of s must cancel out v_b
double range = PI / 2.0;
double center = PI / 4.0;
double scale = 50.0;
int reps = 3;
pair<double, double> min1 = {DBL_MAX, -1};
const double g = 9.807;
while(reps--){
for(double angle = center - range / 2.0; angle <= center + range / 2.0; angle += (range) / 100.0){
double s_a = s_az*cos(angle);
double s_z = s_az*sin(angle);
double time = a/(s_a+v_a);
double height = s_z*time - g/2*time*time;
// quadratic formula
double a = -g/2.0;
double b = s_z;
double c = -height;
double determinant = b*b - 4.0*a*c;
if(determinant < 0) continue;
double first_hit = (-b + sqrt(b*b-4*a*c))/(2.0*a);
// this means that our angle results in us hitting the speaker on the way down, which we don't want
// there's probably a better solution, since the speaker is slightly angled and does allow shooting downwards
if(first_hit + .01 < time) continue;
double dif = abs(-z - height);
if(dif < min1.first){
min1 = make_pair(dif, angle);
}
}
range /= scale;
center = min1.second;
}
cout << center << " "; // angle from vertical
double s_z = s_az*sin(center);
double s_a = s_az*cos(center);
double s_b = -v_b;
double t4 = (s_b == 0 ? PI / 2.0 : atan2(s_a, s_b));
double phi = t4 + t2 - PI / 2.0;
double s_ab = sqrt(s_a*s_a + s_b*s_b);
double s_x = s_ab*cos(phi);
double s_y = s_ab*sin(phi);
cout << remainder(phi, 2.0*PI);
}
At the end, this should output 2 angles, the first being the shooting angle above the horizontal, and the second being the angle of the robot from the x-axis.
Here’s a desmos graph to test out the algorithm:
https://www.desmos.com/3d/3f796660bc
Here’s another one to optimize how many angles are tested each cycle: https://www.desmos.com/calculator/lkmpmox19p
We have still yet to implement air resistance and/or lift into the algorithm, which will pose a significant challenge as well. Air resistance, for example, is proportional to velocity squared, but it’s not as simple as just integrating, since the cross-sectional area of the note also changes as it travels along the parabola. We might end up implementing this using Euler’s method, or just shoot a bit higher and call it a day.