| 1 | /**************************************************************************/ |
| 2 | /* node_3d_editor_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 "node_3d_editor_gizmos.h" |
| 32 | |
| 33 | #include "core/math/geometry_2d.h" |
| 34 | #include "core/math/geometry_3d.h" |
| 35 | #include "editor/editor_node.h" |
| 36 | #include "editor/editor_settings.h" |
| 37 | #include "editor/editor_string_names.h" |
| 38 | #include "editor/plugins/node_3d_editor_plugin.h" |
| 39 | #include "scene/resources/primitive_meshes.h" |
| 40 | |
| 41 | #define HANDLE_HALF_SIZE 9.5 |
| 42 | |
| 43 | bool EditorNode3DGizmo::is_editable() const { |
| 44 | ERR_FAIL_NULL_V(spatial_node, false); |
| 45 | Node *edited_root = spatial_node->get_tree()->get_edited_scene_root(); |
| 46 | if (spatial_node == edited_root) { |
| 47 | return true; |
| 48 | } |
| 49 | if (spatial_node->get_owner() == edited_root) { |
| 50 | return true; |
| 51 | } |
| 52 | |
| 53 | if (edited_root->is_editable_instance(spatial_node->get_owner())) { |
| 54 | return true; |
| 55 | } |
| 56 | |
| 57 | return false; |
| 58 | } |
| 59 | |
| 60 | void EditorNode3DGizmo::clear() { |
| 61 | ERR_FAIL_NULL(RenderingServer::get_singleton()); |
| 62 | for (int i = 0; i < instances.size(); i++) { |
| 63 | if (instances[i].instance.is_valid()) { |
| 64 | RS::get_singleton()->free(instances[i].instance); |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | billboard_handle = false; |
| 69 | collision_segments.clear(); |
| 70 | collision_mesh = Ref<TriangleMesh>(); |
| 71 | instances.clear(); |
| 72 | handles.clear(); |
| 73 | handle_ids.clear(); |
| 74 | secondary_handles.clear(); |
| 75 | secondary_handle_ids.clear(); |
| 76 | } |
| 77 | |
| 78 | void EditorNode3DGizmo::redraw() { |
| 79 | if (!GDVIRTUAL_CALL(_redraw)) { |
| 80 | ERR_FAIL_NULL(gizmo_plugin); |
| 81 | gizmo_plugin->redraw(this); |
| 82 | } |
| 83 | |
| 84 | if (Node3DEditor::get_singleton()->is_current_selected_gizmo(this)) { |
| 85 | Node3DEditor::get_singleton()->update_transform_gizmo(); |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | String EditorNode3DGizmo::get_handle_name(int p_id, bool p_secondary) const { |
| 90 | String ret; |
| 91 | if (GDVIRTUAL_CALL(_get_handle_name, p_id, p_secondary, ret)) { |
| 92 | return ret; |
| 93 | } |
| 94 | |
| 95 | ERR_FAIL_NULL_V(gizmo_plugin, "" ); |
| 96 | return gizmo_plugin->get_handle_name(this, p_id, p_secondary); |
| 97 | } |
| 98 | |
| 99 | bool EditorNode3DGizmo::is_handle_highlighted(int p_id, bool p_secondary) const { |
| 100 | bool success; |
| 101 | if (GDVIRTUAL_CALL(_is_handle_highlighted, p_id, p_secondary, success)) { |
| 102 | return success; |
| 103 | } |
| 104 | |
| 105 | ERR_FAIL_NULL_V(gizmo_plugin, false); |
| 106 | return gizmo_plugin->is_handle_highlighted(this, p_id, p_secondary); |
| 107 | } |
| 108 | |
| 109 | Variant EditorNode3DGizmo::get_handle_value(int p_id, bool p_secondary) const { |
| 110 | Variant value; |
| 111 | if (GDVIRTUAL_CALL(_get_handle_value, p_id, p_secondary, value)) { |
| 112 | return value; |
| 113 | } |
| 114 | |
| 115 | ERR_FAIL_NULL_V(gizmo_plugin, Variant()); |
| 116 | return gizmo_plugin->get_handle_value(this, p_id, p_secondary); |
| 117 | } |
| 118 | |
| 119 | void EditorNode3DGizmo::begin_handle_action(int p_id, bool p_secondary) { |
| 120 | if (GDVIRTUAL_CALL(_begin_handle_action, p_id, p_secondary)) { |
| 121 | return; |
| 122 | } |
| 123 | |
| 124 | ERR_FAIL_NULL(gizmo_plugin); |
| 125 | gizmo_plugin->begin_handle_action(this, p_id, p_secondary); |
| 126 | } |
| 127 | |
| 128 | void EditorNode3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { |
| 129 | if (GDVIRTUAL_CALL(_set_handle, p_id, p_secondary, p_camera, p_point)) { |
| 130 | return; |
| 131 | } |
| 132 | |
| 133 | ERR_FAIL_NULL(gizmo_plugin); |
| 134 | gizmo_plugin->set_handle(this, p_id, p_secondary, p_camera, p_point); |
| 135 | } |
| 136 | |
| 137 | void EditorNode3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { |
| 138 | if (GDVIRTUAL_CALL(_commit_handle, p_id, p_secondary, p_restore, p_cancel)) { |
| 139 | return; |
| 140 | } |
| 141 | |
| 142 | ERR_FAIL_NULL(gizmo_plugin); |
| 143 | gizmo_plugin->commit_handle(this, p_id, p_secondary, p_restore, p_cancel); |
| 144 | } |
| 145 | |
| 146 | int EditorNode3DGizmo::subgizmos_intersect_ray(Camera3D *p_camera, const Vector2 &p_point) const { |
| 147 | int id; |
| 148 | if (GDVIRTUAL_CALL(_subgizmos_intersect_ray, p_camera, p_point, id)) { |
| 149 | return id; |
| 150 | } |
| 151 | |
| 152 | ERR_FAIL_NULL_V(gizmo_plugin, -1); |
| 153 | return gizmo_plugin->subgizmos_intersect_ray(this, p_camera, p_point); |
| 154 | } |
| 155 | |
| 156 | Vector<int> EditorNode3DGizmo::subgizmos_intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum) const { |
| 157 | TypedArray<Plane> frustum; |
| 158 | frustum.resize(p_frustum.size()); |
| 159 | for (int i = 0; i < p_frustum.size(); i++) { |
| 160 | frustum[i] = p_frustum[i]; |
| 161 | } |
| 162 | Vector<int> ret; |
| 163 | if (GDVIRTUAL_CALL(_subgizmos_intersect_frustum, p_camera, frustum, ret)) { |
| 164 | return ret; |
| 165 | } |
| 166 | |
| 167 | ERR_FAIL_NULL_V(gizmo_plugin, Vector<int>()); |
| 168 | return gizmo_plugin->subgizmos_intersect_frustum(this, p_camera, p_frustum); |
| 169 | } |
| 170 | |
| 171 | Transform3D EditorNode3DGizmo::get_subgizmo_transform(int p_id) const { |
| 172 | Transform3D ret; |
| 173 | if (GDVIRTUAL_CALL(_get_subgizmo_transform, p_id, ret)) { |
| 174 | return ret; |
| 175 | } |
| 176 | |
| 177 | ERR_FAIL_NULL_V(gizmo_plugin, Transform3D()); |
| 178 | return gizmo_plugin->get_subgizmo_transform(this, p_id); |
| 179 | } |
| 180 | |
| 181 | void EditorNode3DGizmo::set_subgizmo_transform(int p_id, Transform3D p_transform) { |
| 182 | if (GDVIRTUAL_CALL(_set_subgizmo_transform, p_id, p_transform)) { |
| 183 | return; |
| 184 | } |
| 185 | |
| 186 | ERR_FAIL_NULL(gizmo_plugin); |
| 187 | gizmo_plugin->set_subgizmo_transform(this, p_id, p_transform); |
| 188 | } |
| 189 | |
| 190 | void EditorNode3DGizmo::commit_subgizmos(const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { |
| 191 | TypedArray<Transform3D> restore; |
| 192 | restore.resize(p_restore.size()); |
| 193 | for (int i = 0; i < p_restore.size(); i++) { |
| 194 | restore[i] = p_restore[i]; |
| 195 | } |
| 196 | |
| 197 | if (GDVIRTUAL_CALL(_commit_subgizmos, p_ids, restore, p_cancel)) { |
| 198 | return; |
| 199 | } |
| 200 | |
| 201 | ERR_FAIL_NULL(gizmo_plugin); |
| 202 | gizmo_plugin->commit_subgizmos(this, p_ids, p_restore, p_cancel); |
| 203 | } |
| 204 | |
| 205 | void EditorNode3DGizmo::set_node_3d(Node3D *p_node) { |
| 206 | ERR_FAIL_NULL(p_node); |
| 207 | spatial_node = p_node; |
| 208 | } |
| 209 | |
| 210 | void EditorNode3DGizmo::Instance::create_instance(Node3D *p_base, bool p_hidden) { |
| 211 | instance = RS::get_singleton()->instance_create2(mesh->get_rid(), p_base->get_world_3d()->get_scenario()); |
| 212 | RS::get_singleton()->instance_attach_object_instance_id(instance, p_base->get_instance_id()); |
| 213 | if (skin_reference.is_valid()) { |
| 214 | RS::get_singleton()->instance_attach_skeleton(instance, skin_reference->get_skeleton()); |
| 215 | } |
| 216 | if (extra_margin) { |
| 217 | RS::get_singleton()->instance_set_extra_visibility_margin(instance, 1); |
| 218 | } |
| 219 | RS::get_singleton()->instance_geometry_set_cast_shadows_setting(instance, RS::SHADOW_CASTING_SETTING_OFF); |
| 220 | int layer = p_hidden ? 0 : 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER; |
| 221 | RS::get_singleton()->instance_set_layer_mask(instance, layer); //gizmos are 26 |
| 222 | RS::get_singleton()->instance_geometry_set_flag(instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); |
| 223 | RS::get_singleton()->instance_geometry_set_flag(instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); |
| 224 | } |
| 225 | |
| 226 | void EditorNode3DGizmo::add_mesh(const Ref<Mesh> &p_mesh, const Ref<Material> &p_material, const Transform3D &p_xform, const Ref<SkinReference> &p_skin_reference) { |
| 227 | ERR_FAIL_NULL(spatial_node); |
| 228 | ERR_FAIL_COND_MSG(!p_mesh.is_valid(), "EditorNode3DGizmo.add_mesh() requires a valid Mesh resource." ); |
| 229 | |
| 230 | Instance ins; |
| 231 | |
| 232 | ins.mesh = p_mesh; |
| 233 | ins.skin_reference = p_skin_reference; |
| 234 | ins.material = p_material; |
| 235 | ins.xform = p_xform; |
| 236 | if (valid) { |
| 237 | ins.create_instance(spatial_node, hidden); |
| 238 | RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform() * ins.xform); |
| 239 | if (ins.material.is_valid()) { |
| 240 | RS::get_singleton()->instance_geometry_set_material_override(ins.instance, p_material->get_rid()); |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | instances.push_back(ins); |
| 245 | } |
| 246 | |
| 247 | void EditorNode3DGizmo::add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard, const Color &p_modulate) { |
| 248 | add_vertices(p_lines, p_material, Mesh::PRIMITIVE_LINES, p_billboard, p_modulate); |
| 249 | } |
| 250 | |
| 251 | void EditorNode3DGizmo::add_vertices(const Vector<Vector3> &p_vertices, const Ref<Material> &p_material, Mesh::PrimitiveType p_primitive_type, bool p_billboard, const Color &p_modulate) { |
| 252 | if (p_vertices.is_empty()) { |
| 253 | return; |
| 254 | } |
| 255 | |
| 256 | ERR_FAIL_NULL(spatial_node); |
| 257 | Instance ins; |
| 258 | |
| 259 | Ref<ArrayMesh> mesh = memnew(ArrayMesh); |
| 260 | Array a; |
| 261 | a.resize(Mesh::ARRAY_MAX); |
| 262 | |
| 263 | a[Mesh::ARRAY_VERTEX] = p_vertices; |
| 264 | |
| 265 | Vector<Color> color; |
| 266 | color.resize(p_vertices.size()); |
| 267 | { |
| 268 | Color *w = color.ptrw(); |
| 269 | for (int i = 0; i < p_vertices.size(); i++) { |
| 270 | if (is_selected()) { |
| 271 | w[i] = Color(1, 1, 1, 0.8) * p_modulate; |
| 272 | } else { |
| 273 | w[i] = Color(1, 1, 1, 0.2) * p_modulate; |
| 274 | } |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | a[Mesh::ARRAY_COLOR] = color; |
| 279 | |
| 280 | mesh->add_surface_from_arrays(p_primitive_type, a); |
| 281 | mesh->surface_set_material(0, p_material); |
| 282 | |
| 283 | if (p_billboard) { |
| 284 | float md = 0; |
| 285 | for (int i = 0; i < p_vertices.size(); i++) { |
| 286 | md = MAX(0, p_vertices[i].length()); |
| 287 | } |
| 288 | if (md) { |
| 289 | mesh->set_custom_aabb(AABB(Vector3(-md, -md, -md), Vector3(md, md, md) * 2.0)); |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | ins.mesh = mesh; |
| 294 | if (valid) { |
| 295 | ins.create_instance(spatial_node, hidden); |
| 296 | RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform()); |
| 297 | } |
| 298 | |
| 299 | instances.push_back(ins); |
| 300 | } |
| 301 | |
| 302 | void EditorNode3DGizmo::add_unscaled_billboard(const Ref<Material> &p_material, real_t p_scale, const Color &p_modulate) { |
| 303 | ERR_FAIL_NULL(spatial_node); |
| 304 | Instance ins; |
| 305 | |
| 306 | Vector<Vector3> vs = { |
| 307 | Vector3(-p_scale, p_scale, 0), |
| 308 | Vector3(p_scale, p_scale, 0), |
| 309 | Vector3(p_scale, -p_scale, 0), |
| 310 | Vector3(-p_scale, -p_scale, 0) |
| 311 | }; |
| 312 | |
| 313 | Vector<Vector2> uv = { |
| 314 | Vector2(0, 0), |
| 315 | Vector2(1, 0), |
| 316 | Vector2(1, 1), |
| 317 | Vector2(0, 1) |
| 318 | }; |
| 319 | |
| 320 | Vector<Color> colors = { |
| 321 | p_modulate, |
| 322 | p_modulate, |
| 323 | p_modulate, |
| 324 | p_modulate |
| 325 | }; |
| 326 | |
| 327 | Vector<int> indices = { 0, 1, 2, 0, 2, 3 }; |
| 328 | |
| 329 | Ref<ArrayMesh> mesh = memnew(ArrayMesh); |
| 330 | Array a; |
| 331 | a.resize(Mesh::ARRAY_MAX); |
| 332 | a[Mesh::ARRAY_VERTEX] = vs; |
| 333 | a[Mesh::ARRAY_TEX_UV] = uv; |
| 334 | a[Mesh::ARRAY_INDEX] = indices; |
| 335 | a[Mesh::ARRAY_COLOR] = colors; |
| 336 | mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a); |
| 337 | mesh->surface_set_material(0, p_material); |
| 338 | |
| 339 | float md = 0; |
| 340 | for (int i = 0; i < vs.size(); i++) { |
| 341 | md = MAX(0, vs[i].length()); |
| 342 | } |
| 343 | if (md) { |
| 344 | mesh->set_custom_aabb(AABB(Vector3(-md, -md, -md), Vector3(md, md, md) * 2.0)); |
| 345 | } |
| 346 | |
| 347 | selectable_icon_size = p_scale; |
| 348 | mesh->set_custom_aabb(AABB(Vector3(-selectable_icon_size, -selectable_icon_size, -selectable_icon_size) * 100.0f, Vector3(selectable_icon_size, selectable_icon_size, selectable_icon_size) * 200.0f)); |
| 349 | |
| 350 | ins.mesh = mesh; |
| 351 | if (valid) { |
| 352 | ins.create_instance(spatial_node, hidden); |
| 353 | RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform()); |
| 354 | } |
| 355 | |
| 356 | selectable_icon_size = p_scale; |
| 357 | |
| 358 | instances.push_back(ins); |
| 359 | } |
| 360 | |
| 361 | void EditorNode3DGizmo::add_collision_triangles(const Ref<TriangleMesh> &p_tmesh) { |
| 362 | collision_mesh = p_tmesh; |
| 363 | } |
| 364 | |
| 365 | void EditorNode3DGizmo::add_collision_segments(const Vector<Vector3> &p_lines) { |
| 366 | int from = collision_segments.size(); |
| 367 | collision_segments.resize(from + p_lines.size()); |
| 368 | for (int i = 0; i < p_lines.size(); i++) { |
| 369 | collision_segments.write[from + i] = p_lines[i]; |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref<Material> &p_material, const Vector<int> &p_ids, bool p_billboard, bool p_secondary) { |
| 374 | billboard_handle = p_billboard; |
| 375 | |
| 376 | if (!is_selected() || !is_editable()) { |
| 377 | return; |
| 378 | } |
| 379 | |
| 380 | ERR_FAIL_NULL(spatial_node); |
| 381 | |
| 382 | Vector<Vector3> &handle_list = p_secondary ? secondary_handles : handles; |
| 383 | Vector<int> &id_list = p_secondary ? secondary_handle_ids : handle_ids; |
| 384 | |
| 385 | if (p_ids.is_empty()) { |
| 386 | ERR_FAIL_COND_MSG(!id_list.is_empty(), "IDs must be provided for all handles, as handles with IDs already exist." ); |
| 387 | } else { |
| 388 | ERR_FAIL_COND_MSG(p_handles.size() != p_ids.size(), "The number of IDs should be the same as the number of handles." ); |
| 389 | } |
| 390 | |
| 391 | bool is_current_hover_gizmo = Node3DEditor::get_singleton()->get_current_hover_gizmo() == this; |
| 392 | bool current_hover_handle_secondary; |
| 393 | int current_hover_handle = Node3DEditor::get_singleton()->get_current_hover_gizmo_handle(current_hover_handle_secondary); |
| 394 | |
| 395 | Instance ins; |
| 396 | Ref<ArrayMesh> mesh = memnew(ArrayMesh); |
| 397 | |
| 398 | Array a; |
| 399 | a.resize(RS::ARRAY_MAX); |
| 400 | a[RS::ARRAY_VERTEX] = p_handles; |
| 401 | Vector<Color> colors; |
| 402 | { |
| 403 | colors.resize(p_handles.size()); |
| 404 | Color *w = colors.ptrw(); |
| 405 | for (int i = 0; i < p_handles.size(); i++) { |
| 406 | Color col(1, 1, 1, 1); |
| 407 | if (is_handle_highlighted(i, p_secondary)) { |
| 408 | col = Color(0, 0, 1, 0.9); |
| 409 | } |
| 410 | |
| 411 | int id = p_ids.is_empty() ? i : p_ids[i]; |
| 412 | if (!is_current_hover_gizmo || current_hover_handle != id || p_secondary != current_hover_handle_secondary) { |
| 413 | col.a = 0.8; |
| 414 | } |
| 415 | |
| 416 | w[i] = col; |
| 417 | } |
| 418 | } |
| 419 | a[RS::ARRAY_COLOR] = colors; |
| 420 | mesh->add_surface_from_arrays(Mesh::PRIMITIVE_POINTS, a); |
| 421 | mesh->surface_set_material(0, p_material); |
| 422 | |
| 423 | if (p_billboard) { |
| 424 | float md = 0; |
| 425 | for (int i = 0; i < p_handles.size(); i++) { |
| 426 | md = MAX(0, p_handles[i].length()); |
| 427 | } |
| 428 | if (md) { |
| 429 | mesh->set_custom_aabb(AABB(Vector3(-md, -md, -md), Vector3(md, md, md) * 2.0)); |
| 430 | } |
| 431 | } |
| 432 | |
| 433 | ins.mesh = mesh; |
| 434 | ins.extra_margin = true; |
| 435 | if (valid) { |
| 436 | ins.create_instance(spatial_node, hidden); |
| 437 | RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform()); |
| 438 | } |
| 439 | instances.push_back(ins); |
| 440 | |
| 441 | int current_size = handle_list.size(); |
| 442 | handle_list.resize(current_size + p_handles.size()); |
| 443 | for (int i = 0; i < p_handles.size(); i++) { |
| 444 | handle_list.write[current_size + i] = p_handles[i]; |
| 445 | } |
| 446 | |
| 447 | if (!p_ids.is_empty()) { |
| 448 | current_size = id_list.size(); |
| 449 | id_list.resize(current_size + p_ids.size()); |
| 450 | for (int i = 0; i < p_ids.size(); i++) { |
| 451 | id_list.write[current_size + i] = p_ids[i]; |
| 452 | } |
| 453 | } |
| 454 | } |
| 455 | |
| 456 | void EditorNode3DGizmo::add_solid_box(const Ref<Material> &p_material, Vector3 p_size, Vector3 p_position, const Transform3D &p_xform) { |
| 457 | ERR_FAIL_NULL(spatial_node); |
| 458 | |
| 459 | BoxMesh box_mesh; |
| 460 | box_mesh.set_size(p_size); |
| 461 | |
| 462 | Array arrays = box_mesh.surface_get_arrays(0); |
| 463 | PackedVector3Array vertex = arrays[RS::ARRAY_VERTEX]; |
| 464 | Vector3 *w = vertex.ptrw(); |
| 465 | |
| 466 | for (int i = 0; i < vertex.size(); ++i) { |
| 467 | w[i] += p_position; |
| 468 | } |
| 469 | |
| 470 | arrays[RS::ARRAY_VERTEX] = vertex; |
| 471 | |
| 472 | Ref<ArrayMesh> m = memnew(ArrayMesh); |
| 473 | m->add_surface_from_arrays(box_mesh.surface_get_primitive_type(0), arrays); |
| 474 | add_mesh(m, p_material, p_xform); |
| 475 | } |
| 476 | |
| 477 | bool EditorNode3DGizmo::intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum) { |
| 478 | ERR_FAIL_NULL_V(spatial_node, false); |
| 479 | ERR_FAIL_COND_V(!valid, false); |
| 480 | |
| 481 | if (hidden && !gizmo_plugin->is_selectable_when_hidden()) { |
| 482 | return false; |
| 483 | } |
| 484 | |
| 485 | if (selectable_icon_size > 0.0f) { |
| 486 | Vector3 origin = spatial_node->get_global_transform().get_origin(); |
| 487 | |
| 488 | const Plane *p = p_frustum.ptr(); |
| 489 | int fc = p_frustum.size(); |
| 490 | |
| 491 | bool any_out = false; |
| 492 | |
| 493 | for (int j = 0; j < fc; j++) { |
| 494 | if (p[j].is_point_over(origin)) { |
| 495 | any_out = true; |
| 496 | break; |
| 497 | } |
| 498 | } |
| 499 | |
| 500 | return !any_out; |
| 501 | } |
| 502 | |
| 503 | if (collision_segments.size()) { |
| 504 | const Plane *p = p_frustum.ptr(); |
| 505 | int fc = p_frustum.size(); |
| 506 | |
| 507 | int vc = collision_segments.size(); |
| 508 | const Vector3 *vptr = collision_segments.ptr(); |
| 509 | Transform3D t = spatial_node->get_global_transform(); |
| 510 | |
| 511 | bool any_out = false; |
| 512 | for (int j = 0; j < fc; j++) { |
| 513 | for (int i = 0; i < vc; i++) { |
| 514 | Vector3 v = t.xform(vptr[i]); |
| 515 | if (p[j].is_point_over(v)) { |
| 516 | any_out = true; |
| 517 | break; |
| 518 | } |
| 519 | } |
| 520 | if (any_out) { |
| 521 | break; |
| 522 | } |
| 523 | } |
| 524 | |
| 525 | if (!any_out) { |
| 526 | return true; |
| 527 | } |
| 528 | } |
| 529 | |
| 530 | if (collision_mesh.is_valid()) { |
| 531 | Transform3D t = spatial_node->get_global_transform(); |
| 532 | |
| 533 | Vector3 mesh_scale = t.get_basis().get_scale(); |
| 534 | t.orthonormalize(); |
| 535 | |
| 536 | Transform3D it = t.affine_inverse(); |
| 537 | |
| 538 | Vector<Plane> transformed_frustum; |
| 539 | int plane_count = p_frustum.size(); |
| 540 | transformed_frustum.resize(plane_count); |
| 541 | |
| 542 | for (int i = 0; i < plane_count; i++) { |
| 543 | transformed_frustum.write[i] = it.xform(p_frustum[i]); |
| 544 | } |
| 545 | |
| 546 | Vector<Vector3> convex_points = Geometry3D::compute_convex_mesh_points(transformed_frustum.ptr(), plane_count); |
| 547 | if (collision_mesh->inside_convex_shape(transformed_frustum.ptr(), plane_count, convex_points.ptr(), convex_points.size(), mesh_scale)) { |
| 548 | return true; |
| 549 | } |
| 550 | } |
| 551 | |
| 552 | return false; |
| 553 | } |
| 554 | |
| 555 | void EditorNode3DGizmo::handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id, bool &r_secondary) { |
| 556 | r_id = -1; |
| 557 | r_secondary = false; |
| 558 | |
| 559 | ERR_FAIL_NULL(spatial_node); |
| 560 | ERR_FAIL_COND(!valid); |
| 561 | |
| 562 | if (hidden) { |
| 563 | return; |
| 564 | } |
| 565 | |
| 566 | Transform3D camera_xform = p_camera->get_global_transform(); |
| 567 | Transform3D t = spatial_node->get_global_transform(); |
| 568 | if (billboard_handle) { |
| 569 | t.set_look_at(t.origin, t.origin - camera_xform.basis.get_column(2), camera_xform.basis.get_column(1)); |
| 570 | } |
| 571 | |
| 572 | float min_d = 1e20; |
| 573 | |
| 574 | for (int i = 0; i < secondary_handles.size(); i++) { |
| 575 | Vector3 hpos = t.xform(secondary_handles[i]); |
| 576 | Vector2 p = p_camera->unproject_position(hpos); |
| 577 | |
| 578 | if (p.distance_to(p_point) < HANDLE_HALF_SIZE) { |
| 579 | real_t dp = p_camera->get_transform().origin.distance_to(hpos); |
| 580 | if (dp < min_d) { |
| 581 | min_d = dp; |
| 582 | if (secondary_handle_ids.is_empty()) { |
| 583 | r_id = i; |
| 584 | } else { |
| 585 | r_id = secondary_handle_ids[i]; |
| 586 | } |
| 587 | r_secondary = true; |
| 588 | } |
| 589 | } |
| 590 | } |
| 591 | |
| 592 | if (r_id != -1 && p_shift_pressed) { |
| 593 | return; |
| 594 | } |
| 595 | |
| 596 | min_d = 1e20; |
| 597 | |
| 598 | for (int i = 0; i < handles.size(); i++) { |
| 599 | Vector3 hpos = t.xform(handles[i]); |
| 600 | Vector2 p = p_camera->unproject_position(hpos); |
| 601 | |
| 602 | if (p.distance_to(p_point) < HANDLE_HALF_SIZE) { |
| 603 | real_t dp = p_camera->get_transform().origin.distance_to(hpos); |
| 604 | if (dp < min_d) { |
| 605 | min_d = dp; |
| 606 | if (handle_ids.is_empty()) { |
| 607 | r_id = i; |
| 608 | } else { |
| 609 | r_id = handle_ids[i]; |
| 610 | } |
| 611 | r_secondary = false; |
| 612 | } |
| 613 | } |
| 614 | } |
| 615 | } |
| 616 | |
| 617 | bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal) { |
| 618 | ERR_FAIL_NULL_V(spatial_node, false); |
| 619 | ERR_FAIL_COND_V(!valid, false); |
| 620 | |
| 621 | if (hidden && !gizmo_plugin->is_selectable_when_hidden()) { |
| 622 | return false; |
| 623 | } |
| 624 | |
| 625 | if (selectable_icon_size > 0.0f) { |
| 626 | Transform3D t = spatial_node->get_global_transform(); |
| 627 | Vector3 camera_position = p_camera->get_camera_transform().origin; |
| 628 | if (!camera_position.is_equal_approx(t.origin)) { |
| 629 | t.set_look_at(t.origin, camera_position); |
| 630 | } |
| 631 | |
| 632 | float scale = t.origin.distance_to(p_camera->get_camera_transform().origin); |
| 633 | |
| 634 | if (p_camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL) { |
| 635 | float aspect = p_camera->get_viewport()->get_visible_rect().size.aspect(); |
| 636 | float size = p_camera->get_size(); |
| 637 | scale = size / aspect; |
| 638 | } |
| 639 | |
| 640 | Point2 center = p_camera->unproject_position(t.origin); |
| 641 | |
| 642 | Transform3D orig_camera_transform = p_camera->get_camera_transform(); |
| 643 | |
| 644 | if (!orig_camera_transform.origin.is_equal_approx(t.origin) && |
| 645 | ABS(orig_camera_transform.basis.get_column(Vector3::AXIS_Z).dot(Vector3(0, 1, 0))) < 0.99) { |
| 646 | p_camera->look_at(t.origin); |
| 647 | } |
| 648 | |
| 649 | Vector3 c0 = t.xform(Vector3(selectable_icon_size, selectable_icon_size, 0) * scale); |
| 650 | Vector3 c1 = t.xform(Vector3(-selectable_icon_size, -selectable_icon_size, 0) * scale); |
| 651 | |
| 652 | Point2 p0 = p_camera->unproject_position(c0); |
| 653 | Point2 p1 = p_camera->unproject_position(c1); |
| 654 | |
| 655 | p_camera->set_global_transform(orig_camera_transform); |
| 656 | |
| 657 | Rect2 rect(p0, (p1 - p0).abs()); |
| 658 | |
| 659 | rect.set_position(center - rect.get_size() / 2.0); |
| 660 | |
| 661 | if (rect.has_point(p_point)) { |
| 662 | r_pos = t.origin; |
| 663 | r_normal = -p_camera->project_ray_normal(p_point); |
| 664 | return true; |
| 665 | } |
| 666 | } |
| 667 | |
| 668 | if (collision_segments.size()) { |
| 669 | Plane camp(-p_camera->get_transform().basis.get_column(2).normalized(), p_camera->get_transform().origin); |
| 670 | |
| 671 | int vc = collision_segments.size(); |
| 672 | const Vector3 *vptr = collision_segments.ptr(); |
| 673 | Transform3D t = spatial_node->get_global_transform(); |
| 674 | if (billboard_handle) { |
| 675 | t.set_look_at(t.origin, t.origin - p_camera->get_transform().basis.get_column(2), p_camera->get_transform().basis.get_column(1)); |
| 676 | } |
| 677 | |
| 678 | Vector3 cp; |
| 679 | float cpd = 1e20; |
| 680 | |
| 681 | for (int i = 0; i < vc / 2; i++) { |
| 682 | Vector3 a = t.xform(vptr[i * 2 + 0]); |
| 683 | Vector3 b = t.xform(vptr[i * 2 + 1]); |
| 684 | Vector2 s[2]; |
| 685 | s[0] = p_camera->unproject_position(a); |
| 686 | s[1] = p_camera->unproject_position(b); |
| 687 | |
| 688 | Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, s); |
| 689 | |
| 690 | float pd = p.distance_to(p_point); |
| 691 | |
| 692 | if (pd < cpd) { |
| 693 | float d = s[0].distance_to(s[1]); |
| 694 | Vector3 tcp; |
| 695 | if (d > 0) { |
| 696 | float d2 = s[0].distance_to(p) / d; |
| 697 | tcp = a + (b - a) * d2; |
| 698 | |
| 699 | } else { |
| 700 | tcp = a; |
| 701 | } |
| 702 | |
| 703 | if (camp.distance_to(tcp) < p_camera->get_near()) { |
| 704 | continue; |
| 705 | } |
| 706 | cp = tcp; |
| 707 | cpd = pd; |
| 708 | } |
| 709 | } |
| 710 | |
| 711 | if (cpd < 8) { |
| 712 | r_pos = cp; |
| 713 | r_normal = -p_camera->project_ray_normal(p_point); |
| 714 | return true; |
| 715 | } |
| 716 | } |
| 717 | |
| 718 | if (collision_mesh.is_valid()) { |
| 719 | Transform3D gt = spatial_node->get_global_transform(); |
| 720 | |
| 721 | if (billboard_handle) { |
| 722 | gt.set_look_at(gt.origin, gt.origin - p_camera->get_transform().basis.get_column(2), p_camera->get_transform().basis.get_column(1)); |
| 723 | } |
| 724 | |
| 725 | Transform3D ai = gt.affine_inverse(); |
| 726 | Vector3 ray_from = ai.xform(p_camera->project_ray_origin(p_point)); |
| 727 | Vector3 ray_dir = ai.basis.xform(p_camera->project_ray_normal(p_point)).normalized(); |
| 728 | Vector3 rpos, rnorm; |
| 729 | |
| 730 | if (collision_mesh->intersect_ray(ray_from, ray_dir, rpos, rnorm)) { |
| 731 | r_pos = gt.xform(rpos); |
| 732 | r_normal = gt.basis.xform(rnorm).normalized(); |
| 733 | return true; |
| 734 | } |
| 735 | } |
| 736 | |
| 737 | return false; |
| 738 | } |
| 739 | |
| 740 | bool EditorNode3DGizmo::is_subgizmo_selected(int p_id) const { |
| 741 | Node3DEditor *ed = Node3DEditor::get_singleton(); |
| 742 | ERR_FAIL_NULL_V(ed, false); |
| 743 | return ed->is_current_selected_gizmo(this) && ed->is_subgizmo_selected(p_id); |
| 744 | } |
| 745 | |
| 746 | Vector<int> EditorNode3DGizmo::get_subgizmo_selection() const { |
| 747 | Vector<int> ret; |
| 748 | |
| 749 | Node3DEditor *ed = Node3DEditor::get_singleton(); |
| 750 | ERR_FAIL_NULL_V(ed, ret); |
| 751 | |
| 752 | if (ed->is_current_selected_gizmo(this)) { |
| 753 | ret = ed->get_subgizmo_selection(); |
| 754 | } |
| 755 | |
| 756 | return ret; |
| 757 | } |
| 758 | |
| 759 | void EditorNode3DGizmo::create() { |
| 760 | ERR_FAIL_NULL(spatial_node); |
| 761 | ERR_FAIL_COND(valid); |
| 762 | valid = true; |
| 763 | |
| 764 | for (int i = 0; i < instances.size(); i++) { |
| 765 | instances.write[i].create_instance(spatial_node, hidden); |
| 766 | } |
| 767 | |
| 768 | transform(); |
| 769 | } |
| 770 | |
| 771 | void EditorNode3DGizmo::transform() { |
| 772 | ERR_FAIL_NULL(spatial_node); |
| 773 | ERR_FAIL_COND(!valid); |
| 774 | for (int i = 0; i < instances.size(); i++) { |
| 775 | RS::get_singleton()->instance_set_transform(instances[i].instance, spatial_node->get_global_transform() * instances[i].xform); |
| 776 | } |
| 777 | } |
| 778 | |
| 779 | void EditorNode3DGizmo::free() { |
| 780 | ERR_FAIL_NULL(RenderingServer::get_singleton()); |
| 781 | ERR_FAIL_NULL(spatial_node); |
| 782 | ERR_FAIL_COND(!valid); |
| 783 | |
| 784 | for (int i = 0; i < instances.size(); i++) { |
| 785 | if (instances[i].instance.is_valid()) { |
| 786 | RS::get_singleton()->free(instances[i].instance); |
| 787 | } |
| 788 | instances.write[i].instance = RID(); |
| 789 | } |
| 790 | |
| 791 | clear(); |
| 792 | |
| 793 | valid = false; |
| 794 | } |
| 795 | |
| 796 | void EditorNode3DGizmo::set_hidden(bool p_hidden) { |
| 797 | hidden = p_hidden; |
| 798 | int layer = hidden ? 0 : 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER; |
| 799 | for (int i = 0; i < instances.size(); ++i) { |
| 800 | RS::get_singleton()->instance_set_layer_mask(instances[i].instance, layer); |
| 801 | } |
| 802 | } |
| 803 | |
| 804 | void EditorNode3DGizmo::set_plugin(EditorNode3DGizmoPlugin *p_plugin) { |
| 805 | gizmo_plugin = p_plugin; |
| 806 | } |
| 807 | |
| 808 | void EditorNode3DGizmo::_bind_methods() { |
| 809 | ClassDB::bind_method(D_METHOD("add_lines" , "lines" , "material" , "billboard" , "modulate" ), &EditorNode3DGizmo::add_lines, DEFVAL(false), DEFVAL(Color(1, 1, 1))); |
| 810 | ClassDB::bind_method(D_METHOD("add_mesh" , "mesh" , "material" , "transform" , "skeleton" ), &EditorNode3DGizmo::add_mesh, DEFVAL(Variant()), DEFVAL(Transform3D()), DEFVAL(Ref<SkinReference>())); |
| 811 | ClassDB::bind_method(D_METHOD("add_collision_segments" , "segments" ), &EditorNode3DGizmo::add_collision_segments); |
| 812 | ClassDB::bind_method(D_METHOD("add_collision_triangles" , "triangles" ), &EditorNode3DGizmo::add_collision_triangles); |
| 813 | ClassDB::bind_method(D_METHOD("add_unscaled_billboard" , "material" , "default_scale" , "modulate" ), &EditorNode3DGizmo::add_unscaled_billboard, DEFVAL(1), DEFVAL(Color(1, 1, 1))); |
| 814 | ClassDB::bind_method(D_METHOD("add_handles" , "handles" , "material" , "ids" , "billboard" , "secondary" ), &EditorNode3DGizmo::add_handles, DEFVAL(false), DEFVAL(false)); |
| 815 | ClassDB::bind_method(D_METHOD("set_node_3d" , "node" ), &EditorNode3DGizmo::_set_node_3d); |
| 816 | ClassDB::bind_method(D_METHOD("get_node_3d" ), &EditorNode3DGizmo::get_node_3d); |
| 817 | ClassDB::bind_method(D_METHOD("get_plugin" ), &EditorNode3DGizmo::get_plugin); |
| 818 | ClassDB::bind_method(D_METHOD("clear" ), &EditorNode3DGizmo::clear); |
| 819 | ClassDB::bind_method(D_METHOD("set_hidden" , "hidden" ), &EditorNode3DGizmo::set_hidden); |
| 820 | ClassDB::bind_method(D_METHOD("is_subgizmo_selected" , "id" ), &EditorNode3DGizmo::is_subgizmo_selected); |
| 821 | ClassDB::bind_method(D_METHOD("get_subgizmo_selection" ), &EditorNode3DGizmo::get_subgizmo_selection); |
| 822 | |
| 823 | GDVIRTUAL_BIND(_redraw); |
| 824 | GDVIRTUAL_BIND(_get_handle_name, "id" , "secondary" ); |
| 825 | GDVIRTUAL_BIND(_is_handle_highlighted, "id" , "secondary" ); |
| 826 | |
| 827 | GDVIRTUAL_BIND(_get_handle_value, "id" , "secondary" ); |
| 828 | GDVIRTUAL_BIND(_set_handle, "id" , "secondary" , "camera" , "point" ); |
| 829 | GDVIRTUAL_BIND(_commit_handle, "id" , "secondary" , "restore" , "cancel" ); |
| 830 | |
| 831 | GDVIRTUAL_BIND(_subgizmos_intersect_ray, "camera" , "point" ); |
| 832 | GDVIRTUAL_BIND(_subgizmos_intersect_frustum, "camera" , "frustum" ); |
| 833 | GDVIRTUAL_BIND(_set_subgizmo_transform, "id" , "transform" ); |
| 834 | GDVIRTUAL_BIND(_get_subgizmo_transform, "id" ); |
| 835 | GDVIRTUAL_BIND(_commit_subgizmos, "ids" , "restores" , "cancel" ); |
| 836 | } |
| 837 | |
| 838 | EditorNode3DGizmo::EditorNode3DGizmo() { |
| 839 | valid = false; |
| 840 | billboard_handle = false; |
| 841 | hidden = false; |
| 842 | selected = false; |
| 843 | spatial_node = nullptr; |
| 844 | gizmo_plugin = nullptr; |
| 845 | selectable_icon_size = -1.0f; |
| 846 | } |
| 847 | |
| 848 | EditorNode3DGizmo::~EditorNode3DGizmo() { |
| 849 | if (gizmo_plugin != nullptr) { |
| 850 | gizmo_plugin->unregister_gizmo(this); |
| 851 | } |
| 852 | clear(); |
| 853 | } |
| 854 | |
| 855 | ///// |
| 856 | |
| 857 | void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color &p_color, bool p_billboard, bool p_on_top, bool p_use_vertex_color) { |
| 858 | Color instantiated_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/instantiated" ); |
| 859 | |
| 860 | Vector<Ref<StandardMaterial3D>> mats; |
| 861 | |
| 862 | for (int i = 0; i < 4; i++) { |
| 863 | bool selected = i % 2 == 1; |
| 864 | bool instantiated = i < 2; |
| 865 | |
| 866 | Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); |
| 867 | |
| 868 | Color color = instantiated ? instantiated_color : p_color; |
| 869 | |
| 870 | if (!selected) { |
| 871 | color.a *= 0.3; |
| 872 | } |
| 873 | |
| 874 | material->set_albedo(color); |
| 875 | material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); |
| 876 | material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); |
| 877 | material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1); |
| 878 | material->set_cull_mode(StandardMaterial3D::CULL_DISABLED); |
| 879 | |
| 880 | if (p_use_vertex_color) { |
| 881 | material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); |
| 882 | material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); |
| 883 | } |
| 884 | |
| 885 | if (p_billboard) { |
| 886 | material->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED); |
| 887 | } |
| 888 | |
| 889 | if (p_on_top && selected) { |
| 890 | material->set_on_top_of_alpha(); |
| 891 | } |
| 892 | |
| 893 | mats.push_back(material); |
| 894 | } |
| 895 | |
| 896 | materials[p_name] = mats; |
| 897 | } |
| 898 | |
| 899 | void EditorNode3DGizmoPlugin::create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top, const Color &p_albedo) { |
| 900 | Color instantiated_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/instantiated" ); |
| 901 | |
| 902 | Vector<Ref<StandardMaterial3D>> icons; |
| 903 | |
| 904 | for (int i = 0; i < 4; i++) { |
| 905 | bool selected = i % 2 == 1; |
| 906 | bool instantiated = i < 2; |
| 907 | |
| 908 | Ref<StandardMaterial3D> icon = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); |
| 909 | |
| 910 | Color color = instantiated ? instantiated_color : p_albedo; |
| 911 | |
| 912 | if (!selected) { |
| 913 | color.a *= 0.85; |
| 914 | } |
| 915 | |
| 916 | icon->set_albedo(color); |
| 917 | |
| 918 | icon->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); |
| 919 | icon->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); |
| 920 | icon->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); |
| 921 | icon->set_cull_mode(StandardMaterial3D::CULL_DISABLED); |
| 922 | icon->set_depth_draw_mode(StandardMaterial3D::DEPTH_DRAW_DISABLED); |
| 923 | icon->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); |
| 924 | icon->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, p_texture); |
| 925 | icon->set_flag(StandardMaterial3D::FLAG_FIXED_SIZE, true); |
| 926 | icon->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED); |
| 927 | icon->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN); |
| 928 | |
| 929 | if (p_on_top && selected) { |
| 930 | icon->set_on_top_of_alpha(); |
| 931 | } |
| 932 | |
| 933 | icons.push_back(icon); |
| 934 | } |
| 935 | |
| 936 | materials[p_name] = icons; |
| 937 | } |
| 938 | |
| 939 | void EditorNode3DGizmoPlugin::create_handle_material(const String &p_name, bool p_billboard, const Ref<Texture2D> &p_icon) { |
| 940 | Ref<StandardMaterial3D> handle_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); |
| 941 | |
| 942 | handle_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); |
| 943 | handle_material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true); |
| 944 | Ref<Texture2D> handle_t = p_icon != nullptr ? p_icon : EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Editor3DHandle" ), EditorStringName(EditorIcons)); |
| 945 | handle_material->set_point_size(handle_t->get_width()); |
| 946 | handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle_t); |
| 947 | handle_material->set_albedo(Color(1, 1, 1)); |
| 948 | handle_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); |
| 949 | handle_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); |
| 950 | handle_material->set_on_top_of_alpha(); |
| 951 | if (p_billboard) { |
| 952 | handle_material->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED); |
| 953 | handle_material->set_on_top_of_alpha(); |
| 954 | } |
| 955 | handle_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); |
| 956 | |
| 957 | materials[p_name] = Vector<Ref<StandardMaterial3D>>(); |
| 958 | materials[p_name].push_back(handle_material); |
| 959 | } |
| 960 | |
| 961 | void EditorNode3DGizmoPlugin::add_material(const String &p_name, Ref<StandardMaterial3D> p_material) { |
| 962 | materials[p_name] = Vector<Ref<StandardMaterial3D>>(); |
| 963 | materials[p_name].push_back(p_material); |
| 964 | } |
| 965 | |
| 966 | Ref<StandardMaterial3D> EditorNode3DGizmoPlugin::get_material(const String &p_name, const Ref<EditorNode3DGizmo> &p_gizmo) { |
| 967 | ERR_FAIL_COND_V(!materials.has(p_name), Ref<StandardMaterial3D>()); |
| 968 | ERR_FAIL_COND_V(materials[p_name].size() == 0, Ref<StandardMaterial3D>()); |
| 969 | |
| 970 | if (p_gizmo.is_null() || materials[p_name].size() == 1) { |
| 971 | return materials[p_name][0]; |
| 972 | } |
| 973 | |
| 974 | int index = (p_gizmo->is_selected() ? 1 : 0) + (p_gizmo->is_editable() ? 2 : 0); |
| 975 | |
| 976 | Ref<StandardMaterial3D> mat = materials[p_name][index]; |
| 977 | |
| 978 | if (current_state == ON_TOP && p_gizmo->is_selected()) { |
| 979 | mat->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true); |
| 980 | } else { |
| 981 | mat->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, false); |
| 982 | } |
| 983 | |
| 984 | return mat; |
| 985 | } |
| 986 | |
| 987 | String EditorNode3DGizmoPlugin::get_gizmo_name() const { |
| 988 | String ret; |
| 989 | if (GDVIRTUAL_CALL(_get_gizmo_name, ret)) { |
| 990 | return ret; |
| 991 | } |
| 992 | |
| 993 | WARN_PRINT_ONCE("A 3D editor gizmo has no name defined (it will appear as \"Unnamed Gizmo\" in the \"View > Gizmos\" menu). To resolve this, override the `_get_gizmo_name()` function to return a String in the script that extends EditorNode3DGizmoPlugin." ); |
| 994 | return TTR("Unnamed Gizmo" ); |
| 995 | } |
| 996 | |
| 997 | int EditorNode3DGizmoPlugin::get_priority() const { |
| 998 | int ret; |
| 999 | if (GDVIRTUAL_CALL(_get_priority, ret)) { |
| 1000 | return ret; |
| 1001 | } |
| 1002 | return 0; |
| 1003 | } |
| 1004 | |
| 1005 | Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) { |
| 1006 | if (get_script_instance() && get_script_instance()->has_method("_get_gizmo" )) { |
| 1007 | return get_script_instance()->call("_get_gizmo" , p_spatial); |
| 1008 | } |
| 1009 | |
| 1010 | Ref<EditorNode3DGizmo> ref = create_gizmo(p_spatial); |
| 1011 | |
| 1012 | if (ref.is_null()) { |
| 1013 | return ref; |
| 1014 | } |
| 1015 | |
| 1016 | ref->set_plugin(this); |
| 1017 | ref->set_node_3d(p_spatial); |
| 1018 | ref->set_hidden(current_state == HIDDEN); |
| 1019 | |
| 1020 | current_gizmos.push_back(ref.ptr()); |
| 1021 | return ref; |
| 1022 | } |
| 1023 | |
| 1024 | void EditorNode3DGizmoPlugin::_bind_methods() { |
| 1025 | ClassDB::bind_method(D_METHOD("create_material" , "name" , "color" , "billboard" , "on_top" , "use_vertex_color" ), &EditorNode3DGizmoPlugin::create_material, DEFVAL(false), DEFVAL(false), DEFVAL(false)); |
| 1026 | ClassDB::bind_method(D_METHOD("create_icon_material" , "name" , "texture" , "on_top" , "color" ), &EditorNode3DGizmoPlugin::create_icon_material, DEFVAL(false), DEFVAL(Color(1, 1, 1, 1))); |
| 1027 | ClassDB::bind_method(D_METHOD("create_handle_material" , "name" , "billboard" , "texture" ), &EditorNode3DGizmoPlugin::create_handle_material, DEFVAL(false), DEFVAL(Variant())); |
| 1028 | ClassDB::bind_method(D_METHOD("add_material" , "name" , "material" ), &EditorNode3DGizmoPlugin::add_material); |
| 1029 | |
| 1030 | ClassDB::bind_method(D_METHOD("get_material" , "name" , "gizmo" ), &EditorNode3DGizmoPlugin::get_material, DEFVAL(Ref<EditorNode3DGizmo>())); |
| 1031 | |
| 1032 | GDVIRTUAL_BIND(_has_gizmo, "for_node_3d" ); |
| 1033 | GDVIRTUAL_BIND(_create_gizmo, "for_node_3d" ); |
| 1034 | |
| 1035 | GDVIRTUAL_BIND(_get_gizmo_name); |
| 1036 | GDVIRTUAL_BIND(_get_priority); |
| 1037 | GDVIRTUAL_BIND(_can_be_hidden); |
| 1038 | GDVIRTUAL_BIND(_is_selectable_when_hidden); |
| 1039 | |
| 1040 | GDVIRTUAL_BIND(_redraw, "gizmo" ); |
| 1041 | GDVIRTUAL_BIND(_get_handle_name, "gizmo" , "handle_id" , "secondary" ); |
| 1042 | GDVIRTUAL_BIND(_is_handle_highlighted, "gizmo" , "handle_id" , "secondary" ); |
| 1043 | GDVIRTUAL_BIND(_get_handle_value, "gizmo" , "handle_id" , "secondary" ); |
| 1044 | |
| 1045 | GDVIRTUAL_BIND(_set_handle, "gizmo" , "handle_id" , "secondary" , "camera" , "screen_pos" ); |
| 1046 | GDVIRTUAL_BIND(_commit_handle, "gizmo" , "handle_id" , "secondary" , "restore" , "cancel" ); |
| 1047 | |
| 1048 | GDVIRTUAL_BIND(_subgizmos_intersect_ray, "gizmo" , "camera" , "screen_pos" ); |
| 1049 | GDVIRTUAL_BIND(_subgizmos_intersect_frustum, "gizmo" , "camera" , "frustum_planes" ); |
| 1050 | GDVIRTUAL_BIND(_get_subgizmo_transform, "gizmo" , "subgizmo_id" ); |
| 1051 | GDVIRTUAL_BIND(_set_subgizmo_transform, "gizmo" , "subgizmo_id" , "transform" ); |
| 1052 | GDVIRTUAL_BIND(_commit_subgizmos, "gizmo" , "ids" , "restores" , "cancel" ); |
| 1053 | } |
| 1054 | |
| 1055 | bool EditorNode3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { |
| 1056 | bool success = false; |
| 1057 | GDVIRTUAL_CALL(_has_gizmo, p_spatial, success); |
| 1058 | return success; |
| 1059 | } |
| 1060 | |
| 1061 | Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::create_gizmo(Node3D *p_spatial) { |
| 1062 | Ref<EditorNode3DGizmo> ret; |
| 1063 | if (GDVIRTUAL_CALL(_create_gizmo, p_spatial, ret)) { |
| 1064 | return ret; |
| 1065 | } |
| 1066 | |
| 1067 | Ref<EditorNode3DGizmo> ref; |
| 1068 | if (has_gizmo(p_spatial)) { |
| 1069 | ref.instantiate(); |
| 1070 | } |
| 1071 | return ref; |
| 1072 | } |
| 1073 | |
| 1074 | bool EditorNode3DGizmoPlugin::can_be_hidden() const { |
| 1075 | bool ret = true; |
| 1076 | GDVIRTUAL_CALL(_can_be_hidden, ret); |
| 1077 | return ret; |
| 1078 | } |
| 1079 | |
| 1080 | bool EditorNode3DGizmoPlugin::is_selectable_when_hidden() const { |
| 1081 | bool ret = false; |
| 1082 | GDVIRTUAL_CALL(_is_selectable_when_hidden, ret); |
| 1083 | return ret; |
| 1084 | } |
| 1085 | |
| 1086 | void EditorNode3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { |
| 1087 | GDVIRTUAL_CALL(_redraw, p_gizmo); |
| 1088 | } |
| 1089 | |
| 1090 | bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { |
| 1091 | bool ret = false; |
| 1092 | GDVIRTUAL_CALL(_is_handle_highlighted, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, ret); |
| 1093 | return ret; |
| 1094 | } |
| 1095 | |
| 1096 | String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { |
| 1097 | String ret; |
| 1098 | GDVIRTUAL_CALL(_get_handle_name, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, ret); |
| 1099 | return ret; |
| 1100 | } |
| 1101 | |
| 1102 | Variant EditorNode3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { |
| 1103 | Variant ret; |
| 1104 | GDVIRTUAL_CALL(_get_handle_value, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, ret); |
| 1105 | return ret; |
| 1106 | } |
| 1107 | |
| 1108 | void EditorNode3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) { |
| 1109 | GDVIRTUAL_CALL(_begin_handle_action, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary); |
| 1110 | } |
| 1111 | |
| 1112 | void EditorNode3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { |
| 1113 | GDVIRTUAL_CALL(_set_handle, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, p_camera, p_point); |
| 1114 | } |
| 1115 | |
| 1116 | void EditorNode3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { |
| 1117 | GDVIRTUAL_CALL(_commit_handle, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, p_restore, p_cancel); |
| 1118 | } |
| 1119 | |
| 1120 | int EditorNode3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { |
| 1121 | int ret = -1; |
| 1122 | GDVIRTUAL_CALL(_subgizmos_intersect_ray, Ref<EditorNode3DGizmo>(p_gizmo), p_camera, p_point, ret); |
| 1123 | return ret; |
| 1124 | } |
| 1125 | |
| 1126 | Vector<int> EditorNode3DGizmoPlugin::subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const { |
| 1127 | TypedArray<Plane> frustum; |
| 1128 | frustum.resize(p_frustum.size()); |
| 1129 | for (int i = 0; i < p_frustum.size(); i++) { |
| 1130 | frustum[i] = p_frustum[i]; |
| 1131 | } |
| 1132 | Vector<int> ret; |
| 1133 | GDVIRTUAL_CALL(_subgizmos_intersect_frustum, Ref<EditorNode3DGizmo>(p_gizmo), p_camera, frustum, ret); |
| 1134 | return ret; |
| 1135 | } |
| 1136 | |
| 1137 | Transform3D EditorNode3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const { |
| 1138 | Transform3D ret; |
| 1139 | GDVIRTUAL_CALL(_get_subgizmo_transform, Ref<EditorNode3DGizmo>(p_gizmo), p_id, ret); |
| 1140 | return ret; |
| 1141 | } |
| 1142 | |
| 1143 | void EditorNode3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) { |
| 1144 | GDVIRTUAL_CALL(_set_subgizmo_transform, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_transform); |
| 1145 | } |
| 1146 | |
| 1147 | void EditorNode3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { |
| 1148 | TypedArray<Transform3D> restore; |
| 1149 | restore.resize(p_restore.size()); |
| 1150 | for (int i = 0; i < p_restore.size(); i++) { |
| 1151 | restore[i] = p_restore[i]; |
| 1152 | } |
| 1153 | |
| 1154 | GDVIRTUAL_CALL(_commit_subgizmos, Ref<EditorNode3DGizmo>(p_gizmo), p_ids, restore, p_cancel); |
| 1155 | } |
| 1156 | |
| 1157 | void EditorNode3DGizmoPlugin::set_state(int p_state) { |
| 1158 | current_state = p_state; |
| 1159 | for (int i = 0; i < current_gizmos.size(); ++i) { |
| 1160 | current_gizmos[i]->set_hidden(current_state == HIDDEN); |
| 1161 | } |
| 1162 | } |
| 1163 | |
| 1164 | int EditorNode3DGizmoPlugin::get_state() const { |
| 1165 | return current_state; |
| 1166 | } |
| 1167 | |
| 1168 | void EditorNode3DGizmoPlugin::unregister_gizmo(EditorNode3DGizmo *p_gizmo) { |
| 1169 | current_gizmos.erase(p_gizmo); |
| 1170 | } |
| 1171 | |
| 1172 | EditorNode3DGizmoPlugin::EditorNode3DGizmoPlugin() { |
| 1173 | current_state = VISIBLE; |
| 1174 | } |
| 1175 | |
| 1176 | EditorNode3DGizmoPlugin::~EditorNode3DGizmoPlugin() { |
| 1177 | for (int i = 0; i < current_gizmos.size(); ++i) { |
| 1178 | current_gizmos[i]->set_plugin(nullptr); |
| 1179 | current_gizmos[i]->get_node_3d()->remove_gizmo(current_gizmos[i]); |
| 1180 | } |
| 1181 | if (Node3DEditor::get_singleton()) { |
| 1182 | Node3DEditor::get_singleton()->update_all_gizmos(); |
| 1183 | } |
| 1184 | } |
| 1185 | |
| 1186 | ////// |
| 1187 | |