I need to plot several thousands of quite simple 3D objects (boxes, cylinders, …) and for this I do create a mesh for each one implementing GeometryBasics.coordinates(...)
and GeometryBasics.faces(...)
. Then I plot them either using mesh!(...)
and/or wireframe!(...)
for each created mesh. It works nicely but when I increase the number objects starts being very very slow. In practice, I can not do more than a few thousand.
What I am doing wrong? Should I merge the meshes and call mesh!(...)
once? Is there an utility to do it? Thanks for your help.
If it’s just boxes + cylinders, you should be able to use meshscatter, which uses instancing and should be much faster.
Otherwise, merging is indeed a good idea, here is an example:
Help me improve the performance for plotting many wireframe? The previous answer worked for normal mesh of shapes but I do not know how to apply this to wireframe.
Current MWE exhibiting growing time to plot wireframes:
using GLMakie, GeometryBasics, BenchmarkTools, Printf
function colormesh(geometry,color)
mesh1 = normal_mesh(geometry)
npoints = length(GeometryBasics.coordinates(mesh1))
return GeometryBasics.pointmeta(
mesh1;
shading=fill(false,npoints),
color=fill(color, npoints),
transparency=fill(true,npoints),
overdraw=fill(true,npoints),
)
end
function setup(nRects,alpha,b,p)
time = @elapsed begin
println("\tGenerating random geometries and colors")
#Make a bunch of rectangles with associated color information
rectangles = Rect3f[]
colors = RGBAf[]
base_mean,base_std = b[1],b[2]
pos_mean,pos_std = p[1],p[2]
for i in 1:nRects
base = (rand(Vec3f).-base_mean)./base_std
position = (rand(Vec3f).-pos_mean)./pos_std
rectangle = Rect3f(base,position)
color = RGBAf(rand(Float32,3)...,alpha)
push!(rectangles,rectangle)
push!(colors,color)
end
end
@printf("\tSetup done, took %f seconds\n",time)
return rectangles,colors
end
function basicPlot()
time = @elapsed begin
println("\tCreating figure and axis (basicPlot)")
fig = Figure()
ax = Axis3(
fig[1, 1],
xlabel = "x label",
ylabel = "y label",
zlabel = "y label",
title = "Title"
)
end
@printf("\tBasic fig and ax created, took %f seconds\n",time)
return fig,ax
end
function meshPlotRects(rectangles,colors)
time = @elapsed begin
println("\tCreating figure and axis (meshPlotRects)")
meshes = map(colormesh,rectangles,colors)
println("\t\tMeshes created")
fig = Figure()
fig, ax, meshplot = mesh(merge(meshes))
println("\t\tMesh plotted with fig and ax created")
end
@printf("\tDone, took %f seconds\n",time)
return fig,ax
end
function setView!(ax)
time = @elapsed begin
println("\tSetting the view")
#Setup the view
scene = ax.scene
center!(scene)
cam = cameracontrols(scene)
cam.attributes[:projectiontype][] = Makie.Orthographic
cam.zoom_mult[] = 10.0f0
update_cam!(scene, cam)
end
@printf("\tDone, took %f seconds\n",time)
end
function doWireframe(ax,rectangles,colors)
time = @elapsed begin
println("\tWireframe started")
for (rect,c) in zip(rectangles,colors)
dark_c = RGBAf([c.r,c.g,c.b]*.5...,1.)
wireframe!(ax, rect; color=dark_c, transparency=true)
end
end
@printf("\tWireframe done, took %f seconds\n",time)
end
displayPlots(fs)=[display(GLMakie.Screen(),f) for f in fs]
function main(nRects)
simple = false
alpha = .10
time = @elapsed begin
@printf("\nProcessing for %d rectangles\n",nRects)
rectangles,colors = setup(nRects,alpha,(.5,.05),(10.,2.))
fig,ax = simple ? basicPlot() : meshPlotRects(rectangles,colors)
doWireframe(ax,rectangles,colors)
if !simple setView!(ax) end
end
@printf("Done, took %f seconds\n",time)
return fig
end
figs = (main(i) for i in (1,1,10,100,1_000,10_000))
println("\nDrawing windows")
time = @elapsed displayPlots(figs)
@printf("Drawing took %f seconds\n",time)
println("\n:: PAUSED :: (Press any key to exit.)")
readline()
Which produces the following output:
As you can see the wireframe portion grows nonlinearly in time to execute.
N Time(s) Delay Step
----------------------------------------------------
1 rectangle 0.002785 1x N/A
10 rectangle 0.031733 11x 11x
100 rectangle 0.475575 171x 16x
1_000 rectangle 20.538121 7375x 43x
10_000 rectangle 1645.453709 590,827x 80x
Compared to mesh(merge(meshes)) which seems pretty stable
N Time(s) Delay Step
----------------------------------------------------
1 rectangle 0.007688 1x N/A
10 rectangle 0.042709 6x 6x
100 rectangle 0.019673 3x 0.5x
1_000 rectangle 0.033248 4x 1.3x
10_000 rectangle 0.241189 3x 0.8x
I can appreciate that the timing is not particularly exact but regardless wireframe!() plotting is taking forever for some reason.
PS - I do not think is in any way related to the multiple window display as the same thing occurs for fewer windows or even a single one where the rectangle count is high.
Can you do one wireframe of the merged mesh?
One plot per box is a really bad idea for performance, especially if it goes into the thousands…
As in?
wireframe!(ax, meshes; color=:black, transparency=true)
Does not work:
ERROR: LoadError: MethodError: no method matching coordinates(::Vector{GeometryBasics.Mesh{3, Float32, TriangleP{3, Float32, PointMeta{3, Float32, Point{3, Float32}, (:normals, :shading, :color, :transparency, :overdraw), Tuple{Vec{3, Float32}, Bool, ColorTypes.RGBA{Float32}, Bool, Bool}}}, FaceView{TriangleP{3, Float32, PointMeta{3, Float32, Point{3, Float32}, (:normals, :shading, :color, :transparency, :overdraw), Tuple{Vec{3, Float32}, Bool, ColorTypes.RGBA{Float32}, Bool, Bool}}}, PointMeta{3, Float32, Point{3, Float32}, (:normals, :shading, :color, :transparency, :overdraw), Tuple{Vec{3, Float32}, Bool, ColorTypes.RGBA{Float32}, Bool, Bool}}, NgonFace{3, OffsetInteger{-1, UInt32}}, StructArrays.StructVector{PointMeta{3, Float32, Point{3, Float32}, (:normals, :shading, :color, :transparency, :overdraw), Tuple{Vec{3, Float32}, Bool, ColorTypes.RGBA{Float32}, Bool, Bool}}, NamedTuple{(:position, :normals, :shading, :color, :transparency, :overdraw), Tuple{Vector{Point{3, Float32}}, Vector{Vec{3, Float32}}, Vector{Bool}, Vector{ColorTypes.RGBA{Float32}}, Vector{Bool}, Vector{Bool}}}, Int64}, Vector{NgonFace{3, OffsetInteger{-1, UInt32}}}}}})
Closest candidates are:
coordinates(::TupleView) at C:\Users\DIi.julia\packages\GeometryBasics\KE3OI\src\viewtypes.jl:39
coordinates(::Simplex) at C:\Users\DIi.julia\packages\GeometryBasics\KE3OI\src\basic_types.jl:149
coordinates(::GeometryBasics.Ngon) at C:\Users\DIi.julia\packages\GeometryBasics\KE3OI\src\basic_types.jl:73
…
Neither does
wireframe!(ax, rectangles; color=:black, transparency=true)
ERROR: LoadError: MethodError: no method matching coordinates(::Vector{HyperRectangle{3, Float32}})
Closest candidates are:
coordinates(::TupleView) at C:\Users\DIi.julia\packages\GeometryBasics\KE3OI\src\viewtypes.jl:39
coordinates(::Simplex) at C:\Users\DIi.julia\packages\GeometryBasics\KE3OI\src\basic_types.jl:149
coordinates(::GeometryBasics.Ngon) at C:\Users\DIi.julia\packages\GeometryBasics\KE3OI\src\basic_types.jl:73
…
wireframe docs do not cover a use case other than
wireframe(x, y, z)
wireframe(positions)
wireframe(mesh)
No, the merged mesh, as you do in your example for the mesh
call…
So:
wireframe!(ax, merge(meshes); color=:black, transparency=true)
Ahhh…
That does work. Except each of the cube faces splits into a triangle which is not desired
Versus
Much faster though!
Drawing windows
Processing for 1 rectangles
Generating random geometries and colors
Setup done, took 0.006586 seconds
Creating figure and axis (meshPlotRects)
Meshes created
Mesh plotted with fig and ax created
Done, took 23.402483 seconds
Setting the view
Done, took 0.019364 seconds
Done, took 23.523272 secondsProcessing for 1 rectangles
Generating random geometries and colors
Setup done, took 0.000337 seconds
Creating figure and axis (meshPlotRects)
Meshes created
Mesh plotted with fig and ax created
Done, took 0.012360 seconds
Setting the view
Done, took 0.000512 seconds
Done, took 0.168637 secondsProcessing for 10 rectangles
Generating random geometries and colors
Setup done, took 0.006376 seconds
Creating figure and axis (meshPlotRects)
Meshes created
Mesh plotted with fig and ax created
Done, took 0.037795 seconds
Setting the view
Done, took 0.000732 seconds
Done, took 0.125865 secondsProcessing for 100 rectangles
Generating random geometries and colors
Setup done, took 0.000338 seconds
Creating figure and axis (meshPlotRects)
Meshes created
Mesh plotted with fig and ax created
Done, took 0.022643 seconds
Setting the view
Done, took 0.001973 seconds
Done, took 0.057639 secondsProcessing for 1000 rectangles
Generating random geometries and colors
Setup done, took 0.000724 seconds
Creating figure and axis (meshPlotRects)
Meshes created
Mesh plotted with fig and ax created
Done, took 0.095724 seconds
Setting the view
Done, took 0.016857 seconds
Done, took 0.145963 secondsProcessing for 10000 rectangles
Generating random geometries and colors
Setup done, took 0.012091 seconds
Creating figure and axis (meshPlotRects)
Meshes created
Mesh plotted with fig and ax created
Done, took 0.551681 seconds
Setting the view
Done, took 0.187104 seconds
Done, took 0.804036 seconds
Drawing took 46.364086 seconds
I messed around trying to figure out how else to create valid mesh input of a type that would not be rendered as triangles
- GeometryBasics.Polytope() which calls one of:
"""
The fully concrete Ngon type, when constructed from a point type!
"""
function Polytope(::Type{<:NNgon{N}}, P::Type{<:AbstractPoint{NDim,T}}) where {N,NDim,T}
return Ngon{NDim,T,N,P}
end
"""
The Simplex Polytope element type when indexing an array of points with a SimplexFace
"""
function Polytope(P::Type{<:AbstractPoint{Dim,T}},
::Type{<:AbstractSimplexFace{N}}) where {N,Dim,T}
return Simplex{Dim,T,N,P}
end
"""
The fully concrete Simplex type, when constructed from a point type!
"""
function Polytope(::Type{<:NSimplex{N}}, P::Type{<:AbstractPoint{NDim,T}}) where {N,NDim,T}
return Simplex{NDim,T,N,P}
end
Base.show(io::IO, x::LineP) = print(io, "Line(", x[1], " => ", x[2], ")")
- Simplex{3, Float32, 4} which calls:
function (::Type{<:NSimplex{N1}})(p0::P, points::Vararg{P,N2}) where {P<:AbstractPoint{Dim,T},
N1, N2} where {Dim,T}
@assert N1 == N2+1
return Simplex{Dim,T,N1,P}(SVector(p0, points...))
end
- Or GeometryBasics.Ngon{3, Float32, 4} which is equivalent to GeometryBasics.Quadrilateral{3,Float32} which calls:
function (::Type{<:NNgon{N1}})(p0::P, points::Vararg{P,N2}) where {P<:AbstractPoint{Dim,T},
N1, N2} where {Dim,T}
@assert N1 == N2+1
return Ngon{Dim,T,N1,P}(SVector(p0, points...))
end
Base.show(io::IO, x::NNgon{N}) where {N} = print(io, "Ngon{$N}(", join(x, ", "), ")")
I am out of my depth and could not seem to get to an input of the types expected to produce a non triangular mesh.
A prod in the right direction would be further appreciated
Sorry, GeometryBasics is a mess and I still haven’t found time to clean it up…
Try:
merge(mesh.(rectangles; facetype=QuadFace{Int}))
Btw, sadly only color can be passed as an attribute to the mesh itself, so all the other attributes you have here need to get passed to the mesh call separately: