Skip to content

Commit cfa9224

Browse files
committed
Improve collision generation usability in the new 3D scene import workflow.
With this PR it's possible to add a collision during the Mesh import, directly in editor. To generate the shape is possible to chose between the following options: - Decompose Convex: The Mesh is decomposed in one or many Convex Shapes (Using the VHACD library). - Simple Convex: Is generated a convex shape that enclose the entire mesh. - Trimesh: Generate a trimesh shape using the Mesh faces. - Box: Add a primitive box shape, where you can tweak the `size`, `position`, `rotation`. - Sphere: Add a primitive sphere shape, where you can tweak the `radius`, `position`, `rotation`. - Cylinder: Add a primitive cylinder shape, where you can tweak the `height`, `radius`, `position`, `rotation`. - Capsule: Add a primitive capsule shape, where you can tweak the `height`, `radius`, `position`, `rotation`. It's also possible to chose the generated body, so you can create: - Rigid Body - Static Body - Area
1 parent 3127cc4 commit cfa9224

11 files changed

+494
-53
lines changed

editor/import/resource_importer_scene.cpp

+156-33
Large diffs are not rendered by default.

editor/import/resource_importer_scene.h

+198-4
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ class EditorSceneImporter : public RefCounted {
5858
IMPORT_FAIL_ON_MISSING_DEPENDENCIES = 4,
5959
IMPORT_GENERATE_TANGENT_ARRAYS = 8,
6060
IMPORT_USE_NAMED_SKIN_BINDS = 16,
61-
6261
};
6362

6463
virtual uint32_t get_import_flags() const;
@@ -118,9 +117,25 @@ class ResourceImporterScene : public ResourceImporter {
118117
MESH_OVERRIDE_DISABLE,
119118
};
120119

120+
enum BodyType {
121+
BODY_TYPE_STATIC,
122+
BODY_TYPE_DYNAMIC,
123+
BODY_TYPE_AREA
124+
};
125+
126+
enum ShapeType {
127+
SHAPE_TYPE_DECOMPOSE_CONVEX,
128+
SHAPE_TYPE_SIMPLE_CONVEX,
129+
SHAPE_TYPE_TRIMESH,
130+
SHAPE_TYPE_BOX,
131+
SHAPE_TYPE_SPHERE,
132+
SHAPE_TYPE_CYLINDER,
133+
SHAPE_TYPE_CAPSULE,
134+
};
135+
121136
void _replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner);
122137
void _generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<Vector<uint8_t>> &r_lightmap_caches);
123-
void _add_shapes(Node *p_node, const List<Ref<Shape3D>> &p_shapes);
138+
void _add_shapes(Node *p_node, const Vector<Ref<Shape3D>> &p_shapes);
124139

125140
public:
126141
static ResourceImporterScene *get_singleton() { return singleton; }
@@ -152,14 +167,15 @@ class ResourceImporterScene : public ResourceImporter {
152167

153168
void get_internal_import_options(InternalImportCategory p_category, List<ImportOption> *r_options) const;
154169
bool get_internal_option_visibility(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const;
170+
bool get_internal_option_update_view(InternalImportCategory p_category, const String &p_option, const Map<StringName, Variant> &p_options) const;
155171

156172
virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const override;
157173
virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override;
158174
// Import scenes *after* everything else (such as textures).
159175
virtual int get_import_order() const override { return ResourceImporter::IMPORT_ORDER_SCENE; }
160176

161-
Node *_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map);
162-
Node *_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, List<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps);
177+
Node *_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &collision_map);
178+
Node *_post_fix_node(Node *p_node, Node *p_root, Map<Ref<EditorSceneImporterMesh>, Vector<Ref<Shape3D>>> &collision_map, Set<Ref<EditorSceneImporterMesh>> &r_scanned_meshes, const Dictionary &p_node_data, const Dictionary &p_material_data, const Dictionary &p_animation_data, float p_animation_fps);
163179

