Can Makie plots be dimension agnostic?

Makie is new to me and I’m hoping to use it to plot the results of my port of bivector.net’s C++ reference implementation of projective geometric algebra to a Julia reference implementation of projective geometric algebra. My goal is to implement in Julia a slicer for 3D printing.

Projective geometric algebra is also rather new to me. In their SIBGRAPI 2021 6 video tutorial about projective geometric algebra, Leo Dorst and Steven De Keninck show that, with some care in defining the elements, projective geometric algebra applications are “dimension agnostic” (they also use the alternative phrase “dimension independent”). For example, in Steven De Keninck’s demonstration of an inverse kinematics application, he changes the implementation (including the graphics) from a 2D PGA implementation to a 3D PGA implementation of inverse kinematics by changing a single variable (from 2 to 3) that defines the number of dimensions.

I haven’t yet looked into the javascript graphics code used in that inverse kinematics demonstration, but I’m wondering if Makie is similarly capable of being “dimension agnostic”. As an initial test, I’m wondering if Makie can

  1. draw a 2D scatter plot defined by a single array in which each column defines the 2D coordinates of a point,
  2. draw a 3D scatter plot defined by extending the number of rows in that array from 2 to 3,
  3. make the conversion from 2D to 3D graphics with no change in code other than the change in the number of rows in the array.

In other words, is there some sort of converter from Vector{Float32} to Point2f or Point3f (depending on the length of the vector) that would allow Makie to change from a 2D scatter plot to a 3D scatter plot after changing only the number of rows in the array defining the points?

I think this is not yet possible with Makie.

The reseaon is that Makie uses a clever layouting system for composing figures with various kinds of so called Block elements. A Block element is something like an Axis, Axis3, Colobar, Legend etc which communicate their dimensions with one another automatically such that everything fits (almost always) nicely into one figure.

Now comes the problem: For 2D plots you draw curves into Axis blocks, for 3D you use Axis3. If you would want to just flip a switch from 2D to 3D you would have to first remove an Axis from the layout and then reinsert an Axis3 in the same place. This however is not yet officially supported, I think.

@jules Or did I miss something?

However, if you really need that functionality right now we could hack a custom solution together for you. Either

  1. use only a Axis3 block and freeze the camera to a view that is orthogonal to a plane of the 3D axis to show the 2D data,
  2. or implement the insertion/deletion of blocks with the non-public interface of GridLayoutBase. I have posted this somewhere else here on the forum.

It’s more difficult if you want to flip back and forth between 2d and 3d in an existing interactive figure, for the reasons mentioned above.

But scatter(arr_2d) will give you a 2d plot and scatter(arr_3) a 3d plot.

Fortunately, I don’t think a slicer application needs that functionality. A single 3D subplot (to show the 3D object) along side a single 2D subplot (to show a cross-section of the object) is enough.

However, there seems to be a lot of other applications of projective geometric algebra in which being dimension agnostic would be helpful, for example Steven De Keninck’s demonstration simulating the 2D physics dynamics of a wire frame square dangling on an elastic string. By changing the number of dimensions to 3, the dangling wire frame square becomes a dangling wire frame cube, and by changing the number of dimensions to 4, the dangling wire frame cube becomes a dangling wire frame tesseract.

For those other projective geometric algebra applications where dimension independence would be helpful, is it currently possible to define the number of dimensions as a GLMakie observable that triggers a whole new layout?

I mean you can always write a function that takes the dimension as an input and within the function create create a new Figure and setup the layout according to the dimensions.
Later on you just call the function with the dimension you want.

E.g.

using GLMakie
GLMakie.activate!()

function plot_something(dim)
   f = Figure()
   if dim == 2
     ax = Axis(f[1,1])
     lines!(ax, 1:3, 1:3)
   elseif dim == 3
     ax = Axis3(f[1,1])
     lines!(ax, 1:3, 1:3, 1:3)
   else
     error("...")
   end

   return f
end

display(plot_something(2))
#display(plot_something(3))

