@@ -111,10 +111,13 @@ Create interpolation functions for leading/trailing edges and area.
111111- Tuple of (le_interp, te_interp, area_interp) interpolation functions
112112- Where le_interp and te_interp are tuples themselves, containing the x, y and z interpolations
113113"""
114- function create_interpolations (vertices, circle_center_z, radius, gamma_tip)
115- gamma_range = range (- gamma_tip+ 1e-6 , gamma_tip- 1e-6 , 100 )
114+ function create_interpolations (vertices, circle_center_z, radius, gamma_tip, R; interp_steps= 40 )
115+ gamma_range = range (- gamma_tip+ 1e-6 , gamma_tip- 1e-6 , interp_steps)
116+ stepsize = gamma_range. step. hi
116117 vz_centered = [v[3 ] - circle_center_z for v in vertices]
117118
119+ te_gammas = zeros (length (gamma_range))
120+ le_gammas = zeros (length (gamma_range))
118121 trailing_edges = zeros (3 , length (gamma_range))
119122 leading_edges = zeros (3 , length (gamma_range))
120123 areas = zeros (length (gamma_range))
@@ -124,59 +127,90 @@ function create_interpolations(vertices, circle_center_z, radius, gamma_tip)
124127 leading_edges[1 , j] = Inf
125128 for (i, v) in enumerate (vertices)
126129 # Rotate y coordinate to check box containment
127- rotated_y = v[2 ] * cos (- gamma) - vz_centered[i] * sin (- gamma)
128- if gamma ≤ 0.0 && 0.0 ≤ rotated_y ≤ 0.5
130+ # rotated_y = v[2] * cos(-gamma) - vz_centered[i] * sin(-gamma)
131+ gamma_v = atan (- v[2 ], vz_centered[i])
132+ if gamma < 0 && gamma - stepsize ≤ gamma_v ≤ gamma
129133 if v[1 ] > trailing_edges[1 , j]
130134 trailing_edges[:, j] .= v
135+ te_gammas[j] = gamma_v
131136 end
132137 if v[1 ] < leading_edges[1 , j]
133138 leading_edges[:, j] .= v
139+ le_gammas[j] = gamma_v
134140 end
135- elseif gamma > 0.0 && - 0.5 ≤ rotated_y ≤ 0.0
141+ elseif gamma > 0 && gamma ≤ gamma_v ≤ gamma + stepsize
136142 if v[1 ] > trailing_edges[1 , j]
137143 trailing_edges[:, j] .= v
144+ te_gammas[j] = gamma_v
138145 end
139146 if v[1 ] < leading_edges[1 , j]
140147 leading_edges[:, j] .= v
148+ le_gammas[j] = gamma_v
141149 end
142150 end
143151 end
144- area = norm (leading_edges[:, j] - trailing_edges[:, j]) * gamma_range . step * radius
152+ area = norm (leading_edges[:, j] - trailing_edges[:, j]) * stepsize * radius
145153 last_area = j > 1 ? areas[j- 1 ] : 0.0
146154 areas[j] = last_area + area
147155 end
148156
149- le_interp = ntuple (i -> linear_interpolation (gamma_range, leading_edges[i, :],
157+ for j in eachindex (gamma_range)
158+ leading_edges[:, j] .= R * leading_edges[:, j]
159+ trailing_edges[:, j] .= R * trailing_edges[:, j]
160+ end
161+
162+ le_interp = ntuple (i -> linear_interpolation (te_gammas, leading_edges[i, :],
150163 extrapolation_bc= Line ()), 3 )
151- te_interp = ntuple (i -> linear_interpolation (gamma_range , trailing_edges[i, :],
164+ te_interp = ntuple (i -> linear_interpolation (le_gammas , trailing_edges[i, :],
152165 extrapolation_bc= Line ()), 3 )
153166 area_interp = linear_interpolation (gamma_range, areas, extrapolation_bc= Line ())
154167
155168 return (le_interp, te_interp, area_interp)
156169end
157170
158- # Makes the zero coordinates lie on the com
171+ """
172+ center_to_com!(vertices, faces)
173+
174+ Calculate center of mass of a mesh and translate vertices so that COM is at origin.
175+
176+ # Arguments
177+ - `vertices`: Vector of 3D point coordinates
178+ - `faces`: Vector of vertex indices for each face (can be triangular or non-triangular)
179+
180+ # Returns
181+ - Vector representing the original center of mass before translation
182+
183+ # Notes
184+ - Non-triangular faces are automatically triangulated into triangles
185+ - Assumes uniform surface density
186+ """
159187function center_to_com! (vertices, faces)
160188 area_total = 0.0
161189 com = zeros (3 )
162190
163191 for face in faces
164- v1 = vertices[face[1 ]]
165- v2 = vertices[face[2 ]]
166- v3 = vertices[face[3 ]]
167-
168- # Calculate triangle area and centroid
169- normal = cross (v2 - v1, v3 - v1)
170- area = norm (normal) / 2
171- centroid = (v1 + v2 + v3) / 3
172-
173- area_total += area
174- com += area * centroid
192+ if length (face) == 3
193+ # Triangle case
194+ v1 = vertices[face[1 ]]
195+ v2 = vertices[face[2 ]]
196+ v3 = vertices[face[3 ]]
197+
198+ # Calculate triangle area and centroid
199+ normal = cross (v2 - v1, v3 - v1)
200+ area = norm (normal) / 2
201+ centroid = (v1 + v2 + v3) / 3
202+
203+ area_total += area
204+ com += area * centroid
205+ else
206+ throw (ArgumentError (" Triangulate faces in a CAD program first" ))
207+ end
175208 end
176209
177210 com = com / area_total
178211 ! (abs (com[2 ]) < 0.01 ) && throw (ArgumentError (" Center of mass $com of .obj file has to lie on the xz-plane." ))
179212 @info " Centering vertices of .obj file to the center of mass: $com "
213+ com[2 ] = 0.0
180214 for v in vertices
181215 v .- = com
182216 end
@@ -245,6 +279,40 @@ function calculate_inertia_tensor(vertices, faces, mass, com)
245279 return (mass / total_area) * I / 3
246280end
247281
282+ function calc_inertia_y_rotation (I_b_tensor)
283+ # Function for nonlinear solver - off-diagonal element should be zero
284+ function eq! (F, theta, _)
285+ # Rotation matrix around y-axis
286+ R_y = [
287+ cos (theta[1 ]) 0 sin (theta[1 ]);
288+ 0 1 0 ;
289+ - sin (theta[1 ]) 0 cos (theta[1 ])
290+ ]
291+ # Transform inertia tensor
292+ I_rotated = R_y * I_b_tensor * R_y'
293+ # We want the off-diagonal xz elements to be zero
294+ F[1 ] = I_rotated[1 ,3 ]
295+ end
296+
297+ theta0 = [0.0 ]
298+ prob = NonlinearProblem (eq!, theta0, nothing )
299+ sol = NonlinearSolve. solve (prob, NewtonRaphson ())
300+ theta_opt = sol. u[1 ]
301+
302+ R_b_p = [
303+ cos (theta_opt) 0 sin (theta_opt);
304+ 0 1 0 ;
305+ - sin (theta_opt) 0 cos (theta_opt)
306+ ]
307+ @show theta_opt
308+ # Calculate diagonalized inertia tensor
309+ I_diag = R_b_p * I_b_tensor * R_b_p'
310+ @show I_diag
311+ @assert isapprox (I_diag[1 ,3 ], 0.0 , atol= 1e-5 )
312+ @show R_b_p
313+ return I_diag, R_b_p
314+ end
315+
248316"""
249317 interpolate_matrix_nans!(matrix::Matrix{Float64})
250318
@@ -330,7 +398,6 @@ mutable struct RamAirWing <: AbstractWing
330398 # Additional fields for RamAirWing
331399 non_deformed_sections:: Vector{Section}
332400 mass:: Float64
333- circle_center_z:: Float64
334401 gamma_tip:: Float64
335402 inertia_tensor:: Matrix{Float64}
336403 center_of_mass:: Vector{Float64}
@@ -367,8 +434,8 @@ Constructor for a [RamAirWing](@ref) that allows to use an `.obj` and a `.dat` f
367434"""
368435function RamAirWing (obj_path, dat_path; alpha= 0.0 , crease_frac= 0.75 , wind_vel= 10. , mass= 1.0 ,
369436 n_panels= 54 , n_sections= n_panels+ 1 , spanwise_panel_distribution= UNCHANGED,
370- spanwise_direction= [0.0 , 1.0 , 0.0 ], remove_nan= true ,
371- alpha_range= deg2rad .(- 5 : 1 : 20 ), delta_range= deg2rad .(- 5 : 1 : 20 )
437+ spanwise_direction= [0.0 , 1.0 , 0.0 ], remove_nan= true , align_to_principal = false ,
438+ alpha_range= deg2rad .(- 5 : 1 : 20 ), delta_range= deg2rad .(- 5 : 1 : 20 ), interp_steps = 40
372439 )
373440
374441 ! isapprox (spanwise_direction, [0.0 , 1.0 , 0.0 ]) && throw (ArgumentError (" Spanwise direction has to be [0.0, 1.0, 0.0], not $spanwise_direction " ))
@@ -382,38 +449,48 @@ function RamAirWing(obj_path, dat_path; alpha=0.0, crease_frac=0.75, wind_vel=10
382449 (! isfile (obj_path)) && error (" OBJ file not found: $obj_path " )
383450 info_path = obj_path[1 : end - 4 ] * " _info.bin"
384451
385- if ! ispath (polar_path) || ! ispath (info_path)
452+ if true || ! ispath (info_path)
386453 @info " Reading $obj_path "
387454 vertices, faces = read_faces (obj_path)
388455 center_of_mass = center_to_com! (vertices, faces)
389456 inertia_tensor = calculate_inertia_tensor (vertices, faces, mass, zeros (3 ))
390457
391- circle_center_z, radius, gamma_tip = find_circle_center_and_radius (vertices)
392- le_interp, te_interp, area_interp = create_interpolations (vertices, circle_center_z, radius, gamma_tip)
458+ if align_to_principal
459+ inertia_tensor, R_b_p = calc_inertia_y_rotation (inertia_tensor)
460+ circle_center_z, radius, gamma_tip = find_circle_center_and_radius (vertices)
461+ le_interp, te_interp, area_interp = create_interpolations (vertices, circle_center_z, radius, gamma_tip, R_b_p; interp_steps)
462+ else
463+ circle_center_z, radius, gamma_tip = find_circle_center_and_radius (vertices)
464+ le_interp, te_interp, area_interp = create_interpolations (vertices, circle_center_z, radius, gamma_tip, I (3 ); interp_steps)
465+ end
466+
393467 @info " Writing $info_path "
394- serialize (info_path, (inertia_tensor, center_of_mass, circle_center_z, radius, gamma_tip,
468+ serialize (info_path, (inertia_tensor, center_of_mass, radius, gamma_tip,
395469 le_interp, te_interp, area_interp))
396-
397- width = 2 gamma_tip * radius
398- area = area_interp (gamma_tip)
399- @eval Main begin
400- foil_path, polar_path, v_wind, area, width, x_turn, alpha_range, delta_range =
401- $ dat_path, $ polar_path, $ wind_vel, $ gamma_tip, $ width, $ crease_frac, $ alpha_range, $ delta_range
402- include (joinpath (dirname (@__FILE__ ), " ../scripts/polars.jl" ))
403- end
404470 end
405471
406- @info " Loading polars and kite info from $polar_path and $info_path "
472+ @info " Loading kite info from $info_path and polars from $polar_path "
407473 try
474+ (inertia_tensor:: Matrix , center_of_mass:: Vector ,
475+ radius:: Real , gamma_tip:: Real , le_interp, te_interp, area_interp) = deserialize (info_path)
476+
477+ if ! ispath (polar_path)
478+ width = 2 gamma_tip * radius
479+ area = area_interp (gamma_tip)
480+ @eval Main begin
481+ foil_path, polar_path, v_wind, area, width, x_turn, alpha_range, delta_range =
482+ $ dat_path, $ polar_path, $ wind_vel, $ gamma_tip, $ width, $ crease_frac, $ alpha_range, $ delta_range
483+ include (joinpath (dirname (@__FILE__ ), " ../scripts/polars.jl" ))
484+ end
485+ end
486+
408487 (alpha_range, delta_range, cl_matrix:: Matrix , cd_matrix:: Matrix , cm_matrix:: Matrix ) = deserialize (polar_path)
409488 if remove_nan
410489 interpolate_matrix_nans! (cl_matrix)
411490 interpolate_matrix_nans! (cd_matrix)
412491 interpolate_matrix_nans! (cm_matrix)
413492 end
414- (inertia_tensor:: Matrix , center_of_mass:: Vector , circle_center_z:: Real ,
415- radius:: Real , gamma_tip:: Real , le_interp, te_interp, area_interp) = deserialize (info_path)
416-
493+
417494 # Create sections
418495 sections = Section[]
419496 refined_sections = Section[]
@@ -426,14 +503,15 @@ function RamAirWing(obj_path, dat_path; alpha=0.0, crease_frac=0.75, wind_vel=10
426503 push! (refined_sections, Section (LE_point, TE_point, POLAR_MATRICES, aero_data))
427504 push! (non_deformed_sections, Section (LE_point, TE_point, POLAR_MATRICES, aero_data))
428505 end
429-
506+
430507 RamAirWing (n_panels, spanwise_panel_distribution, spanwise_direction, sections,
431508 refined_sections, remove_nan, non_deformed_sections,
432- mass, circle_center_z, gamma_tip, inertia_tensor, center_of_mass, radius,
509+ mass, gamma_tip, inertia_tensor, center_of_mass, radius,
433510 le_interp, te_interp, area_interp, zeros (n_panels), zeros (n_panels))
434- catch e
511+
512+ catch
435513 if e isa BoundsError
436- @error " Delete $polar_path and $info_path and try again."
514+ @error " Delete $info_path and $polar_path and try again."
437515 end
438516 rethrow (e)
439517 end
@@ -460,32 +538,15 @@ function deform!(wing::RamAirWing, theta_dist::AbstractVector, delta_dist::Abstr
460538
461539 local_y = zeros (MVec3)
462540 chord = zeros (MVec3)
541+ normal = zeros (MVec3)
463542
464543 for i in 1 : wing. n_panels
465544 section1 = wing. non_deformed_sections[i]
466545 section2 = wing. non_deformed_sections[i+ 1 ]
467546 local_y .= normalize (section1. LE_point - section2. LE_point)
468547 chord .= section1. TE_point .- section1. LE_point
469- wing. sections[i]. TE_point .= section1. LE_point .+ rotate_v_around_k (chord, local_y, wing. theta_dist[i])
548+ normal .= chord × local_y
549+ @. wing. sections[i]. TE_point = section1. LE_point + cos (wing. theta_dist[i]) * chord - sin (wing. theta_dist[i]) * normal
470550 end
471551 return nothing
472552end
473-
474- """
475- rotate_v_around_k(v, k, θ)
476-
477- Rotate vector v around axis k by angle θ using Rodrigues' rotation formula.
478-
479- # Arguments
480- - `v`: Vector to rotate
481- - `k`: Rotation axis (will be normalized)
482- - `θ`: Rotation angle in radians
483-
484- # Returns
485- - Rotated vector
486- """
487- function rotate_v_around_k (v, k, θ)
488- k = normalize (k)
489- v_rot = v * cos (θ) + (k × v) * sin (θ) + k * (k ⋅ v) * (1 - cos (θ))
490- return v_rot
491- end
0 commit comments