Expose TriangleMesh api functions wrapped for scripting
Adds script wrapped TriangleMesh api functions to create and query the triangle BVH tree.
This commit is contained in:
@ -182,7 +182,11 @@ void TriangleMesh::create(const Vector<Vector3> &p_faces, const Vector<int32_t>
|
|||||||
valid = true;
|
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);
|
uint32_t *stack = (uint32_t *)alloca(sizeof(int) * max_depth);
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@ -234,6 +238,9 @@ bool TriangleMesh::intersect_segment(const Vector3 &p_begin, const Vector3 &p_en
|
|||||||
if (r_surf_index) {
|
if (r_surf_index) {
|
||||||
*r_surf_index = s.surface_index;
|
*r_surf_index = s.surface_index;
|
||||||
}
|
}
|
||||||
|
if (r_face_index) {
|
||||||
|
*r_face_index = b.face_index;
|
||||||
|
}
|
||||||
inters = true;
|
inters = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -283,7 +290,11 @@ bool TriangleMesh::intersect_segment(const Vector3 &p_begin, const Vector3 &p_en
|
|||||||
return inters;
|
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);
|
uint32_t *stack = (uint32_t *)alloca(sizeof(int) * max_depth);
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@ -335,6 +346,9 @@ bool TriangleMesh::intersect_ray(const Vector3 &p_begin, const Vector3 &p_dir, V
|
|||||||
if (r_surf_index) {
|
if (r_surf_index) {
|
||||||
*r_surf_index = s.surface_index;
|
*r_surf_index = s.surface_index;
|
||||||
}
|
}
|
||||||
|
if (r_face_index) {
|
||||||
|
*r_face_index = b.face_index;
|
||||||
|
}
|
||||||
inters = true;
|
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 {
|
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);
|
uint32_t *stack = (uint32_t *)alloca(sizeof(int) * max_depth);
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@ -503,6 +521,85 @@ Vector<Face3> TriangleMesh::get_faces() const {
|
|||||||
return faces;
|
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() {
|
TriangleMesh::TriangleMesh() {
|
||||||
valid = false;
|
valid = false;
|
||||||
max_depth = 0;
|
max_depth = 0;
|
||||||
|
|||||||
@ -40,9 +40,12 @@ public:
|
|||||||
struct Triangle {
|
struct Triangle {
|
||||||
Vector3 normal;
|
Vector3 normal;
|
||||||
int indices[3];
|
int indices[3];
|
||||||
int32_t surface_index;
|
int32_t surface_index = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void _bind_methods();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Vector<Triangle> triangles;
|
Vector<Triangle> triangles;
|
||||||
Vector<Vector3> vertices;
|
Vector<Vector3> vertices;
|
||||||
@ -50,10 +53,10 @@ private:
|
|||||||
struct BVH {
|
struct BVH {
|
||||||
AABB aabb;
|
AABB aabb;
|
||||||
Vector3 center; //used for sorting
|
Vector3 center; //used for sorting
|
||||||
int left;
|
int left = -1;
|
||||||
int right;
|
int right = -1;
|
||||||
|
|
||||||
int face_index;
|
int32_t face_index = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BVHCmpX {
|
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);
|
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;
|
Vector<BVH> bvh;
|
||||||
int max_depth;
|
int max_depth = 0;
|
||||||
bool valid;
|
bool valid = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool is_valid() const;
|
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_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) 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;
|
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;
|
Vector<Face3> get_faces() const;
|
||||||
|
|
||||||
@ -91,5 +94,13 @@ public:
|
|||||||
void get_indices(Vector<int> *r_triangles_indices) const;
|
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>());
|
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();
|
TriangleMesh();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,58 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?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">
|
<class name="TriangleMesh" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
|
||||||
<brief_description>
|
<brief_description>
|
||||||
Internal mesh type.
|
Triangle geometry for efficient, physicsless intersection queries.
|
||||||
</brief_description>
|
</brief_description>
|
||||||
<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>
|
</description>
|
||||||
<tutorials>
|
<tutorials>
|
||||||
</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>
|
</class>
|
||||||
|
|||||||
82
tests/core/math/test_triangle_mesh.h
Normal file
82
tests/core/math/test_triangle_mesh.h
Normal file
@ -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
|
||||||
@ -161,6 +161,7 @@
|
|||||||
#endif // ADVANCED_GUI_DISABLED
|
#endif // ADVANCED_GUI_DISABLED
|
||||||
|
|
||||||
#ifndef _3D_DISABLED
|
#ifndef _3D_DISABLED
|
||||||
|
#include "tests/core/math/test_triangle_mesh.h"
|
||||||
#include "tests/scene/test_arraymesh.h"
|
#include "tests/scene/test_arraymesh.h"
|
||||||
#include "tests/scene/test_camera_3d.h"
|
#include "tests/scene/test_camera_3d.h"
|
||||||
#include "tests/scene/test_gltf_document.h"
|
#include "tests/scene/test_gltf_document.h"
|
||||||
|
|||||||
Reference in New Issue
Block a user