 Hi, this is Sean Rutledge from the Qt Company, here to talk to you about... Oh, happy listening. Okay. So, should I start it? Yep. How to develop interactive UIs in Qt Quick 3D, which is just now becoming a possibility. I've been a Qt developer since about 2004, joined the Qt Company in 2011, and these are some other things that I've worked on during those times. I'll be going through different scenarios in which you might be able to use Qt Quick 3D for some sort of interactive application. For some disclaimers, I'm not on the graphics team, I'm not a gamer, and there's a lot of things I don't understand yet about how 3D rendering is done internally, but as the Qt Quick maintainer, I wanted to make sure that the interactive aspects work similarly in Qt Quick 3D as they do in Qt Quick so that you don't have to completely start over with different QML types for handling mouse and touch events and things like that. Also, this presentation depends on some forward-looking features that we haven't shipped yet. If you want to reproduce my demos, you can apply these two patches and you should be able to. So, maybe you want to think a little bit about what applications you have in mind where you could actually use Qt Quick 3D. Surely, it's useful for other things besides games, right? But let's start by showing the runtime loader, which is one way of loading a model, and I'm loading a GLTF model here, which I downloaded from the Smithsonian. GLTF is an open format, and they've decided that they're going to use it for digitizing a lot of the format, and they've decided that they're going to use it for digitizing a lot of the items in the museum collection. So this is the Apollo command module, which was in orbit around the moon and came back to Earth afterwards. At least I think it's the original, or maybe it was a spare, I'm not sure. So I downloaded this model. I'm simply loading it with the runtime loader, and while you see that the runtime loader is there, and then at the bottom I have a file dialog, and unaccepted, I set the loader source to the URL that was chosen from the file dialog. And that's really all there is to it, and the rest of it has to do with navigation. There's a wheel handler, a drag handler with the middle button that I can pan around, and the WASD controller is what you usually use for game navigation, where you use the keyboard to walk around, and also you can use the mouse to pan around the scene. When you're working with Qt Quick 3D, these are the most common QML types that you'll encounter. 3D is the base class, implements QObject, then node has position rotation scale. It's abstract, but it can be instantiated, and it can be used as a container. So a node can contain any number of other nodes. Another subclass is the model, which has geometry and materials. This pickable property is important for making the model interactive, if you want to add any interactive content as children of it. And then you have lights, camera, the runtime loader is the one I just showed that was also putting geometry into the scene. Then we have materials. Principled material and default material have the same shader implementation, but they have different properties. Default material has the specular and translucency. Principled material is a more modern PBR approach where it has metalness. Custom material is also similar, but it does allow you to provide your own shaders if you don't want to use the default ones. So you've got a 3D scene and you want to show it. Then you need View 3D. That's a 2D viewport into the 3D scene. It allows you to specify the camera. Skybox typically would be an environment that you would use, and that is an item. So you actually use this to embed it into an ordinary 2D Qt Quick scene. But you're not usually going to write all of that from scratch, right? You might want to start with a modeling tool for creating the 3D objects. The best of the free software from modeling is Blender. It's really worth your time to try to learn that, even though DUI is a bit complicated. It's gotten better in recent years and more organized, easier to work with. But I come from the 3D printing background, so I've been using OpenScand a lot. And OpenScand is only able to output an STL file, which is just a bunch of triangles. It's just a mesh. And so an STL file can be imported into Qt Quick. I'll tell you how. But you're not able to apply textures to it, or at least not without further modification. That's a nice declarative way to work if you like writing code instead of graphically manipulating things. Then OpenScand is fun to work with, but it has its limitations. FreeCAD, I haven't had very much success with so far, but it's able to export GLTF in theory. And then you have the commercial tools, Maya, 3D Studio Max, and a bunch of others to choose from. Because Qt Quick 3D is a real time renderer, our favorite format is GLTF because it was designed for that. And we're eventually going to be able to support all the features of GLTF. STL, like I said, it has its limitations when it comes to applying materials to the result, although you can apply a color and the glossiness and so forth. It's fine to use default material or principled material with STL. Then there's Kalada, FEX, and Wavefront, OBJ. Those are typically used as interchange formats between the different modeling tools. And they have a lot of features. And Qt is not ever probably going to be able to support all of those features because they're not appropriate for a real time rendering. So when it comes to import these different kinds of assets into your scene, I showed you the runtime loader already. And there's also another choice, which is Balsam. That uses our asset import module, which consists of an asset import manager. And then there's a virtual base class for the asset importers. And then we have two subclasses so far. One of them uses ASIMP, which is a third party asset importer library. Qt 3D Studio is obsolete. But if you've been using it so far to create models, then you'll be able to import them with this UIP importer. So anyway, there's a command line tool called Balsam. And it can read any of these formats and output a mesh file, which is a Qt specific way of storing meshes and also the QML for the entire scene. So what you'll get out is typically a node as your root object and then models and materials and stuff inside there. And you just need to create the view 3D to embed it into your 2D scene. There's also Balsam UI, which is just a widget application that provides checkboxes for all the command line options. So that's one way to work. Another way to work is to use Qt Design Studio, which is our commercial only tool. In Qt Creator 5, there's going to be a pretty decent 3D design mode built in. So the ultimate goal is that Design Studio will be able to orchestrate all of this stuff so that designers that are not programmers will be able to import it and create the QML scenes and then they can hand it over to the kind of people who add the interactive functionality to it. If you've been using custom material and writing your own shaders, then probably you want to use ShaderGen to pre-compile the shaders into Spear, which is standard portable intermediate representation. And that makes it more suited for fast runtime loading. But that's an optional step. We're perfectly capable of loading shaders at runtime. So the typical workflow for creating 3D interactive content is that you're going to start with a modeling tool to create your 3D objects and manipulate existing 3D objects that you're able to download, probably Blender, but at any rate you need to choose modeling tools that are able to export GLTF because that's the format that we support best for importing into Qt Quick. And then you probably can use Balsam on the command line to generate your QML if you're not using Creator or Design Studio for that. And then you just need to create your View3D and the rest of your 2D UI. And that's basically it. So I mentioned that Creator is going to be having a design mode, and this is what it looks like. I actually imported a model, which you'll be seeing later on. OK, so let's talk about the most basic use case. If you have a 3D application, you might want to incorporate some sort of a 2D UI into there. And I've simply declared a Busybox 2D item inside a node. And so what this does is it creates a 2D sub-scene inside the 3D scene. You'll see that if I use the slider to change the rotation, now I've got a 3D rotating cube over here, and that actually pokes through. Busybox has a particular size, of course, in pixels, but it's on an infinite plane, and I have a 2D particle system inside there. So you can see that it's filling up the entire space on that plane when the particles are emitted. It's 1024 by 480. This is a 4K screen, and you can actually see that that's taking up about a quarter of my screen width. Another way to use it is to have textures mapped onto 3D objects. So in this case, I've got a node, which actually wasn't really necessary. I could have just had my model directly in the View3D. There's built-in types like cubes, sphere, cylinder, and rectangle. I have a model, which is a cube, and a default material. The diffuse map, meaning the diffuse color, is a texture. And inside there, I declare this busybox. So this is actually creating a 2D item. And I positioned it in such a way that it goes mostly off-screen, and I left 20 pixels showing on the screen, just so you can see that that's actually... You see I move my cursor, it hovers, and you see the little orange ball moving around in the 3D scene as well. And that basically shows you that the 2D item is a separate thing, and we're just grabbing it, grabbing a texture from that, and mapping it onto the surface of the cube. So the way that that works is you set layer-enabled true, and that makes a texture available. And then we set the texture size to 512 by 512 so that we get reasonable resolution in 3D. So you have some control over that. You can either save memory, or you can make it really high-res. The cube built-in type has texture coordinates, meaning UV coordinates, which go from zero to one on each face of the cube. And so the result is that the scene texture actually gets mapped onto each face of the cube at the same time. Now, if I hold down a button, you can see that the button is pressed and all of them at the same time, because this is really only one 2D sub-scene just mapped onto all the faces of the cube. And in addition to pressing buttons, you can also edit the text as a nice alternative to eating it. Okay, let's talk about event delivery a little bit. So in this scenario, we're going to say that the mouse is being pressed and a QPA mouse event comes in to QQI application into process mouse event. And of course, we're going to construct a mouse event and deliver it. QQI application looks for a top-level window to deliver it to. So that'll be my Qt Quick top-level window, which in this case is the entire presentation. So Qt Quick window, its event method gets called. It used to be that Qt Quick window was actually doing the delivery of the event entirely itself, but the new thing in 6.1 is that we have a class called Qt Quick delivery agent, which is doing that now. And the reason is because of these 2D sub-scenes, it's not possible to have the window know about all of that. Qt Quick window now has its own Qt Quick delivery agent by default, and that is simply all of the delivery related code that was in Qt Quick window. So it was a simple refactoring, or at least that's how it started. Qt Quick window was an enormous class anyway, so it felt really good to move all of that code out into another class. So the Qt Quick window is going to forward all of the input events to the delivery agent, and then it does a bunch of internal processing and eventually it finds the 3D viewport, which is our top-level item. It calls event on that. And then view 3D viewport does internal pick, which is a function that does hit testing on the 3D model. Basically, it's shooting a virtual array down into the scene from your mouse position, and it's finding the first 3D object that it hits. It finds the 3D position and world coordinates and the 3D position relative to the model itself. And then it notices that this cube that we hit has a 2D sub-scene as its diffuse map. And so that's how it discovers the sub-scene, but let's just imagine that there is a tap handler on the cube. And so the 3D viewport is going to directly try to deliver the pointer event to that handler. This is the feature that's not actually implemented in 6.2 yet. What I'm proposing is that it'll be possible to install handlers directly on 3D models. And so if the viewport discovers that that has happened, then it's going to call set model position and set world position on the handler, and that in turn stores this in the handler point class so that your handler in QML will be able to see those world and object positions in 3D. And then it's going to call handle pointer event on the handler and react in QML. And the tap handler adds itself as a passive grabber on press because it wants to see the move events later on. So it calls add passive grabber on mouse event, and that in turn calls add passive grabber on the pointing device. And then that adds to the passive grabbers list in the event point data object. And that'll emit a grab change signal which is connected to the delivery agent. The delivery agent then wants it to be remembered that this was the delivery agent that caused this particular grab to happen because that means subsequent mouse moves need to be delivered the same way. So it calls passive grabbers context append itself. The passive grabbers as a list of handlers that are passively grabbing and the passive grabbers context is a parallel list of delivery agents that are responsible for dealing with that. So those have to be updated together. Okay, the next thing that happens is we've already discovered that there is a 2D sub-scene. And so we call event on a second delivery agent which has been created to handle that sub-scene. And then let's say that inside that 2D sub-scene we have an item which is able to handle events itself but it also has a drag handler on the item. So the first thing it's gonna do because we have a rule that handlers go first, it's gonna ask does any pointer handler want this event? And it discovers the drag handler which says yes, I do want the event. So then it delivers the pointer event to the drag handler and the drag handler also wants to add itself as a passive grabber. So it calls add passive grabber on the mouse event. The result is like it was before it will be appended to the passive grabbers list in the event point data and the signal will be emitted. The sub-scene delivery agent is gonna add itself as the context for that particular passive grab. Next, we deliver the event to the item itself because it's one of these old-fashioned items. This legacy logic that we have says that if the item allows the event to remain accepted then that means that it wants an exclusive grab. This legacy logic that we have says that if the item allows the event to remain accepted, this legacy logic that we have says that if the item allows the event to remain accepted then that means that it wants an exclusive grab. So the delivery agent is now going to set it an exclusive grabber to that item. That will again emit the signal that the grab changed. Sub-scene delivery agent is gonna set the exclusive grabber context to itself. The next thing is that the 3D viewport will see that some grabs have happened in that sub-scene, and it also needs to remember how to do the transform from scene to sub-scene coordinates. It creates this viewport transform helper, which is an implementation of a pure virtual class in Qt Quick that lives in Qt Quick 3D and embodies the transformation from the viewport to the 2D sub-scene. So it creates this class and it calls that scene transform on the sub-scene. So then let's say that the next thing that happens is that the mouse moves with the button still being pressed. So this is a drag now. And then it wants to construct a mouse event and it calls event on its top level window. And the Qt Quick window is going to get the endpoint data because now it wants to find out about existing grabbers. So it finds that there is an exclusive grabber and therefore it gets the exclusive grabber context, sees that the exclusive grabber is a sub-scene and it has its own delivery agent. So now rather than going through all of the stuff that we did before, we're skipping picking, we're skipping the event delivery through the main 2D scene and we're going directly to the sub-scene first because that's the context in which the exclusive grabber occurred. So we call event on the sub-scene delivery agent, which will deliver the pointer event. And then it's going to use that, this viewport transform helper that was created here and set. Now it's going to use that and call map on it to directly convert the scene position from the root 2D scene into the sub-scene. And then we start looking again at the grabbers. The exclusive grabber has to go first. So the event is going to be delivered to this item which had the exclusive grab. And then it goes to the passive grabbers and it calls handle pointer event on this drag handler which was the passive grabber in the sub-scene. So now we're done with the sub-scene and we go back to delivering in the main scene. The delivery agent that's constructed along with every window is now going to deliver this pointer event and it sees that there's a passive grabber which is the tap handler on the cube and it calls handle pointer event on that. And we're done. Okay, so here's a little bit more real world example and I've demoed this before. This was from a few months ago. So I have a view 3D and at that point I didn't know how to use Blinder yet. So I actually made this casing of this device in OpenScad and you can see that you can rotate it around in the 3D world. So OpenScad generated me an STL. I used Balsam to export that to a mesh file. It has no texture. It cannot have a texture but it can have a color. So I have a view 3D with a scene environment which is a sky box and that's just something I downloaded from the net. It's an image of somebody's kitchen. And then there's a spotlight and a camera and here's my case mesh and then I separated out the buttons. I made those separate models in OpenScad and so I can actually press these buttons in 3D because each of the buttons has a tap handler on there and then I change the Z position when I press it. If I press this one, I get confetti. It's the 3D particle engine which is something new in 6.1 I think. There's a particle system 3D. And there are some bugs when we're using Qt Quick Controls together with 3D so I'm actually not able to interact with much of this Qt Quick content inside the 2D scene, at least not inside the presentation but if I run this standalone then it works. Okay so this is the one that I was working on last weekend. I thought let's try to get even more realistic and I found this on a website. Somebody has created this model of an old mixer. So let's turn it on and well you can see I animated the VU meters. It's pretending like there's some music playing. That's just an animation and I put lights inside the VU meters because that's what they usually do. So if you turn the ambient lighting way down you can see that there's light coming out of there. But the meters actually are lighting a lot of light out around the edges. They're not really as opaque inside as they would be in the real world. So it's lighting up a lot of other stuff here too. There's light bulbs inside here and it's lighting up the sides of these sliders and so forth and then this slider I wired up to control the directional light here. That one. See the brightness is obj, phono one slider value. And sometimes I'm able to use the wheel handler on this knob here to rotate it and all of these sliders are interactive but they don't really do anything else. So this was a static model that somebody had created. Tried to make a realistic rendering of a real world object and I took that and made it interactive and we'll talk about how in a minute. So there's this mixata QML which was generated by Balsam and I just wired up a bunch of stuff in my UI here to make it a bit alive. So here's the workflow that I used for that model. I found something interesting on Sketchfab, downloaded it as a GLTF, I imported it into Blender because I found that I needed to actually split the meshes of all of those knobs into separate objects so that I would be able to rotate them in QML. In Blender I re-exported it to GLTF. Then I used Balsam to import it and that wrote my QML for me and then I just wrote the rest of the scene, the QML U3D and first I just made sure that the Balsam scene actually worked but anyway, the Blender work, let me just give you a couple of tips here. It turned out that the way that this guy modeled this mixer, he put everything together by material. Well, the knobs are a different material, some phenolic or something like that. So the knobs are a separate node and then this an ISO is the anodized caps on the knobs but I just wanted to work with the knobs themselves. So the first thing I have to do is, you see these eyeball icons, I had to just turn off the things that I don't want to see because when you try to pick vertices, all that other stuff will get in the way and so you see that it looks like the eye is shut, that means you can't see it anymore. So you click on those and then you do lasso selection and you will select some vertices but only the vertices that you can actually see from that particular angle and so then the next thing I do is select linked to find all the vertices that are connected to the edges that those selected vertices are connected to and then I look at the scene from a different angle and I find out there's still some more vertices that aren't selected. So then I hold down the shift key and lasso some more and I select some more vertices and then I select linked again and then I look at it from a different angle and there's even more vertices that aren't selected. So I had to do that several times until I finally got all the vertices of the knob selected and nothing else and then finally I'm able to hit the P key and create it as a separate object. So that was in edit mode where I was editing vertices and then over here I'm in object mode. So here I've got the entire object selected and now I can worry about moving its origin. So object set origin and then you have choices. You can set it to the center of mass or to the, if you've already moved the 3D cursor you can just move the origin there. So it seemed like the center of mass thing works decently although I'm suspicious because there's a cutout here for a set screw so that's going to alter the mass and also there's a cutout for the little indicator on the knob but it seemed like it was pretty much at the center after I had done that. And then I also have to move the 3D cursor to the center and then finally I'm able to, ah maybe it's better to show it live. So I get on this blue thing and I can rotate this knob around its axis now. After I exported the GLTF scene from Blender and imported it with Balsam it wrote this QML for me. It's possible to actually remove these outer nodes and kind of collapse it all so that you just have the root node which is what I did. And you'll see that there's a bunch of textures here for the materials. It writes all that for you, exports the PNG files and the mesh files and we've just got a bunch of different materials and then they end up getting reused when we get down into the actual knobs. You know, here's that micro base knob and it's just reusing the existing material that was declared above inside one of the other knobs and the same thing for all these anodized caps on the knobs and then the view meters I also had to separate out into separate objects so that they're able to be rotated so that I can animate those. So next I take this output from Balsam and try to make it interactive. So I simplified the QML as I mentioned these quaternions are hard to work with in QML because at least I don't know how to change the numbers to make the knob rotate in place. And so I used QQuaternion to Euler angles that's a C++ function to do the conversion to Euler angles but there's isn't a way to do this in QML yet as far as I can tell. So I wrote a little widget application just for that purpose. It works with text. So I can actually just in Qt Creator I can select a quaternion's values and middle mouse pasted into this tool and then it will parse it and convert it to a quaternion and then it converts it to Euler angles and puts the text of that and then I can select that text and paste it back into Creator. I have to declare pickable on each of those knobs that I want to be able to rotate because picking in the view 3D will skip anything that's not set pickable. And then it turned out that I couldn't pick those knobs. Anyway, that's some sort of a bug but at least it worked with a slider handle. So I was able to put handlers on each of those slider knobs and then I'm able to drag them. So if you're using drag handler you'll be interested in this persistent translation property which is a new thing in 6.2 because drag handler translation it's an instantaneous amount of translation which just shows how far the drag handler has been dragged since the time when you pressed but what we actually want for a mixer knob is to keep track of how far it's been dragged for all time. I now think that it was a mistake that I didn't make all of those properties that way from the beginning in pointer handlers but that's my mistake and we can't just make sudden behavior changes now and so therefore we have to start adding more. We have this new pattern that the persistent properties are gonna be called that. So persistent translation is the amount of translation that's happened since the drag handler existed. Wheel handler rotation on the other hand already is persistent. So I think maybe we should rename that now to persistent rotation but anyway, it's the amount that the wheel on the mouse has been rotated since you started your application in degrees. So that's useful for binding to knobs as well. Of course the problem is if you keep rotating your mouse wheel past the stop past the point where you can't rotate your 3D knob anymore then you get too far ahead of it and then you have to rotate back a long way on your mouse wheel. So we need to deal with that somehow. The tap handler pressed property is useful of course. The scale and the translation on the pinch handler are useful but so far we haven't added the persistent translation properties. I'm proposing to add to handler point the model position and the world position as Q vector 3D. You can actually get the 3D positions in QML when you're using any of these handlers. Here's a diff before I actually simplified the root nodes so I still have all this nesting going on. I just started adding properties. I want to keep track of how far each of these knobs have rotated and then I started converting the rotations like I mentioned with that widget application that I wrote from quaternions to Euler rotations. Okay, so I have this property to keep track of for example, how far this micro base knob has been rotated and so I have to actually make the rotation happen here and then there's also a handler down in here somewhere where I attempted to set it and then I had the problem that it's not pickable. So here's a slider. This drag handler actually does work. Then I just use its persistent translation up here to directly set the Y value. And the problem with this is that the drag handler drags in pixels but in 3D we're in completely different units and so I just did a kind of ballpark conversion here divide by 2000 and that's really not very accurate at all. I could have divided it a bit more but we probably need to come up with a better solution for that as well to convert deltas and pixels to deltas in your 3D space so that you can actually drag it or else maybe we need to make the drag handler smart enough to do this on its own but the trouble is drag handler lives in Qt Quick not in 3D according to the separation of concerns principle that's not its concern which is why I was using bindings here. I also have a used a boundary rule on the Y property of the model to make sure that you don't drag it outside things here. I also have a used a boundary rule on the Y property of the model to make sure that you don't drag it outside of its groove and that turned out to work pretty well. Boundary rule was added in Qt Quick mostly to make wheel handler behave and it's a property interceptor it's kind of like behavior so you can apply it to a property and it will make sure that that property never goes out of a certain range. So that turned out to be just as useful in 3D as it is there. Okay, here's a really simple manual test that I'm proposing to add which shows how to use these new handler point properties. So I have a sphere and then I have another little sphere which if I hover you'll see that that sphere moves around to the hover point so there's a hover handler with the IDSHH the spheres hover handler and I'm proposing that its handler point should provide this world position which is in 3D now and so I can directly bind the position of another sphere to that world position and therefore when I hover on the big sphere then the little sphere moves around to the same position just with a plane binding. And then I did the same thing with this cube over here and I'm also using bindings to update some text down here just to show you where you are in both the model and world coordinates and yeah, there's also a tap handler I can tap the sphere, same thing there and I can use pinch handler but I'll have to show you that on a touchscreen. Now I'm showing you the same thing on a touchscreen and now I'm able to either use two fingers to move two handlers at the same time or I can use the pinch handler and I can rotate this cube by dragging my pinch around and I can resize it by scaling the pinch. Okay, one more little demo this is how you can do ray picking in QML. I mentioned earlier that the normal way that View3D does ray picking is it sends a virtual ray straight down into the scene from the mouse cursor position and it finds out what you're hitting. But let's say you're in VR and you have some other kind of controller which can point in arbitrary directions like this. So I have this little spaceship here which is pretending to fire a laser and sometimes it's able to hit an object. So how does this work? The spaceship has a position and it has a forward vector which tells you the direction that it's pointed. So in QML you can call this view as the View3D declared at the top and you can call this ray pick function giving the position and a direction vector and it will tell you the first object that you hit. And so we find that and we measure the distance to that object and then we set the length of the ray which is actually just a cylinder to that distance. And then we have a particle system that's emitting particles at that position as well. So that's why you see that. We have this repeater 3D which is a way of creating actual instances of these donuts. So we're actually making multiple copies of the donut model. The other way to create multiple instances is with instancing then those are not actual copies of the model object there is just a rendering trick. So in this case these donuts are real so to speak their actual copies of this model being repeated by this repeater 3D which is kind of like the repeater in Qt Quick it creates actual instances. So the other interactive aspects are we have a wheel handler on the scene so we can zoom in and out and there's a point handler which reacts to the middle button so I can rotate the scene. So if you're not using the WASD controller that's another way that you can get the same kind of behavior. That's being used to rotate the spaceship in this case instead of the scene. So the WASD controller you can set the controlled object to either the camera or to something else and then you can use the keyboard WASD keys to move it around and you can also use the mouse to change the angle. So what do we have left to work on while we're trying to still get consensus on the team whether this idea of reusing the existing 2D handlers in 3D is actually a good idea. The upside of course is that there will only be one set of handlers to maintain and they are exactly the same and therefore the API should be the same. On the other hand, as soon as I implemented this feature it seemed to break designer a little bit navigation inside this QML puppet that it uses to visualize the scene. It also seems like drag handler is never gonna be able to directly drag 3D models because it lives in Qt Quick it doesn't know about that. And so we're gonna have to come up with a way to convert the 2D mouse deltas to 3D deltas. Hovering doesn't always behave consistently when you've got complicated scenarios with 3D embedded inside 2D, inside 3D and so forth. I think there's this bug that I couldn't pick the knobs in my scene and I don't really understand why I'm not sure if that's a bug in picking or if it's just something wrong with my model. The WASD controller also seems to steal mouse events that you might want to deliver to something interactive and controls are stealing events as soon as you put controls in the scene and they don't work the way they should. So suggestions for you guys, for the community, this is really a lot of fun. I think that you should already enjoy playing with it even though the interactive stuff is incomplete. I mean, you can feel free to pick those two patches and you can play with it and try to imagine that you're living in the future. Try to imagine what you can use this for outside of games, what kind of KDE applications, for example, could really make good use of 3D. When I was writing this simple model viewer for that Apollo command module, I was having a hard time finding toolbar icons for it. So that was something that I noticed. I think if there are designers in the community that are interested in creating icons, it would be nice to have a standard set of icons for 3D applications. For stuff like turning the axes and the grid on and off, rotating to different standard positions in 3D and stuff like that. Just all of the toolbar icons that everybody's gonna want. Yeah, some of this stuff existed from before. We had a hackathon a couple of months ago on which we created this fancy spaceship with the Wayland compositor on different screens inside. Code has not been open sourced yet. At least there's a demo video of that. The digital assistant on the other hand is my creation and that is open source. And there's a demo video of that separately going into more details. And well, that's the end of the presentation. Thanks for watching. I hope you had fun. I hope it inspires you. Thanks. Oops. Sorry. Thanks, Sean. That was very impressive. The chat was already talking about bringing back skewmorphic user interfaces and key-win special effects, but let's not go there. We have two questions. Any chance of a proper vector API in QML? Things like being able to do Q dot vector 3D 1.0, 0.0, 0.0 multiplied per 10 for scaling? That's a good question. Okay, so you're saying that the, I haven't actually, have I tried that? I think maybe I tried that once, yeah. So multiplying by a scalar is actually missing. Is that what you mean, or? That's what they are asking, it seems. Yeah, I mean, that's more of a question for the QML engine guys, but yeah. Wolf and so forth. Another question is any consideration for PR support in Qt Quick 3D? Yeah, and that's something that Andy Nichols is really into. So I think he's probably gonna come up with something, but the last example that I showed with array picking would give you an idea how you could do that, if assuming that the controller gives you a position in a direction, then you should be able to do the picking the same way. Okay, we don't have any other question so far and we are strict with the times. So I guess we can, thanks all Sean for the presentation. So big round of grateful applause for this. And I guess people can contact you on the places you just showed in the slide. So thank you again, and we will continue in two minutes. Okay.