Thanks, I’ll try that … after reading through the ganja.js graphics code to see how it transforms projective geometric algebra elements to 2D and 3D coordinates.

I am still exploring how to plot projective geometric algebra (PGA) expressions but I have run into 3 basic questions about Makie, listed at the end of this post.

Hoping that the dimension of the space can be defined only once to change the “dimension agnostic” behavior of the rest of the PGA application, perhaps that one definition can be in the name of the included PGA code: ripga2d.jl for 2D and ripga3d.jl for 3D. At least that is my current approach, as shown in this initial attempt at porting (from javascript to Julia) the PGA solution to the inverse kinematics problem.

# file: pga2d3d_ik.jl
# interactive graphical demo of Inverse Kinematics 
using GLMakie, Observables
include("ripga3d.jl") 

# translate distance along line
function xlator(line::Vector{Float32},dist::Number) 
	return 1 - 0.5*dist*(e0*normalize(line)*!e0)
end

# inverse kinematics
function ik(nLink::Int64=6)
	nEP = nLink + 1	# number of link endpoints
	armLength = 3	# max reach of robot arm
	C = ["red"; "green"; "blue"] # line colors
	
	# allocate endpoint PGA expressions
	# (appended endpoint in last column is target)
	EPX = Matrix{Float32}(undef, (length(e0),nEP+1))
	
	# define link endpoints
	linkLength::Float32 = armLength / nLink
	for iEP = 1:nEP+1
		EPX[:,iEP] = !(e0 + (iEP*linkLength - 1.5)*e1)
	end
	EPX[:,nEP+1] += 0.2*e013 # offset target point a little
	
	# plot endpoints and target point
	PTS = toPlot(EPX)
	display(PTS)
	fig = Figure()
	ax1 = Axis(fig[1,1], 
		title = "initial endpoints,\n" *
			"the target is separate from robot arm", 
		aspect=1)
	ax2 = Axis(fig[1,2], 
		title = "endpoints after 2 steps\n" *
			"of backward relaxation",
		aspect= 1)
	scatterlines!(ax1, 
		PTS[1:3,1:nEP], color="black")
	scatterlines!(ax1, # target point is at end
		[PTS[1,end]], [PTS[2,end]], [PTS[3,end]],
		color="black")
	
	scatterlines!(ax2,
		[PTS[1,end]], [PTS[2,end]], [PTS[3,end]],
		color="black")
	for iRelax = 1:1 # TODO: set loop count back to 4
		# set tip to target, changing length of last link
		EPX[:,nEP] = EPX[:,nEP+1]
		PTS = toPlot(EPX)
		scatterlines!(ax2,
			PTS[1:3,1:nEP], color = "black")
		
		# restore link lengths from back to front
		for jLink = 1:2 # TODO: revive loop count back to nEP-1
			i = nEP - jLink
			
			iColor = mod(jLink-1,3) + 1
			XL = xlator(EPX[:,i+1]&EPX[:,i],linkLength)
			EPX[:,i] = XL >>> EPX[:,i+1]
			PTS = toPlot(EPX)
			scatterlines!(ax2,
				PTS[1:3,i-1:i+1], color = C[iColor])
		end
	end
	fig
end

The resulting plot:

My questions about Makie:

  1. How to set the plot’s aspect ratio? There is a tutorial about aspect ratio and size control, and it clearly states “This aspect is not concerned with what the data limits are, it’s just about the relative visual length of the axes.” but I don’t see a similar tutorial about how to set the aspect ratio of data values, which I suspect is a more common concern to people plotting their data.

  2. How to get the plot’s value limits? In PGA, the basis e0 defines the origin in nD+1 space and the basis e1 defines the first dimension … but not in a way I was expecting: in 2D PGA e1 is the x=0 line (i.e., the y axis) So to plot even something as simple as e1 in 2D, the limits of the plot are needed.

  3. How to get a right-handed 3D coordinate system? In the plot above, all the z values are zero and the plot reverted to 2D. However when I define some z values, the resulting 3D plot has a left-handed coordinate system which is troublesome for PGA application developers because PGA is sensitive to the ordering of geometric algebra elements.

