1   /* Copyright 2022-2025 Romain Serra
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.forces.radiation;
18  
19  import org.hipparchus.CalculusFieldElement;
20  import org.hipparchus.Field;
21  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
22  import org.hipparchus.geometry.euclidean.threed.Vector3D;
23  import org.orekit.propagation.events.CylindricalShadowEclipseDetector;
24  import org.orekit.propagation.events.EventDetector;
25  import org.orekit.propagation.events.EventDetectionSettings;
26  import org.orekit.propagation.events.FieldCylindricalShadowEclipseDetector;
27  import org.orekit.propagation.events.FieldEventDetector;
28  import org.orekit.propagation.events.FieldEventDetectionSettings;
29  import org.orekit.propagation.events.handlers.FieldResetDerivativesOnEvent;
30  import org.orekit.propagation.events.handlers.ResetDerivativesOnEvent;
31  import org.orekit.utils.ExtendedPositionProvider;
32  
33  import java.util.ArrayList;
34  import java.util.List;
35  
36  /**
37   * Class defining a flux model with a single occulting body, casting a shadow whose shape is a circular cylinder
38   * (equivalent to the light source being infinitely distant). It is less accurate but faster to evaluate than a conical
39   * model.
40   *
41   * @author Romain Serra
42   * @see AbstractSolarLightFluxModel
43   * @see LightFluxModel
44   * @since 12.1
45   */
46  public class CylindricallyShadowedLightFluxModel extends AbstractSolarLightFluxModel {
47  
48      /**
49       * Default max. check interval for eclipse detection.
50       */
51      private static final double CYLINDRICAL_ECLIPSE_MAX_CHECK = 100;
52  
53      /**
54       * Default threshold for eclipse detection.
55       */
56      private static final double CYLINDRICAL_ECLIPSE_THRESHOLD = 1e-7;
57  
58      /**
59       * Constructor.
60       * @param kRef reference flux
61       * @param occultedBody position provider for light source
62       * @param occultingBodyRadius radius of central, occulting body
63       * @param eventDetectionSettings user-defined detection settings for eclipses (if ill-tuned, events might be missed or performance might drop)
64       * @since 12.2
65       */
66      public CylindricallyShadowedLightFluxModel(final double kRef, final ExtendedPositionProvider occultedBody,
67                                                 final double occultingBodyRadius, final EventDetectionSettings eventDetectionSettings) {
68          super(kRef, occultedBody, occultingBodyRadius, eventDetectionSettings);
69      }
70  
71      /**
72       * Constructor with default event detection settings.
73       * @param kRef reference flux
74       * @param occultedBody position provider for light source
75       * @param occultingBodyRadius radius of central, occulting body
76       */
77      public CylindricallyShadowedLightFluxModel(final double kRef, final ExtendedPositionProvider occultedBody,
78                                                 final double occultingBodyRadius) {
79          this(kRef, occultedBody, occultingBodyRadius, getDefaultEclipseDetectionSettings());
80      }
81  
82      /**
83       * Constructor with default value for reference flux.
84       * @param occultedBody position provider for light source
85       * @param occultingBodyRadius radius of central, occulting body
86       */
87      public CylindricallyShadowedLightFluxModel(final ExtendedPositionProvider occultedBody,
88                                                 final double occultingBodyRadius) {
89          super(occultedBody, occultingBodyRadius, getDefaultEclipseDetectionSettings());
90      }
91  
92      /**
93       * Define default detection settings for eclipses.
94       * @return default settings
95       * @since 12.2
96       */
97      public static EventDetectionSettings getDefaultEclipseDetectionSettings() {
98          return new EventDetectionSettings(CYLINDRICAL_ECLIPSE_MAX_CHECK, CYLINDRICAL_ECLIPSE_THRESHOLD,
99                  EventDetectionSettings.DEFAULT_MAX_ITER);
100     }
101 
102     /** {@inheritDoc} */
103     @Override
104     protected double getLightingRatio(final Vector3D position, final Vector3D occultedBodyPosition) {
105         final Vector3D occultedBodyDirection = occultedBodyPosition.normalize();
106         final double dotProduct = position.dotProduct(occultedBodyDirection);
107         if (dotProduct < 0.) {
108             final double distanceToCylinderAxis = (position.subtract(occultedBodyDirection.scalarMultiply(dotProduct))).getNorm();
109             if (distanceToCylinderAxis <= getOccultingBodyRadius()) {
110                 return 0.;
111             }
112         }
113         return 1.;
114     }
115 
116     /** {@inheritDoc} */
117     @Override
118     protected <T extends CalculusFieldElement<T>> T getLightingRatio(final FieldVector3D<T> position,
119                                                                      final FieldVector3D<T> occultedBodyPosition) {
120         final Field<T> field = position.getX().getField();
121         final FieldVector3D<T> occultedBodyDirection = occultedBodyPosition.normalize();
122         final T dotProduct = position.dotProduct(occultedBodyDirection);
123         if (dotProduct.getReal() < 0.) {
124             final T distanceToCylinderAxis = (position.subtract(occultedBodyDirection.scalarMultiply(dotProduct))).getNorm();
125             if (distanceToCylinderAxis.getReal() <= getOccultingBodyRadius()) {
126                 return field.getZero();
127             }
128         }
129         return field.getOne();
130     }
131 
132 
133     /** {@inheritDoc} */
134     @Override
135     public List<EventDetector> getEclipseConditionsDetector() {
136         final List<EventDetector> detectors = new ArrayList<>();
137         detectors.add(createCylindricalShadowEclipseDetector().withDetectionSettings(getEventDetectionSettings()));
138         return detectors;
139     }
140 
141     /**
142      * Method to create a new eclipse detector.
143      * @return detector
144      */
145     private CylindricalShadowEclipseDetector createCylindricalShadowEclipseDetector() {
146         return new CylindricalShadowEclipseDetector(getOccultedBody(), getOccultingBodyRadius(),
147                 new ResetDerivativesOnEvent());
148     }
149 
150     /** {@inheritDoc} */
151     @Override
152     public <T extends CalculusFieldElement<T>> List<FieldEventDetector<T>> getFieldEclipseConditionsDetector(final Field<T> field) {
153         final List<FieldEventDetector<T>> detectors = new ArrayList<>();
154         final FieldEventDetectionSettings<T> detectionSettings = new FieldEventDetectionSettings<>(field, getEventDetectionSettings());
155         detectors.add(createFieldCylindricalShadowEclipseDetector(field).withDetectionSettings(detectionSettings));
156         return detectors;
157     }
158 
159     /**
160      * Method to create a new eclipse detector. Field version.
161      * @param field field
162      * @param <T> field type
163      * @return detector
164      */
165     private <T extends CalculusFieldElement<T>> FieldCylindricalShadowEclipseDetector<T> createFieldCylindricalShadowEclipseDetector(final Field<T> field) {
166         final T occultingBodyRadiusAsField = field.getZero().newInstance(getOccultingBodyRadius());
167         return new FieldCylindricalShadowEclipseDetector<>(getOccultedBody(), occultingBodyRadiusAsField,
168                 new FieldResetDerivativesOnEvent<>());
169     }
170 }