→ Best would be to use the BabylonJS GUI, since it’s nativerly handled by the canvas Do you plan to switch to 100% BJS UI ? (Have a look at my chat if you wonder how much is possible with this tool )
→ Just add frame.html just before the ID, in order to load a wide canvas without code :
Changed table font size to “0.7em” which on my machine is a few pixels bigger. Hopefully it will scale well. Also added “Help” button with credits at the end. (and the link above uses the “frame.html” trick to hopefully avoid the code window).
I used the Babylon GUI in an earlier version. Even used a TextBlock for the satellite label, but found that the text block is a little more pixelated. The html labels are super crisp.
I think implementing the table in Babylon GUI would be very difficult because I use CSS based formatting and javascript-based sorting of rows when the top column heading is selected (tap any column heading to sort by that heading). And javascript/CSS row selection and highlight. Converting the table to Babylon GUI would be a monumental effort. I think it could be done, but I think the memory usage and the effort to implement all the functionality would be pretty extensive.
I kind of expect that. propagate() is called every frame for every satellite and is the fairly extensive mathematical calculation required for accurate orbital determination (taking into account the perturbations causing the difference from an ideal ellipse). An early suggestion from @CrashMaster (link) was to interpolate (at that time, I used .5 second steps).
Now that I’m using thinInstances and matrices for positioning, I could reconsider interpolation. I thought through multiple ways to do it, including circular prediction, elliptical prediction, and linear prediction. In each case I would run propagate at some future time and interpolate for time steps leading up to that future time, then recalculate a new interpolation “curve” when that time is reached. Circular prediction is interesting, where I would calculate a new center of circular motion for each time step, effectively reparent each satellite to a new center and use rotation slerp for in-between time step movement. Elliptical prediction would do the same but with an ellipse. With no built-in elliptical interpolation mechanism, I could create the angular and linear equivalent slerp functions. The best approach might be linear interpolation if I can pick a suitably small time step. Now that I’ve worked through most of the elliptical path calculations and have a better understanding of satellite motion (sort the original purpose of this whole exercise, for me), I think I know enough to work out a timestep based on maximum curvature and speed of the elliptical path.
There’s a bit of latent code in update() around line 1713 that retains past positions from propogate to enable display of a traced path. It’s not currently used, but could be the basis of path prediction instead. I’d want to avoid “linearizing out” the extremes of a highly elliptical orbit.
Another optimmization I could implement is around the Babylon object used as the basis of satellites. Currently using thinInstances each with their own matrix. Given that I’m using spheres for representing each satellite, I only really need just position. I think this is possible with one of the particle systems available. Solid Particle System with 2d sprites looks interesting, as does Points Cloud Particle System with squares (not fully functional in WebGPU as it doesn’t implement size). I like the varying size with distance, too, so PCS seems less usable here. And with my now-custom picking (not based on rays intersecting meshes), picking support isn’t a particular barrier for the visualization. I’d want to retain the ability for a color per satellite when/if I implement visual categorization of satellites (by country, type, launch date, or other characteristic). I would love a thinInstance implementation with a position Buffer instead of a matrix Buffer. I don’t think any of the particle systems is quite the equivalent. I think my use case of spheres is uniquely suitable for a position-only thinInstance, so I don’t think it would be generally useful.
So, yeah…uh…linear interpolation. That’s the ticket!
There are academic theses and NASA-funded efforts on the subject. A promising paper from 2015 is here in pdf. That paper has a simple propagator implemented in a vertex shader (Listing 6.2 page 134), and a more complex propagator implemented in CUDA.
Here is Listing 6.2 Simple object propagation in an OpenGL vertex shader.
vec3 propagate ( float years , float seconds , float sma , float ecc , float inc , float raan , float aop , float phi )
{
float orbit_period = 2 .0* PI * sqrt ( powf( sma , 3.0 f ) / RMUE ) ;
float t = mod( years *SECS_PER_YEAR + seconds , orbit_period ) ;
// c a l c u l a t i n g mean and e x c e n t r i c anomaly
float mean_anomaly = mod( sqrt ( (RMUE * t * t ) / pow ( sma , 3.0 ) ) + phi , 2 .0*PI ) ;
float eccentric_anomaly = mean2excentric ( mean_anomaly , ecc ) ;
// c o n v e r t i n g e x c e n t r i c anomaly t o t r u e anomaly
float sin_ea = sin ( eccentric_anomaly / 2.0 ) ;
float cos_ea = cos ( eccentric_anomaly / 2.0 ) ;
float true_anomaly = 2.0 * atan ( sqrt((1.0 + ecc)/(1.0 − ecc)) * sin_ea/cos_ea ) );
// bas ed on t h e t r u e anomaly , c a l c u l a t e Ca r t e s i an o b j e c t c o o r d i n a t e s
float u = true_anomaly + aop ;
vec3 w = vec3 ( cos ( u ) * cos ( raan ) − sin ( u ) * sin ( raan ) * cos ( inc ) , cos ( u ) * sin ( raan ) + sin ( u ) * cos ( raan ) * cos ( inc ) , sin ( u ) * sin ( inc ) ) ;
float p = sma * ( 1.0 − pow ( ecc , 2.0 ) ) ;
float arg = 1.0 + ( ecc * cos ( true_anomaly ) ) ;
float r = p / EPSILON ;
if ( arg > EPSILON ) r = p / arg ;
return w * r ;
}
This is “just” propagation based on the elliptical shape without perturbations. There are a couple of things missing such as EPSILON, SECS_PER_YEAR, and RMUE. mean2excentric I expect to be similar to my eAnom_rad javascript function.
I’d need to look into whether atan2 is needed rather than atan and if any of the sin or cos functions can be converted to dot or cross products for speed.
The real SGP4 algorithm is much more complex. The “core” sgp4 is about 400 lines of javascript here and needs a lot of setup code for initializating many of the parameters used.
To prepare for sgp4 in a shader, it makes sense to start small. I had already calculated the transform matrix from elliptical parameters to world space, so the above “simple” calculation can be (almost) completely replaced with a single matrix transformation.
I think this would be the shader code. How do I create and use this in Babylon (including setting up the uniform and attributes)?
There are a few questions within the GLSL code that I’m still seeking answers for.
const vertexShader = `void main(void) {
struct satStruct
{
float mean_anomaly_at_epoch;
float anomaly_rate;
float epoch;
float eccentricity;
}
layout(location = 0) in satStruct sat;
layout(location = 4) in mat4 satmat; // can "4" instead be "size of satStruct"?
uniform float time; // will be updated each frame, incremented by deltaTime.
// note that the input vertex position is unused.
// Perhaps it could be an int8 or float indicating size?
// How do I access it here?
float mAnom = sat.mean_anomaly_at_epoch + sat.anomaly_rate * (time-sat.epoch);
float E = mean_eccentric(mAnom, sat.eccentricity);
gl_Position = satmat * vec4(cos(E),sin(E),0,1);
gl_PointSize = // something dependent on camera distance. How?
// Output is of type point.
}
float mean_eccentric(float mAnomRad, float e){
float eAnom = mAnomRad; //(e < 0.98) ? mAnomRad : Math.PI;
for (float i= 0; i<20; i++) {
float dE = (eAnom - e * sin(eAnom) - mAnomRad) / (1 - e * cos(eAnom));
eAnom -= dE;
if (abs(dE)<1e-6) return eAnom;
}
return eAnom;
}`
I’ve got a minimal points ShaderMaterial (with fixed points, no propagation). It took a lot to get even this.
My biggest mistakes were fixed by setting up mesh as UnIndexed, using material PointFillMode, and getting GLSL syntax exactly correct (was missing final semicolon in main, and needed attribute/uniforms declared outside main()). Note the float precision statement was needed in fragment, but not in vertex.