From f2197a1013958a4d5bb2edf104b190b6474e1872 Mon Sep 17 00:00:00 2001 From: smix8 <52464204+smix8@users.noreply.github.com> Date: Tue, 25 Mar 2025 19:43:15 +0100 Subject: [PATCH] Expose TriangleMesh api functions wrapped for scripting Adds script wrapped TriangleMesh api functions to create and query the triangle BVH tree. --- core/math/triangle_mesh.cpp | 101 ++++++++++++++++++++++++++- core/math/triangle_mesh.h | 27 ++++--- doc/classes/TriangleMesh.xml | 51 +++++++++++++- tests/core/math/test_triangle_mesh.h | 82 ++++++++++++++++++++++ tests/test_main.cpp | 1 + 5 files changed, 250 insertions(+), 12 deletions(-) create mode 100644 tests/core/math/test_triangle_mesh.h diff --git a/core/math/triangle_mesh.cpp b/core/math/triangle_mesh.cpp index 01b733183dc..3b24b351eaa 100644 --- a/core/math/triangle_mesh.cpp +++ b/core/math/triangle_mesh.cpp @@ -182,7 +182,11 @@ void TriangleMesh::create(const Vector &p_faces, const Vector valid = true; } -bool TriangleMesh::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal, int32_t *r_surf_index) const { +bool TriangleMesh::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal, int32_t *r_surf_index, int32_t *r_face_index) const { + if (!valid) { + return false; + } + uint32_t *stack = (uint32_t *)alloca(sizeof(int) * max_depth); enum { @@ -234,6 +238,9 @@ bool TriangleMesh::intersect_segment(const Vector3 &p_begin, const Vector3 &p_en if (r_surf_index) { *r_surf_index = s.surface_index; } + if (r_face_index) { + *r_face_index = b.face_index; + } inters = true; } } @@ -283,7 +290,11 @@ bool TriangleMesh::intersect_segment(const Vector3 &p_begin, const Vector3 &p_en return inters; } -bool TriangleMesh::intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, Vector3 &r_point, Vector3 &r_normal, int32_t *r_surf_index) const { +bool TriangleMesh::intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, Vector3 &r_point, Vector3 &r_normal, int32_t *r_surf_index, int32_t *r_face_index) const { + if (!valid) { + return false; + } + uint32_t *stack = (uint32_t *)alloca(sizeof(int) * max_depth); enum { @@ -335,6 +346,9 @@ bool TriangleMesh::intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, V if (r_surf_index) { *r_surf_index = s.surface_index; } + if (r_face_index) { + *r_face_index = b.face_index; + } inters = true; } } @@ -385,6 +399,10 @@ bool TriangleMesh::intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, V } bool TriangleMesh::inside_convex_shape(const Plane *p_planes, int p_plane_count, const Vector3 *p_points, int p_point_count, Vector3 p_scale) const { + if (!valid) { + return false; + } + uint32_t *stack = (uint32_t *)alloca(sizeof(int) * max_depth); enum { @@ -503,6 +521,85 @@ Vector TriangleMesh::get_faces() const { return faces; } +bool TriangleMesh::create_from_faces(const Vector &p_faces) { + create(p_faces); + return is_valid(); +} + +Dictionary TriangleMesh::intersect_segment_scriptwrap(const Vector3 &p_begin, const Vector3 &p_end) const { + if (!valid) { + return Dictionary(); + } + + Vector3 r_point; + Vector3 r_normal; + int32_t r_face_index = -1; + + bool intersected = intersect_segment(p_begin, p_end, r_point, r_normal, nullptr, &r_face_index); + if (!intersected) { + return Dictionary(); + } + + Dictionary result; + result["position"] = r_point; + result["normal"] = r_normal; + result["face_index"] = r_face_index; + + return result; +} + +Dictionary TriangleMesh::intersect_ray_scriptwrap(const Vector3 &p_begin, const Vector3 &p_dir) const { + if (!valid) { + return Dictionary(); + } + + Vector3 r_point; + Vector3 r_normal; + int32_t r_face_index = -1; + + bool intersected = intersect_ray(p_begin, p_dir, r_point, r_normal, nullptr, &r_face_index); + if (!intersected) { + return Dictionary(); + } + + Dictionary result; + result["position"] = r_point; + result["normal"] = r_normal; + result["face_index"] = r_face_index; + + return result; +} + +Vector TriangleMesh::get_faces_scriptwrap() const { + if (!valid) { + return Vector(); + } + + Vector faces; + int ts = triangles.size(); + faces.resize(triangles.size() * 3); + + Vector3 *w = faces.ptrw(); + const Triangle *r = triangles.ptr(); + const Vector3 *rv = vertices.ptr(); + + for (int i = 0; i < ts; i++) { + for (int j = 0; j < 3; j++) { + w[i * 3 + j] = rv[r[i].indices[j]]; + } + } + + return faces; +} + +void TriangleMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("create_from_faces", "faces"), &TriangleMesh::create_from_faces); + ClassDB::bind_method(D_METHOD("get_faces"), &TriangleMesh::get_faces_scriptwrap); + + ClassDB::bind_method(D_METHOD("intersect_segment", "begin", "end"), &TriangleMesh::intersect_segment_scriptwrap); + ClassDB::bind_method(D_METHOD("intersect_ray", "begin", "dir"), &TriangleMesh::intersect_ray_scriptwrap); +} + TriangleMesh::TriangleMesh() { valid = false; max_depth = 0; diff --git a/core/math/triangle_mesh.h b/core/math/triangle_mesh.h index 28f792b4dd4..65fd0ff9b7b 100644 --- a/core/math/triangle_mesh.h +++ b/core/math/triangle_mesh.h @@ -40,9 +40,12 @@ public: struct Triangle { Vector3 normal; int indices[3]; - int32_t surface_index; + int32_t surface_index = 0; }; +protected: + static void _bind_methods(); + private: Vector triangles; Vector vertices; @@ -50,10 +53,10 @@ private: struct BVH { AABB aabb; Vector3 center; //used for sorting - int left; - int right; + int left = -1; + int right = -1; - int face_index; + int32_t face_index = -1; }; struct BVHCmpX { @@ -76,13 +79,13 @@ private: int _create_bvh(BVH *p_bvh, BVH **p_bb, int p_from, int p_size, int p_depth, int &max_depth, int &max_alloc); Vector bvh; - int max_depth; - bool valid; + int max_depth = 0; + bool valid = false; public: bool is_valid() const; - bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal, int32_t *r_surf_index = nullptr) const; - bool intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, Vector3 &r_point, Vector3 &r_normal, int32_t *r_surf_index = nullptr) const; + bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal, int32_t *r_surf_index = nullptr, int32_t *r_face_index = nullptr) const; + bool intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, Vector3 &r_point, Vector3 &r_normal, int32_t *r_surf_index = nullptr, int32_t *r_face_index = nullptr) const; bool inside_convex_shape(const Plane *p_planes, int p_plane_count, const Vector3 *p_points, int p_point_count, Vector3 p_scale = Vector3(1, 1, 1)) const; Vector get_faces() const; @@ -91,5 +94,13 @@ public: void get_indices(Vector *r_triangles_indices) const; void create(const Vector &p_faces, const Vector &p_surface_indices = Vector()); + + // Wrapped functions for compatibility with method bindings + // and user exposed script api that can't use more native types. + bool create_from_faces(const Vector &p_faces); + Dictionary intersect_segment_scriptwrap(const Vector3 &p_begin, const Vector3 &p_end) const; + Dictionary intersect_ray_scriptwrap(const Vector3 &p_begin, const Vector3 &p_dir) const; + Vector get_faces_scriptwrap() const; + TriangleMesh(); }; diff --git a/doc/classes/TriangleMesh.xml b/doc/classes/TriangleMesh.xml index 79bab0ece64..dc4e688b492 100644 --- a/doc/classes/TriangleMesh.xml +++ b/doc/classes/TriangleMesh.xml @@ -1,11 +1,58 @@ - Internal mesh type. + Triangle geometry for efficient, physicsless intersection queries. - Mesh type used internally for collision calculations. + Creates a bounding volume hierarchy (BVH) tree structure around triangle geometry. + The triangle BVH tree can be used for efficient intersection queries without involving a physics engine. + For example, this can be used in editor tools to select objects with complex shapes based on the mouse cursor position. + [b]Performance:[/b] Creating the BVH tree for complex geometry is a slow process and best done in a background thread. + + + + + + Creates the BVH tree from an array of faces. Each 3 vertices of the input [param faces] array represent one triangle (face). + Returns [code]true[/code] if the tree is successfully built, [code]false[/code] otherwise. + + + + + + Returns a copy of the geometry faces. Each 3 vertices of the array represent one triangle (face). + + + + + + + + Tests for intersection with a ray starting at [param begin] and facing [param dir] and extending toward infinity. + If an intersection with a triangle happens, returns a [Dictionary] with the following fields: + [code]position[/code]: The position on the intersected triangle. + [code]normal[/code]: The normal of the intersected triangle. + [code]face_index[/code]: The index of the intersected triangle. + Returns an empty [Dictionary] if no intersection happens. + See also [method intersect_segment], which is similar but uses a finite-length segment. + + + + + + + + Tests for intersection with a segment going from [param begin] to [param end]. + If an intersection with a triangle happens returns a [Dictionary] with the following fields: + [code]position[/code]: The position on the intersected triangle. + [code]normal[/code]: The normal of the intersected triangle. + [code]face_index[/code]: The index of the intersected triangle. + Returns an empty [Dictionary] if no intersection happens. + See also [method intersect_ray], which is similar but uses an infinite-length ray. + + + diff --git a/tests/core/math/test_triangle_mesh.h b/tests/core/math/test_triangle_mesh.h new file mode 100644 index 00000000000..f138b554c37 --- /dev/null +++ b/tests/core/math/test_triangle_mesh.h @@ -0,0 +1,82 @@ +/**************************************************************************/ +/* test_triangle_mesh.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "core/math/triangle_mesh.h" +#include "scene/resources/3d/primitive_meshes.h" + +#include "tests/test_macros.h" + +namespace TestTriangleMesh { + +TEST_CASE("[SceneTree][TriangleMesh] BVH creation and intersection") { + Ref box_mesh; + box_mesh.instantiate(); + + const Vector faces = box_mesh->get_faces(); + + Ref triangle_mesh; + triangle_mesh.instantiate(); + CHECK(triangle_mesh->create_from_faces(Variant(faces))); + + const Vector3 begin = Vector3(0.0, 2.0, 0.0); + const Vector3 end = Vector3(0.0, -2.0, 0.0); + + { + Vector3 point; + Vector3 normal; + int32_t *surf_index = nullptr; + int32_t face_index = -1; + const bool has_result = triangle_mesh->intersect_segment(begin, end, point, normal, surf_index, &face_index); + CHECK(has_result); + CHECK(point.is_equal_approx(Vector3(0.0, 0.5, 0.0))); + CHECK(normal.is_equal_approx(Vector3(0.0, 1.0, 0.0))); + CHECK(surf_index == nullptr); + REQUIRE(face_index != -1); + CHECK(face_index == 8); + } + + { + Vector3 dir = begin.direction_to(end); + Vector3 point; + Vector3 normal; + int32_t *surf_index = nullptr; + int32_t face_index = -1; + const bool has_result = triangle_mesh->intersect_ray(begin, dir, point, normal, surf_index, &face_index); + CHECK(has_result); + CHECK(point.is_equal_approx(Vector3(0.0, 0.5, 0.0))); + CHECK(normal.is_equal_approx(Vector3(0.0, 1.0, 0.0))); + CHECK(surf_index == nullptr); + REQUIRE(face_index != -1); + CHECK(face_index == 8); + } +} +} // namespace TestTriangleMesh diff --git a/tests/test_main.cpp b/tests/test_main.cpp index bf6c9fa4d46..7a3efdb5e31 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -161,6 +161,7 @@ #endif // ADVANCED_GUI_DISABLED #ifndef _3D_DISABLED +#include "tests/core/math/test_triangle_mesh.h" #include "tests/scene/test_arraymesh.h" #include "tests/scene/test_camera_3d.h" #include "tests/scene/test_gltf_document.h"