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 | #ifdef TOOLS_ENABLED |
34 | |
35 | #include "editor/editor_node.h" |
36 | #include "editor/editor_settings.h" |
37 | #include "editor/editor_undo_redo_manager.h" |
38 | #include "editor/plugins/gizmos/gizmo_3d_helper.h" |
39 | #include "editor/plugins/node_3d_editor_plugin.h" |
40 | #include "scene/3d/camera_3d.h" |
41 | |
42 | /////////// |
43 | |
44 | CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() { |
45 | helper.instantiate(); |
46 | |
47 | Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/csg" , Color(0.0, 0.4, 1, 0.15)); |
48 | create_material("shape_union_material" , gizmo_color); |
49 | create_material("shape_union_solid_material" , gizmo_color); |
50 | gizmo_color.invert(); |
51 | create_material("shape_subtraction_material" , gizmo_color); |
52 | create_material("shape_subtraction_solid_material" , gizmo_color); |
53 | gizmo_color.r = 0.95; |
54 | gizmo_color.g = 0.95; |
55 | gizmo_color.b = 0.95; |
56 | create_material("shape_intersection_material" , gizmo_color); |
57 | create_material("shape_intersection_solid_material" , gizmo_color); |
58 | |
59 | create_handle_material("handles" ); |
60 | } |
61 | |
62 | CSGShape3DGizmoPlugin::~CSGShape3DGizmoPlugin() { |
63 | } |
64 | |
65 | String CSGShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { |
66 | CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d()); |
67 | |
68 | if (Object::cast_to<CSGSphere3D>(cs)) { |
69 | return "Radius" ; |
70 | } |
71 | |
72 | if (Object::cast_to<CSGBox3D>(cs)) { |
73 | return helper->box_get_handle_name(p_id); |
74 | } |
75 | |
76 | if (Object::cast_to<CSGCylinder3D>(cs)) { |
77 | return p_id == 0 ? "Radius" : "Height" ; |
78 | } |
79 | |
80 | if (Object::cast_to<CSGTorus3D>(cs)) { |
81 | return p_id == 0 ? "InnerRadius" : "OuterRadius" ; |
82 | } |
83 | |
84 | return "" ; |
85 | } |
86 | |
87 | Variant CSGShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { |
88 | CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d()); |
89 | |
90 | if (Object::cast_to<CSGSphere3D>(cs)) { |
91 | CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs); |
92 | return s->get_radius(); |
93 | } |
94 | |
95 | if (Object::cast_to<CSGBox3D>(cs)) { |
96 | CSGBox3D *s = Object::cast_to<CSGBox3D>(cs); |
97 | return s->get_size(); |
98 | } |
99 | |
100 | if (Object::cast_to<CSGCylinder3D>(cs)) { |
101 | CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs); |
102 | return p_id == 0 ? s->get_radius() : s->get_height(); |
103 | } |
104 | |
105 | if (Object::cast_to<CSGTorus3D>(cs)) { |
106 | CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs); |
107 | return p_id == 0 ? s->get_inner_radius() : s->get_outer_radius(); |
108 | } |
109 | |
110 | return Variant(); |
111 | } |
112 | |
113 | void CSGShape3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) { |
114 | helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform()); |
115 | } |
116 | |
117 | void CSGShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { |
118 | CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d()); |
119 | |
120 | Vector3 sg[2]; |
121 | helper->get_segment(p_camera, p_point, sg); |
122 | |
123 | if (Object::cast_to<CSGSphere3D>(cs)) { |
124 | CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs); |
125 | |
126 | Vector3 ra, rb; |
127 | Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb); |
128 | float d = ra.x; |
129 | if (Node3DEditor::get_singleton()->is_snap_enabled()) { |
130 | d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); |
131 | } |
132 | |
133 | if (d < 0.001) { |
134 | d = 0.001; |
135 | } |
136 | |
137 | s->set_radius(d); |
138 | } |
139 | |
140 | if (Object::cast_to<CSGBox3D>(cs)) { |
141 | CSGBox3D *s = Object::cast_to<CSGBox3D>(cs); |
142 | Vector3 size = s->get_size(); |
143 | Vector3 position; |
144 | helper->box_set_handle(sg, p_id, size, position); |
145 | s->set_size(size); |
146 | s->set_global_position(position); |
147 | } |
148 | |
149 | if (Object::cast_to<CSGCylinder3D>(cs)) { |
150 | CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs); |
151 | |
152 | Vector3 axis; |
153 | axis[p_id == 0 ? 0 : 1] = 1.0; |
154 | Vector3 ra, rb; |
155 | Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); |
156 | float d = axis.dot(ra); |
157 | if (Node3DEditor::get_singleton()->is_snap_enabled()) { |
158 | d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); |
159 | } |
160 | |
161 | if (d < 0.001) { |
162 | d = 0.001; |
163 | } |
164 | |
165 | if (p_id == 0) { |
166 | s->set_radius(d); |
167 | } else if (p_id == 1) { |
168 | s->set_height(d * 2.0); |
169 | } |
170 | } |
171 | |
172 | if (Object::cast_to<CSGTorus3D>(cs)) { |
173 | CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs); |
174 | |
175 | Vector3 axis; |
176 | axis[0] = 1.0; |
177 | Vector3 ra, rb; |
178 | Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); |
179 | float d = axis.dot(ra); |
180 | if (Node3DEditor::get_singleton()->is_snap_enabled()) { |
181 | d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); |
182 | } |
183 | |
184 | if (d < 0.001) { |
185 | d = 0.001; |
186 | } |
187 | |
188 | if (p_id == 0) { |
189 | s->set_inner_radius(d); |
190 | } else if (p_id == 1) { |
191 | s->set_outer_radius(d); |
192 | } |
193 | } |
194 | } |
195 | |
196 | void CSGShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { |
197 | CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d()); |
198 | |
199 | if (Object::cast_to<CSGSphere3D>(cs)) { |
200 | CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs); |
201 | if (p_cancel) { |
202 | s->set_radius(p_restore); |
203 | return; |
204 | } |
205 | |
206 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
207 | ur->create_action(TTR("Change Sphere Shape Radius" )); |
208 | ur->add_do_method(s, "set_radius" , s->get_radius()); |
209 | ur->add_undo_method(s, "set_radius" , p_restore); |
210 | ur->commit_action(); |
211 | } |
212 | |
213 | if (Object::cast_to<CSGBox3D>(cs)) { |
214 | helper->box_commit_handle(TTR("Change Box Shape Size" ), p_cancel, cs); |
215 | } |
216 | |
217 | if (Object::cast_to<CSGCylinder3D>(cs)) { |
218 | CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs); |
219 | if (p_cancel) { |
220 | if (p_id == 0) { |
221 | s->set_radius(p_restore); |
222 | } else { |
223 | s->set_height(p_restore); |
224 | } |
225 | return; |
226 | } |
227 | |
228 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
229 | if (p_id == 0) { |
230 | ur->create_action(TTR("Change Cylinder Radius" )); |
231 | ur->add_do_method(s, "set_radius" , s->get_radius()); |
232 | ur->add_undo_method(s, "set_radius" , p_restore); |
233 | } else { |
234 | ur->create_action(TTR("Change Cylinder Height" )); |
235 | ur->add_do_method(s, "set_height" , s->get_height()); |
236 | ur->add_undo_method(s, "set_height" , p_restore); |
237 | } |
238 | |
239 | ur->commit_action(); |
240 | } |
241 | |
242 | if (Object::cast_to<CSGTorus3D>(cs)) { |
243 | CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs); |
244 | if (p_cancel) { |
245 | if (p_id == 0) { |
246 | s->set_inner_radius(p_restore); |
247 | } else { |
248 | s->set_outer_radius(p_restore); |
249 | } |
250 | return; |
251 | } |
252 | |
253 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
254 | if (p_id == 0) { |
255 | ur->create_action(TTR("Change Torus Inner Radius" )); |
256 | ur->add_do_method(s, "set_inner_radius" , s->get_inner_radius()); |
257 | ur->add_undo_method(s, "set_inner_radius" , p_restore); |
258 | } else { |
259 | ur->create_action(TTR("Change Torus Outer Radius" )); |
260 | ur->add_do_method(s, "set_outer_radius" , s->get_outer_radius()); |
261 | ur->add_undo_method(s, "set_outer_radius" , p_restore); |
262 | } |
263 | |
264 | ur->commit_action(); |
265 | } |
266 | } |
267 | |
268 | bool CSGShape3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { |
269 | 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); |
270 | } |
271 | |
272 | String CSGShape3DGizmoPlugin::get_gizmo_name() const { |
273 | return "CSGShape3D" ; |
274 | } |
275 | |
276 | int CSGShape3DGizmoPlugin::get_priority() const { |
277 | return -1; |
278 | } |
279 | |
280 | bool CSGShape3DGizmoPlugin::is_selectable_when_hidden() const { |
281 | return true; |
282 | } |
283 | |
284 | void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { |
285 | p_gizmo->clear(); |
286 | |
287 | CSGShape3D *cs = Object::cast_to<CSGShape3D>(p_gizmo->get_node_3d()); |
288 | |
289 | Vector<Vector3> faces = cs->get_brush_faces(); |
290 | |
291 | if (faces.size() == 0) { |
292 | return; |
293 | } |
294 | |
295 | Vector<Vector3> lines; |
296 | lines.resize(faces.size() * 2); |
297 | { |
298 | const Vector3 *r = faces.ptr(); |
299 | |
300 | for (int i = 0; i < lines.size(); i += 6) { |
301 | int f = i / 6; |
302 | for (int j = 0; j < 3; j++) { |
303 | int j_n = (j + 1) % 3; |
304 | lines.write[i + j * 2 + 0] = r[f * 3 + j]; |
305 | lines.write[i + j * 2 + 1] = r[f * 3 + j_n]; |
306 | } |
307 | } |
308 | } |
309 | |
310 | Ref<Material> material; |
311 | switch (cs->get_operation()) { |
312 | case CSGShape3D::OPERATION_UNION: |
313 | material = get_material("shape_union_material" , p_gizmo); |
314 | break; |
315 | case CSGShape3D::OPERATION_INTERSECTION: |
316 | material = get_material("shape_intersection_material" , p_gizmo); |
317 | break; |
318 | case CSGShape3D::OPERATION_SUBTRACTION: |
319 | material = get_material("shape_subtraction_material" , p_gizmo); |
320 | break; |
321 | } |
322 | |
323 | Ref<Material> handles_material = get_material("handles" ); |
324 | |
325 | p_gizmo->add_lines(lines, material); |
326 | p_gizmo->add_collision_segments(lines); |
327 | |
328 | if (cs->is_root_shape()) { |
329 | Array csg_meshes = cs->get_meshes(); |
330 | if (csg_meshes.size() == 2) { |
331 | Ref<Mesh> csg_mesh = csg_meshes[1]; |
332 | if (csg_mesh.is_valid()) { |
333 | p_gizmo->add_collision_triangles(csg_mesh->generate_triangle_mesh()); |
334 | } |
335 | } |
336 | } |
337 | |
338 | if (p_gizmo->is_selected()) { |
339 | // Draw a translucent representation of the CSG node |
340 | Ref<ArrayMesh> mesh = memnew(ArrayMesh); |
341 | Array array; |
342 | array.resize(Mesh::ARRAY_MAX); |
343 | array[Mesh::ARRAY_VERTEX] = faces; |
344 | mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array); |
345 | |
346 | Ref<Material> solid_material; |
347 | switch (cs->get_operation()) { |
348 | case CSGShape3D::OPERATION_UNION: |
349 | solid_material = get_material("shape_union_solid_material" , p_gizmo); |
350 | break; |
351 | case CSGShape3D::OPERATION_INTERSECTION: |
352 | solid_material = get_material("shape_intersection_solid_material" , p_gizmo); |
353 | break; |
354 | case CSGShape3D::OPERATION_SUBTRACTION: |
355 | solid_material = get_material("shape_subtraction_solid_material" , p_gizmo); |
356 | break; |
357 | } |
358 | |
359 | p_gizmo->add_mesh(mesh, solid_material); |
360 | } |
361 | |
362 | if (Object::cast_to<CSGSphere3D>(cs)) { |
363 | CSGSphere3D *s = Object::cast_to<CSGSphere3D>(cs); |
364 | |
365 | float r = s->get_radius(); |
366 | Vector<Vector3> handles; |
367 | handles.push_back(Vector3(r, 0, 0)); |
368 | p_gizmo->add_handles(handles, handles_material); |
369 | } |
370 | |
371 | if (Object::cast_to<CSGBox3D>(cs)) { |
372 | CSGBox3D *s = Object::cast_to<CSGBox3D>(cs); |
373 | Vector<Vector3> handles = helper->box_get_handles(s->get_size()); |
374 | p_gizmo->add_handles(handles, handles_material); |
375 | } |
376 | |
377 | if (Object::cast_to<CSGCylinder3D>(cs)) { |
378 | CSGCylinder3D *s = Object::cast_to<CSGCylinder3D>(cs); |
379 | |
380 | Vector<Vector3> handles; |
381 | handles.push_back(Vector3(s->get_radius(), 0, 0)); |
382 | handles.push_back(Vector3(0, s->get_height() * 0.5, 0)); |
383 | p_gizmo->add_handles(handles, handles_material); |
384 | } |
385 | |
386 | if (Object::cast_to<CSGTorus3D>(cs)) { |
387 | CSGTorus3D *s = Object::cast_to<CSGTorus3D>(cs); |
388 | |
389 | Vector<Vector3> handles; |
390 | handles.push_back(Vector3(s->get_inner_radius(), 0, 0)); |
391 | handles.push_back(Vector3(s->get_outer_radius(), 0, 0)); |
392 | p_gizmo->add_handles(handles, handles_material); |
393 | } |
394 | } |
395 | |
396 | EditorPluginCSG::EditorPluginCSG() { |
397 | Ref<CSGShape3DGizmoPlugin> gizmo_plugin = Ref<CSGShape3DGizmoPlugin>(memnew(CSGShape3DGizmoPlugin)); |
398 | Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); |
399 | } |
400 | |
401 | #endif // TOOLS_ENABLED |
402 | |