diff --git a/CHANGELOG.md b/CHANGELOG.md index e02c580..80b6358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/src/structure.md b/docs/src/structure.md index 9e1fe00..b2b635b 100644 --- a/docs/src/structure.md +++ b/docs/src/structure.md @@ -69,7 +69,6 @@ place! place_transforms! reposition! transform_velocities -calc_heading ``` ## Components diff --git a/src/KiteGeometry.jl b/src/KiteGeometry.jl index 60cd9fd..8f7c074 100644 --- a/src/KiteGeometry.jl +++ b/src/KiteGeometry.jl @@ -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 @@ -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 diff --git a/src/transforms.jl b/src/transforms.jl index 95f7312..0d075c9 100644 --- a/src/transforms.jl +++ b/src/transforms.jl @@ -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) @@ -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 diff --git a/test/test-transforms.jl b/test/test-transforms.jl index d229838..a438119 100644 --- a/test/test-transforms.jl +++ b/test/test-transforms.jl @@ -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 @@ -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 @@ -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 @@ -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.""" @@ -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