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