1/**************************************************************************/
2/* occluder_instance_3d_gizmo_plugin.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 "occluder_instance_3d_gizmo_plugin.h"
32
33#include "editor/editor_settings.h"
34#include "editor/editor_undo_redo_manager.h"
35#include "editor/plugins/node_3d_editor_plugin.h"
36#include "scene/3d/occluder_instance_3d.h"
37
38OccluderInstance3DGizmoPlugin::OccluderInstance3DGizmoPlugin() {
39 create_material("line_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/occluder", Color(0.8, 0.5, 1)));
40 create_handle_material("handles");
41}
42
43bool OccluderInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
44 return Object::cast_to<OccluderInstance3D>(p_spatial) != nullptr;
45}
46
47String OccluderInstance3DGizmoPlugin::get_gizmo_name() const {
48 return "OccluderInstance3D";
49}
50
51int OccluderInstance3DGizmoPlugin::get_priority() const {
52 return -1;
53}
54
55String OccluderInstance3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
56 const OccluderInstance3D *cs = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d());
57
58 Ref<Occluder3D> o = cs->get_occluder();
59 if (o.is_null()) {
60 return "";
61 }
62
63 if (Object::cast_to<SphereOccluder3D>(*o)) {
64 return "Radius";
65 }
66
67 if (Object::cast_to<BoxOccluder3D>(*o) || Object::cast_to<QuadOccluder3D>(*o)) {
68 return "Size";
69 }
70
71 return "";
72}
73
74Variant OccluderInstance3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const {
75 OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d());
76
77 Ref<Occluder3D> o = oi->get_occluder();
78 if (o.is_null()) {
79 return Variant();
80 }
81
82 if (Object::cast_to<SphereOccluder3D>(*o)) {
83 Ref<SphereOccluder3D> so = o;
84 return so->get_radius();
85 }
86
87 if (Object::cast_to<BoxOccluder3D>(*o)) {
88 Ref<BoxOccluder3D> bo = o;
89 return bo->get_size();
90 }
91
92 if (Object::cast_to<QuadOccluder3D>(*o)) {
93 Ref<QuadOccluder3D> qo = o;
94 return qo->get_size();
95 }
96
97 return Variant();
98}
99
100void OccluderInstance3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) {
101 OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d());
102
103 Ref<Occluder3D> o = oi->get_occluder();
104 if (o.is_null()) {
105 return;
106 }
107
108 Transform3D gt = oi->get_global_transform();
109 Transform3D gi = gt.affine_inverse();
110
111 Vector3 ray_from = p_camera->project_ray_origin(p_point);
112 Vector3 ray_dir = p_camera->project_ray_normal(p_point);
113
114 Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
115
116 bool snap_enabled = Node3DEditor::get_singleton()->is_snap_enabled();
117 float snap = Node3DEditor::get_singleton()->get_translate_snap();
118
119 if (Object::cast_to<SphereOccluder3D>(*o)) {
120 Ref<SphereOccluder3D> so = o;
121 Vector3 ra, rb;
122 Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb);
123 float d = ra.x;
124 if (snap_enabled) {
125 d = Math::snapped(d, snap);
126 }
127
128 if (d < 0.001) {
129 d = 0.001;
130 }
131
132 so->set_radius(d);
133 }
134
135 if (Object::cast_to<BoxOccluder3D>(*o)) {
136 Vector3 axis;
137 axis[p_id] = 1.0;
138 Ref<BoxOccluder3D> bo = o;
139 Vector3 ra, rb;
140 Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
141 float d = ra[p_id] * 2;
142 if (snap_enabled) {
143 d = Math::snapped(d, snap);
144 }
145
146 if (d < 0.001) {
147 d = 0.001;
148 }
149
150 Vector3 he = bo->get_size();
151 he[p_id] = d;
152 bo->set_size(he);
153 }
154
155 if (Object::cast_to<QuadOccluder3D>(*o)) {
156 Ref<QuadOccluder3D> qo = o;
157 Plane p = Plane(Vector3(0.0f, 0.0f, 1.0f), 0.0f);
158 Vector3 intersection;
159 if (!p.intersects_segment(sg[0], sg[1], &intersection)) {
160 return;
161 }
162
163 if (p_id == 2) {
164 Vector2 s = Vector2(intersection.x, intersection.y) * 2.0f;
165 if (snap_enabled) {
166 s = s.snapped(Vector2(snap, snap));
167 }
168 s = s.max(Vector2(0.001, 0.001));
169 qo->set_size(s);
170 } else {
171 float d = intersection[p_id];
172 if (snap_enabled) {
173 d = Math::snapped(d, snap);
174 }
175
176 if (d < 0.001) {
177 d = 0.001;
178 }
179
180 Vector2 he = qo->get_size();
181 he[p_id] = d * 2.0f;
182 qo->set_size(he);
183 }
184 }
185}
186
187void OccluderInstance3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) {
188 OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d());
189
190 Ref<Occluder3D> o = oi->get_occluder();
191 if (o.is_null()) {
192 return;
193 }
194
195 if (Object::cast_to<SphereOccluder3D>(*o)) {
196 Ref<SphereOccluder3D> so = o;
197 if (p_cancel) {
198 so->set_radius(p_restore);
199 return;
200 }
201
202 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
203 ur->create_action(TTR("Change Sphere Shape Radius"));
204 ur->add_do_method(so.ptr(), "set_radius", so->get_radius());
205 ur->add_undo_method(so.ptr(), "set_radius", p_restore);
206 ur->commit_action();
207 }
208
209 if (Object::cast_to<BoxOccluder3D>(*o)) {
210 Ref<BoxOccluder3D> bo = o;
211 if (p_cancel) {
212 bo->set_size(p_restore);
213 return;
214 }
215
216 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
217 ur->create_action(TTR("Change Box Shape Size"));
218 ur->add_do_method(bo.ptr(), "set_size", bo->get_size());
219 ur->add_undo_method(bo.ptr(), "set_size", p_restore);
220 ur->commit_action();
221 }
222
223 if (Object::cast_to<QuadOccluder3D>(*o)) {
224 Ref<QuadOccluder3D> qo = o;
225 if (p_cancel) {
226 qo->set_size(p_restore);
227 return;
228 }
229
230 EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
231 ur->create_action(TTR("Change Box Shape Size"));
232 ur->add_do_method(qo.ptr(), "set_size", qo->get_size());
233 ur->add_undo_method(qo.ptr(), "set_size", p_restore);
234 ur->commit_action();
235 }
236}
237
238void OccluderInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
239 OccluderInstance3D *occluder_instance = Object::cast_to<OccluderInstance3D>(p_gizmo->get_node_3d());
240
241 p_gizmo->clear();
242
243 Ref<Occluder3D> o = occluder_instance->get_occluder();
244
245 if (!o.is_valid()) {
246 return;
247 }
248
249 Vector<Vector3> lines = o->get_debug_lines();
250 if (!lines.is_empty()) {
251 Ref<Material> material = get_material("line_material", p_gizmo);
252 p_gizmo->add_lines(lines, material);
253 p_gizmo->add_collision_segments(lines);
254 }
255
256 Ref<Material> handles_material = get_material("handles");
257 if (Object::cast_to<SphereOccluder3D>(*o)) {
258 Ref<SphereOccluder3D> so = o;
259 float r = so->get_radius();
260 Vector<Vector3> handles = { Vector3(r, 0, 0) };
261 p_gizmo->add_handles(handles, handles_material);
262 }
263
264 if (Object::cast_to<BoxOccluder3D>(*o)) {
265 Ref<BoxOccluder3D> bo = o;
266
267 Vector<Vector3> handles;
268 for (int i = 0; i < 3; i++) {
269 Vector3 ax;
270 ax[i] = bo->get_size()[i] / 2;
271 handles.push_back(ax);
272 }
273
274 p_gizmo->add_handles(handles, handles_material);
275 }
276
277 if (Object::cast_to<QuadOccluder3D>(*o)) {
278 Ref<QuadOccluder3D> qo = o;
279 Vector2 size = qo->get_size();
280 Vector3 s = Vector3(size.x, size.y, 0.0f) / 2.0f;
281 Vector<Vector3> handles = { Vector3(s.x, 0.0f, 0.0f), Vector3(0.0f, s.y, 0.0f), Vector3(s.x, s.y, 0.0f) };
282 p_gizmo->add_handles(handles, handles_material);
283 }
284}
285