164180
Ref<Animation> _save_animation_to_file(Ref<Animation> anim, bool p_save_to_file, String p_save_to_path, bool p_keep_custom_tracks);
165181
void _create_clips(AnimationPlayer *anim, const Array &p_clips, bool p_bake_all);
@@ -177,6 +193,12 @@ class ResourceImporterScene : public ResourceImporter {
177193
virtual bool can_import_threaded() const override { return false; }
178194

179195
ResourceImporterScene();
196+
197+
template <class M>
198+
static Vector<Ref<Shape3D>> get_collision_shapes(const Ref<Mesh> &p_mesh, const M &p_options);
199+
200+
template <class M>
201+
static Transform3D get_collision_shapes_transform(const M &p_options);
180202
};
181203

182204
class EditorSceneImporterESCN : public EditorSceneImporter {
@@ -189,4 +211,176 @@ class EditorSceneImporterESCN : public EditorSceneImporter {
189211
virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) override;
190212
};
191213

214+
#include "scene/resources/box_shape_3d.h"
215+
#include "scene/resources/capsule_shape_3d.h"
216+
#include "scene/resources/cylinder_shape_3d.h"
217+
#include "scene/resources/sphere_shape_3d.h"
218+
219+
template <class M>
220+
Vector<Ref<Shape3D>> ResourceImporterScene::get_collision_shapes(const Ref<Mesh> &p_mesh, const M &p_options) {
221+
ShapeType generate_shape_type = SHAPE_TYPE_DECOMPOSE_CONVEX;
222+
if (p_options.has(SNAME("physics/shape_type"))) {
223+
generate_shape_type = (ShapeType)p_options[SNAME("physics/shape_type")].operator int();
224+
}
225+
226+
if (generate_shape_type == SHAPE_TYPE_DECOMPOSE_CONVEX) {
227+
Mesh::ConvexDecompositionSettings decomposition_settings;
228+
bool advanced = false;
229+
if (p_options.has(SNAME("decomposition/advanced"))) {
230+
advanced = p_options[SNAME("decomposition/advanced")];
231+
}
232+
233+
if (advanced) {
234+
if (p_options.has(SNAME("decomposition/max_concavity"))) {
235+
decomposition_settings.max_concavity = p_options[SNAME("decomposition/max_concavity")];
236+
}
237+
238+
if (p_options.has(SNAME("decomposition/symmetry_planes_clipping_bias"))) {
239+
decomposition_settings.symmetry_planes_clipping_bias = p_options[SNAME("decomposition/symmetry_planes_clipping_bias")];
240+
}
241+
242+
if (p_options.has(SNAME("decomposition/revolution_axes_clipping_bias"))) {
243+
decomposition_settings.revolution_axes_clipping_bias = p_options[SNAME("decomposition/revolution_axes_clipping_bias")];
244+
}
245+
246+
if (p_options.has(SNAME("decomposition/min_volume_per_convex_hull"))) {
247+
decomposition_settings.min_volume_per_convex_hull = p_options[SNAME("decomposition/min_volume_per_convex_hull")];
248+
}
249+
250+
if (p_options.has(SNAME("decomposition/resolution"))) {
251+
decomposition_settings.resolution = p_options[SNAME("decomposition/resolution")];
252+
}
253+
254+
if (p_options.has(SNAME("decomposition/max_num_vertices_per_convex_hull"))) {
255+
decomposition_settings.max_num_vertices_per_convex_hull = p_options[SNAME("decomposition/max_num_vertices_per_convex_hull")];
256+
}
257+
258+
if (p_options.has(SNAME("decomposition/plane_downsampling"))) {
259+
decomposition_settings.plane_downsampling = p_options[SNAME("decomposition/plane_downsampling")];
260+
}
261+
262+
if (p_options.has(SNAME("decomposition/convexhull_downsampling"))) {
263+
decomposition_settings.convexhull_downsampling = p_options[SNAME("decomposition/convexhull_downsampling")];
264+
}
265+
266+
if (p_options.has(SNAME("decomposition/normalize_mesh"))) {
267+
decomposition_settings.normalize_mesh = p_options[SNAME("decomposition/normalize_mesh")];
268+
}
269+
270+
if (p_options.has(SNAME("decomposition/mode"))) {
271+
decomposition_settings.mode = (Mesh::ConvexDecompositionSettings::Mode)p_options[SNAME("decomposition/mode")].operator int();
272+
}
273+
274+
if (p_options.has(SNAME("decomposition/convexhull_approximation"))) {
275+
decomposition_settings.convexhull_approximation = p_options[SNAME("decomposition/convexhull_approximation")];
276+
}
277+
278+
if (p_options.has(SNAME("decomposition/max_convex_hulls"))) {
279+
decomposition_settings.max_convex_hulls = p_options[SNAME("decomposition/max_convex_hulls")];
280+
}
281+
282+
if (p_options.has(SNAME("decomposition/project_hull_vertices"))) {
283+
decomposition_settings.project_hull_vertices = p_options[SNAME("decomposition/project_hull_vertices")];
284+
}
285+
} else {
286+
int precision_level = 5;
287+
if (p_options.has(SNAME("decomposition/precision"))) {
288+
precision_level = p_options[SNAME("decomposition/precision")];
289+
}
290+
291+
const real_t precision = real_t(precision_level - 1) / 9.0;
292+
293+
decomposition_settings.max_concavity = Math::lerp(real_t(1.0), real_t(0.001), precision);
294+
decomposition_settings.min_volume_per_convex_hull = Math::lerp(real_t(0.01), real_t(0.0001), precision);
295+
decomposition_settings.resolution = Math::lerp(10'000, 100'000, precision);
296+
decomposition_settings.max_num_vertices_per_convex_hull = Math::lerp(32, 64, precision);
297+
decomposition_settings.plane_downsampling = Math::lerp(3, 16, precision);
298+
decomposition_settings.convexhull_downsampling = Math::lerp(3, 16, precision);
299+
decomposition_settings.max_convex_hulls = Math::lerp(1, 32, precision);
300+
}
301+
302+
return p_mesh->convex_decompose(decomposition_settings);
303+
} else if (generate_shape_type == SHAPE_TYPE_SIMPLE_CONVEX) {
304+
Vector<Ref<Shape3D>> shapes;
305+
shapes.push_back(p_mesh->create_convex_shape(true, /*Passing false, otherwise VHACD will be used to simplify (Decompose) the Mesh.*/ false));
306+
return shapes;
307+
} else if (generate_shape_type == SHAPE_TYPE_TRIMESH) {
308+
Vector<Ref<Shape3D>> shapes;
309+
shapes.push_back(p_mesh->create_trimesh_shape());
310+
return shapes;
311+
} else if (generate_shape_type == SHAPE_TYPE_BOX) {
312+
Ref<BoxShape3D> box;
313+
box.instantiate();
314+
if (p_options.has(SNAME("primitive/size"))) {
315+
box->set_size(p_options[SNAME("primitive/size")]);
316+
}
317+
318+
Vector<Ref<Shape3D>> shapes;
319+
shapes.push_back(box);
320+
return shapes;
321+
322+
} else if (generate_shape_type == SHAPE_TYPE_SPHERE) {
323+
Ref<SphereShape3D> sphere;
324+
sphere.instantiate();
325+
if (p_options.has(SNAME("primitive/radius"))) {
326+
sphere->set_radius(p_options[SNAME("primitive/radius")]);
327+
}
328+
329+
Vector<Ref<Shape3D>> shapes;
330+
shapes.push_back(sphere);
331+
return shapes;
332+
} else if (generate_shape_type == SHAPE_TYPE_CYLINDER) {
333+
Ref<CylinderShape3D> cylinder;
334+
cylinder.instantiate();
335+
if (p_options.has(SNAME("primitive/height"))) {
336+
cylinder->set_height(p_options[SNAME("primitive/height")]);
337+
}
338+
if (p_options.has(SNAME("primitive/radius"))) {
339+
cylinder->set_radius(p_options[SNAME("primitive/radius")]);
340+
}
341+
342+
Vector<Ref<Shape3D>> shapes;
343+
shapes.push_back(cylinder);
344+
return shapes;
345+
} else if (generate_shape_type == SHAPE_TYPE_CAPSULE) {
346+
Ref<CapsuleShape3D> capsule;
347+
capsule.instantiate();
348+
if (p_options.has(SNAME("primitive/height"))) {
349+
capsule->set_height(p_options[SNAME("primitive/height")]);
350+
}
351+
if (p_options.has(SNAME("primitive/radius"))) {
352+
capsule->set_radius(p_options[SNAME("primitive/radius")]);
353+
}
354+
355+
Vector<Ref<Shape3D>> shapes;
356+
shapes.push_back(capsule);
357+
return shapes;
358+
}
359+
return Vector<Ref<Shape3D>>();
360+
}
361+
362+
template <class M>
363+
Transform3D ResourceImporterScene::get_collision_shapes_transform(const M &p_options) {
364+
Transform3D transform;
365+
366+
ShapeType generate_shape_type = SHAPE_TYPE_DECOMPOSE_CONVEX;
367+
if (p_options.has(SNAME("physics/shape_type"))) {
368+
generate_shape_type = (ShapeType)p_options[SNAME("physics/shape_type")].operator int();
369+
}
370+
371+
if (generate_shape_type == SHAPE_TYPE_BOX ||
372+
generate_shape_type == SHAPE_TYPE_SPHERE ||
373+
generate_shape_type == SHAPE_TYPE_CYLINDER ||
374+
generate_shape_type == SHAPE_TYPE_CAPSULE) {
375+
if (p_options.has(SNAME("primitive/position"))) {
376+
transform.origin = p_options[SNAME("primitive/position")];
377+
}
378+
379+
if (p_options.has(SNAME("primitive/rotation"))) {
380+
transform.basis.set_euler((p_options[SNAME("primitive/rotation")].operator Vector3() / 180.0) * Math_PI);
381+
}
382+
}
383+
return transform;
384+
}
385+
192386
#endif // RESOURCEIMPORTERSCENE_H

