@@ -232,7 +232,7 @@ mutable struct Wing <: AbstractWing
232232 refined_sections:: Vector{Section}
233233 remove_nan:: Bool
234234 use_prior_polar:: Bool
235- billowing_angle :: Float64 # Half-angle of circular arc [rad] (0=straight, π/2=semicircle )
235+ billowing_percentage :: Float64 # TE billow as percentage of arc length (0=flat )
236236
237237 # Grouping
238238 refined_panel_mapping:: Vector{Int16} # Maps each refined panel index to unrefined section index (1 to n_unrefined_sections)
262262 spanwise_direction::PosVector=MVec3([0.0, 1.0, 0.0]),
263263 remove_nan::Bool=true,
264264 use_prior_polar::Bool=false,
265- billowing_angle ::Float64=0.0)
265+ billowing_percentage ::Float64=0.0)
266266
267267Constructor for a [Wing](@ref) struct with default values that initializes the sections
268268and refined sections as empty arrays. Creates a basic wing suitable for YAML-based construction.
@@ -274,15 +274,15 @@ and refined sections as empty arrays. Creates a basic wing suitable for YAML-bas
274274- `spanwise_direction::MVec3` = MVec3([0.0, 1.0, 0.0]): Wing span direction vector
275275- `remove_nan::Bool`: Whether to remove the NaNs from interpolations or not
276276- `use_prior_polar::Bool`: Reuse prior refined/panel polar mapping during geometry-only updates
277- - `billowing_angle ::Float64`: Half-angle of billowing arc in radians (0=straight, π/2=semicircle )
277+ - `billowing_percentage ::Float64`: TE billow as percentage of arc length (0=flat )
278278"""
279279function Wing (n_panels:: Int ;
280280 n_unrefined_sections= nothing ,
281281 spanwise_distribution:: PanelDistribution = LINEAR,
282282 spanwise_direction:: PosVector = MVec3 ([0.0 , 1.0 , 0.0 ]),
283283 remove_nan= true ,
284284 use_prior_polar= false ,
285- billowing_angle = 0.0 )
285+ billowing_percentage = 0.0 )
286286
287287 # For YAML wings, n_unrefined_sections will be set when sections are added
288288 # Set to 0 as placeholder for now
@@ -293,7 +293,7 @@ function Wing(n_panels::Int;
293293 # Initialize with default/empty values for optional fields
294294 Wing (
295295 n_panels, n_unrefined_sections_value, spanwise_distribution, panel_props, spanwise_direction,
296- Section[], Section[], remove_nan, use_prior_polar, Float64 (billowing_angle ),
296+ Section[], Section[], remove_nan, use_prior_polar, Float64 (billowing_percentage ),
297297 # Grouping
298298 Int16[],
299299 # Deformation fields
@@ -1115,17 +1115,18 @@ function refine_mesh_for_linear_cosine_distribution!(
11151115end
11161116
11171117"""
1118- refine_mesh_by_splitting_provided_sections!(wing; reuse_aero_data, billowing_angle)
1118+ refine_mesh_by_splitting_provided_sections!(wing; reuse_aero_data,
1119+ billowing_percentage)
11191120
11201121Refine mesh by splitting provided sections into desired number of panels.
11211122
1122- When `billowing_angle > 0`, applies circular arc TE displacement to intermediate
1123+ When `billowing_percentage > 0`, applies catenary TE displacement to intermediate
11231124sections within each rib pair (simulating fabric billowing between ribs).
11241125"""
11251126function refine_mesh_by_splitting_provided_sections! (
11261127 wing:: AbstractWing ;
11271128 reuse_aero_data:: Bool = false ,
1128- billowing_angle :: Float64 = 0.0
1129+ billowing_percentage :: Float64 = 0.0
11291130)
11301131 n_sections_provided = length (wing. unrefined_sections)
11311132 n_panels_provided = n_sections_provided - 1
@@ -1212,8 +1213,8 @@ function refine_mesh_by_splitting_provided_sections!(
12121213 reuse_aero_data
12131214 )
12141215
1215- # Apply billowing arc to the just-created sections
1216- if billowing_angle > 0 && idx > start_idx
1216+ # Apply catenary billowing to the just-created sections
1217+ if billowing_percentage > 0 && idx > start_idx
12171218 LE_1 = LE[left_section_index]
12181219 TE_1 = TE[left_section_index]
12191220 LE_2 = LE[left_section_index + 1 ]
@@ -1228,40 +1229,25 @@ function refine_mesh_by_splitting_provided_sections!(
12281229 if z_norm > 1e-10
12291230 z_hat = z_hat / z_norm
12301231
1231- TE_mid = (TE_1 + TE_2) / 2
12321232 d = abs (dot (TE_1 - TE_2, y_hat))
1233- span_len = norm (LE_1 - LE_2)
1234- chord_len_1 = norm (chord_1)
1235- chord_len_2 = norm (chord_2)
12361233
12371234 if d > 1e-12
1238- R = (d / 2 ) / sin (billowing_angle)
1239- h = (d / 2 ) / tan (billowing_angle)
1235+ a = catenary_parameter (
1236+ billowing_percentage, d)
1237+ u = d / (2 a)
1238+ span_len = norm (LE_1 - LE_2)
1239+ TE_mid = (TE_1 + TE_2) / 2
12401240
12411241 for si in start_idx: (idx - 1 )
12421242 sec = wing. refined_sections[si]
12431243 t = dot (sec. LE_point - LE_2,
12441244 y_hat) / span_len
1245-
1246- arc_y = - R * sin (
1247- billowing_angle * (1 - 2 t))
1248- arc_z = - h + R * cos (
1249- billowing_angle * (1 - 2 t))
1250- arc_TE = TE_mid +
1251- arc_y * y_hat + arc_z * z_hat
1252-
1253- chord_dir = arc_TE - sec. LE_point
1254- chord_dir_len = norm (chord_dir)
1255- if chord_dir_len > 1e-12
1256- target_chord = (
1257- t * chord_len_1 +
1258- (1 - t) * chord_len_2)
1259- sec. TE_point .= (
1260- sec. LE_point +
1261- (target_chord /
1262- chord_dir_len) *
1263- chord_dir)
1264- end
1245+ x = d * (t - 0.5 )
1246+ sag = a * (cosh (x / a) -
1247+ cosh (u))
1248+ arc_y = d * (t - 0.5 )
1249+ sec. TE_point .= TE_mid +
1250+ arc_y * y_hat + sag * z_hat
12651251 end
12661252 end
12671253 end
@@ -1293,63 +1279,59 @@ end
12931279
12941280
12951281"""
1296- billowing_angle_from_percentage (percentage)
1282+ catenary_parameter (percentage, d )
12971283
1298- Compute the billowing half-angle θ (in radians) such that the chord between two
1299- circle intersections is `percentage`% shorter than the minor arc .
1284+ Compute the catenary parameter `a` such that a catenary spanning distance `d`
1285+ has an arc length that is `percentage`% longer than `d` .
13001286
1301- Solves `sin(θ) = θ * (1 - percentage / 100)` using Newton's method.
1287+ Solves `u / sinh(u) = 1 - percentage / 100` where `u = d / (2a)`,
1288+ using Newton's method.
13021289
13031290# Arguments
1304- - `percentage::Real`: How much shorter the chord is relative to the arc (0–100).
1305- 0 means chord equals arc (θ → 0), ~36.3 means a semicircle (θ = π/2).
1291+ - `percentage::Real`: How much shorter the chord is relative to the arc
1292+ (0–100). 0 means no sag (flat), larger values mean deeper catenary.
1293+ - `d::Real`: Span distance between the two endpoints.
13061294
13071295# Returns
1308- - `Float64`: The half-angle θ in radians (same convention as `Wing.billowing_angle`).
1309-
1310- # Example
1311- ```julia
1312- julia> rad2deg(billowing_angle_from_percentage(10))
1313- 43.66…
1314- ```
1296+ - `Float64`: The catenary parameter `a`.
13151297"""
1316- function billowing_angle_from_percentage (percentage:: Real )
1317- percentage == 0 && return 0.0
1298+ function catenary_parameter (percentage:: Real , d :: Real )
1299+ percentage == 0 && return Inf
13181300 0 < percentage || throw (ArgumentError (
13191301 " percentage must be ≥ 0, got $percentage " ))
1320- percentage < 100 * (1 - 2 / π) || throw (ArgumentError (
1321- " percentage must be < $(100 * (1 - 2 / π)) (semicircle limit), " *
1322- " got $percentage " ))
1323- factor = 1 - percentage / 100
1324- # Initial guess from Taylor expansion: sin(θ) ≈ θ - θ³/6
1325- θ = sqrt (6 * percentage / 100 )
1302+ percentage < 100 || throw (ArgumentError (
1303+ " percentage must be < 100, got $percentage " ))
1304+ target = 1 - percentage / 100 # u / sinh(u) = target
1305+ # Initial guess from Taylor: u/sinh(u) ≈ 1 - u²/6
1306+ u = sqrt (6 * (1 - target))
13261307 for _ in 1 : 100
1327- f = sin (θ) - θ * factor
1328- df = cos (θ) - factor
1308+ f = u / sinh (u) - target
1309+ # d/du [u/sinh(u)] = (sinh(u) - u*cosh(u)) / sinh(u)^2
1310+ sh = sinh (u)
1311+ df = (sh - u * cosh (u)) / sh^ 2
13291312 δ = f / df
1330- θ -= δ
1313+ u -= δ
13311314 abs (δ) < 1e-12 && break
13321315 end
1333- return θ
1316+ return d / ( 2 u)
13341317end
13351318
13361319"""
13371320 refine_mesh_with_billowing!(wing; reuse_aero_data)
13381321
1339- Refine wing mesh using SPLIT_PROVIDED spacing with circular arc billowing.
1340-
1341- Between each pair of unrefined (rib) sections, the trailing edge follows a circular arc
1342- that bulges in the direction perpendicular to the chord and span (simulating fabric
1343- billowing between ribs on a ram-air kite). The leading edge stays linearly interpolated.
1322+ Refine wing mesh using SPLIT_PROVIDED spacing with catenary TE billowing.
13441323
1345- The arc half-angle is `wing.billowing_angle` (0 = straight line, π/2 = semicircle).
1324+ Between each pair of unrefined (rib) sections, the trailing edge follows a
1325+ catenary curve that sags perpendicular to the chord and span (simulating fabric
1326+ billowing between ribs on a ram-air kite). The leading edge stays linearly
1327+ interpolated.
13461328
13471329Delegates to [`refine_mesh_by_splitting_provided_sections!`](@ref).
13481330"""
13491331function refine_mesh_with_billowing! (wing; reuse_aero_data:: Bool = false )
13501332 refine_mesh_by_splitting_provided_sections! (
13511333 wing; reuse_aero_data,
1352- billowing_angle = wing. billowing_angle
1334+ billowing_percentage = wing. billowing_percentage
13531335 )
13541336end
13551337
0 commit comments