@@ -1213,44 +1213,22 @@ function refine_mesh_by_splitting_provided_sections!(
12131213 reuse_aero_data
12141214 )
12151215
1216- # Apply catenary billowing to the just-created sections
1216+ # Apply billowing by rotating chords around LE
12171217 if billowing_percentage > 0 && idx > start_idx
1218- LE_1 = LE[left_section_index]
1219- TE_1 = TE[left_section_index]
1220- LE_2 = LE[left_section_index + 1 ]
1221- TE_2 = TE[left_section_index + 1 ]
1222-
1223- chord_1 = TE_1 - LE_1
1224- chord_2 = TE_2 - LE_2
1225- x_hat = normalize ((chord_1 + chord_2) / 2 )
1226- y_hat = normalize (LE_1 - LE_2)
1227- z_hat = cross (x_hat, y_hat)
1228- z_norm = norm (z_hat)
1229- if z_norm > 1e-10
1230- z_hat = z_hat / z_norm
1231-
1232- d = abs (dot (TE_1 - TE_2, y_hat))
1233-
1234- if d > 1e-12
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
1240-
1241- for si in start_idx: (idx - 1 )
1242- sec = wing. refined_sections[si]
1243- t = dot (sec. LE_point - LE_2,
1244- y_hat) / span_len
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
1251- end
1252- end
1253- end
1218+ y_hat = normalize (
1219+ LE[left_section_index] -
1220+ LE[left_section_index + 1 ])
1221+ span_len = norm (
1222+ LE[left_section_index] -
1223+ LE[left_section_index + 1 ])
1224+ apply_billowing_to_pair! (
1225+ wing. refined_sections,
1226+ start_idx, idx - 1 ,
1227+ y_hat, span_len,
1228+ LE[left_section_index + 1 ],
1229+ TE[left_section_index],
1230+ TE[left_section_index + 1 ],
1231+ billowing_percentage)
12541232 end
12551233 end
12561234 end
@@ -1279,52 +1257,112 @@ end
12791257
12801258
12811259"""
1282- catenary_parameter(percentage, d)
1260+ billowing_arc_length(sections, start_si, end_si, y_hat,
1261+ span_len, le_ref, te_left, te_right,
1262+ angle_max)
12831263
1284- Compute the catenary parameter `a` such that a catenary spanning distance `d`
1285- has an arc length that is `percentage`% longer than `d`.
1264+ Compute the TE arc length that would result from rotating each
1265+ section's chord around `y_hat` by `angle_max * sin(π t)` radians,
1266+ where `t` is the normalised spanwise position within the rib pair.
12861267
1287- Solves `u / sinh(u) = 1 - percentage / 100` where `u = d / (2a)`,
1288- using Newton's method.
1268+ Non-allocating: uses only stack-allocated MVec3 arithmetic.
1269+ """
1270+ function billowing_arc_length (
1271+ sections, start_si, end_si,
1272+ y_hat, span_len, le_ref,
1273+ te_left, te_right, angle_max
1274+ )
1275+ prev_te = MVec3 (te_left)
1276+ arc = 0.0
1277+ for si in start_si: end_si
1278+ sec = sections[si]
1279+ t = dot (sec. LE_point - le_ref, y_hat) / span_len
1280+ θ = - angle_max * sin (π * t)
1281+ chord_vec = sec. TE_point - sec. LE_point
1282+ ct = cos (θ); st = sin (θ)
1283+ d_y = dot (chord_vec, y_hat)
1284+ rotated = ct * chord_vec +
1285+ st * cross (y_hat, chord_vec) +
1286+ (1 - ct) * d_y * y_hat
1287+ current_te = sec. LE_point + rotated
1288+ arc += norm (current_te - prev_te)
1289+ prev_te = current_te
1290+ end
1291+ arc += norm (te_right - prev_te)
1292+ return arc
1293+ end
12891294
1290- # Arguments
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.
1295+ """
1296+ apply_billowing_to_pair!(sections, start_si, end_si, y_hat,
1297+ span_len, le_ref, te_left, te_right,
1298+ percentage)
12941299
1295- # Returns
1296- - `Float64`: The catenary parameter `a`.
1300+ Apply billowing to refined sections between two ribs by rotating
1301+ each section's chord around `y_hat`. Uses Newton iteration to
1302+ find the rotation amplitude that matches the target TE arc-length
1303+ `percentage`, then applies the rotations in-place.
1304+
1305+ The angle profile is `angle_max * sin(π t)` (zero at ribs,
1306+ maximum at centre). All angles are in radians.
1307+
1308+ Non-allocating: modifies `sections[start_si:end_si].TE_point`
1309+ in-place using only stack-allocated MVec3 arithmetic.
12971310"""
1298- function catenary_parameter (percentage:: Real , d:: Real )
1299- percentage == 0 && return Inf
1300- 0 < percentage || throw (ArgumentError (
1301- " percentage must be ≥ 0, got $percentage " ))
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))
1307- for _ in 1 : 100
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
1312- δ = f / df
1313- u -= δ
1314- abs (δ) < 1e-12 && break
1311+ function apply_billowing_to_pair! (
1312+ sections, start_si, end_si,
1313+ y_hat, span_len, le_ref,
1314+ te_left, te_right, percentage
1315+ )
1316+ percentage <= 0 && return nothing
1317+ straight = norm (te_right - te_left)
1318+ straight < 1e-12 && return nothing
1319+ target_arc = straight / (1 - percentage / 100 )
1320+
1321+ # Newton iteration on angle_max (rad)
1322+ angle_max = sqrt (4 * percentage / (100 - percentage))
1323+ for _ in 1 : 50
1324+ arc = billowing_arc_length (
1325+ sections, start_si, end_si,
1326+ y_hat, span_len, le_ref,
1327+ te_left, te_right, angle_max)
1328+ f = arc - target_arc
1329+ abs (f) < 1e-12 * target_arc && break
1330+
1331+ δ = max (1e-8 , abs (angle_max) * 1e-8 )
1332+ arc_p = billowing_arc_length (
1333+ sections, start_si, end_si,
1334+ y_hat, span_len, le_ref,
1335+ te_left, te_right, angle_max + δ)
1336+ df = (arc_p - arc) / δ
1337+ abs (df) < 1e-30 && break
1338+ angle_max -= f / df
13151339 end
1316- return d / (2 u)
1340+
1341+ # Apply converged rotations in-place
1342+ for si in start_si: end_si
1343+ sec = sections[si]
1344+ t = dot (sec. LE_point - le_ref, y_hat) / span_len
1345+ θ = - angle_max * sin (π * t)
1346+ chord_vec = sec. TE_point - sec. LE_point
1347+ ct = cos (θ); st = sin (θ)
1348+ d_y = dot (chord_vec, y_hat)
1349+ rotated = ct * chord_vec +
1350+ st * cross (y_hat, chord_vec) +
1351+ (1 - ct) * d_y * y_hat
1352+ sec. TE_point .= sec. LE_point + rotated
1353+ end
1354+ return nothing
13171355end
13181356
13191357"""
13201358 refine_mesh_with_billowing!(wing; reuse_aero_data)
13211359
1322- Refine wing mesh using SPLIT_PROVIDED spacing with catenary TE billowing.
1360+ Refine wing mesh using SPLIT_PROVIDED spacing with TE billowing.
13231361
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 .
1362+ Between each pair of unrefined (rib) sections, chord vectors are rotated
1363+ around the leading edge to simulate fabric billowing. The rotation
1364+ amplitude is found iteratively so that the TE arc length matches the
1365+ wing's `billowing_percentage` .
13281366
13291367Delegates to [`refine_mesh_by_splitting_provided_sections!`](@ref).
13301368"""
0 commit comments