Browse Source

Merge pull request #104625 from smix8/trimesh_api

Expose TriangleMesh api functions wrapped for scripting
Thaddeus Crews 5 months ago
parent
commit
74b5ece1fc

+ 99 - 2
core/math/triangle_mesh.cpp

@@ -182,7 +182,11 @@ void TriangleMesh::create(const Vector<Vector3> &p_faces, const Vector<int32_t>
 	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<Face3> TriangleMesh::get_faces() const {
 	return faces;
 }
 
+bool TriangleMesh::create_from_faces(const Vector<Vector3> &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<Vector3> TriangleMesh::get_faces_scriptwrap() const {
+	if (!valid) {
+		return Vector<Vector3>();
+	}
+
+	Vector<Vector3> 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;

+ 19 - 8
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<Triangle> triangles;
 	Vector<Vector3> 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> 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<Face3> get_faces() const;
 
@@ -91,5 +94,13 @@ public:
 	void get_indices(Vector<int> *r_triangles_indices) const;
 
 	void create(const Vector<Vector3> &p_faces, const Vector<int32_t> &p_surface_indices = Vector<int32_t>());
+
+	// 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<Vector3> &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<Vector3> get_faces_scriptwrap() const;
+
 	TriangleMesh();
 };

+ 49 - 2
doc/classes/TriangleMesh.xml

@@ -1,11 +1,58 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <class name="TriangleMesh" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
 	<brief_description>
-		Internal mesh type.
+		Triangle geometry for efficient, physicsless intersection queries.
 	</brief_description>
 	<description>
-		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.
 	</description>
 	<tutorials>
 	</tutorials>
+	<methods>
+		<method name="create_from_faces">
+			<return type="bool" />
+			<param index="0" name="faces" type="PackedVector3Array" />
+			<description>
+				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.
+			</description>
+		</method>
+		<method name="get_faces" qualifiers="const">
+			<return type="PackedVector3Array" />
+			<description>
+				Returns a copy of the geometry faces. Each 3 vertices of the array represent one triangle (face).
+			</description>
+		</method>
+		<method name="intersect_ray" qualifiers="const">
+			<return type="Dictionary" />
+			<param index="0" name="begin" type="Vector3" />
+			<param index="1" name="dir" type="Vector3" />
+			<description>
+				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.
+			</description>
+		</method>
+		<method name="intersect_segment" qualifiers="const">
+			<return type="Dictionary" />
+			<param index="0" name="begin" type="Vector3" />
+			<param index="1" name="end" type="Vector3" />
+			<description>
+				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.
+			</description>
+		</method>
+	</methods>
 </class>

+ 82 - 0
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<BoxMesh> box_mesh;
+	box_mesh.instantiate();
+
+	const Vector<Face3> faces = box_mesh->get_faces();
+
+	Ref<TriangleMesh> 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

+ 1 - 0
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"