Posts
Search
Contact
Cookies
About
RSS

G3N mouse picking and gui (further 3d adventures with golang)

Added 6 Jun 2018, 9:13 a.m. edited 18 Jun 2023, 7:30 p.m.
When I started coding for this blog post, I had in mind a GUI example, but thought I may as well throw in mouse picking in a 3D scene. Still having my OpenGL head on my initial thought was to grab the view matrix from the camera, unproject the mouse coordinates.... and do almost everything myself... then thankfully I had second thoughts and had a proper look at the G3N camera, guess what most of the work is there and already done for you...!
func (a *myApp) onMouseDown(evname string, ev interface{}) {

    me := ev.(*window.MouseEvent)
    w,h := a.Window().Size()

    r := core.NewRaycaster(&math32.Vector3{}, &math32.Vector3{})
    a.CameraPersp().SetRaycaster(r, (-.5 + me.Xpos/float32(w)) *2.0 , (.5 - me.Ypos/float32(h)) *2.0)
    i := r.IntersectObject(a.Scene(), true) 

    var object *core.Node

    if len(i)!=0 {
        object = i[0].Object.GetNode()
        ....
    }

    ....
}
A simple mouse click callback is no mystery, however there are some things that we need to look at in closer detail, for a start we can't simply give the raycaster raw mouse positions. The near view plane is described by 2d coordinates ranging from -1 to +1 regardless of the screens aspect ratio, this is why for example the X coordinate is divided by the screen width, biased by 0.5 and finally multiplied by 2. When you use IntersectObject you can do so recursively which is handy as you can just throw the root of the scene at it, what you get back is an array or slice of nodes that are intersected by the ray, crucially this array is sorted in depth order of distance from the camera - meaning that the first item (index 0) is always nearest to the camera. There is really not too much complication with the GUI widgets, the Application structure handles the root GUI node and automagically resizes it for us when needed. All we have to do is add widgets in a hierarchy to build an interface, there are a number layouts that can be used together to make really complex interfaces that could be made to resize depending on the current screen window (You'd maybe have to manually resize GUI windows when there's a resize event). Encouragingly for the future there is a way to load GUI's from YAML files, while hand creating a YAML file (and getting it correct) might cause some challenges compared with just coding the GUI in GO, it does hold out the hope that someone might contribute a GUI editor that saves out nice YAML files...
    g.window = gui.NewWindow(140,130) 
    g.window.SetTitle("Properties")
    g.window.SetPosition(16, 16)

    g.panel = gui.NewPanel(124,100)
    g.panel.SetBorders(8, 8, 8, 8)
    g.panel.SetColor(math32.NewColor("blue"))
    g.panel.SetBordersColor(math32.NewColor("blue"))

    g.panel.SetLayoutParams(&gui.VBoxLayoutParams{Expand: 8, AlignH: gui.AlignCenter})
    layout := gui.NewVBoxLayout()
    layout.SetSpacing(8)
    g.panel.SetLayout(layout)

    g.window.Add(g.panel)
Having grouped out GUI in a structure we can begin by making a window, setting the various properties of this window is straight forward, in order that we're not cramming GUI elements against the side of the window its as well to add another panel and use that as the "root" of the window, by setting the panels border size we have a nice space between the edge of the window and our active area. While creating and building a GUI in code it can be very helpful to colour the window, panel and even borders with different colours, this will make it much easier to work out whats going on! The final step in setting up our panel is giving it a "layout" to control where the children are positioned (and even sized) The rest of the GUI just consists of adding three sliders onto the panel, in order I didn't duplicate the same code three times I made a simple function although I did anticipate there might be more to it (its a small function), each of the sliders has its own callback and effects a different axis of the currently selected object. Selecting the object is just a case of informing the GUI that a new object has been selected (called from the mouse click callback) Its important to remember that where a slider is effecting different objects that you must set the sliders value appropriately when a different object is selected. From the previous few posts we've gradually added simple functionality and even looking at the whole code for this post I think you'll agree given what we can already do, its actually really simple to produce great things with G3N without lots of complication or boiler plate code...