@@ -155,28 +155,63 @@ function create_interpolations(vertices, circle_center_z, radius, gamma_tip)
155155 return (le_interp, te_interp, area_interp)
156156end
157157
158- # Makes the zero coordinates lie on the com
158+ """
159+ center_to_com!(vertices, faces)
160+
161+ Calculate center of mass of a mesh and translate vertices so that COM is at origin.
162+
163+ # Arguments
164+ - `vertices`: Vector of 3D point coordinates
165+ - `faces`: Vector of vertex indices for each face (can be triangular or non-triangular)
166+
167+ # Returns
168+ - Vector representing the original center of mass before translation
169+
170+ # Notes
171+ - Non-triangular faces are automatically triangulated into triangles
172+ - Assumes uniform surface density
173+ """
159174function center_to_com! (vertices, faces)
160175 area_total = 0.0
161176 com = zeros (3 )
162177
163178 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
179+ if length (face) == 3
180+ # Triangle case
181+ v1 = vertices[face[1 ]]
182+ v2 = vertices[face[2 ]]
183+ v3 = vertices[face[3 ]]
184+
185+ # Calculate triangle area and centroid
186+ normal = cross (v2 - v1, v3 - v1)
187+ area = norm (normal) / 2
188+ centroid = (v1 + v2 + v3) / 3
189+
190+ area_total += area
191+ com += area * centroid
192+ else
193+ println (" found non-triangular face" )
194+ # Non-triangular face case - triangulate the face
195+ v1 = vertices[face[1 ]] # Use first vertex as pivot
196+ for i in 2 : length (face)- 1
197+ v2 = vertices[face[i]]
198+ v3 = vertices[face[i+ 1 ]]
199+
200+ # Calculate triangle area and centroid for this triangle
201+ normal = cross (v2 - v1, v3 - v1)
202+ area = norm (normal) / 2
203+ centroid = (v1 + v2 + v3) / 3
204+
205+ area_total += area
206+ com += area * centroid
207+ end
208+ end
175209 end
176210
177211 com = com / area_total
178212 ! (abs (com[2 ]) < 0.01 ) && throw (ArgumentError (" Center of mass $com of .obj file has to lie on the xz-plane." ))
179213 @info " Centering vertices of .obj file to the center of mass: $com "
214+ com[2 ] = 0.0
180215 for v in vertices
181216 v .- = com
182217 end
@@ -245,6 +280,49 @@ function calculate_inertia_tensor(vertices, faces, mass, com)
245280 return (mass / total_area) * I / 3
246281end
247282
283+ function calc_inertia_y_rotation (I_b_tensor)
284+ # Function for nonlinear solver - off-diagonal element should be zero
285+ function eq! (F, theta, _)
286+ # Rotation matrix around y-axis
287+ R_y = [
288+ cos (theta[1 ]) 0 sin (theta[1 ]);
289+ 0 1 0 ;
290+ - sin (theta[1 ]) 0 cos (theta[1 ])
291+ ]
292+ # Transform inertia tensor
293+ I_rotated = R_y * I_b_tensor * R_y'
294+ # We want the off-diagonal xz elements to be zero
295+ F[1 ] = I_rotated[1 ,3 ]
296+ end
297+
298+ theta0 = [0.0 ]
299+ prob = NonlinearProblem (eq!, theta0, nothing )
300+ sol = NonlinearSolve. solve (prob, NewtonRaphson ())
301+ theta_opt = sol. u[1 ]
302+
303+ R_b_p = [
304+ cos (theta_opt) 0 sin (theta_opt);
305+ 0 1 0 ;
306+ - sin (theta_opt) 0 cos (theta_opt)
307+ ]
308+ @show theta_opt
309+ # Calculate diagonalized inertia tensor
310+ I_diag = R_b_p * I_b_tensor * R_b_p'
311+ @show I_diag
312+ @assert isapprox (I_diag[1 ,3 ], 0.0 , atol= 1e-5 )
313+ @show R_b_p
314+ return I_diag, R_b_p
315+ end
316+
317+ function align_to_principal! (vertices, I_b_tensor)
318+ I_diag, R_b_p = calc_inertia_y_rotation (I_b_tensor)
319+ for v in vertices
320+ v .= R_b_p * v # transform body frame vertices to principal frame
321+ end
322+ # the rotation between body frame and principal frame is now zero
323+ return I_diag
324+ end
325+
248326"""
249327 interpolate_matrix_nans!(matrix::Matrix{Float64})
250328
@@ -367,7 +445,7 @@ Constructor for a [RamAirWing](@ref) that allows to use an `.obj` and a `.dat` f
367445"""
368446function RamAirWing (obj_path, dat_path; alpha= 0.0 , crease_frac= 0.75 , wind_vel= 10. , mass= 1.0 ,
369447 n_panels= 54 , n_sections= n_panels+ 1 , spanwise_panel_distribution= UNCHANGED,
370- spanwise_direction= [0.0 , 1.0 , 0.0 ], remove_nan= true ,
448+ spanwise_direction= [0.0 , 1.0 , 0.0 ], remove_nan= true , align_to_principal = false ,
371449 alpha_range= deg2rad .(- 5 : 1 : 20 ), delta_range= deg2rad .(- 5 : 1 : 20 )
372450 )
373451
@@ -382,38 +460,42 @@ function RamAirWing(obj_path, dat_path; alpha=0.0, crease_frac=0.75, wind_vel=10
382460 (! isfile (obj_path)) && error (" OBJ file not found: $obj_path " )
383461 info_path = obj_path[1 : end - 4 ] * " _info.bin"
384462
385- if ! ispath (polar_path) || ! ispath (info_path)
463+ if true || ! ispath (info_path)
386464 @info " Reading $obj_path "
387465 vertices, faces = read_faces (obj_path)
388466 center_of_mass = center_to_com! (vertices, faces)
389467 inertia_tensor = calculate_inertia_tensor (vertices, faces, mass, zeros (3 ))
468+ align_to_principal && align_to_principal! (vertices, inertia_tensor)
390469
391470 circle_center_z, radius, gamma_tip = find_circle_center_and_radius (vertices)
392471 le_interp, te_interp, area_interp = create_interpolations (vertices, circle_center_z, radius, gamma_tip)
393472 @info " Writing $info_path "
394473 serialize (info_path, (inertia_tensor, center_of_mass, circle_center_z, radius, gamma_tip,
395474 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
404475 end
405476
406- @info " Loading polars and kite info from $polar_path and $info_path "
477+ @info " Loading kite info from $info_path and polars from $polar_path "
407478 try
479+ (inertia_tensor:: Matrix , center_of_mass:: Vector , circle_center_z:: Real ,
480+ radius:: Real , gamma_tip:: Real , le_interp, te_interp, area_interp) = deserialize (info_path)
481+
482+ if ! ispath (polar_path)
483+ width = 2 gamma_tip * radius
484+ area = area_interp (gamma_tip)
485+ @eval Main begin
486+ foil_path, polar_path, v_wind, area, width, x_turn, alpha_range, delta_range =
487+ $ dat_path, $ polar_path, $ wind_vel, $ gamma_tip, $ width, $ crease_frac, $ alpha_range, $ delta_range
488+ include (joinpath (dirname (@__FILE__ ), " ../scripts/polars.jl" ))
489+ end
490+ end
491+
408492 (alpha_range, delta_range, cl_matrix:: Matrix , cd_matrix:: Matrix , cm_matrix:: Matrix ) = deserialize (polar_path)
409493 if remove_nan
410494 interpolate_matrix_nans! (cl_matrix)
411495 interpolate_matrix_nans! (cd_matrix)
412496 interpolate_matrix_nans! (cm_matrix)
413497 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-
498+
417499 # Create sections
418500 sections = Section[]
419501 refined_sections = Section[]
@@ -426,14 +508,15 @@ function RamAirWing(obj_path, dat_path; alpha=0.0, crease_frac=0.75, wind_vel=10
426508 push! (refined_sections, Section (LE_point, TE_point, POLAR_MATRICES, aero_data))
427509 push! (non_deformed_sections, Section (LE_point, TE_point, POLAR_MATRICES, aero_data))
428510 end
429-
511+
430512 RamAirWing (n_panels, spanwise_panel_distribution, spanwise_direction, sections,
431513 refined_sections, remove_nan, non_deformed_sections,
432514 mass, circle_center_z, gamma_tip, inertia_tensor, center_of_mass, radius,
433515 le_interp, te_interp, area_interp, zeros (n_panels), zeros (n_panels))
434- catch e
516+
517+ catch
435518 if e isa BoundsError
436- @error " Delete $polar_path and $info_path and try again."
519+ @error " Delete $info_path and $polar_path and try again."
437520 end
438521 rethrow (e)
439522 end
@@ -486,6 +569,6 @@ Rotate vector v around axis k by angle θ using Rodrigues' rotation formula.
486569"""
487570function rotate_v_around_k (v, k, θ)
488571 k = normalize (k)
489- v_rot = v * cos (θ) + (k × v) * sin (θ) + k * (k ⋅ v) * (1 - cos (θ))
572+ v_rot = v * cos (θ) + (k × v) * sin (θ) + k * (k ⋅ v) * (1 - cos (θ))
490573 return v_rot
491574end
0 commit comments