Skip to content

Commit 74b5ece

Browse files
committed
Merge pull request godotengine#104625 from smix8/trimesh_api
Expose TriangleMesh api functions wrapped for scripting
2 parents 6693836 + f2197a1 commit 74b5ece

File tree

5 files changed

+250
-12
lines changed

5 files changed

+250
-12
lines changed

core/math/triangle_mesh.cpp

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,11 @@ void TriangleMesh::create(const Vector<Vector3> &p_faces, const Vector<int32_t>
182182
valid = true;
183183
}
184184

185-
bool TriangleMesh::intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal, int32_t *r_surf_index) const {
185+
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 {
186+
if (!valid) {
187+
return false;
188+
}
189+
186190
uint32_t *stack = (uint32_t *)alloca(sizeof(int) * max_depth);
187191

188192
enum {
@@ -234,6 +238,9 @@ bool TriangleMesh::intersect_segment(const Vector3 &p_begin, const Vector3 &p_en
234238
if (r_surf_index) {
235239
*r_surf_index = s.surface_index;
236240
}
241+
if (r_face_index) {
242+
*r_face_index = b.face_index;
243+
}
237244
inters = true;
238245
}
239246
}
@@ -283,7 +290,11 @@ bool TriangleMesh::intersect_segment(const Vector3 &p_begin, const Vector3 &p_en
283290
return inters;
284291
}
285292

286-
bool TriangleMesh::intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, Vector3 &r_point, Vector3 &r_normal, int32_t *r_surf_index) const {
293+
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 {
294+
if (!valid) {
295+
return false;
296+
}
297+
287298
uint32_t *stack = (uint32_t *)alloca(sizeof(int) * max_depth);
288299

289300
enum {
@@ -335,6 +346,9 @@ bool TriangleMesh::intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, V
335346
if (r_surf_index) {
336347
*r_surf_index = s.surface_index;
337348
}
349+
if (r_face_index) {
350+
*r_face_index = b.face_index;
351+
}
338352
inters = true;
339353
}
340354
}
@@ -385,6 +399,10 @@ bool TriangleMesh::intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, V
385399
}
386400

387401
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 {
402+
if (!valid) {
403+
return false;
404+
}
405+
388406
uint32_t *stack = (uint32_t *)alloca(sizeof(int) * max_depth);
389407

390408
enum {
@@ -503,6 +521,85 @@ Vector<Face3> TriangleMesh::get_faces() const {
503521
return faces;
504522
}
505523

524+
bool TriangleMesh::create_from_faces(const Vector<Vector3> &p_faces) {
525+
create(p_faces);
526+
return is_valid();
527+
}
528+
529+
Dictionary TriangleMesh::intersect_segment_scriptwrap(const Vector3 &p_begin, const Vector3 &p_end) const {
530+
if (!valid) {
531+
return Dictionary();
532+
}
533+
534+
Vector3 r_point;
535+
Vector3 r_normal;
536+
int32_t r_face_index = -1;
537+
538+
bool intersected = intersect_segment(p_begin, p_end, r_point, r_normal, nullptr, &r_face_index);
539+
if (!intersected) {
540+
return Dictionary();
541+
}
542+
543+
Dictionary result;
544+
result["position"] = r_point;
545+
result["normal"] = r_normal;
546+
result["face_index"] = r_face_index;
547+
548+
return result;
549+
}
550+
551+
Dictionary TriangleMesh::intersect_ray_scriptwrap(const Vector3 &p_begin, const Vector3 &p_dir) const {
552+
if (!valid) {
553+
return Dictionary();
554+
}
555+
556+
Vector3 r_point;
557+
Vector3 r_normal;
558+
int32_t r_face_index = -1;
559+
560+
bool intersected = intersect_ray(p_begin, p_dir, r_point, r_normal, nullptr, &r_face_index);
561+
if (!intersected) {
562+
return Dictionary();
563+
}
564+
565+
Dictionary result;
566+
result["position"] = r_point;
567+
result["normal"] = r_normal;
568+
result["face_index"] = r_face_index;
569+
570+
return result;
571+
}
572+
573+
Vector<Vector3> TriangleMesh::get_faces_scriptwrap() const {
574+
if (!valid) {
575+
return Vector<Vector3>();
576+
}
577+
578+
Vector<Vector3> faces;
579+
int ts = triangles.size();
580+
faces.resize(triangles.size() * 3);
581+
582+
Vector3 *w = faces.ptrw();
583+
const Triangle *r = triangles.ptr();
584+
const Vector3 *rv = vertices.ptr();
585+
586+
for (int i = 0; i < ts; i++) {
587+
for (int j = 0; j < 3; j++) {
588+
w[i * 3 + j] = rv[r[i].indices[j]];
589+
}
590+
}
591+
592+
return faces;
593+
}
594+
595+
void TriangleMesh::_bind_methods() {
596+
ClassDB::bind_method(D_METHOD("create_from_faces", "faces"), &TriangleMesh::create_from_faces);
597+
ClassDB::bind_method(D_METHOD("get_faces"), &TriangleMesh::get_faces_scriptwrap);
598+
599+
ClassDB::bind_method(D_METHOD("intersect_segment", "begin", "end"), &TriangleMesh::intersect_segment_scriptwrap);
600+
ClassDB::bind_method(D_METHOD("intersect_ray", "begin", "dir"), &TriangleMesh::intersect_ray_scriptwrap);
601+
}
602+
506603
TriangleMesh::TriangleMesh() {
507604
valid = false;
508605
max_depth = 0;

core/math/triangle_mesh.h

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,23 @@ class TriangleMesh : public RefCounted {
4040
struct Triangle {
4141
Vector3 normal;
4242
int indices[3];
43-
int32_t surface_index;
43+
int32_t surface_index = 0;
4444
};
4545

46+
protected:
47+
static void _bind_methods();
48+
4649
private:
4750
Vector<Triangle> triangles;
4851
Vector<Vector3> vertices;
4952

5053
struct BVH {
5154
AABB aabb;
5255
Vector3 center; //used for sorting
53-
int left;
54-
int right;
56+
int left = -1;
57+
int right = -1;
5558

56-
int face_index;
59+
int32_t face_index = -1;
5760
};
5861

5962
struct BVHCmpX {
@@ -76,13 +79,13 @@ class TriangleMesh : public RefCounted {
7679
int _create_bvh(BVH *p_bvh, BVH **p_bb, int p_from, int p_size, int p_depth, int &max_depth, int &max_alloc);
7780

7881
Vector<BVH> bvh;
79-
int max_depth;
80-
bool valid;
82+
int max_depth = 0;
83+
bool valid = false;
8184

8285
public:
8386
bool is_valid() const;
84-
bool intersect_segment(const Vector3 &p_begin, const Vector3 &p_end, Vector3 &r_point, Vector3 &r_normal, int32_t *r_surf_index = nullptr) const;
85-
bool intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, Vector3 &r_point, Vector3 &r_normal, int32_t *r_surf_index = nullptr) const;
87+
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;
88+
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;
8689
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;
8790
Vector<Face3> get_faces() const;
8891

@@ -91,5 +94,13 @@ class TriangleMesh : public RefCounted {
9194
void get_indices(Vector<int> *r_triangles_indices) const;
9295

9396
void create(const Vector<Vector3> &p_faces, const Vector<int32_t> &p_surface_indices = Vector<int32_t>());
97+
98+
// Wrapped functions for compatibility with method bindings
99+
// and user exposed script api that can't use more native types.
100+
bool create_from_faces(const Vector<Vector3> &p_faces);
101+
Dictionary intersect_segment_scriptwrap(const Vector3 &p_begin, const Vector3 &p_end) const;
102+
Dictionary intersect_ray_scriptwrap(const Vector3 &p_begin, const Vector3 &p_dir) const;
103+
Vector<Vector3> get_faces_scriptwrap() const;
104+
94105
TriangleMesh();
95106
};

doc/classes/TriangleMesh.xml

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,58 @@
11
<?xml version="1.0" encoding="UTF-8" ?>
22
<class name="TriangleMesh" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
33
<brief_description>
4-
Internal mesh type.
4+
Triangle geometry for efficient, physicsless intersection queries.
55
</brief_description>
66
<description>
7-
Mesh type used internally for collision calculations.
7+
Creates a bounding volume hierarchy (BVH) tree structure around triangle geometry.
8+
The triangle BVH tree can be used for efficient intersection queries without involving a physics engine.
9+
For example, this can be used in editor tools to select objects with complex shapes based on the mouse cursor position.
10+
[b]Performance:[/b] Creating the BVH tree for complex geometry is a slow process and best done in a background thread.
811
</description>
912
<tutorials>
1013
</tutorials>
14+
<methods>
15+
<method name="create_from_faces">
16+
<return type="bool" />
17+
<param index="0" name="faces" type="PackedVector3Array" />
18+
<description>
19+
Creates the BVH tree from an array of faces. Each 3 vertices of the input [param faces] array represent one triangle (face).
20+
Returns [code]true[/code] if the tree is successfully built, [code]false[/code] otherwise.
21+
</description>
22+
</method>
23+
<method name="get_faces" qualifiers="const">
24+
<return type="PackedVector3Array" />
25+
<description>
26+
Returns a copy of the geometry faces. Each 3 vertices of the array represent one triangle (face).
27+
</description>
28+
</method>
29+
<method name="intersect_ray" qualifiers="const">
30+
<return type="Dictionary" />
31+
<param index="0" name="begin" type="Vector3" />
32+
<param index="1" name="dir" type="Vector3" />
33+
<description>
34+
Tests for intersection with a ray starting at [param begin] and facing [param dir] and extending toward infinity.
35+
If an intersection with a triangle happens, returns a [Dictionary] with the following fields:
36+
[code]position[/code]: The position on the intersected triangle.
37+
[code]normal[/code]: The normal of the intersected triangle.
38+
[code]face_index[/code]: The index of the intersected triangle.
39+
Returns an empty [Dictionary] if no intersection happens.
40+
See also [method intersect_segment], which is similar but uses a finite-length segment.
41+
</description>
42+
</method>
43+
<method name="intersect_segment" qualifiers="const">
44+
<return type="Dictionary" />
45+
<param index="0" name="begin" type="Vector3" />
46+
<param index="1" name="end" type="Vector3" />
47+
<description>
48+
Tests for intersection with a segment going from [param begin] to [param end].
49+
If an intersection with a triangle happens returns a [Dictionary] with the following fields:
50+
[code]position[/code]: The position on the intersected triangle.
51+
[code]normal[/code]: The normal of the intersected triangle.
52+
[code]face_index[/code]: The index of the intersected triangle.
53+
Returns an empty [Dictionary] if no intersection happens.
54+
See also [method intersect_ray], which is similar but uses an infinite-length ray.
55+
</description>
56+
</method>
57+
</methods>
1158
</class>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**************************************************************************/
2+
/* test_triangle_mesh.h */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#pragma once
32+
33+
#include "core/math/triangle_mesh.h"
34+
#include "scene/resources/3d/primitive_meshes.h"
35+
36+
#include "tests/test_macros.h"
37+
38+
namespace TestTriangleMesh {
39+
40+
TEST_CASE("[SceneTree][TriangleMesh] BVH creation and intersection") {
41+
Ref<BoxMesh> box_mesh;
42+
box_mesh.instantiate();
43+
44+
const Vector<Face3> faces = box_mesh->get_faces();
45+
46+
Ref<TriangleMesh> triangle_mesh;
47+
triangle_mesh.instantiate();
48+
CHECK(triangle_mesh->create_from_faces(Variant(faces)));
49+
50+
const Vector3 begin = Vector3(0.0, 2.0, 0.0);
51+
const Vector3 end = Vector3(0.0, -2.0, 0.0);
52+
53+
{
54+
Vector3 point;
55+
Vector3 normal;
56+
int32_t *surf_index = nullptr;
57+
int32_t face_index = -1;
58+
const bool has_result = triangle_mesh->intersect_segment(begin, end, point, normal, surf_index, &face_index);
59+
CHECK(has_result);
60+
CHECK(point.is_equal_approx(Vector3(0.0, 0.5, 0.0)));
61+
CHECK(normal.is_equal_approx(Vector3(0.0, 1.0, 0.0)));
62+
CHECK(surf_index == nullptr);
63+
REQUIRE(face_index != -1);
64+
CHECK(face_index == 8);
65+
}
66+
67+
{
68+
Vector3 dir = begin.direction_to(end);
69+
Vector3 point;
70+
Vector3 normal;
71+
int32_t *surf_index = nullptr;
72+
int32_t face_index = -1;
73+
const bool has_result = triangle_mesh->intersect_ray(begin, dir, point, normal, surf_index, &face_index);
74+
CHECK(has_result);
75+
CHECK(point.is_equal_approx(Vector3(0.0, 0.5, 0.0)));
76+
CHECK(normal.is_equal_approx(Vector3(0.0, 1.0, 0.0)));
77+
CHECK(surf_index == nullptr);
78+
REQUIRE(face_index != -1);
79+
CHECK(face_index == 8);
80+
}
81+
}
82+
} // namespace TestTriangleMesh

tests/test_main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@
161161
#endif // ADVANCED_GUI_DISABLED
162162

163163
#ifndef _3D_DISABLED
164+
#include "tests/core/math/test_triangle_mesh.h"
164165
#include "tests/scene/test_arraymesh.h"
165166
#include "tests/scene/test_camera_3d.h"
166167
#include "tests/scene/test_gltf_document.h"

0 commit comments

Comments
 (0)