Skip to content

Commit e66a34b

Browse files
authored
Add trailing edge angle beta and deform (#89)
* use xyz interpolations * alpha deform works * beta works * tests pass * add docs * address remarks * fix wrongly adressed comment
1 parent a9e783a commit e66a34b

File tree

7 files changed

+186
-73
lines changed

7 files changed

+186
-73
lines changed

examples/ram_air_kite.jl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ PLOT = true
1616
wing = KiteWing("data/ram_air_kite_body.obj", "data/ram_air_kite_foil.dat")
1717
body_aero = BodyAerodynamics([wing])
1818

19+
alpha = [0, 0]
20+
beta = [deg2rad(10), 0]
21+
@time VortexStepMethod.deform!(wing, alpha, beta; width=1.0)
22+
@time VortexStepMethod.init!(body_aero)
23+
1924
# Create solvers
2025
vsm_solver = Solver(
2126
aerodynamic_model_type=VSM,
@@ -24,7 +29,7 @@ vsm_solver = Solver(
2429

2530
# Setting velocity conditions
2631
v_a = 15.0
27-
aoa = 15.0
32+
aoa = 10.0
2833
side_slip = 0.0
2934
yaw_rate = 0.0
3035
aoa_rad = deg2rad(aoa)
@@ -48,8 +53,8 @@ PLOT && plot_geometry(
4853
)
4954

5055
# Solving and plotting distributions
51-
results = solve(vsm_solver, body_aero)
52-
@time results = solve(vsm_solver, body_aero)
56+
results = solve(vsm_solver, body_aero; log=true)
57+
@time results = solve(vsm_solver, body_aero; log=true)
5358

5459
body_y_coordinates = [panel.aero_center[2] for panel in body_aero.panels]
5560

examples/update_pos.jl

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/body_aerodynamics.jl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,13 @@ function init!(body_aero::BodyAerodynamics;
7070

7171
# Create panels
7272
for i in 1:wing.n_panels
73-
init!(body_aero.panels[idx],
73+
if wing isa KiteWing
74+
beta = wing.beta_dist[i]
75+
else
76+
beta = 0.0
77+
end
78+
init!(
79+
body_aero.panels[idx],
7480
wing.refined_sections[i],
7581
wing.refined_sections[i+1],
7682
panel_props.aero_centers[i],
@@ -79,7 +85,8 @@ function init!(body_aero::BodyAerodynamics;
7985
panel_props.bound_points_2[i],
8086
panel_props.x_airf[i],
8187
panel_props.y_airf[i],
82-
panel_props.z_airf[i]
88+
panel_props.z_airf[i],
89+
beta
8390
)
8491
idx += 1
8592
end

src/kite_geometry.jl

Lines changed: 153 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
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+
"""
214
function read_faces(filename)
315
vertices = []
416
faces = []
@@ -22,6 +34,20 @@ function read_faces(filename)
2234
return vertices, faces
2335
end
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+
"""
2551
function 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
7197
end
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+
"""
74114
function 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
128178
end
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+
"""
131203
function 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}
212286
end
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
296409
end
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+
"""
299424
function 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

Comments
 (0)