Alternatively you could always put up both methods and explain the differences and people could make up their own minds which they want to use, granted, the less maths heavy approach is always more tempting, at the same time though I'm not scared of learning heavy maths these days if it means more efficient code.

  • xyz replied to this.

    Lethn The "heaviness" of math is equal in both approaches, as far as part in GDScript is concerned.
    In total, the math is much heavier when treating the frustum as a convex mesh. You just don't see it because it's done under the hood 😉

    Out of sight out of mind LOL 😃

    In my defence, what I've been doing most of the time with my stuff is working out game mechanics in general and what can work well versus what can't and how to implement it. Now people are going to see a hell of a lot more from me when it comes to posting up proper projects and won't be able to avoid the spam lol I've got some special things planned for Godot, this engine is impressive overall and I'm going to throw the kitchen sink at it to see how it handles. Looking at this 3D selection box code that @xyz has provided yeah, it needs breaking down way more to be understandable. The theory is fine but definitely some collaboration on making sure there's some sort of up to date documentation is in order and I'll be happy to go through it with people to help lighten the burden a tad, I think it's the frustum stuff that's hurting my poor brain the most.

    • xyz replied to this.

      xyz Exactly, let the engine's battle-tested native code core do the work and keep your code simple. It's probably optimized to skip the convex mesh test when the bboxes don't intersect, and no doubt it uses spatial partitioning to skip the bbox test for objects that aren't even close. And this is cheap compared to full-blown physics, which constantly tests ALL collision meshes against each other.

      Remember, a bbox test alone isn't accurate. For example if you have a spherical object and you drag your selection box over the corner of its bbox, it'll be selected even though you're not actually touching the sphere. That's not necessarily better than a centerpoint test. You might as well use collision system and get partitioning and bbox optimizations "for free".

      • xyz replied to this.

        synthnostate BBoxes with margins are quite sufficient for most cases in practice, and sphere check against a frustum is even less expensive. But for a large number of units it'd surely pay off to use engine's broadphase optimizations, especially if a large number of selectables is in play. I'd love to see this benchmarked in various scenarios.

        synthnostate battle-tested native code

        Not sure Godot 4 physics is actually battle-tested though 😉

          xyz One of the reasons I've been exploring selection boxes so much is I'm doing something of a wave defence/base building game by the way, should be no trouble attempting to break Godot. I've been experimenting with my villagers so far in one of my other projects and Godot 4 has been handling it like a dream, definite performance boost I can confirm compared to Godot 3.5.

          Lethn Looking at this 3D selection box code that @xyz has provided yeah, it needs breaking down way more to be understandable

          Or you can just use it by calling the top-level function as you would with other engine functionality. In this thread, you've got everything you need to quickly make a selection system. With the project_rect() function from my example and going with @synthnostate suggestion to use convex mesh collider for the frustum, it's almost trivial to set up.

          xyz Not sure Godot 4 physics is actually battle-tested though 😉

          lol, true. The physics part needs work. I think the parts we're talking about here are pretty solid though. I'm using Area3D.get_overlapping_bodies() for other purposes with no issues.

          Lethn I think it's the frustum stuff that's hurting my poor brain the most.

          What specifically about the frustum is hurting your brain?

          It's mainly working through all of the shorthand you've written so I understand what I'm even doing here, I need to focus you guys to make this thread more noob friendly lol. I see you've declared three custom variables but aside from the camera and the rect I don't know how to use this. I also don't know what sort of hierarchy setup I'm supposed to be looking at. That's something you definitely have to explain before it goes anywhere near documentation.

          For instance, do I take the rect from my previous selection box code and plug that into the rect you've defined? I take it the AABB is supposed to the the collision? I don't know how to use this in relation to search for a group for example, you write it's easy but I think this is a classic example of you looking at more complex stuff on a daily basis lol.

          • xyz replied to this.

            Lethn Well, this wasn't supposed to be a tutorial. We're is a discussion format here so if anything is not clear you can always ask. There's no setup needed at all. It just does 3d math. You can put the code in any node you like (or a standalone static class) and you only need to interact with one function, namely aabb_intersects_rect(). I'm sure you know how to get a selection rect and a reference to the camera in your scene. Now if you for example want to test if a mesh instance is inside the selection area you just get its aabb (axis-aligned bounding box), transform it to world space and pass it to aabb_intersects_rect(). It's literally one line of code to use this.

            if aabb_intersects_rect($mesh_instance.global_transform * $mesh_instance.get_aabb(), selection_rect, $my_camera):
            	print("INSIDE AREA")

            Same goes for @synthnostate's approach, only that it actually needs some setup but it's a trivial common setup that I think is self evident from the description they provided.

            Okay phew! Slugging through it a bit more now, thanks for the post, I think I've managed to get the rect variable setup correctly, here's how my code is looking so far with the 2D box, let me know if it's the right idea, I've just taken the selection box code I found previously and plugged in the rect to a variable you can grab from elsewhere as a minimal example for people to work from.

            extends Control
            
            var isDragging = false
            
            var dragStart = Vector2()
            var dragEnd = Vector2()
            
            var selectionBoxRect
            
            func _physics_process(delta):
            	if isDragging == true:
            		queue_redraw()
            
            func _unhandled_input(event):
            	if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
            		
            		if event.is_pressed():
            			isDragging = true
            			dragStart = get_global_mouse_position()
            			
            		elif isDragging == true:
            			isDragging = false
            			var dragEnd = get_global_mouse_position()
            			queue_redraw()
            
            func _draw():
            	if isDragging == true:
            		selectionBoxRect = Rect2 (dragStart, get_global_mouse_position() - dragStart)
            		draw_rect(selectionBoxRect, Color.DARK_GREEN, false, 2.0)

            I'm thinking I just plug the selectionBoxRect from the control node into the selection_rect yes? Now I need to look at how to use the axis aligned bounding box too, I hadn't come across this little bit of code before, also here's how my hierarchy is looking at the moment.

            • xyz replied to this.

              Right, now I've had some sleep after all that I'm going to try working through this final part but I do have some more questions @xyz I get where you're coming from but at the same time this stuff needs explaining because of all the shorthand. I was thinking that something you could do which would instantly improve the code you've posted is post up a mini-glossary of your shorthand up at the top and commented if that would be okay? This would help people like me properly get where everything is linked from. Programmers do this all the time and when you're a noob you look this stuff up and most people are just going to see the matrix not knowing what's going on.

              I was thinking too how does this code execute? If it's running off the rect does that mean while the rect is active is going to be constantly checking through the AABB? I'm wanting to add the selection to an array so I'll need to know about this, I suppose a little boolean check of isSelected on the objects themselves would probably work then I can add to the array that way to make sure the code only executes once per unit.

              I've also been searching up about AABB generally because this is my first time even reading the acronym lol.

              https://docs.godotengine.org/en/stable/classes/class_aabb.html

              https://godotengine.org/qa/52026/what-is-aabb

              This is probably going to be the only up to date thread on the whole of the internet to do with Godot selection boxes so I want to make sure the information is all correct too even if it doesn't make it's way to the documentation.

              • xyz replied to this.

                Lethn What do you mean by "shorthand"?

                The code I posted is a simple API (we know what that is I hope 😉). It consists of only one public function; aabb_intersects_rect(). Everything else is helper code used by that main function and can be considered private to API. The API doesn't "run". It's an utility function you call when you need it, it runs on demand. The name of the function is self evident, I hope. It checks if an axis aligned bounding box intersects a frustum. The frustum is a pyramidal viewing volume defined by a 2d rect projected into space using camera's perspective projection parameters. It returns true if intersection happened. Otherwise it returns false. This test is the only "hard" problem when making a 3d selection system. Everything else is pretty much boilerplate and can vary depending on the specifics of a particular system.

                How you use this is up to you. I didn't concern myself with architecture of any particular system. I just wrote you a useful general function that handles the hard math part of it. This function runs whenever you call it. You can call it every frame, or you can call it on mouse release. You can call it for a single mesh's bounding box, or you can call it in a loop for every mesh in the scene. Depends on the needs of your system. My code doesn't handle selecting logic itself, or selected/unselected arrays or any "high level" stuff like that. There's no selection management code, just this single intersection check. Do with it what you will.

                To point out the obvious again - If you don't call this function, none of the posted code will ever run 🙂

                Okay, I think I'm slowly understanding the logic albeit very slowly, when aabb_intersects_rect() you're intersecting with the mesh using the AABB class and grabbing the mesh you're not using a collider, I think that was the thing that threw me off the most because I've not seen code used like that in Godot often.

                When I post shorthand I mean when you use letters etc. as shortened versions of your custom variables and names that you've declared. This isn't me having a go at you in particular I see way too many posts like this of programmers who post up their code to be helpful of something they've made and even to other programmers who are trying to work out what the hell they've done and part of the problem is they've dumped code online with lots of p's and t's and think that's perfectly straightforward and understandable lol. It's just something of a major pet peeve of mine, I think shader code is the worse example of this I've seen and it really helps more when programmers are more verbose with their code for the sake of helping others or at least stick up a mini glossary of the meanings up top so people can look through it line by line without getting a headache. As an example I'm pretty verbose with my code because I know that people are going to potentially want to look through my projects when I'm done with them, so with that in mind I write my code that way.

                shameless plug example: https://github.com/1Lethn1/By-the-gods-0.1/blob/main/MaleAdultVillager

                It's definitely not as complex as yours but there's a lot there and I also want people to be able to mod all of this if they want to, so I've made sure to write it so they can get through it all and make sure anything I've written that's custom code is understandable, but I just thought I'd bring this up as a general thing because again to stress, this is the only proper thread on 3D selection boxes I've seen so far lol.

                If you don't mind me asking more questions about the actual grabbing of the meshes because I am getting a headache even though I've got the general idea, you've declared a mesh_instance in relation to my own code would this be a mesh belonging to a character within the scene for example? I have these bean soldiers I've got up and I want to enable their selection rings and then also add them to an array so they've been selected. I also have an idea thanks to this code you've posted of how I'm going to do selection groups as well, that stuff should be easy and I'll post it up here as well for the sake of helping others get something properly functional.

                • xyz replied to this.

                  Lethn There's a place for long descriptive names and there's a place for short names. Local variables seldom have business bearing long names, especially if they represent abstract values or repeat frequently in statements with lots of operators (which is often the case in shaders). It just makes code hard to read. There are also some widely accepted conventions, most of them carried over from standard math notation. The most common ones:
                  p - point
                  v - vertex, velocity
                  n - normal
                  a - acceleration
                  m - matrix
                  i - iterator, counter
                  x, y, z - you know those
                  a, b, c, d - parameters of a plane or polynomial equation
                  d - distance, difference (delta), diameter
                  t - normalized parameter, time, tangent
                  r - radius

                  Insisting on long names for everything can produce some really hard to read code.
                  Compare for example:

                  var d = -p1.x * n.x - p1.y * n.y - p1.z * n.z

                  With:

                  var fourth_plane_parameter = -plane_point_1.x * plane_normal.x - plane_point_1.y * plane_normal.y - plane_point_1.z * plane_normal.z

                  It's better to provide verbal explanations or code comments rather than to force long names on everything.

                    Yeah I get that, I just wish people actually did that more often in working examples of code lol 😃 thanks for the glossary it has actually really helped with the legibility of the code.

                    xyz x, y, z - you know those

                    x, y, z, w - when dealing with something 4D.