Hoping to show GLMakie can do something similar to ganja.js in displaying an interactive demonstration of the projective geometric algebra inverse kinematics algorithm (in the demonstration, a user drags around the target and the robot arm follows), I added a function iik() (i.e., interactive inverse kinematics) and it works:
# pga2d3d_ik.jl
# interactive graphical demo of Inverse Kinematics
using GLMakie
include("ripga3d.jl")
# translate distance along line
function xlator(line::Vector{Float32},dist::Number)
# return 1 - dist/2*(e0*normalize(line)*!e0)
return ga"1 - dist/2 (e0 normalize(line) e0∗)"
end
# inverse kinematics algorithm; plot of convergence rate
# arguments:
# - coordinates of target
# - number of links in robot arm
function ik(target::Vector{Float64}=[2.5,2.,0.], nLink::Int64=6)
nEP = nLink + 1 # number of link endpoints
armLength = 3 # max reach of robot arm
# initialize figure
fig = Figure(resolution = (1800, 800))
C = ["red"; "green"; "blue"] # line colors
LIM = (-2,3, -2.5,2.5)
YTIC = (-2:1:2)
AX = [
Axis(fig[1,1], limits=LIM, yticks=YTIC, aspect=1,
title = "1. initial endpoints,\n" *
"the target is separate from robot arm");
Axis(fig[1,2], limits=LIM, yticks=YTIC, aspect=1,
title = "2. endpoints after 1st pass\n" *
"of backward relaxation");
Axis(fig[1,3], limits=LIM, yticks=YTIC, aspect=1,
title = "3. endpoints after 1st pass\n" *
"of forward relaxation");
Axis(fig[1,4], limits=LIM, yticks=YTIC, aspect=1,
title = "4. endpoints after 2nd pass\n" *
"of backward relaxation");
Axis(fig[1,5], limits=LIM, yticks=YTIC, aspect=1,
title = "5. endpoints after 2nd pass\n" *
"of forward relaxation");
Axis(fig[2,2], limits=LIM, yticks=YTIC, aspect=1,
title = "6. endpoints after 3rd pass\n" *
"of backward relaxation");
Axis(fig[2,3], limits=LIM, yticks=YTIC, aspect=1,
title = "7. endpoints after 3rd pass\n" *
"of forward relaxation");
Axis(fig[2,4], limits=LIM, yticks=YTIC, aspect=1,
title = "8. endpoints after 4th pass\n" *
"of backward relaxation");
Axis(fig[2,5], limits=LIM, yticks=YTIC, aspect=1,
title = "9. endpoints after 4th pass\n" *
"of forward relaxation");
]
# allocate endpoint PGA expressions
# (appended endpoint in last column is target)
PX = Matrix{Float32}(undef, (length(e0),nEP+1))
# define link endpoints
linkLength::Float32 = armLength / nLink
for iEP = 1:nEP
# PX[:,iEP] = !(e0 + (iEP*linkLength - 1.5)*e1)
PX[:,iEP] = ga"(e0 + (iEP linkLength - 1.5) e1)∗"
end
PX[:,nEP+1] = point(target[1], target[2], target[3])
# plot link endpoints and target point
P = toPlot(PX)
iAx = 1
scatterlines!(AX[iAx], P[1:3,1:nEP], color="black")
scatterlines!(AX[iAx], # target point is at end
[P[1,end]], [P[2,end]], [P[3,end]],
color="black")
# plot results of each relaxation loop
for iRelax = 1:4
# set tip to target, changing length of last link
PX[:,nEP] = PX[:,nEP+1]
P = toPlot(PX)
# restore link lengths from back to front
iAx += 1
scatterlines!(AX[iAx],
P[1:3,1:nEP], color = "light gray")
for jLink = 1:nEP-2
i = nEP - jLink
iColor = mod(jLink-1,3) + 1
# XL = xlator( # define translation along line
# PX[:,i+1] & PX[:,i], linkLength)
# PX[:,i] = XL >>> PX[:,i+1] # perform translation
XL = xlator(ga"PX[:,i+1] ∨ PX[:,i]", linkLength)
PX[:,i] = ga"XL PX[:,i+1] ~XL" # perform translation
P = toPlot(PX)
if i > 2
scatterlines!(AX[iAx],
P[1:3,i:i+1], color = C[iColor])
else
scatterlines!(AX[iAx],
P[1:3,i-1:i+1], color = C[iColor])
end
end
# restore link lengths from front to back
iAx += 1
scatterlines!(AX[iAx],
P[1:3,1:nEP], color = "light gray")
for i = 2:nEP
iColor = mod(i-2,3) + 1
# XL = xlator( # define translation along line
# PX[:,i-1] & PX[:,i], linkLength)
# PX[:,i] = XL >>> PX[:,i-1] # perform translation
XL = xlator(ga"PX[:,i-1] ∨ PX[:,i]", linkLength)
PX[:,i] = ga"XL PX[:,i-1] ~XL" # perform translation
P = toPlot(PX)
scatterlines!(AX[iAx],
P[1:3,i-1:i], color = C[iColor])
end
end
fig
end
function ik_solver(PX::Matrix{Float32},linkLength::Float32)
nEP = size(PX,2) - 1 # -1 because last column is target
# for each relaxation pass
for iRelax = 1:4
# set tip to target, changing length of last link
PX[:,nEP] = PX[:,nEP+1]
# restore link lengths from back to front
for jLink = 1:nEP-2
i = nEP - jLink
# XL = xlator( # define translation along line
# PX[:,i+1] & PX[:,i], linkLength)
# PX[:,i] = XL >>> PX[:,i+1] # perform translation
XL = xlator(ga"PX[:,i+1] ∨ PX[:,i]", linkLength)
PX[:,i] = ga"XL PX[:,i+1] ~XL" # perform translation
end
# restore link lengths from front to back
for i = 2:nEP
# XL = xlator( # define translation along line
# PX[:,i-1] & PX[:,i], linkLength)
# PX[:,i] = XL >>> PX[:,i-1] # perform translation
XL = xlator(ga"PX[:,i-1] ∨ PX[:,i]", linkLength)
PX[:,i] = ga"XL PX[:,i-1] ~XL" # perform translation
end
end
end
# interactive inverse kinematics
# arguments:
# - coordinates of target of robot arm
# - number of links in robot arm
function iik(target::Vector{Float64}=[2.5,2.,0.], nLink::Int64=6)
nEP = nLink + 1 # number of link endpoints
armLength = 3 # max reach of robot arm
# initialize figure
fig = Figure(resolution = (800, 800))
LIM = (-2,3, -2.5,2.5)
YTIC = (-2:1:2)
ax1 = Axis(fig[1,1], limits=LIM, yticks=YTIC, aspect=1,
title = "Interactive demonstration of inverse kinematics algorithm.\n" *
"(The target point is initially separated from the robot arm.\n" *
"Drag that target point around to see how the robot arm reacts.)")
# allocate and define endpoint PGA expressions
# (appended endpoint in last column is target)
PX = Matrix{Float32}(undef, (length(e0),nEP+1))
linkLength::Float32 = armLength / nLink
ANCHOR = [-1; 0; 0]
for iEP = 1:nEP
# PX[:,iEP] = !(e0 + ((iEP-1)*linkLength + ANCHOR[1])*e1)
PX[:,iEP] = ga"(e0 + ((iEP-1) linkLength + ANCHOR[1]) e1)∗"
end
PX[:,nEP+1] = point(target[1], target[2], target[3])
# calculate inverse kinematics
ik_solver(PX, linkLength)
P = toPlot(PX) # convert PGA expressions to Euclidean coordinates
# define observables for plotting
RDATA = Observable(P[1:3,1:nEP]) # robot coordinates
TDATA = Observable([ANCHOR P[1:3,end]]) # target coordinates
# plot robot and target coordinates
scatterlines!(ax1, RDATA, color="black")
scatter!(ax1, TDATA, color="red")
deregister_interaction!(ax1, :rectanglezoom)
register_interaction!(ax1, :my_mouse_interaction) do event::MouseEvent, axis
if Makie.is_mouseinside(ax1.scene)
if event.type === MouseEventTypes.leftdrag
PX[:,end] = point(event.data[1], event.data[2], 0)
ik_solver(PX,linkLength)
P = toPlot(PX)
RDATA[] = P[1:3,1:nEP] # update the plotted observables to
TDATA[] = [ANCHOR P[1:3,end]] # automatically update the plot
end
end
end
fig
end
However, after reading this recent post, I realized that I too was unknowingly using a very old version (0.4.4) of GLMakie:
(@v1.8) pkg> status GLMakie
Status `C:\Users\gsgm2\.julia\environments\v1.8\Project.toml`
⌃ [e9467ef8] GLMakie v0.4.4
julia> Pkg.update()
Updating registry at `C:\Users\gsgm2\.julia\registries\General.toml`
Installed GR_jll ─ v0.71.3+0
Installed PyCall ─ v1.95.0
Installed Plots ── v1.38.1
Installed GR ───── v0.71.3
Downloaded artifact: GR
Updating `C:\Users\gsgm2\.julia\environments\v1.8\Project.toml`
[91a5bcdd] ↑ Plots v1.38.0 ⇒ v1.38.1
Updating `C:\Users\gsgm2\.julia\environments\v1.8\Manifest.toml`
[28b8d3ca] ↑ GR v0.71.2 ⇒ v0.71.3
[91a5bcdd] ↑ Plots v1.38.0 ⇒ v1.38.1
[438e738f] ↑ PyCall v1.94.1 ⇒ v1.95.0
[d2c73de3] ↑ GR_jll v0.71.2+0 ⇒ v0.71.3+0
Building PyCall → `C:\Users\gsgm2\.julia\scratchspaces\44cfe95a-1eb2-52ea-b672-e2afdf69b78f\b32c4b415f41f10c671cba02ae3275027dea8892\build.log`
Precompiling project...
5 dependencies successfully precompiled in 44 seconds. 262 already precompiled.
[ Info: We haven't cleaned this depot up for a bit, running Pkg.gc()...
Active manifest files: 2 found
Active artifact files: 115 found
Active scratchspaces: 13 found
Deleted no artifacts, repos, packages or scratchspaces
julia> Pkg.status("GLMakie")
Status `C:\Users\gsgm2\.julia\environments\v1.8\Project.toml`
⌃ [e9467ef8] GLMakie v0.4.4
Info Packages marked with ⌃ have new versions available and may be upgradable.
julia>
What is the best practice for updating to a newer version of the GLMakie package?