# Makie: how to improve plotting performance with thousands of 3D objects

**URL:** https://discourse.julialang.org/t/makie-how-to-improve-plotting-performance-with-thousands-of-3d-objects/85863
**Category:** Visualization
**Tags:** makie
**Created:** [August 17, 2022, 8:23am UTC](https://discourse.julialang.org/t/makie-how-to-improve-plotting-performance-with-thousands-of-3d-objects/85863 "2022-08-17T08:23:35Z")
**Posts on this page:** 8
**Page:** 1

<div class="post-metadata">

### Author: ![peremato](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/peremato/32/29128_2.png) [@peremato](https://discourse.julialang.org/u/peremato)
#### Post date: [August 17, 2022, 8:23am UTC](https://discourse.julialang.org/t/makie-how-to-improve-plotting-performance-with-thousands-of-3d-objects/85863/1 "2022-08-17T08:23:35Z")

</div>

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.

---

<div class="post-metadata">

### Author: ![sdanisch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sdanisch/32/1406_2.png) [@sdanisch](https://discourse.julialang.org/u/sdanisch)
#### Post date: [August 17, 2022, 9:23am UTC](https://discourse.julialang.org/t/makie-how-to-improve-plotting-performance-with-thousands-of-3d-objects/85863/2 "2022-08-17T09:23:31Z")

</div>

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:

> <https://github.com/JuliaPlots/Makie.jl/blob/master/ReferenceTests/src/tests/examples3d.jl#L30>

---

<div class="post-metadata">

### Author: ![David\_A](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/david_a/32/46657_2.png) [@David\_A](https://discourse.julialang.org/u/David_A)
#### Post date: [February 9, 2023, 7:27pm UTC](https://discourse.julialang.org/t/makie-how-to-improve-plotting-performance-with-thousands-of-3d-objects/85863/3 "2023-02-09T19:27:29Z")

</div>

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:

```julia
 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.

---

<div class="post-metadata">

### Author: ![sdanisch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sdanisch/32/1406_2.png) [@sdanisch](https://discourse.julialang.org/u/sdanisch)
#### Post date: [February 9, 2023, 9:23pm UTC](https://discourse.julialang.org/t/makie-how-to-improve-plotting-performance-with-thousands-of-3d-objects/85863/4 "2023-02-09T21:23:12Z")

</div>

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…

---

<div class="post-metadata">

### Author: ![David\_A](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/david_a/32/46657_2.png) [@David\_A](https://discourse.julialang.org/u/David_A)
#### Post date: [February 14, 2023, 2:29pm UTC](https://discourse.julialang.org/t/makie-how-to-improve-plotting-performance-with-thousands-of-3d-objects/85863/5 "2023-02-14T14:29:52Z")

</div>

> [@sdanisch](#):
>
> 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](https://docs.makie.org/stable/examples/plotting_functions/wireframe/index.html#wireframe) docs do not cover a use case other than

> wireframe(x, y, z)  
> wireframe(positions)  
> wireframe(mesh)

---

<div class="post-metadata">

### Author: ![sdanisch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sdanisch/32/1406_2.png) [@sdanisch](https://discourse.julialang.org/u/sdanisch)
#### Post date: [February 14, 2023, 2:33pm UTC](https://discourse.julialang.org/t/makie-how-to-improve-plotting-performance-with-thousands-of-3d-objects/85863/6 "2023-02-14T14:33:08Z")

</div>

> [@David\_A](#):
>
> `merge(meshes)`

No, the **merged mesh** , as you do in your example for the `mesh` call…  
So:

```julia
wireframe!(ax, merge(meshes); color=:black, transparency=true)

```

---

<div class="post-metadata">

### Author: ![David\_A](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/david_a/32/46657_2.png) [@David\_A](https://discourse.julialang.org/u/David_A)
#### Post date: [February 14, 2023, 5:17pm UTC](https://discourse.julialang.org/t/makie-how-to-improve-plotting-performance-with-thousands-of-3d-objects/85863/7 "2023-02-14T17:17:00Z")

</div>

Ahhh…

That does work. Except each of the cube faces splits into a triangle which is not desired

 ![image](https://global.discourse-cdn.com/julialang/original/3X/5/1/5155ef7cd2f14b4044e884af9ecf4dabac9ac2f5.png)

Versus

 ![image](https://global.discourse-cdn.com/julialang/original/3X/1/7/177b8e3c339650a9095813818abca9b01d025b81.png)

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 seconds
> 
> Processing 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 seconds
> 
> Processing 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 seconds
> 
> Processing 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 seconds
> 
> Processing 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 seconds
> 
> Processing 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:

```julia
"""
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:

```julia
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:

```julia
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 🙂

---

<div class="post-metadata">

### Author: ![sdanisch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sdanisch/32/1406_2.png) [@sdanisch](https://discourse.julialang.org/u/sdanisch)
#### Post date: [February 15, 2023, 8:58am UTC](https://discourse.julialang.org/t/makie-how-to-improve-plotting-performance-with-thousands-of-3d-objects/85863/8 "2023-02-15T08:58:15Z")

</div>

Sorry, GeometryBasics is a mess and I still haven’t found time to [clean it up](https://github.com/JuliaGeometry/GeometryBasics.jl/pull/173)…  
Try:

```julia
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:

> [@David\_A](#):
>
> ```julia
> GeometryBasics.pointmeta(
> mesh1;
> shading=fill(false,npoints),
> color=fill(color, npoints),
> transparency=fill(true,npoints),
> overdraw=fill(true,npoints),
> )
> 
> ```
