@@ -269,79 +269,100 @@ function slice_mesh_at_plane(vertices, faces, point, normal; tol=1e-6)
269269 return segments
270270end
271271
272+ """
273+ cut_station(vertices, faces, point, tangent) -> (; le, te) or nothing
274+
275+ Cut the mesh with the vertical spanwise plane through `point` whose normal is the
276+ spanwise component of `tangent`, and return the crossings with the least and greatest
277+ `x` as the leading and trailing edge; `nothing` where the plane misses the mesh.
278+ """
279+ function cut_station (vertices, faces, point, tangent)
280+ segments = slice_mesh_at_plane (vertices, faces, point,
281+ normalize ([0.0 , tangent[2 ], 0.0 ]))
282+ isempty (segments) && return nothing
283+ crossings = Vector{Float64}[]
284+ for (start_point, end_point) in segments
285+ push! (crossings, start_point)
286+ push! (crossings, end_point)
287+ end
288+ return (; le= argmin (crossing -> crossing[1 ], crossings),
289+ te= argmax (crossing -> crossing[1 ], crossings))
290+ end
291+
292+ """
293+ tip_station(vertices, faces, prev_le, tangent, step) -> (; le, te, point) or nothing
294+
295+ Bisect the step of length `step` from `prev_le` along `tangent` for the outermost cut
296+ that still yields an airfoil section whose leading edge stays within `step` of
297+ `prev_le` chordwise; `nothing` where no such cut exists.
298+ """
299+ function tip_station (vertices, faces, prev_le, tangent, step)
300+ lo, hi, tip = 0.0 , step, nothing
301+ for _ in 1 : 24
302+ mid = (lo + hi) / 2
303+ probe = prev_le .+ mid .* tangent
304+ here = cut_station (vertices, faces, probe, tangent)
305+ valid = here != = nothing &&
306+ abs (here. le[1 ] - prev_le[1 ]) < step &&
307+ build_section (vertices, faces, here. le, here. te, probe, tangent) != = nothing
308+ valid ? (lo = mid; tip = (; here. le, here. te, point= probe)) : (hi = mid)
309+ end
310+ return tip
311+ end
312+
313+ """
314+ march_stations(vertices, faces, start_le, step, direction) -> Vector{NamedTuple}
315+
316+ March the leading edge from `start_le` in steps of arc length `step` towards `+y` for
317+ `direction = 1.0` or `-y` for `-1.0`, returning one `(; le, te, point)` per station,
318+ the tip station last. Stops when the leading edge stops advancing spanwise.
319+ """
320+ function march_stations (vertices, faces, start_le, step, direction)
321+ rows = NamedTuple[]
322+ prev_le = start_le
323+ tangent = [0.0 , direction, 0.0 ]
324+ for _ in 1 : 10_000
325+ probe = prev_le .+ step .* tangent
326+ found = cut_station (vertices, faces, probe, tangent)
327+ if found === nothing
328+ tip = tip_station (vertices, faces, prev_le, tangent, step)
329+ tip === nothing || push! (rows, tip)
330+ break
331+ end
332+ le_step = found. le .- prev_le
333+ (norm (le_step) < 1e-9 || le_step[2 ] * direction <= 0.0 ) && break
334+ push! (rows, (; found. le, found. te, point= probe))
335+ tangent = normalize (le_step)
336+ prev_le = found. le
337+ end
338+ return rows
339+ end
340+
272341"""
273342 march_edges(vertices, faces; step) -> (; le, te, point, tangent)
274343
275- March the leading edge outward from mid-span in both directions in steps of arc
276- length `step`. Each cut is a vertical spanwise plane (both the chordwise and vertical
277- components of the running LE tangent dropped from the normal) so a tip that curls
278- downward can't tilt the plane toward horizontal, where its min-chord "LE" pick would
279- jump across the wing. Marching stops when the leading edge stops advancing spanwise.
280- Cuts sample mesh *edges*, so the picks are robust to vertex density. Returns, ordered
281- along the span, the LE/TE points and each cut's plane origin and tangent. Build the
344+ Cut the mesh at mid-span and march the leading edge outward from there in both
345+ directions with `march_stations`. Returns, ordered along the span, the LE/TE points,
346+ each cut's plane origin, and the LE tangent: the central difference over the
347+ neighbouring stations, where the end stations reuse their inner neighbour's. Build the
282348airfoil for a chosen station with `build_section`.
283349"""
284350function march_edges (vertices, faces; step)
285351 ys = [v[2 ] for v in vertices]
286352 y_min, y_max = extrema (ys)
287353 y_mid = (y_min + y_max) / 2
288354
289- # Vertical spanwise plane (z dropped): a tip curl mustn't tilt the cut horizontal.
290- cut (point, tangent) = begin
291- segments = slice_mesh_at_plane (vertices, faces, point,
292- normalize ([0.0 , tangent[2 ], 0.0 ]))
293- isempty (segments) && return nothing
294- crossings = Vector{Float64}[]
295- for (start_point, end_point) in segments
296- push! (crossings, start_point)
297- push! (crossings, end_point)
298- end
299- return (; le= argmin (pt -> pt[1 ], crossings), te= argmax (pt -> pt[1 ], crossings))
300- end
301-
302- mid_cut = cut ([0.0 , y_mid, 0.0 ], [0.0 , 1.0 , 0.0 ])
355+ mid_cut = cut_station (vertices, faces, [0.0 , y_mid, 0.0 ], [0.0 , 1.0 , 0.0 ])
303356 mid_cut === nothing && error (" Could not slice mid-span; check mesh / rotation." )
304357
305- march (direction) = begin
306- rows = NamedTuple[]
307- prev_le = mid_cut. le
308- tangent = [0.0 , direction, 0.0 ]
309- for _ in 1 : 10_000
310- probe = prev_le .+ step .* tangent
311- found = cut (probe, tangent)
312- if found === nothing
313- # Bisect the last step to land the tip station on the outermost cut
314- # that still yields a valid (non-degenerate) airfoil section.
315- lo, hi, tip = 0.0 , step, nothing
316- for _ in 1 : 24
317- mid = (lo + hi) / 2
318- probe_mid = prev_le .+ mid .* tangent
319- here = cut (probe_mid, tangent)
320- # Reject a near-degenerate tip slice whose min-chord "LE" has jumped
321- # chordwise (an artifact that would misplace the tip station).
322- valid = here != = nothing &&
323- abs (here. le[1 ] - prev_le[1 ]) < step &&
324- build_section (vertices, faces, here. le, here. te,
325- probe_mid, tangent) != = nothing
326- valid ? (lo = mid; tip = (; here. le, here. te, point= probe_mid)) :
327- (hi = mid)
328- end
329- tip === nothing || push! (rows, (; tip. le, tip. te, tip. point, tangent))
330- break
331- end
332- le_step = found. le .- prev_le
333- (norm (le_step) < 1e-9 || le_step[2 ] * direction <= 0.0 ) && break
334- push! (rows, (; found. le, found. te, point= probe, tangent))
335- tangent = normalize (le_step)
336- prev_le = found. le
337- end
338- return rows
339- end
340-
341- center = (; mid_cut. le, mid_cut. te, point= [0.0 , y_mid, 0.0 ], tangent= [0.0 , 1.0 , 0.0 ])
342- rows = vcat (reverse (march (- 1.0 )), [center], march (1.0 ))
343- return (; le= [r. le for r in rows], te= [r. te for r in rows],
344- point= [r. point for r in rows], tangent= [r. tangent for r in rows])
358+ center = (; mid_cut. le, mid_cut. te, point= [0.0 , y_mid, 0.0 ])
359+ negative_y = march_stations (vertices, faces, mid_cut. le, step, - 1.0 )
360+ positive_y = march_stations (vertices, faces, mid_cut. le, step, 1.0 )
361+ rows = vcat (reverse (negative_y), [center], positive_y)
362+ le = [row. le for row in rows]
363+ middle = clamp .(eachindex (le), 2 , length (le) - 1 )
364+ tangent = [normalize (le[j + 1 ] .- le[j - 1 ]) for j in middle]
365+ return (; le, te= [row. te for row in rows], point= [row. point for row in rows], tangent)
345366end
346367
347368"""
0 commit comments