mharitsnf / ExtraSnaps

A Godot 4.X plugin that adds extra snapping functionalities for Node3D objects.
MIT License
39 stars 3 forks source link

Snapping on Mesh3D #1

Closed oparisy closed 1 month ago

oparisy commented 4 months ago

First of all, thanks for sharing your work!

I've tried building levels with modular assets before, but if you look at an example such as this one, it becomes clear that the gridmap is not sufficient to place those, and that a snapping tool is an invaluable help: image

I wanted to know if only being able to snap on collision objects was a strong constraint, or if you could consider snapping on arbitrary Mesh3Ds?

My reasoning being that on the scene above, it seems dispendious to have one collision body per modular asset, while a more coarse-grained collision shape (added after modeling) would usually suffice.

Regards!

mharitsnf commented 4 months ago

Hi, thanks for bringing this up!

The only reason I implemented the snapping only to collision objects is because it was the easiest to get it running (because I can just use ray cast to get the position and normal of the target object). But yes, I have been thinking about ways to get things to snap without having to rely on collision objects.

I would love to have this feature as well, so I'll try to find ways to implement this.

oparisy commented 4 months ago

A possibly related issue: https://github.com/godotengine/godot-proposals/issues/2863

Note the comment about tooling at the beginning of the discussion.

ChildLearningClub commented 1 month ago

The way to implement this would be to apply a temporary collision shape to the Mesh3D objects that you want to place other objects on. and of course to the Mesh3D object you are placing, after it is placed, if you want to continue to build off of that. The final step would be a box select the objects in the area and "commit build button" and either strip collisions or convert to lower resolution collisions.

@oparisy I can't seem to find that pack of Kenney's that you show, do you happen to have a link to that?

oparisy commented 1 month ago

That's more of a workaround, excepted if you're suggesting the tool is adding those collision objects automatically?

Le mar. 30 juil. 2024, 00:53, ChildLearningClub @.***> a écrit :

The way to implement this would be to apply a temporary collision shape to the Mesh3D objects that you want to place other objects on. and of course to the Mesh3D object you are placing, after it is placed, if you want to continue to build off of that. The final step would be a box select the objects in the area and "commit build button" and either strip collisions or convert to lower resolution collisions.

— Reply to this email directly, view it on GitHub https://github.com/mharitsnf/ExtraSnaps/issues/1#issuecomment-2257144244, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAGGPMJVKPJNZMUWBZ6RDJ3ZO3BU3AVCNFSM6AAAAABHKI7GS2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENJXGE2DIMRUGQ . You are receiving this because you authored the thread.Message ID: @.***>

ChildLearningClub commented 1 month ago

That's more of a workaround, excepted if you're suggesting the tool is adding those collision objects automatically?

I think the only alternative would be to have a reference to all the AABB bounding boxes in the environment stored in an array or something, which I think would be a much more difficult option compared to even just having the tool apply Area3Ds to the same AABB and using raycasting. additionally rather then each one being done individually when selected there is the option to use an import script and apply all the collision/areas at import time, which again if desired removed when building of the area is complete. What do you think?

oparisy commented 1 month ago

@oparisy I can't seem to find that pack of Kenney's that you show, do you happen to have a link to that?

Indeed the preview image I used for this issue has been changed in recent version. It's the Modular Buildings pack.

Actually the preview is much more contorted now, and mostly aligned on the vertical axis (probably as an influence of his City Builder Starter Kit), so it does not illustate my point so well anymore 😅

oparisy commented 1 month ago

@ChildLearningClub User experience wise, I feel this is lots of manipulation. I think the current "snap to the nearest object" behavior (as demonstrated by the README video) is a strong feature of this addon and should not be degraded.

Technically wise, if the tool can automatically apply and remove those collision objects "under the hood", user experience would be preserved. It reads like a chicken and egg problem (how do you find the nearest object to apply the collisition object to, since that collision object is used by the addon to detect the nearest object in the first place?), but I guess using AABBs could help by efficiently providing a coarse "view" of the surrounding.

