rsspice 0.1.0

Pure Rust port of the SPICE Toolkit for space geometry
Documentation
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
//
// GENERATED FILE
//

use super::*;
use f2rust_std::*;

const INERTL: i32 = 1;
const PCK: i32 = (INERTL + 1);
const CK: i32 = (PCK + 1);
const TK: i32 = (CK + 1);
const DYN: i32 = (TK + 1);
const SWTCH: i32 = (DYN + 1);
const ALL: i32 = -1;
const NABCOR: i32 = 15;
const ABATSZ: i32 = 6;
const GEOIDX: i32 = 1;
const LTIDX: i32 = (GEOIDX + 1);
const STLIDX: i32 = (LTIDX + 1);
const CNVIDX: i32 = (STLIDX + 1);
const XMTIDX: i32 = (CNVIDX + 1);
const RELIDX: i32 = (XMTIDX + 1);
const CORLEN: i32 = 5;
const RNAME: &[u8] = b"ZZSFXCOR";
const CNVLIM: f64 = 0.00000000000000001;
const RNDTOL: f64 = 0.00000000000001;
const MARGIN: f64 = 1.01;
const MAXITR: i32 = 10;
const J2CODE: i32 = 1;

struct SaveVars {
    LOCCOR: Vec<u8>,
    PRVCOR: Vec<u8>,
    FIRST: bool,
}

impl SaveInit for SaveVars {
    fn new() -> Self {
        let mut LOCCOR = vec![b' '; CORLEN as usize];
        let mut PRVCOR = vec![b' '; CORLEN as usize];
        let mut FIRST: bool = false;

        FIRST = true;
        fstr::assign(&mut PRVCOR, b" ");

        Self {
            LOCCOR,
            PRVCOR,
            FIRST,
        }
    }
}

//$Procedure ZZSFXCOR ( Ray-surface intercept core algorithm )
pub fn ZZSFXCOR(
    UDNEAR: fn(&[f64], &[f64], f64, &mut [f64], &mut f64, &mut Context) -> f2rust_std::Result<()>,
    UDMAXR: fn(&mut f64, &mut Context) -> (),
    UDRAYX: fn(&[f64], &[f64], f64, &mut [f64], &mut bool, &mut Context) -> f2rust_std::Result<()>,
    TRGCDE: i32,
    ET: f64,
    ABCORR: &[u8],
    USELT: bool,
    USECN: bool,
    USESTL: bool,
    XMIT: bool,
    FIXREF: &[u8],
    OBSCDE: i32,
    DFRCDE: i32,
    DCLASS: i32,
    DCENTR: i32,
    DVEC: &[f64],
    SPOINT: &mut [f64],
    TRGEPC: &mut f64,
    SRFVEC: &mut [f64],
    FOUND: &mut bool,
    ctx: &mut Context,
) -> f2rust_std::Result<()> {
    let save = ctx.get_vars::<SaveVars>();
    let save = &mut *save.borrow_mut();

    let DVEC = DummyArray::new(DVEC, 1..=3);
    let mut SPOINT = DummyArrayMut::new(SPOINT, 1..=3);
    let mut SRFVEC = DummyArrayMut::new(SRFVEC, 1..=3);
    let mut DIST: f64 = 0.0;
    let mut ETDIFF: f64 = 0.0;
    let mut J2DIR = StackArray::<f64, 3>::new(1..=3);
    let mut J2EST = StackArray::<f64, 3>::new(1..=3);
    let mut J2GEOM = StackArray::<f64, 3>::new(1..=3);
    let mut J2POS = StackArray::<f64, 3>::new(1..=3);
    let mut J2TMAT = StackArray2D::<f64, 9>::new(1..=3, 1..=3);
    let mut LT: f64 = 0.0;
    let mut LTCENT: f64 = 0.0;
    let mut LTDIFF: f64 = 0.0;
    let mut MAXRAD: f64 = 0.0;
    let mut NEGPOS = StackArray::<f64, 3>::new(1..=3);
    let mut OBSPOS = StackArray::<f64, 3>::new(1..=3);
    let mut PNEAR = StackArray::<f64, 3>::new(1..=3);
    let mut PREVET: f64 = 0.0;
    let mut PREVLT: f64 = 0.0;
    let mut R2JMAT = StackArray2D::<f64, 9>::new(1..=3, 1..=3);
    let mut RANGE: f64 = 0.0;
    let mut RAYALT: f64 = 0.0;
    let mut REFEPC: f64 = 0.0;
    let mut REJECT: f64 = 0.0;
    let mut RELERR: f64 = 0.0;
    let mut RPOS = StackArray::<f64, 3>::new(1..=3);
    let mut S: f64 = 0.0;
    let mut SRFLEN: f64 = 0.0;
    let mut SSBOST = StackArray::<f64, 6>::new(1..=6);
    let mut SSBTST = StackArray::<f64, 6>::new(1..=6);
    let mut STLDIR = StackArray::<f64, 3>::new(1..=3);
    let mut STLERR = StackArray::<f64, 3>::new(1..=3);
    let mut STLTMP = StackArray::<f64, 3>::new(1..=3);
    let mut TPOS = StackArray::<f64, 3>::new(1..=3);
    let mut TRGDIR = StackArray::<f64, 3>::new(1..=3);
    let mut UDIR = StackArray::<f64, 3>::new(1..=3);
    let mut XFORM = StackArray2D::<f64, 9>::new(1..=3, 1..=3);
    let mut I: i32 = 0;
    let mut NITR: i32 = 0;

    //
    // SPICELIB functions
    //

    //
    // Local parameters
    //

    //
    // This value will become system-dependent when systems
    // using 128-bit d.p. numbers are supported by SPICELIB.
    // CNVLIM, when added to 1.0D0, should yield 1.0D0.
    //

    //
    // Round-off error limit for arc sine input:
    //

    //
    // Fraction of target body angular radius used to define
    // region outside of which rays are immediately rejected
    // as non-intersecting.
    //

    //
    // Code for the frame J2000.
    //

    //
    // Local variables
    //

    //
    // Saved variables
    //

    //
    // Initial values
    //

    //
    // Standard SPICE error handling.
    //
    if RETURN(ctx) {
        return Ok(());
    }

    CHKIN(RNAME, ctx)?;

    //
    // Nothing found yet.
    //
    *FOUND = false;

    //
    // Check for a zero ray direction vector.
    //
    if VZERO(DVEC.as_slice()) {
        SETMSG(
            b"Input ray direction was the zero vector; this vector must be non-zero.",
            ctx,
        );
        SIGERR(b"SPICE(ZEROVECTOR)", ctx)?;
        CHKOUT(RNAME, ctx)?;
        return Ok(());
    }

    //
    // Get the sign S prefixing LT in the expression for TRGEPC.
    // When light time correction is not used, setting S = 0
    // allows us to seamlessly set TRGEPC equal to ET.
    //
    if USELT {
        if XMIT {
            S = 1.0;
        } else {
            S = -1.0;
        }
    } else {
        S = 0.0;
    }

    if (save.FIRST || fstr::ne(ABCORR, &save.PRVCOR)) {
        //
        // Construct aberration correction string without stellar
        // aberration specification.
        //
        if USELT {
            if XMIT {
                fstr::assign(&mut save.LOCCOR, b"X");
            } else {
                fstr::assign(&mut save.LOCCOR, b" ");
            }

            if USECN {
                SUFFIX(b"CN", 0, &mut save.LOCCOR);
            } else {
                SUFFIX(b"LT", 0, &mut save.LOCCOR);
            }
        } else {
            fstr::assign(&mut save.LOCCOR, b"NONE");
        }

        fstr::assign(&mut save.PRVCOR, ABCORR);
        save.FIRST = false;
    }

    //
    // Determine the position of the observer in target
    // body-fixed coordinates.
    //
    //     -  Call SPKEZP to compute the position of the target body as
    //        seen from the observing body and the light time (LT)
    //        between them. We request that the coordinates of POS be
    //        returned relative to the body fixed reference frame
    //        associated with the target body, using aberration
    //        corrections specified by LOCCOR; these are the corrections
    //        the input argument ABCORR, minus the stellar aberration
    //        correction if it was called for.
    //
    //     -  Call VMINUS to negate the direction of the vector (OBSPOS)
    //        so it will be the position of the observer as seen from
    //        the target body in target body fixed coordinates.
    //
    //        Note that this result is not the same as the result of
    //        calling SPKEZP with the target and observer switched. We
    //        computed the vector FROM the observer TO the target in
    //        order to get the proper light time and stellar aberration
    //        corrections (if requested). Now we need the inverse of
    //        that corrected vector in order to compute the intercept
    //        point.
    //

    SPKEZP(
        TRGCDE,
        ET,
        FIXREF,
        &save.LOCCOR,
        OBSCDE,
        TPOS.as_slice_mut(),
        &mut LT,
        ctx,
    )?;

    if FAILED(ctx) {
        CHKOUT(RNAME, ctx)?;
        return Ok(());
    }

    //
    // Negate the target's position to obtain the position of the
    // observer relative to the target.
    //
    VMINUS(TPOS.as_slice(), OBSPOS.as_slice_mut());

    //
    // We now need to convert the direction vector into the
    // body fixed frame associated with the target. The target
    // epoch is dependent on the aberration correction. The
    // coefficient S has been set to give us the correct answer
    // for each case.
    //
    *TRGEPC = (ET + (S * LT));

    //
    // Transform the direction vector from frame DREF to the body-fixed
    // frame associated with the target. The epoch TRGEPC associated
    // with the body-fixed frame has been set already.
    //
    // We'll compute the transformation in two parts: first
    // from frame DREF to J2000, then from J2000 to the target
    // frame.
    //
    if (DCLASS == INERTL) {
        //
        // Inertial frames can be evaluated at any epoch.
        //
        REFEPC = ET;
    } else if !USELT {
        //
        // We're not using light time corrections (converged or
        // otherwise), so there's no time offset.
        //
        REFEPC = ET;
    } else if (DCENTR == OBSCDE) {
        //
        // If the center of frame DREF is the observer (which is
        // usually the case if the observer is a spacecraft), then
        // the epoch of frame DREF is simply ET.
        //
        // There's no offset between the center for frame DREF
        // and the observer.
        //
        REFEPC = ET;
    } else {
        //
        // Find the light time from the observer to the center of
        // frame DREF.
        //
        SPKEZP(
            DCENTR,
            ET,
            b"J2000",
            ABCORR,
            OBSCDE,
            RPOS.as_slice_mut(),
            &mut LTCENT,
            ctx,
        )?;

        if FAILED(ctx) {
            CHKOUT(RNAME, ctx)?;
            return Ok(());
        }

        REFEPC = (ET + (S * LTCENT));
    }

    //
    // The epoch REFEPC associated with frame DREF has been set.
    //
    // Compute the transformation from frame DREF to J2000 and the
    // transformation from J2000 to the target body-fixed frame.
    //
    // Map DVEC to both the J2000 and target body-fixed frames. We'll
    // store DVEC, expressed relative to the J2000 frame, in the
    // variable J2DIR. DVEC in the target body-fixed frame will be
    // stored in TRGDIR.
    //
    // We may need both versions of DVEC: if we use light time
    // correction, we'll update "intercept epoch", and hence the
    // transformation between J2000 and the target body-fixed frame.
    // The transformation between DREF and J2000 doesn't change, on the
    // other hand, so we don't have to recompute J2DIR. We need TRGDIR
    // in all cases.
    //
    REFCHG(DFRCDE, J2CODE, REFEPC, R2JMAT.as_slice_mut(), ctx)?;

    if FAILED(ctx) {
        CHKOUT(RNAME, ctx)?;
        return Ok(());
    }

    MXV(R2JMAT.as_slice(), DVEC.as_slice(), J2DIR.as_slice_mut());

    //
    // Save this version of J2DIR as J2GEOM. Later we'll
    // modify J2DIR, if necessary, to account for stellar
    // aberration.
    //
    VEQU(J2DIR.as_slice(), J2GEOM.as_slice_mut());

    //
    // Map J2DIR (in the J2000 frame) to the target body-fixed
    // frame.
    //
    PXFORM(b"J2000", FIXREF, *TRGEPC, J2TMAT.as_slice_mut(), ctx)?;

    if FAILED(ctx) {
        CHKOUT(RNAME, ctx)?;
        return Ok(());
    }

    MXV(J2TMAT.as_slice(), J2DIR.as_slice(), TRGDIR.as_slice_mut());

    //
    // At this point,
    //
    //    TRGEPC is set.
    //    TRGDIR is set.
    //    J2DIR is set.
    //
    //
    // Get the J2000-relative state of the observer relative to
    // the solar system barycenter at ET. We'll use this in
    // several places later.
    //
    SPKSSB(OBSCDE, ET, b"J2000", SSBOST.as_slice_mut(), ctx)?;

    if FAILED(ctx) {
        CHKOUT(RNAME, ctx)?;
        return Ok(());
    }

    //
    // If we're using stellar aberration correction, at this point we'll
    // account for it. We're going to find a surface point such that
    // the radiation path from that point to the observer, after
    // correction for stellar aberration, is parallel to the ray. So
    // by applying the inverse of the correction to the ray, we obtain
    // the ray with which we must perform our intercept computation.
    //
    if USESTL {
        //
        // We approximate the inverse stellar aberration correction by
        // using the correction for the reverse transmission direction.
        // If we're in the reception case, we apply the transmission
        // stellar aberration correction to J2DIR and vice versa.
        //
        // We iterate our estimates until we have the desired level
        // of convergence or reach the iteration limit.
        //
        NITR = 5;

        if XMIT {
            //
            // Use reception stellar aberration correction
            // routine STELAB to generate a first estimate of
            // the direction vector after stellar aberration
            // has been "removed"---that is, apply the inverse
            // of the transmission stellar aberration correction
            // mapping to J2DIR.
            //
            STELAB(
                J2DIR.as_slice(),
                SSBOST.subarray(4),
                STLDIR.as_slice_mut(),
                ctx,
            )?;

            //
            // Now improve our estimate.
            //
            RELERR = 1.0;
            I = 1;

            while ((I <= NITR) && (RELERR > CNVLIM)) {
                //
                // Estimate the error in our previous approximation
                // by applying the reception stellar aberration
                // to STLDIR and finding the difference with J2DIR.
                //
                STLABX(
                    STLDIR.as_slice(),
                    SSBOST.subarray(4),
                    J2EST.as_slice_mut(),
                    ctx,
                )?;
                VSUB(J2DIR.as_slice(), J2EST.as_slice(), STLERR.as_slice_mut());

                //
                // Adding the error in the reception mapping to STLDIR
                // will give us an improved estimate of the inverse.
                //
                VADD(STLERR.as_slice(), STLDIR.as_slice(), STLTMP.as_slice_mut());
                VEQU(STLTMP.as_slice(), STLDIR.as_slice_mut());

                RELERR = (VNORM(STLERR.as_slice()) / VNORM(STLDIR.as_slice()));
                I = (I + 1);
            }

        //
        // At this point we've found a good estimate of the
        // direction vector under the inverse of the transmission
        // stellar aberration correction mapping.
        //
        } else {
            //
            // Use transmission stellar aberration correction
            // routine STLABX to generate a first estimate of
            // the direction vector after stellar aberration
            // has been "removed."
            //
            STLABX(
                J2DIR.as_slice(),
                SSBOST.subarray(4),
                STLDIR.as_slice_mut(),
                ctx,
            )?;

            //
            // Now improve our estimate.
            //
            RELERR = 1.0;
            I = 1;

            while ((I <= NITR) && (RELERR > CNVLIM)) {
                //
                // Estimate the error in our previous approximation
                // by applying the reception stellar aberration
                // to STLDIR and finding the difference with J2DIR.
                //
                STELAB(
                    STLDIR.as_slice(),
                    SSBOST.subarray(4),
                    J2EST.as_slice_mut(),
                    ctx,
                )?;
                VSUB(J2DIR.as_slice(), J2EST.as_slice(), STLERR.as_slice_mut());

                //
                // Adding the error in the reception mapping to STLDIR
                // will give us an improved estimate of the inverse.
                //
                VADD(STLERR.as_slice(), STLDIR.as_slice(), STLTMP.as_slice_mut());
                VEQU(STLTMP.as_slice(), STLDIR.as_slice_mut());

                RELERR = (VNORM(STLERR.as_slice()) / VNORM(STLDIR.as_slice()));
                I = (I + 1);
            }

            //
            // At this point we've found a good estimate of the
            // direction vector under the inverse of the reception
            // stellar aberration correction mapping.
            //
        }

        //
        // Replace the J2000-relative ray direction with the corrected
        // direction.
        //
        VEQU(STLDIR.as_slice(), J2DIR.as_slice_mut());
        MXV(J2TMAT.as_slice(), J2DIR.as_slice(), TRGDIR.as_slice_mut());
    }

    //
    // Find the surface intercept point and distance from observer to
    // intercept point using the specified geometric definition.
    //
    // Find the surface intercept given the target epoch,
    // observer-target position, and target body orientation we've
    // already computed. If we're not using light time correction, this
    // is all we must do. Otherwise, our result will give us an initial
    // estimate of the target epoch, which we'll then improve.
    //
    // Make an easy test to see whether we can quit now because an
    // intercept cannot exist. If the ray is separated from the
    // observer-target center vector by more than (MARGIN * the maximum
    // target radius), we're done. Let REJECT be the angular
    // separation limit.
    //
    UDMAXR(&mut MAXRAD, ctx);

    RANGE = VNORM(OBSPOS.as_slice());

    if (RANGE == 0.0) {
        //
        // We've already ensured that observer and target are
        // distinct, so this should be a very unusual occurrence.
        //
        SETMSG(
            b"Observer-target distance is zero. Observer ID is #; target ID is #.",
            ctx,
        );
        ERRINT(b"#", OBSCDE, ctx);
        ERRINT(b"#", TRGCDE, ctx);
        SIGERR(b"SPICE(NOSEPARATION)", ctx)?;
        CHKOUT(RNAME, ctx)?;
        return Ok(());
    }

    if (RANGE > (MARGIN * MAXRAD)) {
        //
        // Compute the arc sine with SPICE error checking.
        //
        REJECT = DASINE(((MARGIN * MAXRAD) / RANGE), RNDTOL, ctx)?;

        VMINUS(OBSPOS.as_slice(), NEGPOS.as_slice_mut());

        if (VSEP(NEGPOS.as_slice(), TRGDIR.as_slice(), ctx) > REJECT) {
            //
            // The angular separation of ray and target is too great
            // for a solution to exist, even with a better light time
            // estimate.
            //
            CHKOUT(RNAME, ctx)?;
            return Ok(());
        }
    }

    //
    // Locate the intercept of the ray with the target; if there's no
    // intercept, find the closest point on the target to the ray.
    //
    UDRAYX(
        OBSPOS.as_slice(),
        TRGDIR.as_slice(),
        *TRGEPC,
        SPOINT.as_slice_mut(),
        FOUND,
        ctx,
    )?;

    if FAILED(ctx) {
        CHKOUT(RNAME, ctx)?;
        return Ok(());
    }

    //
    // If we found an intercept, and if we're not using light time
    // corrections, we're almost done now. We still need SRFVEC.
    // SPOINT, TRGEPC, and FOUND have already been set.
    //
    if (*FOUND && !USELT) {
        VSUB(SPOINT.as_slice(), OBSPOS.as_slice(), SRFVEC.as_slice_mut());

        CHKOUT(RNAME, ctx)?;
        return Ok(());
    }

    //
    // From this point onward, we're dealing with a case calling for
    // light time and possibly stellar aberration corrections.
    //
    if !*FOUND {
        //
        // If there's no intercept, we're probably done. However, we need
        // to guard against the possibility that the ray does intersect
        // the ellipsoid but we haven't discovered it because our first
        // light time estimate was too poor.
        //
        // We'll make an improved light time estimate as follows: Find
        // the nearest point on the ellipsoid to the ray. Find the light
        // time between the observer and this point.
        //
        // If we're using converged Newtonian corrections, we iterate
        // this procedure up to three times.
        //
        if USECN {
            NITR = 3;
        } else {
            NITR = 1;
        }

        I = 1;

        while ((I <= NITR) && !*FOUND) {
            UDNEAR(
                OBSPOS.as_slice(),
                TRGDIR.as_slice(),
                ET,
                PNEAR.as_slice_mut(),
                &mut RAYALT,
                ctx,
            )?;

            if FAILED(ctx) {
                CHKOUT(RNAME, ctx)?;
                return Ok(());
            }

            LT = (VDIST(OBSPOS.as_slice(), PNEAR.as_slice()) / CLIGHT());

            //
            // Use the new light time estimate to repeat the intercept
            // computation.
            //
            *TRGEPC = (ET + (S * LT));

            //
            // Get the J2000-relative state of the target relative to
            // the solar system barycenter at the target epoch.
            //
            SPKSSB(TRGCDE, *TRGEPC, b"J2000", SSBTST.as_slice_mut(), ctx)?;

            if FAILED(ctx) {
                CHKOUT(RNAME, ctx)?;
                return Ok(());
            }

            //
            // Find the position of the observer relative to the target.
            // Convert this vector from the J2000 frame to the target
            // frame at TRGEPC.
            //
            VSUB(SSBOST.as_slice(), SSBTST.as_slice(), J2POS.as_slice_mut());
            PXFORM(b"J2000", FIXREF, *TRGEPC, XFORM.as_slice_mut(), ctx)?;

            if FAILED(ctx) {
                CHKOUT(RNAME, ctx)?;
                return Ok(());
            }

            //
            // Convert the observer's position relative to the target
            // from the J2000 frame to the target frame at the target
            // epoch.
            //
            MXV(XFORM.as_slice(), J2POS.as_slice(), OBSPOS.as_slice_mut());

            //
            // Convert the ray's direction vector from the J2000 frame
            // to the target frame at the target epoch.
            //
            MXV(XFORM.as_slice(), J2DIR.as_slice(), TRGDIR.as_slice_mut());

            //
            // Repeat the intercept computation.
            //
            UDRAYX(
                OBSPOS.as_slice(),
                TRGDIR.as_slice(),
                *TRGEPC,
                SPOINT.as_slice_mut(),
                FOUND,
                ctx,
            )?;

            if FAILED(ctx) {
                CHKOUT(RNAME, ctx)?;
                return Ok(());
            }

            I = (I + 1);
        }

        //
        // If there's still no intercept, we're done.
        //
        if !*FOUND {
            CHKOUT(RNAME, ctx)?;
            return Ok(());
        }
    }

    //
    // Making it to this point means we've got an intersection, given
    // our current light time estimate. It's possible that a better
    // light time estimate will yield no intersection.
    //
    // Since we're using light time corrections, we're going to make
    // an estimate of light time to the intercept point, then re-do
    // our computation of the target position and orientation using
    // the new light time value.
    //
    if USECN {
        NITR = MAXITR;
    } else {
        NITR = 1;
    }

    //
    // Compute new light time estimate and new target epoch.
    //
    DIST = VDIST(OBSPOS.as_slice(), SPOINT.as_slice());
    LT = (DIST / CLIGHT());
    *TRGEPC = (ET + (S * LT));

    PREVLT = 0.0;
    PREVET = *TRGEPC;

    I = 0;
    LTDIFF = 1.0;
    ETDIFF = 1.0;

    while (((I < NITR) && (LTDIFF > (CNVLIM * f64::abs(LT)))) && (ETDIFF > 0.0)) {
        //
        // Get the J2000-relative state of the target relative to
        // the solar system barycenter at the target epoch.
        //
        SPKSSB(TRGCDE, *TRGEPC, b"J2000", SSBTST.as_slice_mut(), ctx)?;

        if FAILED(ctx) {
            CHKOUT(RNAME, ctx)?;
            return Ok(());
        }

        //
        // Find the position of the observer relative to the target.
        // Convert this vector from the J2000 frame to the target frame
        // at TRGEPC.
        //
        // Note that SSBOST contains the J2000-relative state of the
        // observer relative to the solar system barycenter at ET.
        //
        VSUB(SSBOST.as_slice(), SSBTST.as_slice(), J2POS.as_slice_mut());
        PXFORM(b"J2000", FIXREF, *TRGEPC, XFORM.as_slice_mut(), ctx)?;

        if FAILED(ctx) {
            CHKOUT(RNAME, ctx)?;
            return Ok(());
        }

        //
        // Convert the observer's position relative to the target from
        // the J2000 frame to the target frame at the target epoch.
        //
        MXV(XFORM.as_slice(), J2POS.as_slice(), OBSPOS.as_slice_mut());
        VMINUS(OBSPOS.as_slice(), NEGPOS.as_slice_mut());

        //
        // Convert the ray's direction vector from the J2000 frame
        // to the target frame at the target epoch.
        //
        MXV(XFORM.as_slice(), J2DIR.as_slice(), TRGDIR.as_slice_mut());

        //
        // Repeat the intercept computation.
        //
        UDRAYX(
            OBSPOS.as_slice(),
            TRGDIR.as_slice(),
            *TRGEPC,
            SPOINT.as_slice_mut(),
            FOUND,
            ctx,
        )?;

        if FAILED(ctx) {
            CHKOUT(RNAME, ctx)?;
            return Ok(());
        }

        //
        // If there's no intercept, try to get an estimate of the
        // intercept location and the light time by using the nearest
        // point to the ellipsoid on the line containing the ray. This is
        // useful only if the iteration count has not reached its maximum
        // value (the termination value minus one), since the point of
        // this is to make it possible to find an intercept.
        //
        // Note that an intercept was already found using the initial
        // aberration corrections, so we can't get to this case unless
        // we have near-intercept geometry.
        //
        if !*FOUND {
            if (I < (NITR - 1)) {
                //
                // SPOINT is the nearest point to the ellipsoid on the
                // line containing the ray.
                //
                UDNEAR(
                    OBSPOS.as_slice(),
                    TRGDIR.as_slice(),
                    ET,
                    PNEAR.as_slice_mut(),
                    &mut RAYALT,
                    ctx,
                )?;

                NPLNPT(
                    OBSPOS.as_slice(),
                    TRGDIR.as_slice(),
                    PNEAR.as_slice(),
                    SPOINT.as_slice_mut(),
                    &mut RAYALT,
                    ctx,
                )?;
            } else {
                //
                // We're not going to find an intercept.
                //
                CHKOUT(RNAME, ctx)?;
                return Ok(());
            }
        }

        //
        // Compute the distance between intercept and observer.
        //
        DIST = VDIST(OBSPOS.as_slice(), SPOINT.as_slice());

        //
        // Compute a new light time estimate and a new target epoch.
        //
        LT = (DIST / CLIGHT());

        *TRGEPC = (ET + (S * LT));

        //
        // We use the d.p. identity function TOUCHD to force the compiler
        // to create double precision arguments from the differences
        // LT-PREVLT and TRGEPC-PREVET. Some compilers will perform
        // extended-precision register arithmetic, which can prevent a
        // difference from rounding to zero. Simply storing the result of
        // the subtraction in a double precision variable doesn't solve
        // the problem, because that variable can be optimized out of
        // existence.
        //
        LTDIFF = f64::abs(TOUCHD((LT - PREVLT)));
        ETDIFF = f64::abs(TOUCHD((*TRGEPC - PREVET)));
        PREVLT = LT;
        PREVET = *TRGEPC;
        I = (I + 1);
    }

    //
    // FOUND, SPOINT, TRGEPC, and OBSPOS have been set at this point.
    // We need SRFVEC. Since OBSPOS doesn't take into account stellar
    // aberration, we can' derive SRFVEC from OBSPOS as is done in
    // the related routines SUBPNT and SUBSLR. Here, we derive
    // SRFVEC from J2GEOM, which is the input ray direction expressed in
    // the J2000 frame. We use XFORM, which is computed in the loop
    // above, to convert J2GEOM to FIXREF, evaluated at TRGEPC.
    //
    MXV(XFORM.as_slice(), J2GEOM.as_slice(), UDIR.as_slice_mut());
    VHATIP(UDIR.as_slice_mut());

    //
    // Let SRFLEN be the length of SRFVEC; we CAN get this
    // length from OBSPOS and SPOINT, since stellar
    // aberration correction (as implemented in SPICE)
    // doesn't change the length of the vector SPOINT-OBSPOS.
    //
    SRFLEN = VDIST(SPOINT.as_slice(), OBSPOS.as_slice());

    //
    // Scale UDIR to obtain the desired value of SRFVEC.
    //
    VSCL(SRFLEN, UDIR.as_slice(), SRFVEC.as_slice_mut());

    CHKOUT(RNAME, ctx)?;
    Ok(())
}