How to scale all the vertices in a GeometryBasics.Mesh?

I’m attempting to port Steven De Keninck’s 3D slicer example application (listed here and demonstrated here) from JavaScript to Julia. The example application loads a 3D model of a bunny (from https://enkimute.github.io/ganja.js/examples/bunny.obj having 5002 triangle faces) and calculates cross sections of it using projective geometric algebra. The demonstration also shows that projective geometric algebra can efficiently calculate the cross section circumference and area, which would be particularly helpful in a slicer application for 3D printing.

In the JavaScript version the bunny object text file is parsed line by line, during which all the vertices are scaled up by a factor of 13. In contrast, the Julia version can parse the whole bunny object text file using Makie.FileIO’s load(), but, to follow the JavaScript version, the Julia version should then scale up all the vertices by a factor of 13 in the immutable GeometryBasics.Mesh structure returned by Makie.FileIO load(). Any tips on how to do that? Specifically, how can the vertices in the GeometryBasics.Mesh structure be converted to some array that can be scaled and then put back in a GeometryBasics.Mesh structure to be plotted by Makie?

using GLMakie, Makie.FileIO, GeometryBasics

# load  faces of bunny object
F = load("bunny.obj")
# (TODO: scale the vertices of F by 13)

# plot bunny using Axis3
fig = Figure(resolution = (1000, 700))
ax3d = Axis3(fig[1,1],
	elevation = pi/2,
	azimuth = 3*pi/2,
	aspect = (1,1,1))
m = mesh!(ax3d, F)
fig

P.S., At first, I thought this was a general question about how to modify elements of an immutable structure and I posted the question here. However, the question of how to convert the vertices to an array that can be scaled appears to be specific to GeometryBasics. Of course, it is possible to scale the vertices while parsing the 3D object text file line by line in the Julia version just like in the JavaScript version, but I would like the Julia port to be as elegant as possible because the example will be used in an essay proposing that Julia is a good language choice when implementing projective geometric algebra applications.

What about the following:

map(F) do t
    Triangle(map(v->13*v, t)...)
end |> Mesh

(I can’t test the result with Makie right now but the output type looks correct.)

1 Like

maybe this will help you a little bit with the vertices. (Here, I scaled the bunny).

1 Like

I’m still reading the Julia docs about functions, tuples, etc to try to better understand your nifty suggestion. It appears that the following mapping does not create the correct type because GLMakie can plot F but not F13 even though the triangle vertices in F13 are correctly scaled.

using GLMakie, Makie.FileIO, GeometryBasics

julia> F = load("bunny.obj");

julia> F13 = map(F) do t
           Triangle(map(v->13*v, t)...)
       end |> GeometryBasics.Mesh;

julia> typeof(F)
GeometryBasics.Mesh{3, Float32, GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}, SimpleFaceView{3, Float32, 3, OffsetInteger{-1, UInt32}, Point{3, Float32}, NgonFace{3, OffsetInteger{-1, UInt32}}}}

julia> typeof(F13)
GeometryBasics.Mesh{3, Float32, GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}, Vector{GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}}}

julia> F[1]
Triangle(Float32[-0.089491, 0.143926, 0.0124885], Float32[-0.0865619, 0.142492, 0.00843268], Float32[-0.0896984, 0.139713, 0.0137748])

julia> F13[1]
Triangle(Float32[-1.163383, 1.871038, 0.1623505], Float32[-1.1253047, 1.852396, 0.10962484], Float32[-1.1660792, 1.816269, 0.1790724])

Qualifying that Mesh is defined by GLMakie doesn’t seem to help:

julia> F13 = map(F) do t
           Triangle(map(v->13*v, t)...)
       end |> Mesh;
WARNING: both GeometryBasics and GLMakie export "Mesh"; uses of it in module Main must be qualified
ERROR: UndefVarError: Mesh not defined
Stacktrace:
 [1] top-level scope
   @ REPL[7]:1

julia> F13 = map(F) do t
           Triangle(map(v->13*v, t)...)
       end |> GLMakie.Mesh;
ERROR: MethodError: no method matching (MakieCore.Mesh)(::Vector{GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}})
Closest candidates are:
  (::Type{<:Combined})(::Any, ::Any, ::Any, ::Any, ::Any) at C:\Users\gsgm2\.julia\packages\Makie\Za3LL\src\interfaces.jl:122
  (::Type{<:AbstractPlot{Typ}})(::Union{AbstractScene, MakieCore.ScenePlot}, ::Attributes, ::Any) where Typ at C:\Users\gsgm2\.julia\packages\Makie\Za3LL\src\interfaces.jl:183
  (::Type{<:AbstractPlot{Typ}})(::Union{AbstractScene, MakieCore.ScenePlot}, ::Attributes, ::Any, ::Any) where Typ at C:\Users\gsgm2\.julia\packages\Makie\Za3LL\src\interfaces.jl:204
Stacktrace:
 [1] |>(x::Vector{GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}}, f::Type{MakieCore.Mesh})
   @ Base .\operators.jl:911
 [2] top-level scope
   @ REPL[8]:1

julia>

It seems that just passing a list of triangles to Mesh is not enough (note that we need GeometryBasics.Mesh here, to create the geometric structure rather than plot it).

The following works for me:

using CairoMakie
using GeometryBasics
using FileIO

F = load("bunny.obj")

c, f = coordinates(F), faces(F)
F13 = GeometryBasics.Mesh(13*c, f)

fig = Figure(resolution = (1000, 700))
ax3d = Axis3(fig[1,1],
	elevation = pi/2,
	azimuth = 3*pi/2,
	aspect = (1,1,1),
)
m = mesh!(ax3d, F13)
fig
1 Like

Thanks! (It works with GLMakie too.)