1+ """
2+ read_faces(filename)
3+
4+ Read vertices and faces from an OBJ file.
5+
6+ # Arguments
7+ - `filename::String`: Path to .obj file
18
9+ # Returns
10+ - Tuple of (vertices, faces) where:
11+ - vertices: Vector of 3D coordinates [x,y,z]
12+ - faces: Vector of triangle vertex indices
13+ """
214function read_faces (filename)
315 vertices = []
416 faces = []
@@ -22,6 +34,20 @@ function read_faces(filename)
2234 return vertices, faces
2335end
2436
37+ """
38+ find_circle_center_and_radius(vertices)
39+
40+ Find the center and radius of the kite's curvature circle.
41+
42+ # Arguments
43+ - `vertices`: Vector of 3D point coordinates
44+
45+ # Returns
46+ - Tuple of (z_center, radius, gamma_tip) where:
47+ - z_center: Z-coordinate of circle center
48+ - radius: Circle radius
49+ - gamma_tip: Angle of the kite tip from z-axis
50+ """
2551function find_circle_center_and_radius (vertices)
2652 r = zeros (2 )
2753 v_min = zeros (3 )
@@ -70,36 +96,60 @@ function find_circle_center_and_radius(vertices)
7096 return z, r[1 ], gamma_tip
7197end
7298
73- # Create interpolations for max and min x coordinates
99+ """
100+ create_interpolations(vertices, circle_center_z, radius, gamma_tip)
101+
102+ Create interpolation functions for leading/trailing edges and area.
103+
104+ # Arguments
105+ - `vertices`: Vector of 3D point coordinates
106+ - `circle_center_z`: Z-coordinate of circle center
107+ - `radius`: Circle radius
108+ - `gamma_tip`: Maximum angular extent
109+
110+ # Returns
111+ - Tuple of (le_interp, te_interp, area_interp) interpolation functions
112+ - Where le_interp and te_interp are tuples themselves, containing the x, y and z interpolations
113+ """
74114function create_interpolations (vertices, circle_center_z, radius, gamma_tip)
75115 gamma_range = range (- gamma_tip+ 1e-6 , gamma_tip- 1e-6 , 100 )
76116 vz_centered = [v[3 ] - circle_center_z for v in vertices]
77117
78- max_xs = zeros (length (gamma_range))
79- min_xs = zeros (length (gamma_range))
118+ trailing_edges = zeros (3 , length (gamma_range))
119+ leading_edges = zeros (3 , length (gamma_range))
80120 areas = zeros (length (gamma_range))
81121
82122 for (j, gamma) in enumerate (gamma_range)
83- max_xs[ j] = - Inf
84- min_xs[ j] = Inf
123+ trailing_edges[ 1 , j] = - Inf
124+ leading_edges[ 1 , j] = Inf
85125 for (i, v) in enumerate (vertices)
86126 # Rotate y coordinate to check box containment
87127 rotated_y = v[2 ] * cos (gamma) - vz_centered[i] * sin (gamma)
88128 if gamma ≤ 0.0 && - 0.5 ≤ rotated_y ≤ 0.0
89- max_xs[j] = max (max_xs[j], v[1 ])
90- min_xs[j] = min (min_xs[j], v[1 ])
129+ if v[1 ] > trailing_edges[1 , j]
130+ trailing_edges[:, j] .= v
131+ end
132+ if v[1 ] < leading_edges[1 , j]
133+ leading_edges[:, j] .= v
134+ end
91135 elseif gamma > 0.0 && 0.0 ≤ rotated_y ≤ 0.5
92- max_xs[j] = max (max_xs[j], v[1 ])
93- min_xs[j] = min (min_xs[j], v[1 ])
136+ if v[1 ] > trailing_edges[1 , j]
137+ trailing_edges[:, j] .= v
138+ end
139+ if v[1 ] < leading_edges[1 , j]
140+ leading_edges[:, j] .= v
141+ end
94142 end
95143 end
96- area = abs (max_xs[ j] - min_xs[ j]) * gamma_range. step * radius
144+ area = norm (leading_edges[:, j] - trailing_edges[:, j]) * gamma_range. step * radius
97145 last_area = j > 1 ? areas[j- 1 ] : 0.0
98146 areas[j] = last_area + area
99147 end
100148
101- te_interp = linear_interpolation (gamma_range, max_xs, extrapolation_bc= Line ())
102- le_interp = linear_interpolation (gamma_range, min_xs, extrapolation_bc= Line ())
149+ le_interp = ntuple (i -> linear_interpolation (gamma_range, leading_edges[i, :],
150+ extrapolation_bc= Line ()), 3 )
151+ te_interp = ntuple (i -> linear_interpolation (gamma_range, trailing_edges[i, :],
152+ extrapolation_bc= Line ()), 3 )
103153 area_interp = linear_interpolation (gamma_range, areas, extrapolation_bc= Line ())
104154
105155 return (le_interp, te_interp, area_interp)
@@ -127,7 +177,29 @@ function calculate_com(vertices, faces)
127177 return com / area_total
128178end
129179
130- # Calculate inertia tensor for a triangular surface mesh with given mass
180+ """
181+ calculate_inertia_tensor(vertices, faces, mass, com)
182+
183+ Calculate the inertia tensor for a triangulated surface mesh, assuming a thin shell with uniform
184+ surface density.
185+
186+ # Arguments
187+ - `vertices`: Vector of 3D point coordinates representing mesh vertices
188+ - `faces`: Vector of triangle indices, each defining a face of the mesh
189+ - `mass`: Total mass of the shell in kg
190+ - `com`: Center of mass coordinates [x,y,z]
191+
192+ # Method
193+ Uses the thin shell approximation where:
194+ 1. Mass is distributed uniformly over the surface area
195+ 2. Each triangle contributes to the inertia based on its area and position
196+ 3. For each triangle vertex p, contribution to diagonal terms is: area * (sum(p²) - p_i²)
197+ 4. For off-diagonal terms: area * (-p_i * p_j)
198+ 5. Final tensor is scaled by mass/(3*total_area) to get correct units
199+
200+ # Returns
201+ - 3×3 matrix representing the inertia tensor in kg⋅m²
202+ """
131203function calculate_inertia_tensor (vertices, faces, mass, com)
132204 # Initialize inertia tensor
133205 I = zeros (3 , 3 )
@@ -186,8 +258,8 @@ Represents a curved wing that inherits from Wing with additional geometric prope
186258 - gamma_tip::Float64
187259 - inertia_tensor::Matrix{Float64}
188260 - radius::Float64: Radius of curvature
189- - le_interp::Extrapolation: see: [Extrapolation](https://juliamath.github.io/Interpolations.jl/stable/extrapolation/)
190- - te_interp::Extrapolation
261+ - le_interp::NTuple{3, Extrapolation} : see: [Extrapolation](https://juliamath.github.io/Interpolations.jl/stable/extrapolation/)
262+ - te_interp::NTuple{3, Extrapolation}
191263 - area_interp::Extrapolation
192264
193265"""
@@ -199,16 +271,18 @@ mutable struct KiteWing <: AbstractWing
199271 refined_sections:: Vector{Section}
200272
201273 # Additional fields for KiteWing
274+ non_deformed_sections:: Vector{Section}
202275 mass:: Float64
203276 center_of_mass:: Vector{Float64}
204277 circle_center_z:: Float64
205278 gamma_tip:: Float64
206279 inertia_tensor:: Matrix{Float64}
207280 radius:: Float64
208- le_interp:: Extrapolation
209- te_interp:: Extrapolation
281+ le_interp:: NTuple{3, Extrapolation}
282+ te_interp:: NTuple{3, Extrapolation}
210283 area_interp:: Extrapolation
211-
284+ alpha_dist:: Vector{Float64}
285+ beta_dist:: Vector{Float64}
212286end
213287
214288"""
@@ -237,7 +311,7 @@ function KiteWing(obj_path, dat_path; alpha=0.0, crease_frac=0.75, wind_vel=10.,
237311 n_panels= 54 , n_sections= n_panels+ 1 , spanwise_panel_distribution= UNCHANGED,
238312 spanwise_direction= [0.0 , 1.0 , 0.0 ])
239313
240- ! isapprox (spanwise_direction, [0.0 , 1.0 , 0.0 ]) && @error " Spanwise direction has to be [0.0, 1.0, 0.0]"
314+ ! 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 " ))
241315
242316 # Load or create polars
243317 (! endswith (dat_path, " .dat" )) && (dat_path *= " .dat" )
@@ -280,22 +354,73 @@ function KiteWing(obj_path, dat_path; alpha=0.0, crease_frac=0.75, wind_vel=10.,
280354 sections = Section[]
281355 for gamma in range (- gamma_tip, gamma_tip, n_sections)
282356 aero_data = (collect (alpha_range), collect (beta_range), cl_matrix, cd_matrix, cm_matrix)
283- LE_point = [0.0 , 0.0 , circle_center_z] .+ [le_interp (gamma), sin (gamma) * radius, cos (gamma) * radius]
284- if ! isapprox (alpha, 0.0 )
285- local_y_vec = [0.0 , sin (- gamma), cos (gamma)] × [1.0 , 0.0 , 0.0 ]
286- TE_point = LE_point .+ rotate_v_around_k ([te_interp (gamma) - le_interp (gamma), 0.0 , 0.0 ], local_y_vec, alpha)
287- else
288- TE_point = LE_point .+ [te_interp (gamma) - le_interp (gamma), 0.0 , 0.0 ]
289- end
357+ LE_point = [le_interp[i](gamma) for i in 1 : 3 ]
358+ TE_point = [te_interp[i](gamma) for i in 1 : 3 ]
290359 push! (sections, Section (LE_point, TE_point, POLAR_MATRICES, aero_data))
291360 end
292361
293- KiteWing (n_panels, spanwise_panel_distribution, spanwise_direction, sections, sections,
362+ KiteWing (n_panels, spanwise_panel_distribution, spanwise_direction, sections, sections, sections,
294363 mass, center_of_mass, circle_center_z, gamma_tip, inertia_tensor, radius,
295- le_interp, te_interp, area_interp)
364+ le_interp, te_interp, area_interp, zeros (n_panels), zeros (n_panels))
365+ end
366+
367+ """
368+ deform!(wing::KiteWing, alphas::AbstractVector, betas::AbstractVector; width)
369+
370+ Deform wing by applying left and right alpha and beta.
371+
372+ # Arguments
373+ - `wing`: KiteWing to deform
374+ - `alphas`: [left, right] the angle between of the kite and the body x-axis in radians
375+ - `betas`: [left, right] the deformation of the trailing edges
376+ - `width`: Transition width in meters to smoothe out the transition from left to right deformation
377+
378+ # Effects
379+ Updates wing.sections with deformed geometry
380+ """
381+ function deform! (wing:: KiteWing , alphas:: AbstractVector , betas:: AbstractVector ; width)
382+ local_y = zeros (MVec3)
383+ chord = zeros (MVec3)
384+
385+ for (i, gamma) in enumerate (range (- wing. gamma_tip, wing. gamma_tip, wing. n_panels))
386+ normalized_gamma = (gamma * wing. radius / width + 0.5 ) # Maps [-0.5, 0.5] to [0, 1]
387+ wing. alpha_dist[i] = if normalized_gamma <= 0.0
388+ alphas[1 ]
389+ elseif normalized_gamma >= 1.0
390+ alphas[2 ]
391+ else
392+ alphas[1 ] * (1.0 - normalized_gamma) + alphas[2 ] * normalized_gamma
393+ end
394+ wing. beta_dist[i] = if normalized_gamma <= 0.0
395+ betas[1 ]
396+ elseif normalized_gamma >= 1.0
397+ betas[2 ]
398+ else
399+ betas[1 ] * (1.0 - normalized_gamma) + betas[2 ] * normalized_gamma
400+ end
401+
402+ section1 = wing. non_deformed_sections[i]
403+ section2 = wing. non_deformed_sections[i+ 1 ]
404+ local_y .= normalize (section1. LE_point - section2. LE_point)
405+ chord .= section1. TE_point .- section1. LE_point
406+ wing. sections[i]. TE_point .= section1. LE_point .+ rotate_v_around_k (chord, local_y, wing. alpha_dist[i])
407+ end
408+ return nothing
296409end
297410
411+ """
412+ rotate_v_around_k(v, k, θ)
413+
414+ Rotate vector v around axis k by angle θ using Rodrigues' rotation formula.
298415
416+ # Arguments
417+ - `v`: Vector to rotate
418+ - `k`: Rotation axis (will be normalized)
419+ - `θ`: Rotation angle in radians
420+
421+ # Returns
422+ - Rotated vector
423+ """
299424function rotate_v_around_k (v, k, θ)
300425 k = normalize (k)
301426 v_rot = v * cos (θ) + (k × v) * sin (θ) + k * (k ⋅ v) * (1 - cos (θ))
0 commit comments