@@ -217,8 +217,7 @@ static public List<Segment> BuildSegments(MissionGraph graph, VehicleClass vehic
217217 segments . AddRange ( GenerateSplineSegments ( graph , edge , flags , overrideSrcPos ) ) ;
218218 break ;
219219 case SegmentKind . ArcTurn :
220- // TODO: generate arc turn segment
221- segments . Add ( GenerateStraightSegment ( graph , edge , flags , overrideSrcPos , overrideDestPos ) ) ;
220+ segments . Add ( GenerateArcSegment ( graph , edge , flags , overrideSrcPos , overrideDestPos ) ) ;
222221 break ;
223222 }
224223 }
@@ -293,6 +292,64 @@ static bool TryGetSegmentKind(MissionNode dest, out SegmentKind kind)
293292 }
294293 }
295294
295+ static Segment GenerateArcSegment ( MissionGraph graph , MissionEdge edge , SegmentFlags flags , PointLatLngAlt overrideSrcPos , PointLatLngAlt overrideDestPos , int samplesPerFullTurn = 40 )
296+ {
297+ var src = edge . FromNode ;
298+ var dest = edge . ToNode ;
299+ var srcPos = overrideSrcPos ?? new PointLatLngAlt ( src . Command ) ;
300+ var destPos = overrideDestPos ?? new PointLatLngAlt ( dest . Command ) ;
301+ var chordDist = srcPos . GetDistance2 ( destPos ) ;
302+ var arcAngleDeg = ( double ) dest . Command . p1 ;
303+ var halfAngleRad = arcAngleDeg * Math . PI / 360.0 ;
304+ var sinHalf = Math . Sin ( halfAngleRad ) ;
305+
306+ // Fall back to straight when sin(angle/2) is near zero (radius blows up). The threshold
307+ // is 1 degree from multiples of 360: inferred from the 359 in the MAVLink spec.
308+ const double halfTolerance = 0.5 * Math . PI / 180.0 ;
309+ if ( Math . Abs ( sinHalf ) < Math . Sin ( halfTolerance ) )
310+ {
311+ return GenerateStraightSegment ( graph , edge , flags , overrideSrcPos , overrideDestPos ) ;
312+ }
313+
314+ // Signed perpendicular offset from chord midpoint to arc center. Positive means center
315+ // is to the right of the chord (looking from src to dest) and negative means left.
316+ // tan(half) naturally handles all four cases: CW/CCW and <180/>180.
317+ var centerOffset = chordDist / ( 2.0 * Math . Tan ( halfAngleRad ) ) ;
318+
319+ var chordBearing = srcPos . GetBearing ( destPos ) ;
320+ var midpoint = srcPos . newpos ( chordBearing , chordDist / 2.0 ) ;
321+ var center = midpoint . newpos ( chordBearing + 90.0 , centerOffset ) ;
322+ var startBearing = center . GetBearing ( srcPos ) ;
323+
324+ // AP supports angles beyond 360 (e.g. 540 = full circle then semicircle). We strip off
325+ // full turns beyond two turns to bound the vertex counts; two turns (not one) so the
326+ // midpoint marker doesn't land on top of either endpoint.
327+ var absAngle = Math . Abs ( arcAngleDeg ) ;
328+ if ( absAngle > 720.0 )
329+ absAngle -= Math . Ceiling ( ( absAngle - 720.0 ) / 360.0 ) * 360.0 ;
330+ int samples = Math . Max ( 2 , ( int ) ( samplesPerFullTurn * absAngle / 360.0 ) + 2 ) ;
331+ var drawSweep = Math . Sign ( arcAngleDeg ) * absAngle ;
332+
333+ var radius = chordDist / ( 2.0 * Math . Abs ( sinHalf ) ) ;
334+ var path = new List < PointLatLngAlt > ( samples + 1 ) ;
335+ for ( int i = 0 ; i <= samples ; i ++ )
336+ {
337+ double bearing = startBearing + drawSweep * ( i / ( double ) samples ) ;
338+ path . Add ( center . newpos ( bearing , radius ) ) ;
339+ }
340+
341+ var midBearing = startBearing + drawSweep * 0.5 ;
342+ return new Segment
343+ {
344+ Kind = SegmentKind . ArcTurn ,
345+ Flags = flags ,
346+ StartNode = src ,
347+ EndNode = dest ,
348+ Path = path ,
349+ Midpoint = center . newpos ( midBearing , radius ) ,
350+ } ;
351+ }
352+
296353 static Segment GenerateStraightSegment ( MissionGraph graph , MissionEdge edge , SegmentFlags flags , PointLatLngAlt overrideSrcPos , PointLatLngAlt overrideDestPos )
297354 {
298355 var src = edge . FromNode ;
0 commit comments