editor/import/scene_import_settings.cpp

+74
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ class SceneImportSettingsData : public Object {
5353
}
5454

5555
current[p_name] = p_value;
56+
57+
if (ResourceImporterScene::get_singleton()->get_internal_option_update_view(category, p_name, current)) {
58+
SceneImportSettings::get_singleton()->update_view();
59+
}
60+
5661
return true;
5762
}
5863
return false;
@@ -317,6 +322,13 @@ void SceneImportSettings::_fill_scene(Node *p_node, TreeItem *p_parent_item) {
317322
if (mesh_node && mesh_node->get_mesh().is_valid()) {
318323
_fill_mesh(scene_tree, mesh_node->get_mesh(), item);
319324

325+
// Add the collider view.
326+
MeshInstance3D *collider_view = memnew(MeshInstance3D);
327+
collider_view->set_name("collider_view");
328+
collider_view->set_visible(false);
329+
mesh_node->add_child(collider_view);
330+
collider_view->set_owner(mesh_node);
331+
320332
Transform3D accum_xform;
321333
Node3D *base = mesh_node;
322334
while (base) {
@@ -346,6 +358,54 @@ void SceneImportSettings::_update_scene() {
346358
_fill_scene(scene, nullptr);
347359
}
348360

361+
void SceneImportSettings::_update_view_gizmos() {
362+
for (const KeyValue<String, NodeData> &e : node_map) {
363+
bool generate_collider = false;
364+
if (e.value.settings.has(SNAME("generate/physics"))) {
365+
generate_collider = e.value.settings[SNAME("generate/physics")];
366+
}
367+
368+
MeshInstance3D *mesh_node = Object::cast_to<MeshInstance3D>(e.value.node);
369+
if (mesh_node == nullptr || mesh_node->get_mesh().is_null()) {
370+
// Nothing to do
371+
continue;
372+
}
373+
374+
MeshInstance3D *collider_view = static_cast<MeshInstance3D *>(mesh_node->find_node("collider_view"));
375+
CRASH_COND_MSG(collider_view == nullptr, "This is unreachable, since the collider view is always created even when the collision is not used! If this is triggered there is a bug on the function `_fill_scene`.");
376+
377+
collider_view->set_visible(generate_collider);
378+
if (generate_collider) {
379+
// This collider_view doesn't have a mesh so we need to generate a new one.
380+
381+
// Generate the mesh collider.
382+
Vector<Ref<Shape3D>> shapes = ResourceImporterScene::get_collision_shapes(mesh_node->get_mesh(), e.value.settings);
383+
const Transform3D transform = ResourceImporterScene::get_collision_shapes_transform(e.value.settings);
384+
385+
Ref<ArrayMesh> collider_view_mesh;
386+
collider_view_mesh.instantiate();
387+
for (Ref<Shape3D> shape : shapes) {
388+
Ref<ArrayMesh> debug_shape_mesh;
389+
if (shape.is_valid()) {
390+
debug_shape_mesh = shape->get_debug_mesh();
391+
}
392+
if (debug_shape_mesh.is_valid()) {
393+
collider_view_mesh->add_surface_from_arrays(
394+
debug_shape_mesh->surface_get_primitive_type(0),
395+
debug_shape_mesh->surface_get_arrays(0));
396+
397+
collider_view_mesh->surface_set_material(
398+
collider_view_mesh->get_surface_count() - 1,
399+
collider_mat);
400+
}
401+
}
402+
403+
collider_view->set_mesh(collider_view_mesh);
404+
collider_view->set_transform(transform);
405+
}
406+
}
407+
}
408+
349409
void SceneImportSettings::_update_camera() {
350410
AABB camera_aabb;
351411

@@ -404,11 +464,16 @@ void SceneImportSettings::_load_default_subresource_settings(Map<StringName, Var
404464
}
405465
}
406466

467+
void SceneImportSettings::update_view() {
468+
_update_view_gizmos();
469+
}
470+
407471
void SceneImportSettings::open_settings(const String &p_path) {
408472
if (scene) {
409473
memdelete(scene);
410474
scene = nullptr;
411475
}
476+
scene_import_settings_data->settings = nullptr;
412477
scene = ResourceImporterScene::get_singleton()->pre_import(p_path);
413478
if (scene == nullptr) {
414479
EditorNode::get_singleton()->show_warning(TTR("Error opening scene"));
@@ -463,6 +528,7 @@ void SceneImportSettings::open_settings(const String &p_path) {
463528
}
464529

465530
popup_centered_ratio();
531+
_update_view_gizmos();
466532
_update_camera();
467533

468534
set_title(vformat(TTR("Advanced Import Settings for '%s'"), base_path.get_file()));
@@ -629,6 +695,7 @@ void SceneImportSettings::_material_tree_selected() {
629695

630696
_select(material_tree, type, import_id);
631697
}
698+
632699
void SceneImportSettings::_mesh_tree_selected() {
633700
if (selecting) {
634701
return;
@@ -640,6 +707,7 @@ void SceneImportSettings::_mesh_tree_selected() {
640707

641708
_select(mesh_tree, type, import_id);
642709
}
710+
643711
void SceneImportSettings::_scene_tree_selected() {
644712
if (selecting) {
645713
return;
@@ -1144,6 +1212,12 @@ SceneImportSettings::SceneImportSettings() {
11441212
material_preview.instantiate();
11451213
}
11461214

1215+
{
1216+
collider_mat.instantiate();
1217+
collider_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
1218+
collider_mat->set_albedo(Color(0.5, 0.5, 1.0));
1219+
}
1220+
11471221
inspector = memnew(EditorInspector);
11481222
inspector->set_custom_minimum_size(Size2(300 * EDSCALE, 0));
11491223

0 commit comments

Comments
 (0)