I still think the best solution would be to have some support from the physics server to raycast to non-collision objects (as evoked in https://github.com/godotengine/godot-proposals/issues/2863).

Since this is not currently available, another solution would be for this plugin to use some efficient spatial queries data structure, and do the raycasting itself. But this is quite a heavy lifting for GDScript.

mharitsnf commented 1 month ago

One approach that I want to try is to use ray-triangle intersection (as seen here), which involves (1) getting all triangles in the scene, (2) finding an intersection between one of the triangles with the currently selected object, and (3) placing the selected object at the intersection point (and optionally align the orientation of the selected object with the triangle's normal).

Technically wise, if the tool can automatically apply and remove those collision objects "under the hood", user experience would be preserved. It reads like a chicken and egg problem (how do you find the nearest object to apply the collision object to, since that collision object is used by the addon to detect the nearest object in the first place?), but I guess using AABBs could help by efficiently providing a coarse "view" of the surrounding.

TBH, I think this approach is pretty interesting and I think it could work as well. Since the addon already have a function that can retrieve all mesh instances in a scene, we can firstly try to retrieve all mesh instances in the scene, create a temporary trimesh collision for all of those mesh instances, and removing them as soon as the snapping is finished.

I don't know how well these approaches will perform in a scene with a lot of meshes, I figure it might be pretty slow in that scenario. But I think we can resort to using AABBs like you mentioned or use the camera frustrum to limit the number of meshes to process to improve the performance.

oparisy commented 1 month ago

Having something that works on small to medium scenes would be awesome!

Then it can be challenged and optimized for larger scenes. I think it's better to start simple, then improve algorithms and data structures where and when needed.

Le jeu. 1 août 2024, 09:18, Harits @.***> a écrit :

One approach that I want to try is to use ray-triangle intersection (as seen here https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection.html), which involves (1) getting all triangles in the scene, (2) finding an intersection between one of the triangles with the currently selected object, and (3) placing the selected object at the intersection point (and optionally align the orientation of the selected object with the triangle's normal).

Technically wise, if the tool can automatically apply and remove those collision objects "under the hood", user experience would be preserved. It reads like a chicken and egg problem (how do you find the nearest object to apply the collision object to, since that collision object is used by the addon to detect the nearest object in the first place?), but I guess using AABBs could help by efficiently providing a coarse "view" of the surrounding.

TBH, I think this approach is pretty interesting and I think it could work as well. Since the addon already have a function that can retrieve all mesh instances in a scene, we can firstly try to retrieve all mesh instances in the scene, create a temporary trimesh collision https://docs.godotengine.org/en/stable/classes/class_meshinstance3d.html#class-meshinstance3d-method-create-trimesh-collision for all of those mesh instances, and removing them as soon as the snapping is finished.

I don't know how well these approaches will perform in a scene with a lot of meshes, I figure it might be pretty slow in that scenario. But I think we can resort to using AABBs like you mentioned or use the camera frustrum to limit the number of meshes to process to improve the performance.

— Reply to this email directly, view it on GitHub https://github.com/mharitsnf/ExtraSnaps/issues/1#issuecomment-2262226855, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAGGPMPKKIAVM2IX7VZMHKTZPHOMJAVCNFSM6AAAAABHKI7GS2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENRSGIZDMOBVGU . You are receiving this because you were mentioned.Message ID: @.***>

ChildLearningClub commented 1 month ago

Yeah, I think creating the temporary trimesh collision would be the way to go for the accurate snapping that would provide, The initial building of all the trimesh I can see slowing things down a bit (and this would just be for large static mesh projects imported), As new objects are brought in they would individually get the trimesh applied. All the collisions that are created can be added to a group can be stripped out later. What do you guys think?

I don’t know if there is a way through code to do this stripping out process, I imagine there is? So there is this extra step involved compared to what I think your suggesting @mharitsnf instantiating the collisions as they come into view and then freeing or removing them as they exit the view? Anyway, some great ideas!

mharitsnf commented 1 month ago

Yeah, as you mentioned, I think we can put the collision (StaticBody3Ds) in a group then delete them with queue_free once we are done.

For a quick update: I managed to make it work with ray-triangle intersection, but sometimes it just couldn't detect an intersection on some part of the mesh (take a look at the video). I've no clue why this happens lol but I'll try to look at it a bit closer later.

https://github.com/user-attachments/assets/8e32d74b-4bcb-4522-80cd-b60e93b1cb58

If this doesn't work, I'll probably try to create static bodies on the meshes, although I don't really like this approach because you can visibly see the static body nodes being added and removed from the scene tree.

oparisy commented 1 month ago

For a quick update: I managed to make it work with ray-triangle intersection, but sometimes it just couldn't detect an intersection on some part of the mesh (take a look at the video). I've no clue why this happens lol but I'll try to look at it a bit closer later.

Maybe a Godot bug worthy of reporting, especially if you can reliably reproduce it? I had a quick look at their tracker but did not find a related issue.

ChildLearningClub commented 1 month ago

@mharitsnf Wow! Great job!. I never knew it was possible to get a meshes position from just the mesh itself. This might be of help with the issue you are having. Or at least give you some ideas? I hope it is useful. https://forum.godotengine.org/t/get-face-of-colliding-mesh/2206/2

Godot 4.3 Geometry = Geometry3D rad2deg = rad_to_deg

ChildLearningClub commented 1 month ago

Maybe not a bug but a limitation of the angle maybe? Not sure myself?

https://github.com/user-attachments/assets/349e60fb-a9a4-46a7-87c9-7562fcefd77e

@mharitsnf turns out it is a limitation of our brains ;). What's the length of your raycast? can bet it is too short.

https://github.com/user-attachments/assets/04dce5f3-c841-4353-bd96-e823903c7535

mharitsnf commented 1 month ago

Hmmm, I think we are using a different approach, as I'm not using raycasts. But your snippets look cool! I thought raycasts require collision bodies, but you only have meshes in the scene tree. Or perhaps there is a way without using collision bodies?

In the video I posted before, I used ray_intersects_triangle function from the Geometry3D class. It doesn't require ray length because it uses some mathematical calculations between a triangle and a ray origin and direction.

Maybe a Godot bug worthy of reporting, especially if you can reliably reproduce it? I had a quick look at their tracker but did not find a related issue.

It could be. I will try to figure out how to reproduce it.

Edit: wrong function name

mharitsnf commented 1 month ago

Nevermind... I was being dumb. My for loop was the main issue 😅 Anyways, here's how it looks like, currently works with CSGs and meshes:

https://github.com/user-attachments/assets/b7bda221-a731-4afa-acc9-4ca88d7f335e

I think it looks nice; though I'll go through it again to make sure it actually works well. Hopefully I can make a PR soon enough :D

ChildLearningClub commented 1 month ago

You are correct, I am not using the raycast, I'm pretty you are also right that it requires a collision body to function It is just to visualize the position and was using the position and raycast target information from it. I'm using the ray_intersects_triangle as well, so we are probably not to far off each other :). Yours looks smooth! were you able to figure out how to narrow down the process to only the mesh target, or are they all processed or just the ones in camera view? the AABB class has the intersects_ray () function which I think will solve any issues with that. Struggling now to get it to work through. think I have to convert the aabb to global space.

Edit: Oh, also I have to look at it more, but think the get_triangle_barycentric_coords from the Geometry class might also be useful with smoothing the transitions over the surfaces if that was desirable. https://github.com/godotengine/godot/pull/71233

Edit 2: Took forever to find this. the xform in godot 4 visual_instance.global_transform * visual_instance.get_aabb(). https://stackoverflow.com/questions/76982738/how-to-convert-form-aabb-space-get-center-method-to-world-environment-in-godot

ChildLearningClub commented 1 month ago

@mharitsnf if you are not already limiting the process I found using the intersects_segment( ) function within the AABB class worked, I was having trouble with the intersects_ray ().

var aabb_global: AABB = mesh_instance.global_transform * mesh_instance.mesh.get_aabb()

if aabb_global.intersects_segment(editor_camera3d.project_ray_origin(mouse_pos), (editor_camera3d.project_ray_origin(mouse_pos) + editor_camera3d.project_ray_normal(mouse_pos) * 1000)):

https://docs.godotengine.org/en/stable/classes/class_aabb.html#class-aabb-method-intersects-segment

mharitsnf commented 1 month ago

Hi folks, I just made a pull request for this feature (check here: #8)! I ended up using AABB intersection to determine which object to process as mentioned by @ChildLearningClub, as it helps with the performance when I tested it (although it wasn't a comprehensive test at all)

Please give it a try, see how it works for you 😊

ChildLearningClub commented 1 month ago

It works well! again, great Job! Now it's time @oparisy to test it on that buildings pack :)

mharitsnf commented 1 month ago

Thanks all for the help and contributions! I'll close this issue as I'll merge the PR.

ChildLearningClub commented 1 month ago

@mharitsnf would possibly obscuring the option to switch between the two object types be desirable? I'm trying to think of when you would only want one or the other and if that could not be resolved by the user viewing which have collision shapes? What are your thoughts? And @oparisy?

https://github.com/user-attachments/assets/20e1cc20-cf6c-4565-a916-a9066a101298

Edit: Now that I think about it, if the scene got really crowded, I guess it might be a good thing to have.

Edit2: @mharitsnf also wanted to say that implementing the snapping at the distance to the vertices rather then the vertices themselves was a great idea!

Edit 3. Okay, I see the Mesh option allows for you to snap to all mesh and the using raycasts will allow for just snapping to those objects with collision nodes. So you can completely disregard this post :) I guess the question now is whether the collision object snapping is even needed when the mesh snapping will do the job for both? Oh yeah, you could have custom collision shapes and want to snap to those, although not sure I know the real use cases? Anyway, Okay, I'm done.