1   /* Copyright 2022-2025 Luc Maisonobe
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.bodies;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
21  import org.hipparchus.geometry.euclidean.twod.FieldVector2D;
22  import org.hipparchus.util.FastMath;
23  import org.hipparchus.util.FieldSinCos;
24  import org.orekit.frames.Frame;
25  import org.orekit.utils.TimeStampedFieldPVCoordinates;
26  
27  /**
28   * Model of a 2D ellipse in 3D space.
29   * <p>
30   * These ellipses are mainly created as plane sections of general 3D ellipsoids,
31   * but can be used for other purposes.
32   * </p>
33   * <p>
34   * Instances of this class are guaranteed to be immutable.
35   * </p>
36   * @see Ellipsoid#getPlaneSection(FieldVector3D, FieldVector3D)
37   * @param <T> the type of the field elements
38   * @since 12.0
39   * @author Luc Maisonobe
40   */
41  public class FieldEllipse<T extends CalculusFieldElement<T>> {
42  
43      /** Convergence limit. */
44      private static final double ANGULAR_THRESHOLD = 1.0e-12;
45  
46      /** Center of the 2D ellipse. */
47      private final FieldVector3D<T> center;
48  
49      /** Unit vector along the major axis. */
50      private final FieldVector3D<T> u;
51  
52      /** Unit vector along the minor axis. */
53      private final FieldVector3D<T> v;
54  
55      /** Semi major axis. */
56      private final T a;
57  
58      /** Semi minor axis. */
59      private final T b;
60  
61      /** Frame in which the ellipse is defined. */
62      private final Frame frame;
63  
64      /** Semi major axis radius power 2. */
65      private final T a2;
66  
67      /** Semi minor axis power 2. */
68      private final T b2;
69  
70      /** Eccentricity power 2. */
71      private final T e2;
72  
73      /** 1 minus flatness. */
74      private final T g;
75  
76      /** g * g. */
77      private final T g2;
78  
79      /** Evolute factor along major axis. */
80      private final T evoluteFactorX;
81  
82      /** Evolute factor along minor axis. */
83      private final T evoluteFactorY;
84  
85      /** Simple constructor.
86       * @param center center of the 2D ellipse
87       * @param u unit vector along the major axis
88       * @param v unit vector along the minor axis
89       * @param a semi major axis
90       * @param b semi minor axis
91       * @param frame frame in which the ellipse is defined
92       */
93      public FieldEllipse(final FieldVector3D<T> center, final FieldVector3D<T> u,
94                          final FieldVector3D<T> v, final T a, final T b,
95                          final Frame frame) {
96          this.center = center;
97          this.u      = u;
98          this.v      = v;
99          this.a      = a;
100         this.b      = b;
101         this.frame  = frame;
102         this.a2     = a.square();
103         this.g      = b.divide(a);
104         this.g2     = g.multiply(g);
105         this.e2     = g2.negate().add(1);
106         this.b2     = b.square();
107         this.evoluteFactorX = a2.subtract(b2).divide(a2.square());
108         this.evoluteFactorY = b2.subtract(a2).divide(b2.square());
109     }
110 
111     /** Get the center of the 2D ellipse.
112      * @return center of the 2D ellipse
113      */
114     public FieldVector3D<T> getCenter() {
115         return center;
116     }
117 
118     /** Get the unit vector along the major axis.
119      * @return unit vector along the major axis
120      */
121     public FieldVector3D<T> getU() {
122         return u;
123     }
124 
125     /** Get the unit vector along the minor axis.
126      * @return unit vector along the minor axis
127      */
128     public FieldVector3D<T> getV() {
129         return v;
130     }
131 
132     /** Get the semi major axis.
133      * @return semi major axis
134      */
135     public T getA() {
136         return a;
137     }
138 
139     /** Get the semi minor axis.
140      * @return semi minor axis
141      */
142     public T getB() {
143         return b;
144     }
145 
146     /** Get the defining frame.
147      * @return defining frame
148      */
149     public Frame getFrame() {
150         return frame;
151     }
152 
153     /** Get a point of the 2D ellipse.
154      * @param theta angular parameter on the ellipse (really the eccentric anomaly)
155      * @return ellipse point at theta, in underlying ellipsoid frame
156      */
157     public FieldVector3D<T> pointAt(final T theta) {
158         final FieldSinCos<T> scTheta = FastMath.sinCos(theta);
159         return toSpace(new FieldVector2D<>(a.multiply(scTheta.cos()), b.multiply(scTheta.sin())));
160     }
161 
162     /** Create a point from its ellipse-relative coordinates.
163      * @param p point defined with respect to ellipse
164      * @return point defined with respect to 3D frame
165      * @see #toPlane(FieldVector3D)
166      */
167     public FieldVector3D<T> toSpace(final FieldVector2D<T> p) {
168         return new FieldVector3D<>(a.getField().getOne(), center, p.getX(), u, p.getY(), v);
169     }
170 
171     /** Project a point to the ellipse plane.
172      * @param p point defined with respect to 3D frame
173      * @return point defined with respect to ellipse
174      * @see #toSpace(FieldVector2D)
175      */
176     public FieldVector2D<T> toPlane(final FieldVector3D<T> p) {
177         final FieldVector3D<T> delta = p.subtract(center);
178         return new FieldVector2D<>(FieldVector3D.dotProduct(delta, u), FieldVector3D.dotProduct(delta, v));
179     }
180 
181     /** Find the closest ellipse point.
182      * @param p point in the ellipse plane to project on the ellipse itself
183      * @return closest point belonging to 2D meridian ellipse
184      */
185     public FieldVector2D<T> projectToEllipse(final FieldVector2D<T> p) {
186 
187         final T x = FastMath.abs(p.getX());
188         final T y = p.getY();
189 
190         if (x.getReal() <= ANGULAR_THRESHOLD * FastMath.abs(y.getReal())) {
191             // the point is almost on the minor axis, approximate the ellipse with
192             // the osculating circle whose center is at evolute cusp along minor axis
193             final T osculatingRadius = a2.divide(b);
194             final T evoluteCuspZ     = FastMath.copySign(a.multiply(e2).divide(g), y.negate());
195             final T deltaZ           = y.subtract(evoluteCuspZ);
196             final T ratio            = osculatingRadius.divide(FastMath.hypot(deltaZ, x));
197             return new FieldVector2D<>(FastMath.copySign(ratio.multiply(x), p.getX()),
198                                        evoluteCuspZ.add(ratio.multiply(deltaZ)));
199         }
200 
201         if (FastMath.abs(y.getReal()) <= ANGULAR_THRESHOLD * x.getReal()) {
202             // the point is almost on the major axis
203 
204             final T osculatingRadius = b2.divide(a);
205             final T evoluteCuspR     = a.multiply(e2);
206             final T deltaR           = x.subtract(evoluteCuspR);
207             if (deltaR.getReal() >= 0) {
208                 // the point is outside of the ellipse evolute, approximate the ellipse
209                 // with the osculating circle whose center is at evolute cusp along major axis
210                 final T ratio = osculatingRadius.divide(FastMath.hypot(y, deltaR));
211                 return new FieldVector2D<>(FastMath.copySign(evoluteCuspR.add(ratio.multiply(deltaR)), p.getX()),
212                                            ratio.multiply(y));
213             }
214 
215             // the point is on the part of the major axis within ellipse evolute
216             // we can compute the closest ellipse point analytically
217             final T rEllipse = x.divide(e2);
218             return new FieldVector2D<>(FastMath.copySign(rEllipse, p.getX()),
219                                        FastMath.copySign(g.multiply(FastMath.sqrt(a2.subtract(rEllipse.multiply(rEllipse)))), y));
220 
221         } else {
222 
223             // initial point at evolute cusp along major axis
224             T omegaX = a.multiply(e2);
225             T omegaY = a.getField().getZero();
226 
227             T projectedX  = x;
228             T projectedY  = y;
229             double deltaX = Double.POSITIVE_INFINITY;
230             double deltaY = Double.POSITIVE_INFINITY;
231             int count = 0;
232             final double threshold = ANGULAR_THRESHOLD * ANGULAR_THRESHOLD * a2.getReal();
233             while ((deltaX * deltaX + deltaY * deltaY) > threshold && count++ < 100) { // this loop usually converges in 3 iterations
234 
235                 // find point at the intersection of ellipse and line going from query point to evolute point
236                 final T dx         = x.subtract(omegaX);
237                 final T dy         = y.subtract(omegaY);
238                 final T alpha      = b2.multiply(dx).multiply(dx).add(a2.multiply(dy).multiply(dy));
239                 final T betaPrime  = b2.multiply(omegaX).multiply(dx).add(a2.multiply(omegaY).multiply(dy));
240                 final T gamma      = b2.multiply(omegaX).multiply(omegaX).add(a2.multiply(omegaY).multiply(omegaY)).subtract(a2.multiply(b2));
241                 final T deltaPrime = a.linearCombination(betaPrime, betaPrime, alpha.negate(), gamma);
242                 final T ratio      = (betaPrime.getReal() <= 0) ?
243                                           FastMath.sqrt(deltaPrime).subtract(betaPrime).divide(alpha) :
244                                           gamma.negate().divide(FastMath.sqrt(deltaPrime).add(betaPrime));
245                 final T previousX  = projectedX;
246                 final T previousY  = projectedY;
247                 projectedX = omegaX.add(ratio.multiply(dx));
248                 projectedY = omegaY.add(ratio.multiply(dy));
249 
250                 // find new evolute point
251                 omegaX     = evoluteFactorX.multiply(projectedX).multiply(projectedX).multiply(projectedX);
252                 omegaY     = evoluteFactorY.multiply(projectedY).multiply(projectedY).multiply(projectedY);
253 
254                 // compute convergence parameters
255                 deltaX     = projectedX.subtract(previousX).getReal();
256                 deltaY     = projectedY.subtract(previousY).getReal();
257 
258             }
259             return new FieldVector2D<>(FastMath.copySign(projectedX, p.getX()), projectedY);
260         }
261     }
262 
263     /** Project position-velocity-acceleration on an ellipse.
264      * @param pv position-velocity-acceleration to project, in the reference frame
265      * @return projected position-velocity-acceleration
266      */
267     public TimeStampedFieldPVCoordinates<T> projectToEllipse(final TimeStampedFieldPVCoordinates<T> pv) {
268 
269         // find the closest point in the meridian plane
270         final FieldVector2D<T>p2D = toPlane(pv.getPosition());
271         final FieldVector2D<T>e2D = projectToEllipse(p2D);
272 
273         // tangent to the ellipse
274         final T fx = a2.negate().multiply(e2D.getY());
275         final T fy = b2.multiply(e2D.getX());
276         final T f2 = fx.square().add(fy.square());
277         final T f  = FastMath.sqrt(f2);
278         final FieldVector2D<T>tangent = new FieldVector2D<>(fx.divide(f), fy.divide(f));
279 
280         // normal to the ellipse (towards interior)
281         final FieldVector2D<T>normal = new FieldVector2D<>(tangent.getY().negate(), tangent.getX());
282 
283         // center of curvature
284         final T x2     = e2D.getX().multiply(e2D.getX());
285         final T y2     = e2D.getY().multiply(e2D.getY());
286         final T eX     = evoluteFactorX.multiply(x2);
287         final T eY     = evoluteFactorY.multiply(y2);
288         final T omegaX = eX.multiply(e2D.getX());
289         final T omegaY = eY.multiply(e2D.getY());
290 
291         // velocity projection ratio
292         final T rho                = FastMath.hypot(e2D.getX().subtract(omegaX), e2D.getY().subtract(omegaY));
293         final T d                  = FastMath.hypot(p2D.getX().subtract(omegaX), p2D.getY().subtract(omegaY));
294         final T projectionRatio    = rho.divide(d);
295 
296         // tangential velocity
297         final FieldVector2D<T>pDot2D     = new FieldVector2D<>(FieldVector3D.dotProduct(pv.getVelocity(), u),
298                                                               FieldVector3D.dotProduct(pv.getVelocity(), v));
299         final T   pDotTangent            = pDot2D.dotProduct(tangent);
300         final T   pDotNormal             = pDot2D.dotProduct(normal);
301         final T   eDotTangent            = projectionRatio.multiply(pDotTangent);
302         final FieldVector2D<T>eDot2D     = new FieldVector2D<>(eDotTangent, tangent);
303         final FieldVector2D<T>tangentDot = new FieldVector2D<>(a2.multiply(b2).
304                                                                multiply(e2D.getX().multiply(eDot2D.getY()).
305                                                                         subtract(e2D.getY().multiply(eDot2D.getX()))).
306                                                                divide(f2),
307                                                                normal);
308 
309         // velocity of the center of curvature in the meridian plane
310         final T omegaXDot          = eX.multiply(eDotTangent).multiply(tangent.getX()).multiply(3);
311         final T omegaYDot          = eY.multiply(eDotTangent).multiply(tangent.getY()).multiply(3);
312 
313         // derivative of the projection ratio
314         final T voz                = omegaXDot.multiply(tangent.getY()).subtract(omegaYDot.multiply(tangent.getX()));
315         final T vsz                = pDotNormal.negate();
316         final T projectionRatioDot = rho.subtract(d).multiply(voz).subtract(rho.multiply(vsz)).divide(d.multiply(d));
317 
318         // acceleration
319         final FieldVector2D<T>pDotDot2D  = new FieldVector2D<>(FieldVector3D.dotProduct(pv.getAcceleration(), u),
320                                                                FieldVector3D.dotProduct(pv.getAcceleration(), v));
321         final T   pDotDotTangent         = pDotDot2D.dotProduct(tangent);
322         final T   pDotTangentDot         = pDot2D.dotProduct(tangentDot);
323         final T   eDotDotTangent         = projectionRatio.multiply(pDotDotTangent.add(pDotTangentDot)).
324                                            add(projectionRatioDot.multiply(pDotTangent));
325         final FieldVector2D<T>eDotDot2D = new FieldVector2D<>(eDotDotTangent, tangent, eDotTangent, tangentDot);
326 
327         // back to 3D
328         final FieldVector3D<T> e3D       = toSpace(e2D);
329         final FieldVector3D<T> eDot3D    = new FieldVector3D<>(eDot2D.getX(), u, eDot2D.getY(), v);
330         final FieldVector3D<T> eDotDot3D = new FieldVector3D<>(eDotDot2D.getX(), u, eDotDot2D.getY(), v);
331 
332         return new TimeStampedFieldPVCoordinates<>(pv.getDate(), e3D, eDot3D, eDotDot3D);
333 
334     }
335 
336     /** Find the center of curvature (point on the evolute) at the nadir of a point.
337      * @param point point in the ellipse plane
338      * @return center of curvature of the ellipse directly at point nadir
339      */
340     public FieldVector2D<T> getCenterOfCurvature(final FieldVector2D<T> point) {
341         final FieldVector2D<T>projected = projectToEllipse(point);
342         return new FieldVector2D<>(evoluteFactorX.multiply(projected.getX()).multiply(projected.getX()).multiply(projected.getX()),
343                                    evoluteFactorY.multiply(projected.getY()).multiply(projected.getY()).multiply(projected.getY()));
344     }
345 
346 }