From Python-cgkit to Makie.jl

I am trying to convert the 3D viewer of my kitepower
system simulator (https://bitbucket.org/ufechner/freekitesim)
to Julia/ Makie.jl.

The old Python/C++ library is unsupported since 8 years and
the newest system where it works is Ubuntu 14.04, so I
need something that is easier to install and well maintained.

But the quality, performance and simplicity of
cgkit is still unmatched, so perhaps it can still serve
as inspiration for Julia developers.
see: https://web.archive.org/web/20180611223914/http://cgkit.sourceforge.net/doc2/

Here a screenshot to show what I want to achieve:


A 3D view of a scene, updated 20 times per second.

With Julia 1.6 the load times are becoming acceptable,
so I thought this is a nice project for the lockdown.

First try in Julia:

using Makie, GLMakie

function demo3(scene)
    X = zeros(2)
    Y = [-10.0, 10.0]
    Z = zeros(2)
    lines!(scene, X, Y, Z, color = :black, linewidth = 2)
    X = [0.0, 10.0]
    Y = zeros(2)
    Z = zeros(2)
    lines!(scene, X, Y, Z, color = :black, linewidth = 2)
    X = zeros(2)
    Y = zeros(2)
    Z = [0.0, 10.0]
    lines!(scene, X, Y, Z, color = :black, linewidth = 2)    
    a = 10
    X = range(0, stop=10, length=5)
    Y = zeros(length(X)) 
    Z = a .* cosh.(X./a) .- 10.0
    lines!(scene, X, Y, Z, color = :yellow, linewidth = 5)
    cam = cameracontrols(scene)
    cam.lookat[] = [5,0,5]
    cam.eyeposition[] = [5,-15,5]
    update_cam!(scene)
    zoom!(scene, [0,0,5], 6, false)
end

function show()
    scene=Scene(show_axis=false, limits = Rect(0,-10.0,0, 11,10,11), resolution = (660, 660), camera=cam3d_cad!)
    demo3(scene)
    return scene
end

Still a long way to go to reach the beauty of the Python based solution…

By the way, this is the link to the Python code of the viewer: https://bitbucket.org/ufechner/freekitesim/src/master/3D_Viewer/FreeKiteViewer.py

Next step: Add arrows, balls and cylinders…
I found the answer to my first question myself… :slight_smile:

Some progress:

using GeometryBasics, Makie, GLMakie

const SCALE = 1.2

function create_coordinate_system(scene, points = 10, length = 10)
    # create origin
    mesh!(scene, Sphere(Point3f0(0, 0, 0), 0.1 * SCALE), color=RGBf0(0.7, 0.7, 0.7))
    
    # create x-axis in red
    points += 2
    for x in range(1, length=points)
        mesh!(scene, Sphere(Point3f0(x * SCALE, 0, 0), 0.1 * SCALE), color=:red)
    end
    mesh!(scene, Cylinder(Point3f0(-SCALE, 0, 0), Point3f0(points * SCALE, 0, 0), Float32(0.05 * SCALE)), color=:red)
    for i in range(0, length=10)
        start = Point3f0((points + 0.07 * (i-0.5)) * SCALE, 0, 0)
        stop = Point3f0((points + 0.07 * (i+0.5)) * SCALE, 0, 0)
        mesh!(scene, Cylinder(start, stop, Float32(0.018 * (10 - i) * SCALE)), color=:red)
    end
    
    # create y-axis in green
    points -= 3
    for y in range(0, length = 2points + 1)
        if y - points != 0
            mesh!(scene, Sphere(Point3f0(0, (y - points) * SCALE, 0), 0.1 * SCALE), color=:green)
        end
    end
    mesh!(scene, Cylinder(Point3f0(0, -(points+1) * SCALE, 0), Point3f0(0, (points+1) * SCALE , 0), Float32(0.05 * SCALE)), color=:green)
    for i in range(0, length=10)
        start = Point3f0(0, (points+1 + 0.07 * (i-0.5)) * SCALE, 0)
        stop = Point3f0(0, (points+1 + 0.07 * (i+0.5)) * SCALE, 0)
        mesh!(scene, Cylinder(start, stop, Float32(0.018 * (10 - i) * SCALE)), color=:green)
    end

    # create z-axis in blue
    points += 1
    for z in range(2, length=points)
        mesh!(scene, Sphere(Point3f0(0, 0, (z - 1) * SCALE), 0.1 * SCALE), color=:mediumblue)
    end
    mesh!(scene, Cylinder(Point3f0(0, 0, -SCALE), Point3f0(0, 0, (points+1) * SCALE), Float32(0.05 * SCALE)), color=:mediumblue)
    for i in range(0, length=10)
        start = Point3f0(0, 0, (points+1 + 0.07 * (i-0.5)) * SCALE)
        stop = Point3f0(0, 0, (points+1 + 0.07 * (i+0.5)) * SCALE)
        mesh!(scene, Cylinder(start, stop, Float32(0.018 * (10 - i) * SCALE)), color=:dodgerblue3)
    end 
end

function show_tether(scene)
    a = 10
    X = range(0, stop=10, length=8)
    Y = zeros(length(X)) 
    Z = (a .* cosh.(X./a) .- a)
    lines!(scene, X, Y, Z, color = :yellow, linewidth = 3)
end

function main()
    scene=Scene(show_axis=false, limits = Rect(-5,-10.0,0, 11,10,11), resolution = (800, 800), backgroundcolor = RGBf0(0.7, 0.8, 1), camera=cam3d_cad!)
    create_coordinate_system(scene)
    show_tether(scene)

    cam = cameracontrols(scene)
    cam.lookat[] = [0,0,5]
    cam.eyeposition[] = [-10,-15,5]
    update_cam!(scene)
    return scene
end

When comparing cgkit and Makie.jl, I can say:

  1. For me, Makie is easier to use and more logical, because for cylinders it does not require an orientation, but a start point and an end point.
  2. cgkit is more powerful because it supports cylinders and other geometrical objects with rounded edges. I could not find a way to implement that with Makie until now.

Question:
If I zoom and pan with the mouse, is there a way to get the related numbers (camera position, view target) from the julia console?

1 Like

Something like this seems to work:

using AbstractPlotting
scene = Scene(camera=cam3d!)
linesegments!(scene, [Point3f0(0) => Point3f0(1,0,0),]) # I've found the scene has to contain something first
# To print to console whenever the view changes:
foo = lift(x -> @info(x, scene.camera.projectionview[]), scene.camera.eyeposition)
1 Like

Thanks a lot!