Thanks for the help. When the port from javascript to Julia finally works, I think it will help the PGA application development community as well as the Julia community.

Have a look at https://docs.makie.org/v0.19.1/examples/blocks/axis/index.html#auto-reset_behavior and the sections Controlling Axis Aspect ratios, Controlling data aspect ratios, Linking axes.

How to get the plot’s value limits?

If by plot's value limits you mean the axis limits, then try

f = Figure()
ax = Axis(f[1,1])
((xmin, xmax), (ymin, ymax)) = Makie.limits(ax.finallimits[])

How to get a right-handed 3D coordinate system?

That’s a good question. I don’t think there is currently a build-in option to do that.
There are some transformation methods, like Makie.scale!, Makie.rotate!, but they can only be applied to plot elements like Lines, ScatterLines <: Makie.MakieCore.Transformables but not to block elements like Axis, Axis3. (Also: Blindly applying it to ax.blockscene rips the whole axis apart :laughing: ).
Maybe @jules has an idea for that?

Both Axis3 and LScene produce right handed coordinate systems. With red x, green y, blue z:

2 Likes

Thanks, I’ll give them a try.

The 2D plots now look good. I’ve never seen bivector.net’s inverse kinematics algorithm before (even without PGA) but the following plot shows the algorithm quickly converges:

The code that generates that plot is below. I’m rather new to Julia, so if anyone notices things that would improve efficiency or style, please let me know. I’ll post the ripga3d.jl (i.e., Reference Implementation of Projective Geometric Algebra 3D) code when it is able to plot more PGA expressions than just points.

# pga2d3d_ik.jl
# interactive graphical demo of Inverse Kinematics 
using GLMakie, Observables
include("ripga3d.jl") 

# translate distance along line
function xlator(line::Vector{Float32},dist::Number) 
	return 1 - 0.5*dist*(e0*normalize(line)*!e0)
end

# inverse kinematics
# arguments:
# - coordinates of target of robot arm
# - 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,1], limits=LIM, yticks=YTIC, aspect=1,
			title = "6. endpoints after 3rd pass\n" *
				"of backward relaxation");
		Axis(fig[2,2], limits=LIM, yticks=YTIC, aspect=1, 
			title = "7. endpoints after 3rd pass\n" *
				"of forward relaxation");
		Axis(fig[2,3], limits=LIM, yticks=YTIC, aspect=1,
			title = "8. endpoints after 4th pass\n" *
				"of backward relaxation");
		Axis(fig[2,4], 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)
	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(PX[:,i+1]&PX[:,i],linkLength)
			PX[:,i] = XL >>> PX[:,i+1]
			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(PX[:,i-1]&PX[:,i], linkLength)
			PX[:,i] = XL >>> PX[:,i-1]
			P = toPlot(PX)
			scatterlines!(AX[iAx],
				P[1:3,i-1:i], color = C[iColor])
		end
	end
	fig
end

Thanks for clarifying.

Somehow I was tricked into thinking that the intersection of the three back panels is the coordinate origin :sweat_smile: But adding arrows makes it clearer :slight_smile:

Is CGAL helpful for the OP’ question?

Axis3 is rotated differently than the LScene axis because I assumed it was more natural to expect the xy plane to stay similar to Axis in that x increases to the right and y increases to the top, z then increases upwards. For me, LScene is impractical in that regard, maybe we should decide on one behavior.

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?

Usually, Pkg.update() should be enough.

However, it can happen that some packages are holding back others from upgrading.
This issue is often encountered when people install lots of projects into the global environment, which is the on Julia starts with (unless told to not). Your output suggests that this is the case, because your active environment is environments\v1.8\Project.toml (that’s the default). However, I am not 100% sure that this why it blocks the GLMakie upgrade (I thought there is a little symbol next to the pkg name indicating that).

Anyways, what you could also do is to move all of your inverse kinematics code into a separate package, see ] help generate in the REPL, and then add only the needed packages in there, e.g. ] add GLMakie. This will always pull in the latest version. Also see: 2. Getting Started · Pkg.jl

1 Like

In my JavaScript to Julia port of a 3D slicer example application, some simple way to orient/scale/offset 3D model axes would have been helpful.

The JavaScript version of the application loads a Wavefront 3D object text file that defines all the triangle vertices and the triangle faces of a model of a bunny. As the JavaScript version plots the bunny rotating, it also calculates horizontal cross sections.

The Julia/GLMake/FileIO version of the application load()-ed the bunny object on its side (because that old Wavefront object text file format defined the model with the y-axis as the azimuth but GLMakie apparently expects the z-axis to be the azimuth). I ended up writing the following script to change the azimuth axis, and also change vertex scaling and offsets.

# xobj.jl
# Transform a wavefront 3D object's 
# - axis orientations (new y = -old z; new z = old y)
# - scale
# - offsets
function xobj(fn="xbunny.obj", scale=13., 
	xOff=0.218413, yOff=-0.01927, zOff=-0.4330287)
	
	# initialize
	iLine = 0
	i1 = 1
	LIM = [Inf -Inf; Inf -Inf; Inf -Inf]
	
	# open output file
	open(fn, "w") do io
	
		# for each line of input file
		for line in eachline(fn[2:end])
			iLine += 1
			
			# if line does not define a vertex
			if line[1] != 'v'
				println(io, line) # copy it
			
			# else line defines a vertex
			else
				nChar = length(line)
				print(io, "v")
				iState = 0
				vTmp = 0.
				i1 = 3
				for iChar = 3:nChar
					if isspace(line[iChar]) || iChar==nChar
						iState += 1
						i2 = iChar==nChar ? iChar : iChar - 1
						v = parse(Float32,line[i1:i2])
						v *= Float32(scale)
						if iState == 1 # x
							v += Float32(xOff)
							print(io, ' ') # prefix space
							print(io, v)
							if LIM[1,1] > v
								LIM[1,1] = v
							end
							if LIM[1,2] < v
								LIM[1,2] = v
							end
						elseif iState == 2 # y
							vTmp = v
						elseif iState == 3 # z
							v = -v + Float32(yOff)
							print(io, ' ') # prefix space
							print(io, v)
							if LIM[2,1] > v
								LIM[2,1] = v
							end
							if LIM[2,2] < v
								LIM[2,2] = v
							end
							
							vTmp += Float32(zOff)
							print(io, ' ') # prefix space
							println(io, vTmp)
							if LIM[3,1] > vTmp
								LIM[3,1] = vTmp
							end
							if LIM[3,2] < vTmp
								LIM[3,2] = vTmp
							end
							break
						end
						i1 = iChar + 1
					end # if character ends a field
				end # for each character
			end # else line defines a vertex
		end # input file
		display(LIM)
	end # output file
end

With the modified orientation of axes, GLMakie greatly simplifies this 3D slicer example application by using the azimuth parameter to rotate the object, avoiding the JavaScript application’s approach of implementing a rotor and moving a camera. (When I finally get the Julia version’s projective geometric algebra code to calculate the slices, I’ll post the code here.)

A quick follow up showing some results …

sl.small

The geometric algebra community is impressed by the speed and looks of GLMakie plots. I think GLMakie will be helpful to them. The above animation is a port of their 3D slicer application example from javascript (ganja.js) to Julia and GLMakie. On my laptop computer, Julia and GLMakie generated the 33 second .mp4 video in 10 seconds.

Similarly, I think some of the work from the geometric algebra community could be helpful in Makie. For example in the above animation, projective geometric algebra (PGA) easily calculates some integrals that would be tricky to calculate without PGA. Concretely,

  • the area of the cross section polygon is calculated by summing the polygon edges, and
  • the volume of the 3D object is calculated by summing the polygon faces.

The idea that integration can happen by adding PGA surface objects still seems like magic to me but it works. The Julia and GLMakie code (and chocolate bunny mesh) that generated the above 3D slicer animation is in example 3.2 of the Julia and Projective Geometric Algebra essay.