Skip to content

Commit 971c198

Browse files
committed
Add option to align frames
1 parent 257b525 commit 971c198

File tree

2 files changed

+115
-32
lines changed

2 files changed

+115
-32
lines changed

examples/ram_air_kite.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ using ControlPlots
22
using VortexStepMethod
33
using LinearAlgebra
44

5-
PLOT = true
5+
PLOT = false
66
USE_TEX = false
77
DEFORM = false
88

@@ -46,7 +46,7 @@ set_va!(body_aero, vel_app)
4646
PLOT && plot_polar_data(body_aero)
4747

4848
# Plotting geometry
49-
PLOT && plot_geometry(
49+
true && plot_geometry(
5050
body_aero,
5151
"";
5252
data_type=".svg",

src/kite_geometry.jl

Lines changed: 113 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -155,28 +155,63 @@ function create_interpolations(vertices, circle_center_z, radius, gamma_tip)
155155
return (le_interp, te_interp, area_interp)
156156
end
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+
"""
159174
function 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
246281
end
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
"""
368446
function 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 = 2gamma_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 = 2gamma_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
"""
487570
function 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
491574
end

0 commit comments

Comments
 (0)