Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ SPDX-License-Identifier: MIT

- A row whose model is an instance of a registered parametric model type, such as `BodyModel{AeroVSM, RigidDynamics}` registered as `BodyModel`, is written under that model's name rather than refused as unregistered.

### Changed

- BREAKING: KiteGeometry no longer has a `calc_heading(R_b_to_w, pos)` of its own; use KiteUtils' `calc_heading(attitude, elevation, azimuth)`. `using KiteUtils, KiteGeometry` no longer leaves `calc_heading` ambiguous.

## KiteGeometry v0.3.2 2026-10-21

### Added
Expand Down
1 change: 0 additions & 1 deletion docs/src/structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ place!
place_transforms!
reposition!
transform_velocities
calc_heading
```

## Components
Expand Down
5 changes: 2 additions & 3 deletions src/KiteGeometry.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ for more information.
module KiteGeometry

using JSON: JSON
using KiteUtils: KiteUtils, Logger, Settings, SysLog, save_log, wrap2pi
using KiteUtils: KiteUtils, Logger, Settings, SysLog, calc_heading, save_log, wrap2pi
using LinearAlgebra: Diagonal, I, cross, dot, norm, normalize
using OrderedCollections: OrderedDict, OrderedSet
using SHA: sha256
Expand All @@ -24,8 +24,7 @@ using YAML: YAML

export SystemDefinition, Rows, Metadata, Material, Point, Segment, SpanwiseStation, Pulley,
Tether, Winch, Wing, CanopyFace, Body, Tube, pos_KA
export Transform, Pose, place!, place_transforms!, reposition!, transform_velocities,
calc_heading
export Transform, Pose, place!, place_transforms!, reposition!, transform_velocities
export NameRef, AbstractModel, NoModel, SegmentSpring, register_model!, model_type,
model_columns
export load_structure, load_authoring, structure_document, connectivity_sha
Expand Down
30 changes: 4 additions & 26 deletions src/transforms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -99,31 +99,6 @@ Pose(placement::Placement) = Pose(placement.pos, placement.body_pos, placement.b

# ==================== TANGENTIAL FRAME ==================== #

"""
calc_R_t_to_w(pos)

Rotation from the tangential frame at `pos` [m] into the world: z radial along `pos`, y
azimuthal and level, x towards elevation, `y × z`.
"""
function calc_R_t_to_w(pos)
z = normalize(pos)
y = abs(pos[1]) < 1e-6 && abs(pos[2]) < 1e-6 ? Vec3(0, 1, 0) :
normalize(Vec3(-pos[2], pos[1], 0))
return hcat(cross(y, z), y, z)
end

"""
calc_heading(R_b_to_w, pos)

Heading [rad] of the frame `R_b_to_w` at `pos` [m] from the base: the angle of its x axis
in the tangential plane, from the elevation direction towards the azimuthal one; zero
with the nose towards the base.
"""
function calc_heading(R_b_to_w, pos)
e_x_t = calc_R_t_to_w(pos)' * R_b_to_w[:, 1]
return atan(e_x_t[2], e_x_t[1])
end

"""
radial_direction(transform)

Expand Down Expand Up @@ -205,7 +180,10 @@ function turn!(pose, transform, base_pos)
radial = radial_direction(transform)
rotate_members!(pose, transform, base_pos, min_rotation(normalize(rel_pos), radial)...)
isnothing(transform.body) && return nothing
heading = calc_heading(pose.body_R[transform.body], radial)
cos_el = cos(transform.elevation)
# KiteUtils' azimuth runs from east to north; its heading turns the other way to ours
heading = -calc_heading(pose.body_R[transform.body], transform.elevation,
atan(radial[2] / cos_el, radial[1] / cos_el))
rotate_members!(pose, transform, base_pos, radial,
wrap2pi(transform.heading - heading))
return nothing
Expand Down
28 changes: 25 additions & 3 deletions test/test-transforms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# SPDX-License-Identifier: MIT

using KiteGeometry: radial_direction
using KiteUtils: Settings, set_data_path
using KiteUtils: KiteUtils, Settings, calc_heading, set_data_path
using LinearAlgebra: norm, normalize
using YAML

Expand Down Expand Up @@ -34,6 +34,13 @@ function tilt_kite!(transforms)
return transforms
end

"""Heading [rad] of the kite body of `pose`, in the sense of a transform's `heading`, in
the tangential frame of where its origin stands from the KCU."""
function kite_heading(pose)
x, y, z = pose.pos[7] - pose.pos[2]
return -calc_heading(pose.body_R[1], atan(z, hypot(x, y)), atan(y, x); respos=false)
end

"""`data`, the chained system's YAML, with its kite tilted as [`tilt_kite!`](@ref)."""
function tilt_kite_row!(data)
data["transforms"]["data"][2][2] = 48.0
Expand Down Expand Up @@ -75,7 +82,7 @@ end
ground, kcu, kite_origin = pose.pos[1], pose.pos[2], pose.pos[7]
@test normalize(kcu - ground) ≈ radial_direction(main_tf)
@test normalize(kite_origin - kcu) ≈ radial_direction(kite_tilt)
@test calc_heading(pose.body_R[1], kite_origin - kcu) ≈ kite_tilt.heading
@test kite_heading(pose) ≈ kite_tilt.heading
@test norm(pose.pos[4] - kite_origin) ≈ kite_rigid
end

Expand All @@ -85,7 +92,16 @@ end
kcu, kite_origin = pose.pos[2], pose.pos[7]
@test pose.pos[2] == system.points[2].pos_ENU
@test normalize(kite_origin - kcu) ≈ radial_direction(transforms[2])
@test calc_heading(pose.body_R[1], kite_origin - kcu) ≈ deg2rad(-35)
@test kite_heading(pose) ≈ deg2rad(-35)
end

@testset "a heading body turns smoothly through the zenith" begin
kite_frame_at(elevation) = begin
system, transforms = load_chained()
transforms[2].elevation = deg2rad(elevation)
return Pose(place!(system, transforms)).body_R[1]
end
@test norm(kite_frame_at(90.1) - kite_frame_at(89.9)) < 0.01
end

"""`data`, the chained system's YAML, with its first transform turning no point."""
Expand Down Expand Up @@ -135,3 +151,9 @@ end
@test iszero(body_spin[1]) == (moving == 1)
end
end

@testset "calc_heading is KiteUtils' alone" begin
probe = Module()
Core.eval(probe, :(using KiteUtils, KiteGeometry))
@test Core.eval(probe, :calc_heading) === KiteUtils.calc_heading
end
Loading