Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/csg/editor/csg_gizmos.cpp
20943 views
1
/**************************************************************************/
2
/* csg_gizmos.cpp */
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
#include "csg_gizmos.h"
32
33
#include "core/math/geometry_3d.h"
34
#include "editor/editor_node.h"
35
#include "editor/editor_undo_redo_manager.h"
36
#include "editor/scene/3d/gizmos/gizmo_3d_helper.h"
37
#include "editor/scene/3d/node_3d_editor_plugin.h"
38
#include "editor/settings/editor_settings.h"
39
#include "scene/3d/camera_3d.h"
40
#include "scene/3d/mesh_instance_3d.h"
41
#include "scene/3d/physics/collision_shape_3d.h"
42
#include "scene/gui/dialogs.h"
43
#include "scene/gui/menu_button.h"
44
45
void CSGShapeEditor::_node_removed(Node *p_node) {
46
if (p_node == node) {
47
node = nullptr;
48
options->hide();
49
}
50
}
51
52
void CSGShapeEditor::edit(CSGShape3D *p_csg_shape) {
53
node = p_csg_shape;
54
if (node) {
55
options->show();
56
} else {
57
options->hide();
58
}
59
}
60
61
void CSGShapeEditor::_notification(int p_what) {
62
switch (p_what) {
63
case NOTIFICATION_THEME_CHANGED: {
64
options->set_button_icon(get_editor_theme_icon(SNAME("CSGCombiner3D")));
65
} break;
66
}
67
}
68
69
void CSGShapeEditor::_menu_option(int p_option) {
70
Array meshes = node->get_meshes();
71
if (meshes.is_empty()) {
72
err_dialog->set_text(TTR("CSG operation returned an empty array."));
73
err_dialog->popup_centered();
74
return;
75
}
76
77
switch (p_option) {
78
case MENU_OPTION_BAKE_MESH_INSTANCE: {
79
_create_baked_mesh_instance();
80
} break;
81
case MENU_OPTION_BAKE_COLLISION_SHAPE: {
82
_create_baked_collision_shape();
83
} break;
84
}
85
}
86
87
void CSGShapeEditor::_create_baked_mesh_instance() {
88
if (node == get_tree()->get_edited_scene_root()) {
89
err_dialog->set_text(TTR("Can not add a baked mesh as sibling for the scene root.\nMove the CSG root node below a parent node."));
90
err_dialog->popup_centered();
91
return;
92
}
93
94
Ref<ArrayMesh> mesh = node->bake_static_mesh();
95
if (mesh.is_null()) {
96
err_dialog->set_text(TTR("CSG operation returned an empty mesh."));
97
err_dialog->popup_centered();
98
return;
99
}
100
101
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
102
ur->create_action(TTR("Create baked CSGShape3D Mesh Instance"));
103
104
Node *owner = get_tree()->get_edited_scene_root();
105
106
MeshInstance3D *mi = memnew(MeshInstance3D);
107
mi->set_mesh(mesh);
108
mi->set_name("CSGBakedMeshInstance3D");
109
mi->set_transform(node->get_transform());
110
ur->add_do_method(node, "add_sibling", mi, true);
111
ur->add_do_method(mi, "set_owner", owner);
112
ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), mi);
113
114
ur->add_do_reference(mi);
115
ur->add_undo_method(node->get_parent(), "remove_child", mi);
116
117
ur->commit_action();
118
}
119
120
void CSGShapeEditor::_create_baked_collision_shape() {
121
if (node == get_tree()->get_edited_scene_root()) {
122
err_dialog->set_text(TTR("Can not add a baked collision shape as sibling for the scene root.\nMove the CSG root node below a parent node."));
123
err_dialog->popup_centered();
124
return;
125
}
126
127
Ref<Shape3D> shape = node->bake_collision_shape();
128
if (shape.is_null()) {
129
err_dialog->set_text(TTR("CSG operation returned an empty shape."));
130
err_dialog->popup_centered();
131
return;
132
}
133
134
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
135
ur->create_action(TTR("Create baked CSGShape3D Collision Shape"));
136
137
Node *owner = get_tree()->get_edited_scene_root();
138
139
CollisionShape3D *cshape = memnew(CollisionShape3D);
140
cshape->set_shape(shape);
141
cshape->set_name("CSGBakedCollisionShape3D");
142
cshape->set_transform(node->get_transform());
143
ur->add_do_method(node, "add_sibling", cshape, true);
144
ur->add_do_method(cshape, "set_owner", owner);
145
ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), cshape);
146
147
ur->add_do_reference(cshape);
148
ur->add_undo_method(node->get_parent(), "remove_child", cshape);
149
150
ur->commit_action();
151
}
152
153
CSGShapeEditor::CSGShapeEditor() {
154
options = memnew(MenuButton);
155
options->hide();
156
options->set_text(TTR("CSG"));
157
options->set_switch_on_hover(true);
158
options->set_flat(false);
159
options->set_theme_type_variation("FlatMenuButton");
160
Node3DEditor::get_singleton()->add_control_to_menu_panel(options);
161
162
options->get_popup()->add_item(TTR("Bake Mesh Instance"), MENU_OPTION_BAKE_MESH_INSTANCE);
163
options->get_popup()->add_item(TTR("Bake Collision Shape"), MENU_OPTION_BAKE_COLLISION_SHAPE);
164
165
options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CSGShapeEditor::_menu_option));
166
167
err_dialog = memnew(AcceptDialog);
168
add_child(err_dialog);
169
}
170
171
///////////
172
173
CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() {
174
helper.instantiate();
175
176
Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/csg");
177
create_material("shape_union_material", gizmo_color);
178
create_material("shape_union_solid_material", gizmo_color);
179
gizmo_color.invert();
180
create_material("shape_subtraction_material", gizmo_color);
181
create_material("shape_subtraction_solid_material", gizmo_color);
182
gizmo_color.r = 0.95;
183
gizmo_color.g = 0.95;
184
gizmo_color.b = 0.95;
185
create_material("shape_intersection_material", gizmo_color);
186
create_material("shape_intersection_solid_material", gizmo_color);
187
188
create_handle_material("handles");
189
}
190
191
String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
192
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
193
194
if (Object::cast_to<CSGSphere3D>(cs)) {
195
return "Radius";
196
}
197
198
if (Object::cast_to<CSGBox3D>(cs)) {
199
return helper->box_get_handle_name(p_id);
200
}
201
202
if (Object::cast_to<CSGCylinder3D>(cs)) {
203
return p_id == 0 ? "Radius" : "Height";
204
}
205
206
if (Object::cast_to<CSGTorus3D>(cs)) {
207
return p_id == 0 ? "InnerRadius" : "OuterRadius";
208
}
209
210
return "";
211
}
212
213
Variant CSGShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
214
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
215
216
if (Object::cast_to<CSGSphere3D>(cs)) {
217
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
218
return s->get_radius();
219
}
220
221
if (Object::cast_to<CSGBox3D>(cs)) {
222
CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
223
return s->get_size();
224
}
225
226
if (Object::cast_to<CSGCylinder3D>(cs)) {
227
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
228
return Vector2(s->get_radius(), s->get_height());
229
}
230
231
if (Object::cast_to<CSGTorus3D>(cs)) {
232
CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
233
return p_id == 0 ? s->get_inner_radius() : s->get_outer_radius();
234
}
235
236
return Variant();
237
}
238
239
void CSGShape3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) {
240
helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform());
241
}
242
243
void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
244
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
245
246
Vector3 sg[2];
247
helper->get_segment(p_camera, p_point, sg);
248
249
if (Object::cast_to<CSGSphere3D>(cs)) {
250
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
251
252
Vector3 ra, rb;
253
Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb);
254
float d = ra.x;
255
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
256
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
257
}
258
259
if (d < 0.001) {
260
d = 0.001;
261
}
262
263
s->set_radius(d);
264
}
265
266
if (Object::cast_to<CSGBox3D>(cs)) {
267
CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
268
Vector3 size = s->get_size();
269
Vector3 position;
270
helper->box_set_handle(sg, p_id, size, position);
271
s->set_size(size);
272
s->set_global_position(position);
273
}
274
275
if (Object::cast_to<CSGCylinder3D>(cs)) {
276
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
277
278
real_t height = s->get_height();
279
real_t radius = s->get_radius();
280
Vector3 position;
281
helper->cylinder_set_handle(sg, p_id, height, radius, position);
282
s->set_height(height);
283
s->set_radius(radius);
284
s->set_global_position(position);
285
}
286
287
if (Object::cast_to<CSGTorus3D>(cs)) {
288
CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
289
290
Vector3 axis;
291
axis[0] = 1.0;
292
Vector3 ra, rb;
293
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
294
float d = axis.dot(ra);
295
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
296
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
297
}
298
299
if (d < 0.001) {
300
d = 0.001;
301
}
302
303
if (p_id == 0) {
304
s->set_inner_radius(d);
305
} else if (p_id == 1) {
306
s->set_outer_radius(d);
307
}
308
}
309
}
310
311
void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
312
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
313
314
if (Object::cast_to<CSGSphere3D>(cs)) {
315
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
316
if (p_cancel) {
317
s->set_radius(p_restore);
318
return;
319
}
320
321
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
322
ur->create_action(TTR("Change Sphere Shape Radius"));
323
ur->add_do_method(s, "set_radius", s->get_radius());
324
ur->add_undo_method(s, "set_radius", p_restore);
325
ur->commit_action();
326
}
327
328
if (Object::cast_to<CSGBox3D>(cs)) {
329
helper->box_commit_handle(TTR("Change CSG Box Size"), p_cancel, cs);
330
}
331
332
if (Object::cast_to<CSGCylinder3D>(cs)) {
333
helper->cylinder_commit_handle(p_id, TTR("Change CSG Cylinder Radius"), TTR("Change CSG Cylinder Height"), p_cancel, cs);
334
}
335
336
if (Object::cast_to<CSGTorus3D>(cs)) {
337
CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
338
if (p_cancel) {
339
if (p_id == 0) {
340
s->set_inner_radius(p_restore);
341
} else {
342
s->set_outer_radius(p_restore);
343
}
344
return;
345
}
346
347
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
348
if (p_id == 0) {
349
ur->create_action(TTR("Change Torus Inner Radius"));
350
ur->add_do_method(s, "set_inner_radius", s->get_inner_radius());
351
ur->add_undo_method(s, "set_inner_radius", p_restore);
352
} else {
353
ur->create_action(TTR("Change Torus Outer Radius"));
354
ur->add_do_method(s, "set_outer_radius", s->get_outer_radius());
355
ur->add_undo_method(s, "set_outer_radius", p_restore);
356
}
357
358
ur->commit_action();
359
}
360
}
361
362
bool CSGShape3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
363
return Object::cast_to<CSGSphere3D>(p_spatial) || Object::cast_to<CSGBox3D>(p_spatial) || Object::cast_to<CSGCylinder3D>(p_spatial) || Object::cast_to<CSGTorus3D>(p_spatial) || Object::cast_to<CSGMesh3D>(p_spatial) || Object::cast_to<CSGPolygon3D>(p_spatial);
364
}
365
366
String CSGShape3DGizmoPlugin::get_gizmo_name() const {
367
return "CSGShape3D";
368
}
369
370
int CSGShape3DGizmoPlugin::get_priority() const {
371
return -1;
372
}
373
374
bool CSGShape3DGizmoPlugin::is_selectable_when_hidden() const {
375
return true;
376
}
377
378
void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
379
p_gizmo->clear();
380
381
CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d());
382
383
Vector<Vector3> faces = cs->get_brush_faces();
384
385
if (faces.is_empty()) {
386
return;
387
}
388
389
Vector<Vector3> lines;
390
lines.resize(faces.size() * 2);
391
{
392
const Vector3 *r = faces.ptr();
393
394
for (int i = 0; i < lines.size(); i += 6) {
395
int f = i / 6;
396
for (int j = 0; j < 3; j++) {
397
int j_n = (j + 1) % 3;
398
lines.write[i + j * 2 + 0] = r[f * 3 + j];
399
lines.write[i + j * 2 + 1] = r[f * 3 + j_n];
400
}
401
}
402
}
403
404
Ref<Material> material;
405
switch (cs->get_operation()) {
406
case CSGShape3D::OPERATION_UNION:
407
material = get_material("shape_union_material", p_gizmo);
408
break;
409
case CSGShape3D::OPERATION_INTERSECTION:
410
material = get_material("shape_intersection_material", p_gizmo);
411
break;
412
case CSGShape3D::OPERATION_SUBTRACTION:
413
material = get_material("shape_subtraction_material", p_gizmo);
414
break;
415
}
416
417
Ref<Material> handles_material = get_material("handles");
418
419
p_gizmo->add_lines(lines, material);
420
p_gizmo->add_collision_segments(lines);
421
422
if (cs->is_root_shape()) {
423
Array csg_meshes = cs->get_meshes();
424
if (csg_meshes.size() == 2) {
425
Ref<Mesh> csg_mesh = csg_meshes[1];
426
if (csg_mesh.is_valid()) {
427
p_gizmo->add_collision_triangles(csg_mesh->generate_triangle_mesh());
428
}
429
}
430
}
431
432
if (p_gizmo->is_selected()) {
433
// Draw a translucent representation of the CSG node
434
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
435
Array array;
436
array.resize(Mesh::ARRAY_MAX);
437
array[Mesh::ARRAY_VERTEX] = faces;
438
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array);
439
440
Ref<Material> solid_material;
441
switch (cs->get_operation()) {
442
case CSGShape3D::OPERATION_UNION:
443
solid_material = get_material("shape_union_solid_material", p_gizmo);
444
break;
445
case CSGShape3D::OPERATION_INTERSECTION:
446
solid_material = get_material("shape_intersection_solid_material", p_gizmo);
447
break;
448
case CSGShape3D::OPERATION_SUBTRACTION:
449
solid_material = get_material("shape_subtraction_solid_material", p_gizmo);
450
break;
451
}
452
453
p_gizmo->add_mesh(mesh, solid_material);
454
}
455
456
if (Object::cast_to<CSGSphere3D>(cs)) {
457
CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs);
458
459
float r = s->get_radius();
460
Vector<Vector3> handles;
461
handles.push_back(Vector3(r, 0, 0));
462
p_gizmo->add_handles(handles, handles_material);
463
}
464
465
if (Object::cast_to<CSGBox3D>(cs)) {
466
CSGBox3D *s = Object::cast_to<CSGBox3D>(cs);
467
Vector<Vector3> handles = helper->box_get_handles(s->get_size());
468
p_gizmo->add_handles(handles, handles_material);
469
}
470
471
if (Object::cast_to<CSGCylinder3D>(cs)) {
472
CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs);
473
474
Vector<Vector3> handles = helper->cylinder_get_handles(s->get_height(), s->get_radius());
475
p_gizmo->add_handles(handles, handles_material);
476
}
477
478
if (Object::cast_to<CSGTorus3D>(cs)) {
479
CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs);
480
481
Vector<Vector3> handles;
482
handles.push_back(Vector3(s->get_inner_radius(), 0, 0));
483
handles.push_back(Vector3(s->get_outer_radius(), 0, 0));
484
p_gizmo->add_handles(handles, handles_material);
485
}
486
}
487
488
void EditorPluginCSG::edit(Object *p_object) {
489
CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object);
490
if (csg_shape && csg_shape->is_root_shape()) {
491
csg_shape_editor->edit(csg_shape);
492
} else {
493
csg_shape_editor->edit(nullptr);
494
}
495
}
496
497
bool EditorPluginCSG::handles(Object *p_object) const {
498
CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object);
499
return csg_shape && csg_shape->is_root_shape();
500
}
501
502
EditorPluginCSG::EditorPluginCSG() {
503
Ref<CSGShape3DGizmoPlugin> gizmo_plugin = Ref<CSGShape3DGizmoPlugin>(memnew(CSGShape3DGizmoPlugin));
504
Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
505
506
csg_shape_editor = memnew(CSGShapeEditor);
507
EditorNode::get_singleton()->get_gui_base()->add_child(csg_shape_editor);
508
}
509
510