diff --git a/Project.toml b/Project.toml index 78ac9f0..6b6b609 100644 --- a/Project.toml +++ b/Project.toml @@ -10,9 +10,11 @@ RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" [weakdeps] +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" [extensions] +RotationsMakieExt = "Makie" RotationsRecipesBaseExt = "RecipesBase" [compat] @@ -21,6 +23,7 @@ BenchmarkTools = "1" ForwardDiff = "0.10" InteractiveUtils = "1" LinearAlgebra = "1" +Makie = "0.24" Plots = "1" Quaternions = "0.5.3, 0.6, 0.7" Random = "1" @@ -36,6 +39,7 @@ BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Quaternions = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" diff --git a/docs/Project.toml b/docs/Project.toml index f54d610..99e92cf 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,4 +1,5 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" diff --git a/docs/make.jl b/docs/make.jl index 7fb63c2..e56ed8a 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,10 +1,12 @@ using Rotations using Documenter +using Makie DocMeta.setdocmeta!(Rotations, :DocTestSetup, :(using Rotations); recursive=true) +RotationsMakieExt = Base.get_extension(Rotations, :RotationsMakieExt) makedocs(; - modules=[Rotations], + modules=[Rotations, RotationsMakieExt], repo="https://github.com/JuliaGeometry/Rotations.jl/blob/{commit}{path}#{line}", sitename="Rotations.jl", format=Documenter.HTML(; diff --git a/docs/src/visualizing.md b/docs/src/visualizing.md index d88ce01..2195279 100644 --- a/docs/src/visualizing.md +++ b/docs/src/visualizing.md @@ -1,12 +1,75 @@ -# Visualizing Rotation -In general, ``3 \times 3`` matrix has ``9`` parameters, so it's hard to visualize a matrix as a point. -However, the Lie group ``SO(3)`` and ``SU(2)`` are ``3``-dimensional space, and it is able to visualize its element as a point in our ``3``-dimensional space. +```@setup ext +using Rotations, Makie +Ext = Base.get_extension(Rotations, :RotationsMakieExt) +``` +# Visualizing Rotations -## using `MRP` -(TBW) +Visualizing rotations is crucial for understanding their behavior and effects. While a general ``3 \times 3`` rotation matrix has 9 parameters, rotations actually live on the 3-dimensional manifold ``SO(3)``, making them suitable for 3D visualization. -## using `RodriguesParam` -(TBW) +This package provides plotting capabilities through [Makie.jl](https://makie.juliaplots.org/) via the `RotationsMakieExt` extension, which is automatically loaded when both `Rotations` and a Makie backend are available. -## using `RotationVec` -(TBW) +## Plotting Rotation Matrices + +### Basic 2D Rotation Plotting + +```julia +using Rotations +using CairoMakie # or GLMakie for interactive plots + +# Create a 2D rotation +rot2d = RotMatrix{2}(π/4) # 45-degree rotation + +# Plot the rotation +fig, ax, plt = plot(rot2d) +``` + +```@docs +RotationsMakieExt.rotation2plot +RotationsMakieExt.rotation2plot! +RotationsMakieExt.Rotation2Plot +``` + +The functions are also accessible from the `Visualization` module of the package. + +### Basic 3D Rotation Plotting + +```julia +# Create a 3D rotation from Euler angles +rot3d = RotXYZ(π/6, π/4, π/3) + +# Plot the rotation +fig, ax, plt = plot(rot3d) +``` + +```@docs +RotationsMakieExt.rotation3plot +RotationsMakieExt.rotation3plot! +RotationsMakieExt.Rotation3Plot +``` + +### Combining Multiple Rotations + +```julia +using GLMakie + +# Create several rotations +rotations = [ + RotX(π/6), + RotY(π/4), + RotZ(π/3), + RotXYZ(π/6, π/4, π/3) +] + +# Plot them in a grid +fig = Figure(resolution = (800, 800)) + +for (i, rot) in enumerate(rotations) + ax = Axis3(fig[div(i-1, 2) + 1, mod(i-1, 2) + 1], + title = "Rotation $i") + plot!(ax, rot, + boxcolor = [:red, :green, :blue, :orange][i], + axissize = 1.5) +end + +fig +``` diff --git a/ext/RotationsMakieExt.jl b/ext/RotationsMakieExt.jl new file mode 100644 index 0000000..7eade6b --- /dev/null +++ b/ext/RotationsMakieExt.jl @@ -0,0 +1,132 @@ +module RotationsMakieExt + +using Rotations +using Makie +using StaticArrays + +import Rotations.Visualization: rotation2plot, rotation2plot!,rotation3plot ,rotation3plot + +Makie.plottype(::Rotation{2}) = Rotation2Plot +Makie.plottype(::Rotation{3}) = Rotation3Plot + +@recipe Rotation2Plot (rot::Rotation{2},) begin + begin + arrows_attributes = Makie.documented_attributes(Arrows2D) + filter!(a -> a.first ∉ [:color], arrows_attributes.d) + arrows_attributes + end... + "Origin point for the 2D rotation visualization" + origin = SVector(0, 0) + "Size of the reference square" + boxsize = 0.2 + "Length of the axis lines" + axissize = 1.0 + "Colours for the two axes (x, y)" + axiscolors = [:red, :green] + "Colour of the reference square" + boxcolor = :gray +end + +function Makie.plot!(p::Rotation2Plot{<:Tuple{<:Rotation{2}}}) + # Use ComputationPipeline for dynamic updates + map!(p.attributes, [:rot, :origin, :boxsize, :axissize], [:arrows_origin, :arrows1_end, :arrows2_end, :box_points]) do rot, origin, l, L + e₁ = rot[:, 1] + e₂ = rot[:, 2] + ox, oy = origin + + # Compute box corners + box_points = [Point2f(ox, oy) + rot * Point2f(x, y) for x in (-l, l) for y in (-l, l)][[1, 2, 4, 3]] + + return (Point2f(ox, oy), Point2f(ox + L * e₁[1], oy + L * e₁[2]), Point2f(ox + L * e₂[1], oy + L * e₂[2]), box_points) + end + + map!(p.attributes, [:axiscolors], [:arrows1_color, :arrows2_color]) do axiscolors + (axiscolors[1], axiscolors[2]) + end + + arrows2d!(p, p.attributes, p.arrows_origin, p.arrows1_end; color=p.arrows1_color) + arrows2d!(p, p.attributes, p.arrows_origin, p.arrows2_end; color=p.arrows2_color) + poly!(p, p.attributes, p.box_points; color=p.boxcolor) + + return p +end + +@recipe Rotation3Plot (rot::Rotation{3},) begin + begin + arrows_attributes = Makie.documented_attributes(Arrows3D) + filter!(a -> a.first ∉ [:color], arrows_attributes.d) + arrows_attributes + end... + "Origin point for the 3D rotation visualization" + origin = SVector(0, 0, 0) + "Size of the reference cube" + boxsize = 0.2 + "Length of the axis lines" + axissize = 1.0 + "Colours for the three axes (x, y, z)" + axiscolors = [:red, :green, :blue] + "Colour of the reference cube faces" + boxcolor = :gray + "Colour of cube edges" + edgecolor = :black +end + +function Makie.plot!(p::Rotation3Plot{<:Tuple{<:Rotation{3}}}) + # Use ComputationPipeline for dynamic updates + map!(p.attributes, [:rot, :origin, :boxsize, :axissize], [:arrows_origin, :arrows1_end, :arrows2_end, :arrows3_end, :cube_points, :cube_faces, :cube_edges]) do rot, origin, l, L + e₁ = rot[:, 1] + e₂ = rot[:, 2] + e₃ = rot[:, 3] + ox, oy, oz = origin + + # Compute cube vertices + cube_points = [Point3f(ox, oy, oz) + rot * Point3f(x, y, z) for x in (-l, l) for y in (-l, l) for z in (-l, l)] + + # Define cube faces (triangulated) + cube_faces = [ + 1, 2, 4, 4, 3, 1, # bottom + 5, 6, 8, 8, 7, 5, # top + 1, 2, 6, 6, 5, 1, # front + 3, 4, 8, 8, 7, 3, # back + 1, 3, 7, 7, 5, 1, # left + 2, 4, 8, 8, 6, 2 # right + ] + + # Define cube edges for wireframe + cube_edges = [ + # Bottom face edges + cube_points[1], cube_points[2], cube_points[2], cube_points[4], + cube_points[4], cube_points[3], cube_points[3], cube_points[1], + # Top face edges + cube_points[5], cube_points[6], cube_points[6], cube_points[8], + cube_points[8], cube_points[7], cube_points[7], cube_points[5], + # Vertical edges + cube_points[1], cube_points[5], cube_points[2], cube_points[6], + cube_points[3], cube_points[7], cube_points[4], cube_points[8] + ] + + return ( + Point3f(ox, oy, oz), + Point3f(ox + L * e₁[1], oy + L * e₁[2], oz + L * e₁[3]), + Point3f(ox + L * e₂[1], oy + L * e₂[2], oz + L * e₂[3]), + Point3f(ox + L * e₃[1], oy + L * e₃[2], oz + L * e₃[3]), + cube_points, cube_faces, cube_edges + ) + end + + map!(p.attributes, [:axiscolors], [:arrows1_color, :arrows2_color, :arrows3_color]) do axiscolors + (axiscolors[1], axiscolors[2], axiscolors[3]) + end + + arrows3d!(p, p.attributes, p.arrows_origin, p.arrows1_end, color=p.arrows1_color) + arrows3d!(p, p.attributes, p.arrows_origin, p.arrows2_end, color=p.arrows2_color) + arrows3d!(p, p.attributes, p.arrows_origin, p.arrows3_end, color=p.arrows3_color) + mesh!(p, p.attributes, p.cube_points, p.cube_faces; color=p.boxcolor) + linesegments!(p, p.attributes, p.cube_edges; color=p.edgecolor) + + return p +end + +Makie.preferred_axis_type(::Rotation3Plot) = Makie.Axis3 + +end diff --git a/src/Rotations.jl b/src/Rotations.jl index 42b7281..2a941ef 100644 --- a/src/Rotations.jl +++ b/src/Rotations.jl @@ -28,6 +28,7 @@ include("eigen.jl") include("rand.jl") include("rotation_between.jl") include("deprecated.jl") +include("extensions.jl") export # Rotation types diff --git a/src/extensions.jl b/src/extensions.jl new file mode 100644 index 0000000..2b4ca0a --- /dev/null +++ b/src/extensions.jl @@ -0,0 +1,8 @@ +module Visualization +function rotation2plot end +function rotation2plot! end +function rotation3plot end +function rotation3plot! end + +export rotation2plot, rotation2plot!, rotation3plot, rotation3plot! +end