5ffed49907
This PR and commit adds a new IK system for 3D with the Skeleton3D node that adds several new IK solvers, as well as additional changes and functionality for making bone manipulation in Godot easier. This work was sponsored by GSoC 2020 and TwistedTwigleg Full list of changes: * Adds a SkeletonModification3D resource * This resource is the base where all IK code is written and executed * Adds a SkeletonModificationStack3D resource * This node oversees the execution of the modifications and acts as a bridge of sorts for the modifications to the Skeleton3D node * Adds SkeletonModification3D resources for LookAt, CCDIK, FABRIK, Jiggle, and TwoBoneIK * Each modification is in it's own file * Several changes to Skeletons, listed below: * Added local_pose_override, which acts just like global_pose_override but keeps bone-child relationships intract * So if you move a bone using local_pose_override, all of the bones that are children will also be moved. This is different than global_pose_override, which only affects the individual bone * Internally bones keep track of their children. This removes the need of a processing list, makes it possible to update just a few select bones at a time, and makes it easier to traverse down the bone chain * Additional functions added for converting from world transform to global poses, global poses to local poses, and all the same changes but backwards (local to global, global to world). This makes it much easier to work with bone transforms without needing to think too much about how to convert them. * New signal added, bone_pose_changed, that can be used to tell if a specific bone changed its transform. Needed for BoneAttachment3D * Added functions for getting the forward position of a bone * BoneAttachment3D node refactored heavily * BoneAttachment3D node is now completely standalone in its functionality. * This makes the code easier and less interconnected, as well as allowing them to function properly without being direct children of Skeleton3D nodes * BoneAttachment3D now can be set either using the index or the bone name. * BoneAttachment3D nodes can now set the bone transform instead of just following it. This is disabled by default for compatibility * BoneAttachment3D now shows a warning when not configured correctly * Added rotate_to_align function in Basis * Added class reference documentation for all changes
5430 lines
171 KiB
C++
5430 lines
171 KiB
C++
/*************************************************************************/
|
|
/* node_3d_editor_gizmos.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
|
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
|
/* */
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
/* a copy of this software and associated documentation files (the */
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
/* the following conditions: */
|
|
/* */
|
|
/* The above copyright notice and this permission notice shall be */
|
|
/* included in all copies or substantial portions of the Software. */
|
|
/* */
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
/*************************************************************************/
|
|
|
|
#include "node_3d_editor_gizmos.h"
|
|
|
|
#include "core/math/convex_hull.h"
|
|
#include "core/math/geometry_2d.h"
|
|
#include "core/math/geometry_3d.h"
|
|
#include "editor/plugins/node_3d_editor_plugin.h"
|
|
#include "scene/3d/audio_stream_player_3d.h"
|
|
#include "scene/3d/camera_3d.h"
|
|
#include "scene/3d/collision_polygon_3d.h"
|
|
#include "scene/3d/collision_shape_3d.h"
|
|
#include "scene/3d/cpu_particles_3d.h"
|
|
#include "scene/3d/decal.h"
|
|
#include "scene/3d/gpu_particles_3d.h"
|
|
#include "scene/3d/gpu_particles_collision_3d.h"
|
|
#include "scene/3d/light_3d.h"
|
|
#include "scene/3d/lightmap_gi.h"
|
|
#include "scene/3d/lightmap_probe.h"
|
|
#include "scene/3d/listener_3d.h"
|
|
#include "scene/3d/mesh_instance_3d.h"
|
|
#include "scene/3d/navigation_region_3d.h"
|
|
#include "scene/3d/occluder_instance_3d.h"
|
|
#include "scene/3d/physics_joint_3d.h"
|
|
#include "scene/3d/position_3d.h"
|
|
#include "scene/3d/ray_cast_3d.h"
|
|
#include "scene/3d/reflection_probe.h"
|
|
#include "scene/3d/soft_body_3d.h"
|
|
#include "scene/3d/spring_arm_3d.h"
|
|
#include "scene/3d/sprite_3d.h"
|
|
#include "scene/3d/vehicle_body_3d.h"
|
|
#include "scene/3d/visible_on_screen_notifier_3d.h"
|
|
#include "scene/3d/voxel_gi.h"
|
|
#include "scene/resources/box_shape_3d.h"
|
|
#include "scene/resources/capsule_shape_3d.h"
|
|
#include "scene/resources/concave_polygon_shape_3d.h"
|
|
#include "scene/resources/convex_polygon_shape_3d.h"
|
|
#include "scene/resources/cylinder_shape_3d.h"
|
|
#include "scene/resources/height_map_shape_3d.h"
|
|
#include "scene/resources/primitive_meshes.h"
|
|
#include "scene/resources/sphere_shape_3d.h"
|
|
#include "scene/resources/surface_tool.h"
|
|
#include "scene/resources/world_margin_shape_3d.h"
|
|
|
|
#define HANDLE_HALF_SIZE 9.5
|
|
|
|
bool EditorNode3DGizmo::is_editable() const {
|
|
ERR_FAIL_COND_V(!spatial_node, false);
|
|
Node *edited_root = spatial_node->get_tree()->get_edited_scene_root();
|
|
if (spatial_node == edited_root) {
|
|
return true;
|
|
}
|
|
if (spatial_node->get_owner() == edited_root) {
|
|
return true;
|
|
}
|
|
|
|
if (edited_root->is_editable_instance(spatial_node->get_owner())) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void EditorNode3DGizmo::clear() {
|
|
for (int i = 0; i < instances.size(); i++) {
|
|
if (instances[i].instance.is_valid()) {
|
|
RS::get_singleton()->free(instances[i].instance);
|
|
}
|
|
}
|
|
|
|
billboard_handle = false;
|
|
collision_segments.clear();
|
|
collision_mesh = Ref<TriangleMesh>();
|
|
instances.clear();
|
|
handles.clear();
|
|
secondary_handles.clear();
|
|
}
|
|
|
|
void EditorNode3DGizmo::redraw() {
|
|
if (get_script_instance() && get_script_instance()->has_method("_redraw")) {
|
|
get_script_instance()->call("_redraw");
|
|
} else {
|
|
ERR_FAIL_COND(!gizmo_plugin);
|
|
gizmo_plugin->redraw(this);
|
|
}
|
|
|
|
if (Node3DEditor::get_singleton()->is_current_selected_gizmo(this)) {
|
|
Node3DEditor::get_singleton()->update_transform_gizmo();
|
|
}
|
|
}
|
|
|
|
String EditorNode3DGizmo::get_handle_name(int p_id) const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_get_handle_name")) {
|
|
return get_script_instance()->call("_get_handle_name", p_id);
|
|
}
|
|
|
|
ERR_FAIL_COND_V(!gizmo_plugin, "");
|
|
return gizmo_plugin->get_handle_name(this, p_id);
|
|
}
|
|
|
|
bool EditorNode3DGizmo::is_handle_highlighted(int p_id) const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_is_handle_highlighted")) {
|
|
return get_script_instance()->call("_is_handle_highlighted", p_id);
|
|
}
|
|
|
|
ERR_FAIL_COND_V(!gizmo_plugin, false);
|
|
return gizmo_plugin->is_handle_highlighted(this, p_id);
|
|
}
|
|
|
|
Variant EditorNode3DGizmo::get_handle_value(int p_id) const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_get_handle_value")) {
|
|
return get_script_instance()->call("_get_handle_value", p_id);
|
|
}
|
|
|
|
ERR_FAIL_COND_V(!gizmo_plugin, Variant());
|
|
return gizmo_plugin->get_handle_value(this, p_id);
|
|
}
|
|
|
|
void EditorNode3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
if (get_script_instance() && get_script_instance()->has_method("_set_handle")) {
|
|
get_script_instance()->call("_set_handle", p_id, p_camera, p_point);
|
|
return;
|
|
}
|
|
|
|
ERR_FAIL_COND(!gizmo_plugin);
|
|
gizmo_plugin->set_handle(this, p_id, p_camera, p_point);
|
|
}
|
|
|
|
void EditorNode3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p_cancel) {
|
|
if (get_script_instance() && get_script_instance()->has_method("_commit_handle")) {
|
|
get_script_instance()->call("_commit_handle", p_id, p_restore, p_cancel);
|
|
return;
|
|
}
|
|
|
|
ERR_FAIL_COND(!gizmo_plugin);
|
|
gizmo_plugin->commit_handle(this, p_id, p_restore, p_cancel);
|
|
}
|
|
|
|
int EditorNode3DGizmo::subgizmos_intersect_ray(Camera3D *p_camera, const Vector2 &p_point) const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_ray")) {
|
|
return get_script_instance()->call("_subgizmos_intersect_ray", p_camera, p_point);
|
|
}
|
|
|
|
ERR_FAIL_COND_V(!gizmo_plugin, -1);
|
|
return gizmo_plugin->subgizmos_intersect_ray(this, p_camera, p_point);
|
|
}
|
|
|
|
Vector<int> EditorNode3DGizmo::subgizmos_intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum) const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_frustum")) {
|
|
Array frustum;
|
|
for (int i = 0; i < p_frustum.size(); i++) {
|
|
frustum[i] = p_frustum[i];
|
|
}
|
|
return get_script_instance()->call("_subgizmos_intersect_frustum", p_camera, frustum);
|
|
}
|
|
|
|
ERR_FAIL_COND_V(!gizmo_plugin, Vector<int>());
|
|
return gizmo_plugin->subgizmos_intersect_frustum(this, p_camera, p_frustum);
|
|
}
|
|
|
|
Transform3D EditorNode3DGizmo::get_subgizmo_transform(int p_id) const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_get_subgizmo_transform")) {
|
|
return get_script_instance()->call("_get_subgizmo_transform", p_id);
|
|
}
|
|
|
|
ERR_FAIL_COND_V(!gizmo_plugin, Transform3D());
|
|
return gizmo_plugin->get_subgizmo_transform(this, p_id);
|
|
}
|
|
|
|
void EditorNode3DGizmo::set_subgizmo_transform(int p_id, Transform3D p_transform) {
|
|
if (get_script_instance() && get_script_instance()->has_method("_set_subgizmo_transform")) {
|
|
get_script_instance()->call("_set_subgizmo_transform", p_id, p_transform);
|
|
return;
|
|
}
|
|
|
|
ERR_FAIL_COND(!gizmo_plugin);
|
|
gizmo_plugin->set_subgizmo_transform(this, p_id, p_transform);
|
|
}
|
|
|
|
void EditorNode3DGizmo::commit_subgizmos(const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) {
|
|
if (get_script_instance() && get_script_instance()->has_method("_commit_subgizmos")) {
|
|
Array ids;
|
|
for (int i = 0; i < p_ids.size(); i++) {
|
|
ids[i] = p_ids[i];
|
|
}
|
|
|
|
Array restore;
|
|
for (int i = 0; i < p_restore.size(); i++) {
|
|
restore[i] = p_restore[i];
|
|
}
|
|
|
|
get_script_instance()->call("_commit_subgizmos", ids, restore, p_cancel);
|
|
return;
|
|
}
|
|
|
|
ERR_FAIL_COND(!gizmo_plugin);
|
|
gizmo_plugin->commit_subgizmos(this, p_ids, p_restore, p_cancel);
|
|
}
|
|
|
|
void EditorNode3DGizmo::set_spatial_node(Node3D *p_node) {
|
|
ERR_FAIL_NULL(p_node);
|
|
spatial_node = p_node;
|
|
}
|
|
|
|
void EditorNode3DGizmo::Instance::create_instance(Node3D *p_base, bool p_hidden) {
|
|
instance = RS::get_singleton()->instance_create2(mesh->get_rid(), p_base->get_world_3d()->get_scenario());
|
|
RS::get_singleton()->instance_attach_object_instance_id(instance, p_base->get_instance_id());
|
|
if (skin_reference.is_valid()) {
|
|
RS::get_singleton()->instance_attach_skeleton(instance, skin_reference->get_skeleton());
|
|
}
|
|
if (extra_margin) {
|
|
RS::get_singleton()->instance_set_extra_visibility_margin(instance, 1);
|
|
}
|
|
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(instance, RS::SHADOW_CASTING_SETTING_OFF);
|
|
int layer = p_hidden ? 0 : 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER;
|
|
RS::get_singleton()->instance_set_layer_mask(instance, layer); //gizmos are 26
|
|
RS::get_singleton()->instance_geometry_set_flag(instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
|
|
}
|
|
|
|
void EditorNode3DGizmo::add_mesh(const Ref<ArrayMesh> &p_mesh, const Ref<Material> &p_material, const Transform3D &p_xform, const Ref<SkinReference> &p_skin_reference) {
|
|
ERR_FAIL_COND(!spatial_node);
|
|
Instance ins;
|
|
|
|
ins.mesh = p_mesh;
|
|
ins.skin_reference = p_skin_reference;
|
|
ins.material = p_material;
|
|
ins.xform = p_xform;
|
|
if (valid) {
|
|
ins.create_instance(spatial_node, hidden);
|
|
RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform() * ins.xform);
|
|
if (ins.material.is_valid()) {
|
|
RS::get_singleton()->instance_geometry_set_material_override(ins.instance, p_material->get_rid());
|
|
}
|
|
}
|
|
|
|
instances.push_back(ins);
|
|
}
|
|
|
|
void EditorNode3DGizmo::add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard, const Color &p_modulate) {
|
|
add_vertices(p_lines, p_material, Mesh::PRIMITIVE_LINES, p_billboard, p_modulate);
|
|
}
|
|
|
|
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) {
|
|
if (p_vertices.is_empty()) {
|
|
return;
|
|
}
|
|
|
|
ERR_FAIL_COND(!spatial_node);
|
|
Instance ins;
|
|
|
|
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
|
|
Array a;
|
|
a.resize(Mesh::ARRAY_MAX);
|
|
|
|
a[Mesh::ARRAY_VERTEX] = p_vertices;
|
|
|
|
Vector<Color> color;
|
|
color.resize(p_vertices.size());
|
|
{
|
|
Color *w = color.ptrw();
|
|
for (int i = 0; i < p_vertices.size(); i++) {
|
|
if (is_selected()) {
|
|
w[i] = Color(1, 1, 1, 0.8) * p_modulate;
|
|
} else {
|
|
w[i] = Color(1, 1, 1, 0.2) * p_modulate;
|
|
}
|
|
}
|
|
}
|
|
|
|
a[Mesh::ARRAY_COLOR] = color;
|
|
|
|
mesh->add_surface_from_arrays(p_primitive_type, a);
|
|
mesh->surface_set_material(0, p_material);
|
|
|
|
if (p_billboard) {
|
|
float md = 0;
|
|
for (int i = 0; i < p_vertices.size(); i++) {
|
|
md = MAX(0, p_vertices[i].length());
|
|
}
|
|
if (md) {
|
|
mesh->set_custom_aabb(AABB(Vector3(-md, -md, -md), Vector3(md, md, md) * 2.0));
|
|
}
|
|
}
|
|
|
|
ins.mesh = mesh;
|
|
if (valid) {
|
|
ins.create_instance(spatial_node, hidden);
|
|
RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform());
|
|
}
|
|
|
|
instances.push_back(ins);
|
|
}
|
|
|
|
void EditorNode3DGizmo::add_unscaled_billboard(const Ref<Material> &p_material, real_t p_scale, const Color &p_modulate) {
|
|
ERR_FAIL_COND(!spatial_node);
|
|
Instance ins;
|
|
|
|
Vector<Vector3> vs;
|
|
Vector<Vector2> uv;
|
|
Vector<Color> colors;
|
|
|
|
vs.push_back(Vector3(-p_scale, p_scale, 0));
|
|
vs.push_back(Vector3(p_scale, p_scale, 0));
|
|
vs.push_back(Vector3(p_scale, -p_scale, 0));
|
|
vs.push_back(Vector3(-p_scale, -p_scale, 0));
|
|
|
|
uv.push_back(Vector2(0, 0));
|
|
uv.push_back(Vector2(1, 0));
|
|
uv.push_back(Vector2(1, 1));
|
|
uv.push_back(Vector2(0, 1));
|
|
|
|
colors.push_back(p_modulate);
|
|
colors.push_back(p_modulate);
|
|
colors.push_back(p_modulate);
|
|
colors.push_back(p_modulate);
|
|
|
|
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
|
|
Array a;
|
|
a.resize(Mesh::ARRAY_MAX);
|
|
a[Mesh::ARRAY_VERTEX] = vs;
|
|
a[Mesh::ARRAY_TEX_UV] = uv;
|
|
Vector<int> indices;
|
|
indices.push_back(0);
|
|
indices.push_back(1);
|
|
indices.push_back(2);
|
|
indices.push_back(0);
|
|
indices.push_back(2);
|
|
indices.push_back(3);
|
|
a[Mesh::ARRAY_INDEX] = indices;
|
|
a[Mesh::ARRAY_COLOR] = colors;
|
|
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a);
|
|
mesh->surface_set_material(0, p_material);
|
|
|
|
float md = 0;
|
|
for (int i = 0; i < vs.size(); i++) {
|
|
md = MAX(0, vs[i].length());
|
|
}
|
|
if (md) {
|
|
mesh->set_custom_aabb(AABB(Vector3(-md, -md, -md), Vector3(md, md, md) * 2.0));
|
|
}
|
|
|
|
selectable_icon_size = p_scale;
|
|
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));
|
|
|
|
ins.mesh = mesh;
|
|
if (valid) {
|
|
ins.create_instance(spatial_node, hidden);
|
|
RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform());
|
|
}
|
|
|
|
selectable_icon_size = p_scale;
|
|
|
|
instances.push_back(ins);
|
|
}
|
|
|
|
void EditorNode3DGizmo::add_collision_triangles(const Ref<TriangleMesh> &p_tmesh) {
|
|
collision_mesh = p_tmesh;
|
|
}
|
|
|
|
void EditorNode3DGizmo::add_collision_segments(const Vector<Vector3> &p_lines) {
|
|
int from = collision_segments.size();
|
|
collision_segments.resize(from + p_lines.size());
|
|
for (int i = 0; i < p_lines.size(); i++) {
|
|
collision_segments.write[from + i] = p_lines[i];
|
|
}
|
|
}
|
|
|
|
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) {
|
|
billboard_handle = p_billboard;
|
|
|
|
if (!is_selected() || !is_editable()) {
|
|
return;
|
|
}
|
|
|
|
ERR_FAIL_COND(!spatial_node);
|
|
|
|
if (p_ids.is_empty()) {
|
|
ERR_FAIL_COND_MSG((!handles.is_empty() && !handle_ids.is_empty()) || (!secondary_handles.is_empty() && !secondary_handle_ids.is_empty()), "Fail");
|
|
} else {
|
|
ERR_FAIL_COND_MSG(handles.size() != handle_ids.size() || secondary_handles.size() != secondary_handle_ids.size(), "Fail");
|
|
}
|
|
|
|
bool is_current_hover_gizmo = Node3DEditor::get_singleton()->get_current_hover_gizmo() == this;
|
|
int current_hover_handle = Node3DEditor::get_singleton()->get_current_hover_gizmo_handle();
|
|
|
|
Instance ins;
|
|
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
|
|
|
|
Array a;
|
|
a.resize(RS::ARRAY_MAX);
|
|
a[RS::ARRAY_VERTEX] = p_handles;
|
|
Vector<Color> colors;
|
|
{
|
|
colors.resize(p_handles.size());
|
|
Color *w = colors.ptrw();
|
|
for (int i = 0; i < p_handles.size(); i++) {
|
|
Color col(1, 1, 1, 1);
|
|
if (is_handle_highlighted(i)) {
|
|
col = Color(0, 0, 1, 0.9);
|
|
}
|
|
|
|
int id = p_ids.is_empty() ? i : p_ids[i];
|
|
if (!is_current_hover_gizmo || current_hover_handle != id) {
|
|
col.a = 0.8;
|
|
}
|
|
|
|
w[i] = col;
|
|
}
|
|
}
|
|
a[RS::ARRAY_COLOR] = colors;
|
|
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_POINTS, a);
|
|
mesh->surface_set_material(0, p_material);
|
|
|
|
if (p_billboard) {
|
|
float md = 0;
|
|
for (int i = 0; i < p_handles.size(); i++) {
|
|
md = MAX(0, p_handles[i].length());
|
|
}
|
|
if (md) {
|
|
mesh->set_custom_aabb(AABB(Vector3(-md, -md, -md), Vector3(md, md, md) * 2.0));
|
|
}
|
|
}
|
|
|
|
ins.mesh = mesh;
|
|
ins.extra_margin = true;
|
|
if (valid) {
|
|
ins.create_instance(spatial_node, hidden);
|
|
RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform());
|
|
}
|
|
instances.push_back(ins);
|
|
|
|
Vector<Vector3> &h = p_secondary ? secondary_handles : handles;
|
|
int current_size = h.size();
|
|
h.resize(current_size + p_handles.size());
|
|
for (int i = 0; i < p_handles.size(); i++) {
|
|
h.write[current_size + i] = p_handles[i];
|
|
}
|
|
|
|
if (!p_ids.is_empty()) {
|
|
Vector<int> &ids = p_secondary ? secondary_handle_ids : handle_ids;
|
|
current_size = ids.size();
|
|
ids.resize(current_size + p_ids.size());
|
|
for (int i = 0; i < p_ids.size(); i++) {
|
|
ids.write[current_size + i] = p_ids[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
void EditorNode3DGizmo::add_solid_box(Ref<Material> &p_material, Vector3 p_size, Vector3 p_position, const Transform3D &p_xform) {
|
|
ERR_FAIL_COND(!spatial_node);
|
|
|
|
BoxMesh box_mesh;
|
|
box_mesh.set_size(p_size);
|
|
|
|
Array arrays = box_mesh.surface_get_arrays(0);
|
|
PackedVector3Array vertex = arrays[RS::ARRAY_VERTEX];
|
|
Vector3 *w = vertex.ptrw();
|
|
|
|
for (int i = 0; i < vertex.size(); ++i) {
|
|
w[i] += p_position;
|
|
}
|
|
|
|
arrays[RS::ARRAY_VERTEX] = vertex;
|
|
|
|
Ref<ArrayMesh> m = memnew(ArrayMesh);
|
|
m->add_surface_from_arrays(box_mesh.surface_get_primitive_type(0), arrays);
|
|
add_mesh(m, p_material, p_xform);
|
|
}
|
|
|
|
bool EditorNode3DGizmo::intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum) {
|
|
ERR_FAIL_COND_V(!spatial_node, false);
|
|
ERR_FAIL_COND_V(!valid, false);
|
|
|
|
if (hidden && !gizmo_plugin->is_selectable_when_hidden()) {
|
|
return false;
|
|
}
|
|
|
|
if (selectable_icon_size > 0.0f) {
|
|
Vector3 origin = spatial_node->get_global_transform().get_origin();
|
|
|
|
const Plane *p = p_frustum.ptr();
|
|
int fc = p_frustum.size();
|
|
|
|
bool any_out = false;
|
|
|
|
for (int j = 0; j < fc; j++) {
|
|
if (p[j].is_point_over(origin)) {
|
|
any_out = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return !any_out;
|
|
}
|
|
|
|
if (collision_segments.size()) {
|
|
const Plane *p = p_frustum.ptr();
|
|
int fc = p_frustum.size();
|
|
|
|
int vc = collision_segments.size();
|
|
const Vector3 *vptr = collision_segments.ptr();
|
|
Transform3D t = spatial_node->get_global_transform();
|
|
|
|
bool any_out = false;
|
|
for (int j = 0; j < fc; j++) {
|
|
for (int i = 0; i < vc; i++) {
|
|
Vector3 v = t.xform(vptr[i]);
|
|
if (p[j].is_point_over(v)) {
|
|
any_out = true;
|
|
break;
|
|
}
|
|
}
|
|
if (any_out) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!any_out) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (collision_mesh.is_valid()) {
|
|
Transform3D t = spatial_node->get_global_transform();
|
|
|
|
Vector3 mesh_scale = t.get_basis().get_scale();
|
|
t.orthonormalize();
|
|
|
|
Transform3D it = t.affine_inverse();
|
|
|
|
Vector<Plane> transformed_frustum;
|
|
int plane_count = p_frustum.size();
|
|
transformed_frustum.resize(plane_count);
|
|
|
|
for (int i = 0; i < plane_count; i++) {
|
|
transformed_frustum.write[i] = it.xform(p_frustum[i]);
|
|
}
|
|
|
|
Vector<Vector3> convex_points = Geometry3D::compute_convex_mesh_points(transformed_frustum.ptr(), plane_count);
|
|
if (collision_mesh->inside_convex_shape(transformed_frustum.ptr(), plane_count, convex_points.ptr(), convex_points.size(), mesh_scale)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void EditorNode3DGizmo::handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id) {
|
|
r_id = -1;
|
|
|
|
ERR_FAIL_COND(!spatial_node);
|
|
ERR_FAIL_COND(!valid);
|
|
|
|
if (hidden) {
|
|
return;
|
|
}
|
|
|
|
Transform3D camera_xform = p_camera->get_global_transform();
|
|
Transform3D t = spatial_node->get_global_transform();
|
|
if (billboard_handle) {
|
|
t.set_look_at(t.origin, t.origin - camera_xform.basis.get_axis(2), camera_xform.basis.get_axis(1));
|
|
}
|
|
|
|
float min_d = 1e20;
|
|
|
|
for (int i = 0; i < secondary_handles.size(); i++) {
|
|
Vector3 hpos = t.xform(secondary_handles[i]);
|
|
Vector2 p = p_camera->unproject_position(hpos);
|
|
|
|
if (p.distance_to(p_point) < HANDLE_HALF_SIZE) {
|
|
real_t dp = p_camera->get_transform().origin.distance_to(hpos);
|
|
if (dp < min_d) {
|
|
min_d = dp;
|
|
if (secondary_handle_ids.is_empty()) {
|
|
r_id = i;
|
|
} else {
|
|
r_id = secondary_handle_ids[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (r_id != -1 && p_shift_pressed) {
|
|
return;
|
|
}
|
|
|
|
min_d = 1e20;
|
|
|
|
for (int i = 0; i < handles.size(); i++) {
|
|
Vector3 hpos = t.xform(handles[i]);
|
|
Vector2 p = p_camera->unproject_position(hpos);
|
|
|
|
if (p.distance_to(p_point) < HANDLE_HALF_SIZE) {
|
|
real_t dp = p_camera->get_transform().origin.distance_to(hpos);
|
|
if (dp < min_d) {
|
|
min_d = dp;
|
|
if (handle_ids.is_empty()) {
|
|
r_id = i;
|
|
} else {
|
|
r_id = handle_ids[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal) {
|
|
ERR_FAIL_COND_V(!spatial_node, false);
|
|
ERR_FAIL_COND_V(!valid, false);
|
|
|
|
if (hidden && !gizmo_plugin->is_selectable_when_hidden()) {
|
|
return false;
|
|
}
|
|
|
|
if (selectable_icon_size > 0.0f) {
|
|
Transform3D t = spatial_node->get_global_transform();
|
|
Vector3 camera_position = p_camera->get_camera_transform().origin;
|
|
if (!camera_position.is_equal_approx(t.origin)) {
|
|
t.set_look_at(t.origin, camera_position);
|
|
}
|
|
|
|
float scale = t.origin.distance_to(p_camera->get_camera_transform().origin);
|
|
|
|
if (p_camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL) {
|
|
float aspect = p_camera->get_viewport()->get_visible_rect().size.aspect();
|
|
float size = p_camera->get_size();
|
|
scale = size / aspect;
|
|
}
|
|
|
|
Point2 center = p_camera->unproject_position(t.origin);
|
|
|
|
Transform3D orig_camera_transform = p_camera->get_camera_transform();
|
|
|
|
if (!orig_camera_transform.origin.is_equal_approx(t.origin) &&
|
|
ABS(orig_camera_transform.basis.get_axis(Vector3::AXIS_Z).dot(Vector3(0, 1, 0))) < 0.99) {
|
|
p_camera->look_at(t.origin);
|
|
}
|
|
|
|
Vector3 c0 = t.xform(Vector3(selectable_icon_size, selectable_icon_size, 0) * scale);
|
|
Vector3 c1 = t.xform(Vector3(-selectable_icon_size, -selectable_icon_size, 0) * scale);
|
|
|
|
Point2 p0 = p_camera->unproject_position(c0);
|
|
Point2 p1 = p_camera->unproject_position(c1);
|
|
|
|
p_camera->set_global_transform(orig_camera_transform);
|
|
|
|
Rect2 rect(p0, (p1 - p0).abs());
|
|
|
|
rect.set_position(center - rect.get_size() / 2.0);
|
|
|
|
if (rect.has_point(p_point)) {
|
|
r_pos = t.origin;
|
|
r_normal = -p_camera->project_ray_normal(p_point);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (collision_segments.size()) {
|
|
Plane camp(p_camera->get_transform().origin, (-p_camera->get_transform().basis.get_axis(2)).normalized());
|
|
|
|
int vc = collision_segments.size();
|
|
const Vector3 *vptr = collision_segments.ptr();
|
|
Transform3D t = spatial_node->get_global_transform();
|
|
if (billboard_handle) {
|
|
t.set_look_at(t.origin, t.origin - p_camera->get_transform().basis.get_axis(2), p_camera->get_transform().basis.get_axis(1));
|
|
}
|
|
|
|
Vector3 cp;
|
|
float cpd = 1e20;
|
|
|
|
for (int i = 0; i < vc / 2; i++) {
|
|
Vector3 a = t.xform(vptr[i * 2 + 0]);
|
|
Vector3 b = t.xform(vptr[i * 2 + 1]);
|
|
Vector2 s[2];
|
|
s[0] = p_camera->unproject_position(a);
|
|
s[1] = p_camera->unproject_position(b);
|
|
|
|
Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, s);
|
|
|
|
float pd = p.distance_to(p_point);
|
|
|
|
if (pd < cpd) {
|
|
float d = s[0].distance_to(s[1]);
|
|
Vector3 tcp;
|
|
if (d > 0) {
|
|
float d2 = s[0].distance_to(p) / d;
|
|
tcp = a + (b - a) * d2;
|
|
|
|
} else {
|
|
tcp = a;
|
|
}
|
|
|
|
if (camp.distance_to(tcp) < p_camera->get_near()) {
|
|
continue;
|
|
}
|
|
cp = tcp;
|
|
cpd = pd;
|
|
}
|
|
}
|
|
|
|
if (cpd < 8) {
|
|
r_pos = cp;
|
|
r_normal = -p_camera->project_ray_normal(p_point);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (collision_mesh.is_valid()) {
|
|
Transform3D gt = spatial_node->get_global_transform();
|
|
|
|
if (billboard_handle) {
|
|
gt.set_look_at(gt.origin, gt.origin - p_camera->get_transform().basis.get_axis(2), p_camera->get_transform().basis.get_axis(1));
|
|
}
|
|
|
|
Transform3D ai = gt.affine_inverse();
|
|
Vector3 ray_from = ai.xform(p_camera->project_ray_origin(p_point));
|
|
Vector3 ray_dir = ai.basis.xform(p_camera->project_ray_normal(p_point)).normalized();
|
|
Vector3 rpos, rnorm;
|
|
|
|
if (collision_mesh->intersect_ray(ray_from, ray_dir, rpos, rnorm)) {
|
|
r_pos = gt.xform(rpos);
|
|
r_normal = gt.basis.xform(rnorm).normalized();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EditorNode3DGizmo::is_subgizmo_selected(int p_id) const {
|
|
Node3DEditor *ed = Node3DEditor::get_singleton();
|
|
ERR_FAIL_COND_V(!ed, false);
|
|
return ed->is_current_selected_gizmo(this) && ed->is_subgizmo_selected(p_id);
|
|
}
|
|
|
|
Vector<int> EditorNode3DGizmo::get_subgizmo_selection() const {
|
|
Vector<int> ret;
|
|
|
|
Node3DEditor *ed = Node3DEditor::get_singleton();
|
|
ERR_FAIL_COND_V(!ed, ret);
|
|
|
|
if (ed->is_current_selected_gizmo(this)) {
|
|
ret = ed->get_subgizmo_selection();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void EditorNode3DGizmo::create() {
|
|
ERR_FAIL_COND(!spatial_node);
|
|
ERR_FAIL_COND(valid);
|
|
valid = true;
|
|
|
|
for (int i = 0; i < instances.size(); i++) {
|
|
instances.write[i].create_instance(spatial_node, hidden);
|
|
}
|
|
|
|
transform();
|
|
}
|
|
|
|
void EditorNode3DGizmo::transform() {
|
|
ERR_FAIL_COND(!spatial_node);
|
|
ERR_FAIL_COND(!valid);
|
|
for (int i = 0; i < instances.size(); i++) {
|
|
RS::get_singleton()->instance_set_transform(instances[i].instance, spatial_node->get_global_transform() * instances[i].xform);
|
|
}
|
|
}
|
|
|
|
void EditorNode3DGizmo::free() {
|
|
ERR_FAIL_COND(!spatial_node);
|
|
ERR_FAIL_COND(!valid);
|
|
|
|
for (int i = 0; i < instances.size(); i++) {
|
|
if (instances[i].instance.is_valid()) {
|
|
RS::get_singleton()->free(instances[i].instance);
|
|
}
|
|
instances.write[i].instance = RID();
|
|
}
|
|
|
|
clear();
|
|
|
|
valid = false;
|
|
}
|
|
|
|
void EditorNode3DGizmo::set_hidden(bool p_hidden) {
|
|
hidden = p_hidden;
|
|
int layer = hidden ? 0 : 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER;
|
|
for (int i = 0; i < instances.size(); ++i) {
|
|
RS::get_singleton()->instance_set_layer_mask(instances[i].instance, layer);
|
|
}
|
|
}
|
|
|
|
void EditorNode3DGizmo::set_plugin(EditorNode3DGizmoPlugin *p_plugin) {
|
|
gizmo_plugin = p_plugin;
|
|
}
|
|
|
|
void EditorNode3DGizmo::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("add_lines", "lines", "material", "billboard", "modulate"), &EditorNode3DGizmo::add_lines, DEFVAL(false), DEFVAL(Color(1, 1, 1)));
|
|
ClassDB::bind_method(D_METHOD("add_mesh", "mesh", "material", "transform", "skeleton"), &EditorNode3DGizmo::add_mesh, DEFVAL(Variant()), DEFVAL(Transform3D()), DEFVAL(Ref<SkinReference>()));
|
|
ClassDB::bind_method(D_METHOD("add_collision_segments", "segments"), &EditorNode3DGizmo::add_collision_segments);
|
|
ClassDB::bind_method(D_METHOD("add_collision_triangles", "triangles"), &EditorNode3DGizmo::add_collision_triangles);
|
|
ClassDB::bind_method(D_METHOD("add_unscaled_billboard", "material", "default_scale", "modulate"), &EditorNode3DGizmo::add_unscaled_billboard, DEFVAL(1), DEFVAL(Color(1, 1, 1)));
|
|
ClassDB::bind_method(D_METHOD("add_handles", "handles", "material", "ids", "billboard", "secondary"), &EditorNode3DGizmo::add_handles, DEFVAL(false), DEFVAL(false));
|
|
ClassDB::bind_method(D_METHOD("set_spatial_node", "node"), &EditorNode3DGizmo::_set_spatial_node);
|
|
ClassDB::bind_method(D_METHOD("get_spatial_node"), &EditorNode3DGizmo::get_spatial_node);
|
|
ClassDB::bind_method(D_METHOD("get_plugin"), &EditorNode3DGizmo::get_plugin);
|
|
ClassDB::bind_method(D_METHOD("clear"), &EditorNode3DGizmo::clear);
|
|
ClassDB::bind_method(D_METHOD("set_hidden", "hidden"), &EditorNode3DGizmo::set_hidden);
|
|
ClassDB::bind_method(D_METHOD("is_subgizmo_selected"), &EditorNode3DGizmo::is_subgizmo_selected);
|
|
ClassDB::bind_method(D_METHOD("get_subgizmo_selection"), &EditorNode3DGizmo::get_subgizmo_selection);
|
|
|
|
BIND_VMETHOD(MethodInfo("_redraw"));
|
|
BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_handle_name", PropertyInfo(Variant::INT, "id")));
|
|
BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_handle_highlighted", PropertyInfo(Variant::INT, "id")));
|
|
|
|
MethodInfo hvget(Variant::NIL, "_get_handle_value", PropertyInfo(Variant::INT, "id"));
|
|
hvget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
|
|
BIND_VMETHOD(hvget);
|
|
|
|
BIND_VMETHOD(MethodInfo("_set_handle", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
|
|
MethodInfo cm = MethodInfo("_commit_handle", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
|
|
cm.default_arguments.push_back(false);
|
|
BIND_VMETHOD(cm);
|
|
|
|
BIND_VMETHOD(MethodInfo(Variant::INT, "_subgizmos_intersect_ray", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
|
|
BIND_VMETHOD(MethodInfo(Variant::PACKED_INT32_ARRAY, "_subgizmos_intersect_frustum", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::ARRAY, "frustum")));
|
|
BIND_VMETHOD(MethodInfo(Variant::TRANSFORM3D, "_get_subgizmo_transform", PropertyInfo(Variant::INT, "id")));
|
|
BIND_VMETHOD(MethodInfo("_set_subgizmo_transform", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::TRANSFORM3D, "transform")));
|
|
MethodInfo cs = MethodInfo("_commit_subgizmos", PropertyInfo(Variant::PACKED_INT32_ARRAY, "ids"), PropertyInfo(Variant::ARRAY, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
|
|
cs.default_arguments.push_back(false);
|
|
BIND_VMETHOD(cs);
|
|
}
|
|
|
|
EditorNode3DGizmo::EditorNode3DGizmo() {
|
|
valid = false;
|
|
billboard_handle = false;
|
|
hidden = false;
|
|
selected = false;
|
|
spatial_node = nullptr;
|
|
gizmo_plugin = nullptr;
|
|
selectable_icon_size = -1.0f;
|
|
}
|
|
|
|
EditorNode3DGizmo::~EditorNode3DGizmo() {
|
|
if (gizmo_plugin != nullptr) {
|
|
gizmo_plugin->unregister_gizmo(this);
|
|
}
|
|
clear();
|
|
}
|
|
|
|
/////
|
|
|
|
void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color &p_color, bool p_billboard, bool p_on_top, bool p_use_vertex_color) {
|
|
Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6));
|
|
|
|
Vector<Ref<StandardMaterial3D>> mats;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
bool selected = i % 2 == 1;
|
|
bool instantiated = i < 2;
|
|
|
|
Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
|
|
|
|
Color color = instantiated ? instantiated_color : p_color;
|
|
|
|
if (!selected) {
|
|
color.a *= 0.3;
|
|
}
|
|
|
|
material->set_albedo(color);
|
|
material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
|
|
material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
|
material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1);
|
|
material->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
|
|
|
|
if (p_use_vertex_color) {
|
|
material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
|
|
material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
|
|
}
|
|
|
|
if (p_billboard) {
|
|
material->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);
|
|
}
|
|
|
|
if (p_on_top && selected) {
|
|
material->set_on_top_of_alpha();
|
|
}
|
|
|
|
mats.push_back(material);
|
|
}
|
|
|
|
materials[p_name] = mats;
|
|
}
|
|
|
|
void EditorNode3DGizmoPlugin::create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top, const Color &p_albedo) {
|
|
Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6));
|
|
|
|
Vector<Ref<StandardMaterial3D>> icons;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
bool selected = i % 2 == 1;
|
|
bool instantiated = i < 2;
|
|
|
|
Ref<StandardMaterial3D> icon = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
|
|
|
|
Color color = instantiated ? instantiated_color : p_albedo;
|
|
|
|
if (!selected) {
|
|
color.a *= 0.85;
|
|
}
|
|
|
|
icon->set_albedo(color);
|
|
|
|
icon->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
|
|
icon->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
|
|
icon->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
|
|
icon->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
|
|
icon->set_depth_draw_mode(StandardMaterial3D::DEPTH_DRAW_DISABLED);
|
|
icon->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
|
icon->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, p_texture);
|
|
icon->set_flag(StandardMaterial3D::FLAG_FIXED_SIZE, true);
|
|
icon->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);
|
|
icon->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN);
|
|
|
|
if (p_on_top && selected) {
|
|
icon->set_on_top_of_alpha();
|
|
}
|
|
|
|
icons.push_back(icon);
|
|
}
|
|
|
|
materials[p_name] = icons;
|
|
}
|
|
|
|
void EditorNode3DGizmoPlugin::create_handle_material(const String &p_name, bool p_billboard, const Ref<Texture2D> &p_icon) {
|
|
Ref<StandardMaterial3D> handle_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
|
|
|
|
handle_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
|
|
handle_material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true);
|
|
Ref<Texture2D> handle_t = p_icon != nullptr ? p_icon : Node3DEditor::get_singleton()->get_theme_icon(SNAME("Editor3DHandle"), SNAME("EditorIcons"));
|
|
handle_material->set_point_size(handle_t->get_width());
|
|
handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle_t);
|
|
handle_material->set_albedo(Color(1, 1, 1));
|
|
handle_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
|
|
handle_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
|
|
handle_material->set_on_top_of_alpha();
|
|
if (p_billboard) {
|
|
handle_material->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);
|
|
handle_material->set_on_top_of_alpha();
|
|
}
|
|
handle_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
|
|
|
materials[p_name] = Vector<Ref<StandardMaterial3D>>();
|
|
materials[p_name].push_back(handle_material);
|
|
}
|
|
|
|
void EditorNode3DGizmoPlugin::add_material(const String &p_name, Ref<StandardMaterial3D> p_material) {
|
|
materials[p_name] = Vector<Ref<StandardMaterial3D>>();
|
|
materials[p_name].push_back(p_material);
|
|
}
|
|
|
|
Ref<StandardMaterial3D> EditorNode3DGizmoPlugin::get_material(const String &p_name, const Ref<EditorNode3DGizmo> &p_gizmo) {
|
|
ERR_FAIL_COND_V(!materials.has(p_name), Ref<StandardMaterial3D>());
|
|
ERR_FAIL_COND_V(materials[p_name].size() == 0, Ref<StandardMaterial3D>());
|
|
|
|
if (p_gizmo.is_null() || materials[p_name].size() == 1) {
|
|
return materials[p_name][0];
|
|
}
|
|
|
|
int index = (p_gizmo->is_selected() ? 1 : 0) + (p_gizmo->is_editable() ? 2 : 0);
|
|
|
|
Ref<StandardMaterial3D> mat = materials[p_name][index];
|
|
|
|
if (current_state == ON_TOP && p_gizmo->is_selected()) {
|
|
mat->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
|
|
} else {
|
|
mat->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, false);
|
|
}
|
|
|
|
return mat;
|
|
}
|
|
|
|
String EditorNode3DGizmoPlugin::get_gizmo_name() const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_get_gizmo_name")) {
|
|
return get_script_instance()->call("_get_gizmo_name");
|
|
}
|
|
return TTR("Nameless gizmo");
|
|
}
|
|
|
|
int EditorNode3DGizmoPlugin::get_priority() const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_get_priority")) {
|
|
return get_script_instance()->call("_get_priority");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) {
|
|
if (get_script_instance() && get_script_instance()->has_method("_get_gizmo")) {
|
|
return get_script_instance()->call("_get_gizmo", p_spatial);
|
|
}
|
|
|
|
Ref<EditorNode3DGizmo> ref = create_gizmo(p_spatial);
|
|
|
|
if (ref.is_null()) {
|
|
return ref;
|
|
}
|
|
|
|
ref->set_plugin(this);
|
|
ref->set_spatial_node(p_spatial);
|
|
ref->set_hidden(current_state == HIDDEN);
|
|
|
|
current_gizmos.push_back(ref.ptr());
|
|
return ref;
|
|
}
|
|
|
|
void EditorNode3DGizmoPlugin::_bind_methods() {
|
|
#define GIZMO_REF PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "EditorNode3DGizmo")
|
|
|
|
BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
|
|
BIND_VMETHOD(MethodInfo(GIZMO_REF, "_create_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
|
|
|
|
ClassDB::bind_method(D_METHOD("create_material", "name", "color", "billboard", "on_top", "use_vertex_color"), &EditorNode3DGizmoPlugin::create_material, DEFVAL(false), DEFVAL(false), DEFVAL(false));
|
|
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)));
|
|
ClassDB::bind_method(D_METHOD("create_handle_material", "name", "billboard", "texture"), &EditorNode3DGizmoPlugin::create_handle_material, DEFVAL(false), DEFVAL(Variant()));
|
|
ClassDB::bind_method(D_METHOD("add_material", "name", "material"), &EditorNode3DGizmoPlugin::add_material);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_material", "name", "gizmo"), &EditorNode3DGizmoPlugin::get_material, DEFVAL(Ref<EditorNode3DGizmo>()));
|
|
|
|
BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_gizmo_name"));
|
|
BIND_VMETHOD(MethodInfo(Variant::INT, "_get_priority"));
|
|
BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_be_hidden"));
|
|
BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_selectable_when_hidden"));
|
|
|
|
BIND_VMETHOD(MethodInfo("_redraw", GIZMO_REF));
|
|
BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_handle_name", GIZMO_REF, PropertyInfo(Variant::INT, "id")));
|
|
BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_handle_highlighted", GIZMO_REF, PropertyInfo(Variant::INT, "id")));
|
|
|
|
MethodInfo hvget(Variant::NIL, "_get_handle_value", GIZMO_REF, PropertyInfo(Variant::INT, "id"));
|
|
hvget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
|
|
BIND_VMETHOD(hvget);
|
|
|
|
BIND_VMETHOD(MethodInfo("_set_handle", GIZMO_REF, PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
|
|
MethodInfo cm = MethodInfo("_commit_handle", GIZMO_REF, PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
|
|
cm.default_arguments.push_back(false);
|
|
BIND_VMETHOD(cm);
|
|
|
|
BIND_VMETHOD(MethodInfo(Variant::INT, "_subgizmos_intersect_ray", GIZMO_REF, PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
|
|
BIND_VMETHOD(MethodInfo(Variant::PACKED_INT32_ARRAY, "_subgizmos_intersect_frustum", GIZMO_REF, PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::ARRAY, "frustum")));
|
|
BIND_VMETHOD(MethodInfo(Variant::TRANSFORM3D, "_get_subgizmo_transform", GIZMO_REF, PropertyInfo(Variant::INT, "id")));
|
|
BIND_VMETHOD(MethodInfo("_set_subgizmo_transform", GIZMO_REF, PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::TRANSFORM3D, "transform")));
|
|
MethodInfo cs = MethodInfo("_commit_subgizmos", GIZMO_REF, PropertyInfo(Variant::PACKED_INT32_ARRAY, "ids"), PropertyInfo(Variant::ARRAY, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
|
|
cs.default_arguments.push_back(false);
|
|
BIND_VMETHOD(cs);
|
|
|
|
#undef GIZMO_REF
|
|
}
|
|
|
|
bool EditorNode3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
if (get_script_instance() && get_script_instance()->has_method("_has_gizmo")) {
|
|
return get_script_instance()->call("_has_gizmo", p_spatial);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::create_gizmo(Node3D *p_spatial) {
|
|
if (get_script_instance() && get_script_instance()->has_method("_create_gizmo")) {
|
|
return get_script_instance()->call("_create_gizmo", p_spatial);
|
|
}
|
|
|
|
Ref<EditorNode3DGizmo> ref;
|
|
if (has_gizmo(p_spatial)) {
|
|
ref.instantiate();
|
|
}
|
|
return ref;
|
|
}
|
|
|
|
bool EditorNode3DGizmoPlugin::can_be_hidden() const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_can_be_hidden")) {
|
|
return get_script_instance()->call("_can_be_hidden");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool EditorNode3DGizmoPlugin::is_selectable_when_hidden() const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_is_selectable_when_hidden")) {
|
|
return get_script_instance()->call("_is_selectable_when_hidden");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void EditorNode3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
if (get_script_instance() && get_script_instance()->has_method("_redraw")) {
|
|
Ref<EditorNode3DGizmo> ref(p_gizmo);
|
|
get_script_instance()->call("_redraw", ref);
|
|
}
|
|
}
|
|
|
|
bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_is_handle_highlighted")) {
|
|
return get_script_instance()->call("_is_handle_highlighted", p_gizmo, p_id);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_get_handle_name")) {
|
|
return get_script_instance()->call("_get_handle_name", p_gizmo, p_id);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
Variant EditorNode3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_get_handle_value")) {
|
|
return get_script_instance()->call("_get_handle_value", p_gizmo, p_id);
|
|
}
|
|
return Variant();
|
|
}
|
|
|
|
void EditorNode3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
if (get_script_instance() && get_script_instance()->has_method("_set_handle")) {
|
|
get_script_instance()->call("_set_handle", p_gizmo, p_id, p_camera, p_point);
|
|
}
|
|
}
|
|
|
|
void EditorNode3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
if (get_script_instance() && get_script_instance()->has_method("_commit_handle")) {
|
|
get_script_instance()->call("_commit_handle", p_gizmo, p_id, p_restore, p_cancel);
|
|
}
|
|
}
|
|
|
|
int EditorNode3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_ray")) {
|
|
return get_script_instance()->call("_subgizmos_intersect_ray", p_camera, p_point);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
Vector<int> EditorNode3DGizmoPlugin::subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_frustum")) {
|
|
Array frustum;
|
|
for (int i = 0; i < p_frustum.size(); i++) {
|
|
frustum[i] = p_frustum[i];
|
|
}
|
|
return get_script_instance()->call("_subgizmos_intersect_frustum", p_camera, frustum);
|
|
}
|
|
|
|
return Vector<int>();
|
|
}
|
|
|
|
Transform3D EditorNode3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
if (get_script_instance() && get_script_instance()->has_method("_get_subgizmo_transform")) {
|
|
return get_script_instance()->call("_get_subgizmo_transform", p_id);
|
|
}
|
|
|
|
return Transform3D();
|
|
}
|
|
|
|
void EditorNode3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) {
|
|
if (get_script_instance() && get_script_instance()->has_method("_set_subgizmo_transform")) {
|
|
get_script_instance()->call("_set_subgizmo_transform", p_id, p_transform);
|
|
}
|
|
}
|
|
|
|
void EditorNode3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) {
|
|
if (get_script_instance() && get_script_instance()->has_method("_commit_subgizmos")) {
|
|
Array ids;
|
|
for (int i = 0; i < p_ids.size(); i++) {
|
|
ids[i] = p_ids[i];
|
|
}
|
|
|
|
Array restore;
|
|
for (int i = 0; i < p_restore.size(); i++) {
|
|
restore[i] = p_restore[i];
|
|
}
|
|
|
|
get_script_instance()->call("_commit_subgizmos", ids, restore, p_cancel);
|
|
}
|
|
}
|
|
|
|
void EditorNode3DGizmoPlugin::set_state(int p_state) {
|
|
current_state = p_state;
|
|
for (int i = 0; i < current_gizmos.size(); ++i) {
|
|
current_gizmos[i]->set_hidden(current_state == HIDDEN);
|
|
}
|
|
}
|
|
|
|
int EditorNode3DGizmoPlugin::get_state() const {
|
|
return current_state;
|
|
}
|
|
|
|
void EditorNode3DGizmoPlugin::unregister_gizmo(EditorNode3DGizmo *p_gizmo) {
|
|
current_gizmos.erase(p_gizmo);
|
|
}
|
|
|
|
EditorNode3DGizmoPlugin::EditorNode3DGizmoPlugin() {
|
|
current_state = VISIBLE;
|
|
}
|
|
|
|
EditorNode3DGizmoPlugin::~EditorNode3DGizmoPlugin() {
|
|
for (int i = 0; i < current_gizmos.size(); ++i) {
|
|
current_gizmos[i]->set_plugin(nullptr);
|
|
current_gizmos[i]->get_spatial_node()->remove_gizmo(current_gizmos[i]);
|
|
}
|
|
if (Node3DEditor::get_singleton()) {
|
|
Node3DEditor::get_singleton()->update_all_gizmos();
|
|
}
|
|
}
|
|
|
|
//// light gizmo
|
|
|
|
Light3DGizmoPlugin::Light3DGizmoPlugin() {
|
|
// Enable vertex colors for the materials below as the gizmo color depends on the light color.
|
|
create_material("lines_primary", Color(1, 1, 1), false, false, true);
|
|
create_material("lines_secondary", Color(1, 1, 1, 0.35), false, false, true);
|
|
create_material("lines_billboard", Color(1, 1, 1), true, false, true);
|
|
|
|
create_icon_material("light_directional_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoDirectionalLight"), SNAME("EditorIcons")));
|
|
create_icon_material("light_omni_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoLight"), SNAME("EditorIcons")));
|
|
create_icon_material("light_spot_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoSpotLight"), SNAME("EditorIcons")));
|
|
|
|
create_handle_material("handles");
|
|
create_handle_material("handles_billboard", true);
|
|
}
|
|
|
|
bool Light3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<Light3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String Light3DGizmoPlugin::get_gizmo_name() const {
|
|
return "Light3D";
|
|
}
|
|
|
|
int Light3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
String Light3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
if (p_id == 0) {
|
|
return "Radius";
|
|
} else {
|
|
return "Aperture";
|
|
}
|
|
}
|
|
|
|
Variant Light3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node());
|
|
if (p_id == 0) {
|
|
return light->get_param(Light3D::PARAM_RANGE);
|
|
}
|
|
if (p_id == 1) {
|
|
return light->get_param(Light3D::PARAM_SPOT_ANGLE);
|
|
}
|
|
|
|
return Variant();
|
|
}
|
|
|
|
static float _find_closest_angle_to_half_pi_arc(const Vector3 &p_from, const Vector3 &p_to, float p_arc_radius, const Transform3D &p_arc_xform) {
|
|
//bleh, discrete is simpler
|
|
static const int arc_test_points = 64;
|
|
float min_d = 1e20;
|
|
Vector3 min_p;
|
|
|
|
for (int i = 0; i < arc_test_points; i++) {
|
|
float a = i * Math_PI * 0.5 / arc_test_points;
|
|
float an = (i + 1) * Math_PI * 0.5 / arc_test_points;
|
|
Vector3 p = Vector3(Math::cos(a), 0, -Math::sin(a)) * p_arc_radius;
|
|
Vector3 n = Vector3(Math::cos(an), 0, -Math::sin(an)) * p_arc_radius;
|
|
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(p, n, p_from, p_to, ra, rb);
|
|
|
|
float d = ra.distance_to(rb);
|
|
if (d < min_d) {
|
|
min_d = d;
|
|
min_p = ra;
|
|
}
|
|
}
|
|
|
|
//min_p = p_arc_xform.affine_inverse().xform(min_p);
|
|
float a = (Math_PI * 0.5) - Vector2(min_p.x, -min_p.z).angle();
|
|
return Math::rad2deg(a);
|
|
}
|
|
|
|
void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node());
|
|
Transform3D gt = light->get_global_transform();
|
|
Transform3D gi = gt.affine_inverse();
|
|
|
|
Vector3 ray_from = p_camera->project_ray_origin(p_point);
|
|
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
|
|
|
|
Vector3 s[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
|
|
if (p_id == 0) {
|
|
if (Object::cast_to<SpotLight3D>(light)) {
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(0, 0, -4096), s[0], s[1], ra, rb);
|
|
|
|
float d = -ra.z;
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
if (d <= 0) { // Equal is here for negative zero.
|
|
d = 0;
|
|
}
|
|
|
|
light->set_param(Light3D::PARAM_RANGE, d);
|
|
} else if (Object::cast_to<OmniLight3D>(light)) {
|
|
Plane cp = Plane(gt.origin, p_camera->get_transform().basis.get_axis(2));
|
|
|
|
Vector3 inters;
|
|
if (cp.intersects_ray(ray_from, ray_dir, &inters)) {
|
|
float r = inters.distance_to(gt.origin);
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
r = Math::snapped(r, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
light->set_param(Light3D::PARAM_RANGE, r);
|
|
}
|
|
}
|
|
|
|
} else if (p_id == 1) {
|
|
float a = _find_closest_angle_to_half_pi_arc(s[0], s[1], light->get_param(Light3D::PARAM_RANGE), gt);
|
|
light->set_param(Light3D::PARAM_SPOT_ANGLE, CLAMP(a, 0.01, 89.99));
|
|
}
|
|
}
|
|
|
|
void Light3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node());
|
|
if (p_cancel) {
|
|
light->set_param(p_id == 0 ? Light3D::PARAM_RANGE : Light3D::PARAM_SPOT_ANGLE, p_restore);
|
|
|
|
} else if (p_id == 0) {
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change Light Radius"));
|
|
ur->add_do_method(light, "set_param", Light3D::PARAM_RANGE, light->get_param(Light3D::PARAM_RANGE));
|
|
ur->add_undo_method(light, "set_param", Light3D::PARAM_RANGE, p_restore);
|
|
ur->commit_action();
|
|
} else if (p_id == 1) {
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change Light Radius"));
|
|
ur->add_do_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, light->get_param(Light3D::PARAM_SPOT_ANGLE));
|
|
ur->add_undo_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, p_restore);
|
|
ur->commit_action();
|
|
}
|
|
}
|
|
|
|
void Light3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node());
|
|
|
|
Color color = light->get_color();
|
|
// Make the gizmo color as bright as possible for better visibility
|
|
color.set_hsv(color.get_h(), color.get_s(), 1);
|
|
|
|
p_gizmo->clear();
|
|
|
|
if (Object::cast_to<DirectionalLight3D>(light)) {
|
|
Ref<Material> material = get_material("lines_primary", p_gizmo);
|
|
Ref<Material> icon = get_material("light_directional_icon", p_gizmo);
|
|
|
|
const int arrow_points = 7;
|
|
const float arrow_length = 1.5;
|
|
|
|
Vector3 arrow[arrow_points] = {
|
|
Vector3(0, 0, -1),
|
|
Vector3(0, 0.8, 0),
|
|
Vector3(0, 0.3, 0),
|
|
Vector3(0, 0.3, arrow_length),
|
|
Vector3(0, -0.3, arrow_length),
|
|
Vector3(0, -0.3, 0),
|
|
Vector3(0, -0.8, 0)
|
|
};
|
|
|
|
int arrow_sides = 2;
|
|
|
|
Vector<Vector3> lines;
|
|
|
|
for (int i = 0; i < arrow_sides; i++) {
|
|
for (int j = 0; j < arrow_points; j++) {
|
|
Basis ma(Vector3(0, 0, 1), Math_PI * i / arrow_sides);
|
|
|
|
Vector3 v1 = arrow[j] - Vector3(0, 0, arrow_length);
|
|
Vector3 v2 = arrow[(j + 1) % arrow_points] - Vector3(0, 0, arrow_length);
|
|
|
|
lines.push_back(ma.xform(v1));
|
|
lines.push_back(ma.xform(v2));
|
|
}
|
|
}
|
|
|
|
p_gizmo->add_lines(lines, material, false, color);
|
|
p_gizmo->add_unscaled_billboard(icon, 0.05, color);
|
|
}
|
|
|
|
if (Object::cast_to<OmniLight3D>(light)) {
|
|
// Use both a billboard circle and 3 non-billboard circles for a better sphere-like representation
|
|
const Ref<Material> lines_material = get_material("lines_secondary", p_gizmo);
|
|
const Ref<Material> lines_billboard_material = get_material("lines_billboard", p_gizmo);
|
|
const Ref<Material> icon = get_material("light_omni_icon", p_gizmo);
|
|
|
|
OmniLight3D *on = Object::cast_to<OmniLight3D>(light);
|
|
const float r = on->get_param(Light3D::PARAM_RANGE);
|
|
Vector<Vector3> points;
|
|
Vector<Vector3> points_billboard;
|
|
|
|
for (int i = 0; i < 120; i++) {
|
|
// Create a circle
|
|
const float ra = Math::deg2rad((float)(i * 3));
|
|
const float rb = Math::deg2rad((float)((i + 1) * 3));
|
|
const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
|
|
const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
|
|
|
|
// Draw axis-aligned circles
|
|
points.push_back(Vector3(a.x, 0, a.y));
|
|
points.push_back(Vector3(b.x, 0, b.y));
|
|
points.push_back(Vector3(0, a.x, a.y));
|
|
points.push_back(Vector3(0, b.x, b.y));
|
|
points.push_back(Vector3(a.x, a.y, 0));
|
|
points.push_back(Vector3(b.x, b.y, 0));
|
|
|
|
// Draw a billboarded circle
|
|
points_billboard.push_back(Vector3(a.x, a.y, 0));
|
|
points_billboard.push_back(Vector3(b.x, b.y, 0));
|
|
}
|
|
|
|
p_gizmo->add_lines(points, lines_material, true, color);
|
|
p_gizmo->add_lines(points_billboard, lines_billboard_material, true, color);
|
|
p_gizmo->add_unscaled_billboard(icon, 0.05, color);
|
|
|
|
Vector<Vector3> handles;
|
|
handles.push_back(Vector3(r, 0, 0));
|
|
p_gizmo->add_handles(handles, get_material("handles_billboard"), Vector<int>(), true);
|
|
}
|
|
|
|
if (Object::cast_to<SpotLight3D>(light)) {
|
|
const Ref<Material> material_primary = get_material("lines_primary", p_gizmo);
|
|
const Ref<Material> material_secondary = get_material("lines_secondary", p_gizmo);
|
|
const Ref<Material> icon = get_material("light_spot_icon", p_gizmo);
|
|
|
|
Vector<Vector3> points_primary;
|
|
Vector<Vector3> points_secondary;
|
|
SpotLight3D *sl = Object::cast_to<SpotLight3D>(light);
|
|
|
|
float r = sl->get_param(Light3D::PARAM_RANGE);
|
|
float w = r * Math::sin(Math::deg2rad(sl->get_param(Light3D::PARAM_SPOT_ANGLE)));
|
|
float d = r * Math::cos(Math::deg2rad(sl->get_param(Light3D::PARAM_SPOT_ANGLE)));
|
|
|
|
for (int i = 0; i < 120; i++) {
|
|
// Draw a circle
|
|
const float ra = Math::deg2rad((float)(i * 3));
|
|
const float rb = Math::deg2rad((float)((i + 1) * 3));
|
|
const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w;
|
|
const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w;
|
|
|
|
points_primary.push_back(Vector3(a.x, a.y, -d));
|
|
points_primary.push_back(Vector3(b.x, b.y, -d));
|
|
|
|
if (i % 15 == 0) {
|
|
// Draw 8 lines from the cone origin to the sides of the circle
|
|
points_secondary.push_back(Vector3(a.x, a.y, -d));
|
|
points_secondary.push_back(Vector3());
|
|
}
|
|
}
|
|
|
|
points_primary.push_back(Vector3(0, 0, -r));
|
|
points_primary.push_back(Vector3());
|
|
|
|
p_gizmo->add_lines(points_primary, material_primary, false, color);
|
|
p_gizmo->add_lines(points_secondary, material_secondary, false, color);
|
|
|
|
Vector<Vector3> handles;
|
|
handles.push_back(Vector3(0, 0, -r));
|
|
handles.push_back(Vector3(w, 0, -d));
|
|
|
|
p_gizmo->add_handles(handles, get_material("handles"));
|
|
p_gizmo->add_unscaled_billboard(icon, 0.05, color);
|
|
}
|
|
}
|
|
|
|
//////
|
|
|
|
//// player gizmo
|
|
AudioStreamPlayer3DGizmoPlugin::AudioStreamPlayer3DGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/stream_player_3d", Color(0.4, 0.8, 1));
|
|
|
|
create_icon_material("stream_player_3d_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("Gizmo3DSamplePlayer"), SNAME("EditorIcons")));
|
|
create_material("stream_player_3d_material_primary", gizmo_color);
|
|
create_material("stream_player_3d_material_secondary", gizmo_color * Color(1, 1, 1, 0.35));
|
|
create_handle_material("handles");
|
|
}
|
|
|
|
bool AudioStreamPlayer3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<AudioStreamPlayer3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String AudioStreamPlayer3DGizmoPlugin::get_gizmo_name() const {
|
|
return "AudioStreamPlayer3D";
|
|
}
|
|
|
|
int AudioStreamPlayer3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
String AudioStreamPlayer3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
return "Emission Radius";
|
|
}
|
|
|
|
Variant AudioStreamPlayer3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node());
|
|
return player->get_emission_angle();
|
|
}
|
|
|
|
void AudioStreamPlayer3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node());
|
|
|
|
Transform3D gt = player->get_global_transform();
|
|
Transform3D gi = gt.affine_inverse();
|
|
|
|
Vector3 ray_from = p_camera->project_ray_origin(p_point);
|
|
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
|
|
Vector3 ray_to = ray_from + ray_dir * 4096;
|
|
|
|
ray_from = gi.xform(ray_from);
|
|
ray_to = gi.xform(ray_to);
|
|
|
|
float closest_dist = 1e20;
|
|
float closest_angle = 1e20;
|
|
|
|
for (int i = 0; i < 180; i++) {
|
|
float a = Math::deg2rad((float)i);
|
|
float an = Math::deg2rad((float)(i + 1));
|
|
|
|
Vector3 from(Math::sin(a), 0, -Math::cos(a));
|
|
Vector3 to(Math::sin(an), 0, -Math::cos(an));
|
|
|
|
Vector3 r1, r2;
|
|
Geometry3D::get_closest_points_between_segments(from, to, ray_from, ray_to, r1, r2);
|
|
float d = r1.distance_to(r2);
|
|
if (d < closest_dist) {
|
|
closest_dist = d;
|
|
closest_angle = i;
|
|
}
|
|
}
|
|
|
|
if (closest_angle < 91) {
|
|
player->set_emission_angle(closest_angle);
|
|
}
|
|
}
|
|
|
|
void AudioStreamPlayer3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node());
|
|
|
|
if (p_cancel) {
|
|
player->set_emission_angle(p_restore);
|
|
|
|
} else {
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change AudioStreamPlayer3D Emission Angle"));
|
|
ur->add_do_method(player, "set_emission_angle", player->get_emission_angle());
|
|
ur->add_undo_method(player, "set_emission_angle", p_restore);
|
|
ur->commit_action();
|
|
}
|
|
}
|
|
|
|
void AudioStreamPlayer3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
const AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
const Ref<Material> icon = get_material("stream_player_3d_icon", p_gizmo);
|
|
|
|
if (player->is_emission_angle_enabled()) {
|
|
const float pc = player->get_emission_angle();
|
|
const float ofs = -Math::cos(Math::deg2rad(pc));
|
|
const float radius = Math::sin(Math::deg2rad(pc));
|
|
|
|
Vector<Vector3> points_primary;
|
|
points_primary.resize(200);
|
|
|
|
real_t step = Math_TAU / 100.0;
|
|
for (int i = 0; i < 100; i++) {
|
|
const float a = i * step;
|
|
const float an = (i + 1) * step;
|
|
|
|
const Vector3 from(Math::sin(a) * radius, Math::cos(a) * radius, ofs);
|
|
const Vector3 to(Math::sin(an) * radius, Math::cos(an) * radius, ofs);
|
|
|
|
points_primary.write[i * 2 + 0] = from;
|
|
points_primary.write[i * 2 + 1] = to;
|
|
}
|
|
|
|
const Ref<Material> material_primary = get_material("stream_player_3d_material_primary", p_gizmo);
|
|
p_gizmo->add_lines(points_primary, material_primary);
|
|
|
|
Vector<Vector3> points_secondary;
|
|
points_secondary.resize(16);
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
const float a = i * (Math_TAU / 8.0);
|
|
const Vector3 from(Math::sin(a) * radius, Math::cos(a) * radius, ofs);
|
|
|
|
points_secondary.write[i * 2 + 0] = from;
|
|
points_secondary.write[i * 2 + 1] = Vector3();
|
|
}
|
|
|
|
const Ref<Material> material_secondary = get_material("stream_player_3d_material_secondary", p_gizmo);
|
|
p_gizmo->add_lines(points_secondary, material_secondary);
|
|
|
|
Vector<Vector3> handles;
|
|
const float ha = Math::deg2rad(player->get_emission_angle());
|
|
handles.push_back(Vector3(Math::sin(ha), 0, -Math::cos(ha)));
|
|
p_gizmo->add_handles(handles, get_material("handles"));
|
|
}
|
|
|
|
p_gizmo->add_unscaled_billboard(icon, 0.05);
|
|
}
|
|
|
|
//////
|
|
|
|
Camera3DGizmoPlugin::Camera3DGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/camera", Color(0.8, 0.4, 0.8));
|
|
|
|
create_material("camera_material", gizmo_color);
|
|
create_handle_material("handles");
|
|
}
|
|
|
|
bool Camera3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<Camera3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String Camera3DGizmoPlugin::get_gizmo_name() const {
|
|
return "Camera3D";
|
|
}
|
|
|
|
int Camera3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
String Camera3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
|
|
|
|
if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
|
|
return "FOV";
|
|
} else {
|
|
return "Size";
|
|
}
|
|
}
|
|
|
|
Variant Camera3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
|
|
|
|
if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
|
|
return camera->get_fov();
|
|
} else {
|
|
return camera->get_size();
|
|
}
|
|
}
|
|
|
|
void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
|
|
|
|
Transform3D gt = camera->get_global_transform();
|
|
Transform3D gi = gt.affine_inverse();
|
|
|
|
Vector3 ray_from = p_camera->project_ray_origin(p_point);
|
|
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
|
|
|
|
Vector3 s[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
|
|
|
|
if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
|
|
Transform3D gt2 = camera->get_global_transform();
|
|
float a = _find_closest_angle_to_half_pi_arc(s[0], s[1], 1.0, gt2);
|
|
camera->set("fov", CLAMP(a * 2.0, 1, 179));
|
|
} else {
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(Vector3(0, 0, -1), Vector3(4096, 0, -1), s[0], s[1], ra, rb);
|
|
float d = ra.x * 2.0;
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
d = CLAMP(d, 0.1, 16384);
|
|
|
|
camera->set("size", d);
|
|
}
|
|
}
|
|
|
|
void Camera3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
|
|
|
|
if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
|
|
if (p_cancel) {
|
|
camera->set("fov", p_restore);
|
|
} else {
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change Camera FOV"));
|
|
ur->add_do_property(camera, "fov", camera->get_fov());
|
|
ur->add_undo_property(camera, "fov", p_restore);
|
|
ur->commit_action();
|
|
}
|
|
|
|
} else {
|
|
if (p_cancel) {
|
|
camera->set("size", p_restore);
|
|
} else {
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change Camera Size"));
|
|
ur->add_do_property(camera, "size", camera->get_size());
|
|
ur->add_undo_property(camera, "size", p_restore);
|
|
ur->commit_action();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Vector<Vector3> lines;
|
|
Vector<Vector3> handles;
|
|
|
|
Ref<Material> material = get_material("camera_material", p_gizmo);
|
|
|
|
#define ADD_TRIANGLE(m_a, m_b, m_c) \
|
|
{ \
|
|
lines.push_back(m_a); \
|
|
lines.push_back(m_b); \
|
|
lines.push_back(m_b); \
|
|
lines.push_back(m_c); \
|
|
lines.push_back(m_c); \
|
|
lines.push_back(m_a); \
|
|
}
|
|
|
|
#define ADD_QUAD(m_a, m_b, m_c, m_d) \
|
|
{ \
|
|
lines.push_back(m_a); \
|
|
lines.push_back(m_b); \
|
|
lines.push_back(m_b); \
|
|
lines.push_back(m_c); \
|
|
lines.push_back(m_c); \
|
|
lines.push_back(m_d); \
|
|
lines.push_back(m_d); \
|
|
lines.push_back(m_a); \
|
|
}
|
|
|
|
switch (camera->get_projection()) {
|
|
case Camera3D::PROJECTION_PERSPECTIVE: {
|
|
// The real FOV is halved for accurate representation
|
|
float fov = camera->get_fov() / 2.0;
|
|
|
|
Vector3 side = Vector3(Math::sin(Math::deg2rad(fov)), 0, -Math::cos(Math::deg2rad(fov)));
|
|
Vector3 nside = side;
|
|
nside.x = -nside.x;
|
|
Vector3 up = Vector3(0, side.x, 0);
|
|
|
|
ADD_TRIANGLE(Vector3(), side + up, side - up);
|
|
ADD_TRIANGLE(Vector3(), nside + up, nside - up);
|
|
ADD_TRIANGLE(Vector3(), side + up, nside + up);
|
|
ADD_TRIANGLE(Vector3(), side - up, nside - up);
|
|
|
|
handles.push_back(side);
|
|
side.x *= 0.25;
|
|
nside.x *= 0.25;
|
|
Vector3 tup(0, up.y * 3 / 2, side.z);
|
|
ADD_TRIANGLE(tup, side + up, nside + up);
|
|
|
|
} break;
|
|
case Camera3D::PROJECTION_ORTHOGONAL: {
|
|
float size = camera->get_size();
|
|
|
|
float hsize = size * 0.5;
|
|
Vector3 right(hsize, 0, 0);
|
|
Vector3 up(0, hsize, 0);
|
|
Vector3 back(0, 0, -1.0);
|
|
Vector3 front(0, 0, 0);
|
|
|
|
ADD_QUAD(-up - right, -up + right, up + right, up - right);
|
|
ADD_QUAD(-up - right + back, -up + right + back, up + right + back, up - right + back);
|
|
ADD_QUAD(up + right, up + right + back, up - right + back, up - right);
|
|
ADD_QUAD(-up + right, -up + right + back, -up - right + back, -up - right);
|
|
|
|
handles.push_back(right + back);
|
|
|
|
right.x *= 0.25;
|
|
Vector3 tup(0, up.y * 3 / 2, back.z);
|
|
ADD_TRIANGLE(tup, right + up + back, -right + up + back);
|
|
|
|
} break;
|
|
case Camera3D::PROJECTION_FRUSTUM: {
|
|
float hsize = camera->get_size() / 2.0;
|
|
|
|
Vector3 side = Vector3(hsize, 0, -camera->get_near()).normalized();
|
|
Vector3 nside = side;
|
|
nside.x = -nside.x;
|
|
Vector3 up = Vector3(0, side.x, 0);
|
|
Vector3 offset = Vector3(camera->get_frustum_offset().x, camera->get_frustum_offset().y, 0.0);
|
|
|
|
ADD_TRIANGLE(Vector3(), side + up + offset, side - up + offset);
|
|
ADD_TRIANGLE(Vector3(), nside + up + offset, nside - up + offset);
|
|
ADD_TRIANGLE(Vector3(), side + up + offset, nside + up + offset);
|
|
ADD_TRIANGLE(Vector3(), side - up + offset, nside - up + offset);
|
|
|
|
side.x *= 0.25;
|
|
nside.x *= 0.25;
|
|
Vector3 tup(0, up.y * 3 / 2, side.z);
|
|
ADD_TRIANGLE(tup + offset, side + up + offset, nside + up + offset);
|
|
}
|
|
}
|
|
|
|
#undef ADD_TRIANGLE
|
|
#undef ADD_QUAD
|
|
|
|
p_gizmo->add_lines(lines, material);
|
|
p_gizmo->add_handles(handles, get_material("handles"));
|
|
|
|
ClippedCamera3D *clipcam = Object::cast_to<ClippedCamera3D>(camera);
|
|
if (clipcam) {
|
|
Node3D *parent = Object::cast_to<Node3D>(camera->get_parent());
|
|
if (!parent) {
|
|
return;
|
|
}
|
|
Vector3 cam_normal = -camera->get_global_transform().basis.get_axis(Vector3::AXIS_Z).normalized();
|
|
Vector3 cam_x = camera->get_global_transform().basis.get_axis(Vector3::AXIS_X).normalized();
|
|
Vector3 cam_y = camera->get_global_transform().basis.get_axis(Vector3::AXIS_Y).normalized();
|
|
Vector3 cam_pos = camera->get_global_transform().origin;
|
|
Vector3 parent_pos = parent->get_global_transform().origin;
|
|
|
|
Plane parent_plane(parent_pos, cam_normal);
|
|
Vector3 ray_from = parent_plane.project(cam_pos);
|
|
|
|
lines.clear();
|
|
lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5);
|
|
lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5);
|
|
|
|
lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5);
|
|
lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5);
|
|
|
|
lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5);
|
|
lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5);
|
|
|
|
lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5);
|
|
lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5);
|
|
|
|
if (parent_plane.distance_to(cam_pos) < 0) {
|
|
lines.push_back(ray_from);
|
|
lines.push_back(cam_pos);
|
|
}
|
|
|
|
Transform3D local = camera->get_global_transform().affine_inverse();
|
|
for (int i = 0; i < lines.size(); i++) {
|
|
lines.write[i] = local.xform(lines[i]);
|
|
}
|
|
|
|
p_gizmo->add_lines(lines, material);
|
|
}
|
|
}
|
|
|
|
//////
|
|
|
|
MeshInstance3DGizmoPlugin::MeshInstance3DGizmoPlugin() {
|
|
}
|
|
|
|
bool MeshInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<MeshInstance3D>(p_spatial) != nullptr && Object::cast_to<SoftBody3D>(p_spatial) == nullptr;
|
|
}
|
|
|
|
String MeshInstance3DGizmoPlugin::get_gizmo_name() const {
|
|
return "MeshInstance3D";
|
|
}
|
|
|
|
int MeshInstance3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
bool MeshInstance3DGizmoPlugin::can_be_hidden() const {
|
|
return false;
|
|
}
|
|
|
|
void MeshInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
MeshInstance3D *mesh = Object::cast_to<MeshInstance3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Ref<Mesh> m = mesh->get_mesh();
|
|
|
|
if (!m.is_valid()) {
|
|
return; //none
|
|
}
|
|
|
|
Ref<TriangleMesh> tm = m->generate_triangle_mesh();
|
|
if (tm.is_valid()) {
|
|
p_gizmo->add_collision_triangles(tm);
|
|
}
|
|
}
|
|
|
|
/////
|
|
|
|
OccluderInstance3DGizmoPlugin::OccluderInstance3DGizmoPlugin() {
|
|
create_material("line_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/occluder", Color(0.8, 0.5, 1)));
|
|
}
|
|
|
|
bool OccluderInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<OccluderInstance3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String OccluderInstance3DGizmoPlugin::get_gizmo_name() const {
|
|
return "OccluderInstance3D";
|
|
}
|
|
|
|
int OccluderInstance3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
void OccluderInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
OccluderInstance3D *occluder_instance = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Ref<Occluder3D> o = occluder_instance->get_occluder();
|
|
|
|
if (!o.is_valid()) {
|
|
return;
|
|
}
|
|
|
|
Vector<Vector3> lines = o->get_debug_lines();
|
|
if (!lines.is_empty()) {
|
|
Ref<Material> material = get_material("line_material", p_gizmo);
|
|
p_gizmo->add_lines(lines, material);
|
|
p_gizmo->add_collision_segments(lines);
|
|
}
|
|
}
|
|
|
|
/////
|
|
|
|
Sprite3DGizmoPlugin::Sprite3DGizmoPlugin() {
|
|
}
|
|
|
|
bool Sprite3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<Sprite3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String Sprite3DGizmoPlugin::get_gizmo_name() const {
|
|
return "Sprite3D";
|
|
}
|
|
|
|
int Sprite3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
bool Sprite3DGizmoPlugin::can_be_hidden() const {
|
|
return false;
|
|
}
|
|
|
|
void Sprite3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
Sprite3D *sprite = Object::cast_to<Sprite3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Ref<TriangleMesh> tm = sprite->generate_triangle_mesh();
|
|
if (tm.is_valid()) {
|
|
p_gizmo->add_collision_triangles(tm);
|
|
}
|
|
}
|
|
|
|
///
|
|
|
|
Position3DGizmoPlugin::Position3DGizmoPlugin() {
|
|
pos3d_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
|
|
cursor_points = Vector<Vector3>();
|
|
|
|
Vector<Color> cursor_colors;
|
|
const float cs = 0.25;
|
|
// Add more points to create a "hard stop" in the color gradient.
|
|
cursor_points.push_back(Vector3(+cs, 0, 0));
|
|
cursor_points.push_back(Vector3());
|
|
cursor_points.push_back(Vector3());
|
|
cursor_points.push_back(Vector3(-cs, 0, 0));
|
|
|
|
cursor_points.push_back(Vector3(0, +cs, 0));
|
|
cursor_points.push_back(Vector3());
|
|
cursor_points.push_back(Vector3());
|
|
cursor_points.push_back(Vector3(0, -cs, 0));
|
|
|
|
cursor_points.push_back(Vector3(0, 0, +cs));
|
|
cursor_points.push_back(Vector3());
|
|
cursor_points.push_back(Vector3());
|
|
cursor_points.push_back(Vector3(0, 0, -cs));
|
|
|
|
// Use the axis color which is brighter for the positive axis.
|
|
// Use a darkened axis color for the negative axis.
|
|
// This makes it possible to see in which direction the Position3D node is rotated
|
|
// (which can be important depending on how it's used).
|
|
const Color color_x = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("axis_x_color"), SNAME("Editor"));
|
|
cursor_colors.push_back(color_x);
|
|
cursor_colors.push_back(color_x);
|
|
// FIXME: Use less strong darkening factor once GH-48573 is fixed.
|
|
// The current darkening factor compensates for lines being too bright in the 3D editor.
|
|
cursor_colors.push_back(color_x.lerp(Color(0, 0, 0), 0.75));
|
|
cursor_colors.push_back(color_x.lerp(Color(0, 0, 0), 0.75));
|
|
|
|
const Color color_y = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("axis_y_color"), SNAME("Editor"));
|
|
cursor_colors.push_back(color_y);
|
|
cursor_colors.push_back(color_y);
|
|
cursor_colors.push_back(color_y.lerp(Color(0, 0, 0), 0.75));
|
|
cursor_colors.push_back(color_y.lerp(Color(0, 0, 0), 0.75));
|
|
|
|
const Color color_z = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("axis_z_color"), SNAME("Editor"));
|
|
cursor_colors.push_back(color_z);
|
|
cursor_colors.push_back(color_z);
|
|
cursor_colors.push_back(color_z.lerp(Color(0, 0, 0), 0.75));
|
|
cursor_colors.push_back(color_z.lerp(Color(0, 0, 0), 0.75));
|
|
|
|
Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
|
|
mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
|
|
mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
|
|
mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
|
|
mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
|
|
|
Array d;
|
|
d.resize(RS::ARRAY_MAX);
|
|
d[Mesh::ARRAY_VERTEX] = cursor_points;
|
|
d[Mesh::ARRAY_COLOR] = cursor_colors;
|
|
pos3d_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, d);
|
|
pos3d_mesh->surface_set_material(0, mat);
|
|
}
|
|
|
|
bool Position3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<Position3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String Position3DGizmoPlugin::get_gizmo_name() const {
|
|
return "Position3D";
|
|
}
|
|
|
|
int Position3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
void Position3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
p_gizmo->clear();
|
|
p_gizmo->add_mesh(pos3d_mesh);
|
|
p_gizmo->add_collision_segments(cursor_points);
|
|
}
|
|
|
|
/////
|
|
|
|
Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4));
|
|
create_material("skeleton_material", gizmo_color);
|
|
}
|
|
|
|
bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<Skeleton3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String Skeleton3DGizmoPlugin::get_gizmo_name() const {
|
|
return "Skeleton3D";
|
|
}
|
|
|
|
int Skeleton3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Ref<Material> material = get_material("skeleton_material", p_gizmo);
|
|
|
|
Ref<SurfaceTool> surface_tool(memnew(SurfaceTool));
|
|
|
|
surface_tool->begin(Mesh::PRIMITIVE_LINES);
|
|
surface_tool->set_material(material);
|
|
LocalVector<Transform3D> grests;
|
|
grests.resize(skel->get_bone_count());
|
|
|
|
LocalVector<int> bones;
|
|
LocalVector<real_t> weights;
|
|
bones.resize(4);
|
|
weights.resize(4);
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
bones[i] = 0;
|
|
weights[i] = 0;
|
|
}
|
|
|
|
weights[0] = 1;
|
|
|
|
AABB aabb;
|
|
|
|
Color bonecolor = Color(1.0, 0.4, 0.4, 0.3);
|
|
Color rootcolor = Color(0.4, 1.0, 0.4, 0.1);
|
|
|
|
//LocalVector<int> bones_to_process = skel->get_parentless_bones();
|
|
LocalVector<int> bones_to_process;
|
|
bones_to_process = skel->get_parentless_bones();
|
|
|
|
while (bones_to_process.size() > 0) {
|
|
int current_bone_idx = bones_to_process[0];
|
|
bones_to_process.erase(current_bone_idx);
|
|
|
|
LocalVector<int> child_bones_vector;
|
|
child_bones_vector = skel->get_bone_children(current_bone_idx);
|
|
int child_bones_size = child_bones_vector.size();
|
|
|
|
// You have children but no parent, then you must be a root/parentless bone.
|
|
if (child_bones_size >= 0 && skel->get_bone_parent(current_bone_idx) <= 0) {
|
|
grests[current_bone_idx] = skel->global_pose_to_local_pose(current_bone_idx, skel->get_bone_global_pose(current_bone_idx));
|
|
}
|
|
|
|
for (int i = 0; i < child_bones_size; i++) {
|
|
int child_bone_idx = child_bones_vector[i];
|
|
|
|
grests[child_bone_idx] = skel->global_pose_to_local_pose(child_bone_idx, skel->get_bone_global_pose(child_bone_idx));
|
|
Vector3 v0 = grests[current_bone_idx].origin;
|
|
Vector3 v1 = grests[child_bone_idx].origin;
|
|
Vector3 d = skel->get_bone_rest(child_bone_idx).origin.normalized();
|
|
real_t dist = skel->get_bone_rest(child_bone_idx).origin.length();
|
|
|
|
// Find closest axis.
|
|
int closest = -1;
|
|
real_t closest_d = 0.0;
|
|
for (int j = 0; j < 3; j++) {
|
|
real_t dp = Math::abs(grests[current_bone_idx].basis[j].normalized().dot(d));
|
|
if (j == 0 || dp > closest_d) {
|
|
closest = j;
|
|
}
|
|
}
|
|
|
|
// Find closest other.
|
|
Vector3 first;
|
|
Vector3 points[4];
|
|
int point_idx = 0;
|
|
for (int j = 0; j < 3; j++) {
|
|
bones[0] = current_bone_idx;
|
|
surface_tool->set_bones(bones);
|
|
surface_tool->set_weights(weights);
|
|
surface_tool->set_color(rootcolor);
|
|
surface_tool->add_vertex(v0 - grests[current_bone_idx].basis[j].normalized() * dist * 0.05);
|
|
surface_tool->set_bones(bones);
|
|
surface_tool->set_weights(weights);
|
|
surface_tool->set_color(rootcolor);
|
|
surface_tool->add_vertex(v0 + grests[current_bone_idx].basis[j].normalized() * dist * 0.05);
|
|
|
|
if (j == closest) {
|
|
continue;
|
|
}
|
|
|
|
Vector3 axis;
|
|
if (first == Vector3()) {
|
|
axis = d.cross(d.cross(grests[current_bone_idx].basis[j])).normalized();
|
|
first = axis;
|
|
} else {
|
|
axis = d.cross(first).normalized();
|
|
}
|
|
|
|
for (int k = 0; k < 2; k++) {
|
|
if (k == 1) {
|
|
axis = -axis;
|
|
}
|
|
Vector3 point = v0 + d * dist * 0.2;
|
|
point += axis * dist * 0.1;
|
|
|
|
bones[0] = current_bone_idx;
|
|
surface_tool->set_bones(bones);
|
|
surface_tool->set_weights(weights);
|
|
surface_tool->set_color(bonecolor);
|
|
surface_tool->add_vertex(v0);
|
|
surface_tool->set_bones(bones);
|
|
surface_tool->set_weights(weights);
|
|
surface_tool->set_color(bonecolor);
|
|
surface_tool->add_vertex(point);
|
|
|
|
bones[0] = current_bone_idx;
|
|
surface_tool->set_bones(bones);
|
|
surface_tool->set_weights(weights);
|
|
surface_tool->set_color(bonecolor);
|
|
surface_tool->add_vertex(point);
|
|
bones[0] = child_bone_idx;
|
|
surface_tool->set_bones(bones);
|
|
surface_tool->set_weights(weights);
|
|
surface_tool->set_color(bonecolor);
|
|
surface_tool->add_vertex(v1);
|
|
points[point_idx++] = point;
|
|
}
|
|
}
|
|
SWAP(points[1], points[2]);
|
|
for (int j = 0; j < 4; j++) {
|
|
bones[0] = current_bone_idx;
|
|
surface_tool->set_bones(bones);
|
|
surface_tool->set_weights(weights);
|
|
surface_tool->set_color(bonecolor);
|
|
surface_tool->add_vertex(points[j]);
|
|
surface_tool->set_bones(bones);
|
|
surface_tool->set_weights(weights);
|
|
surface_tool->set_color(bonecolor);
|
|
surface_tool->add_vertex(points[(j + 1) % 4]);
|
|
}
|
|
|
|
// Add the bone's children to the list of bones to be processed.
|
|
bones_to_process.push_back(child_bones_vector[i]);
|
|
}
|
|
}
|
|
|
|
Ref<ArrayMesh> m = surface_tool->commit();
|
|
p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skel->register_skin(Ref<Skin>()));
|
|
}
|
|
|
|
////
|
|
|
|
PhysicalBone3DGizmoPlugin::PhysicalBone3DGizmoPlugin() {
|
|
create_material("joint_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1)));
|
|
}
|
|
|
|
bool PhysicalBone3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<PhysicalBone3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String PhysicalBone3DGizmoPlugin::get_gizmo_name() const {
|
|
return "PhysicalBone3D";
|
|
}
|
|
|
|
int PhysicalBone3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
void PhysicalBone3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
p_gizmo->clear();
|
|
|
|
PhysicalBone3D *physical_bone = Object::cast_to<PhysicalBone3D>(p_gizmo->get_spatial_node());
|
|
|
|
if (!physical_bone) {
|
|
return;
|
|
}
|
|
|
|
Skeleton3D *sk(physical_bone->find_skeleton_parent());
|
|
if (!sk) {
|
|
return;
|
|
}
|
|
|
|
PhysicalBone3D *pb(sk->get_physical_bone(physical_bone->get_bone_id()));
|
|
if (!pb) {
|
|
return;
|
|
}
|
|
|
|
PhysicalBone3D *pbp(sk->get_physical_bone_parent(physical_bone->get_bone_id()));
|
|
if (!pbp) {
|
|
return;
|
|
}
|
|
|
|
Vector<Vector3> points;
|
|
|
|
switch (physical_bone->get_joint_type()) {
|
|
case PhysicalBone3D::JOINT_TYPE_PIN: {
|
|
Joint3DGizmoPlugin::CreatePinJointGizmo(physical_bone->get_joint_offset(), points);
|
|
} break;
|
|
case PhysicalBone3D::JOINT_TYPE_CONE: {
|
|
const PhysicalBone3D::ConeJointData *cjd(static_cast<const PhysicalBone3D::ConeJointData *>(physical_bone->get_joint_data()));
|
|
Joint3DGizmoPlugin::CreateConeTwistJointGizmo(
|
|
physical_bone->get_joint_offset(),
|
|
physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
|
|
pb->get_global_transform(),
|
|
pbp->get_global_transform(),
|
|
cjd->swing_span,
|
|
cjd->twist_span,
|
|
&points,
|
|
&points);
|
|
} break;
|
|
case PhysicalBone3D::JOINT_TYPE_HINGE: {
|
|
const PhysicalBone3D::HingeJointData *hjd(static_cast<const PhysicalBone3D::HingeJointData *>(physical_bone->get_joint_data()));
|
|
Joint3DGizmoPlugin::CreateHingeJointGizmo(
|
|
physical_bone->get_joint_offset(),
|
|
physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
|
|
pb->get_global_transform(),
|
|
pbp->get_global_transform(),
|
|
hjd->angular_limit_lower,
|
|
hjd->angular_limit_upper,
|
|
hjd->angular_limit_enabled,
|
|
points,
|
|
&points,
|
|
&points);
|
|
} break;
|
|
case PhysicalBone3D::JOINT_TYPE_SLIDER: {
|
|
const PhysicalBone3D::SliderJointData *sjd(static_cast<const PhysicalBone3D::SliderJointData *>(physical_bone->get_joint_data()));
|
|
Joint3DGizmoPlugin::CreateSliderJointGizmo(
|
|
physical_bone->get_joint_offset(),
|
|
physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
|
|
pb->get_global_transform(),
|
|
pbp->get_global_transform(),
|
|
sjd->angular_limit_lower,
|
|
sjd->angular_limit_upper,
|
|
sjd->linear_limit_lower,
|
|
sjd->linear_limit_upper,
|
|
points,
|
|
&points,
|
|
&points);
|
|
} break;
|
|
case PhysicalBone3D::JOINT_TYPE_6DOF: {
|
|
const PhysicalBone3D::SixDOFJointData *sdofjd(static_cast<const PhysicalBone3D::SixDOFJointData *>(physical_bone->get_joint_data()));
|
|
Joint3DGizmoPlugin::CreateGeneric6DOFJointGizmo(
|
|
physical_bone->get_joint_offset(),
|
|
|
|
physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
|
|
pb->get_global_transform(),
|
|
pbp->get_global_transform(),
|
|
|
|
sdofjd->axis_data[0].angular_limit_lower,
|
|
sdofjd->axis_data[0].angular_limit_upper,
|
|
sdofjd->axis_data[0].linear_limit_lower,
|
|
sdofjd->axis_data[0].linear_limit_upper,
|
|
sdofjd->axis_data[0].angular_limit_enabled,
|
|
sdofjd->axis_data[0].linear_limit_enabled,
|
|
|
|
sdofjd->axis_data[1].angular_limit_lower,
|
|
sdofjd->axis_data[1].angular_limit_upper,
|
|
sdofjd->axis_data[1].linear_limit_lower,
|
|
sdofjd->axis_data[1].linear_limit_upper,
|
|
sdofjd->axis_data[1].angular_limit_enabled,
|
|
sdofjd->axis_data[1].linear_limit_enabled,
|
|
|
|
sdofjd->axis_data[2].angular_limit_lower,
|
|
sdofjd->axis_data[2].angular_limit_upper,
|
|
sdofjd->axis_data[2].linear_limit_lower,
|
|
sdofjd->axis_data[2].linear_limit_upper,
|
|
sdofjd->axis_data[2].angular_limit_enabled,
|
|
sdofjd->axis_data[2].linear_limit_enabled,
|
|
|
|
points,
|
|
&points,
|
|
&points);
|
|
} break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
Ref<Material> material = get_material("joint_material", p_gizmo);
|
|
|
|
p_gizmo->add_collision_segments(points);
|
|
p_gizmo->add_lines(points, material);
|
|
}
|
|
|
|
/////
|
|
|
|
RayCast3DGizmoPlugin::RayCast3DGizmoPlugin() {
|
|
const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
|
|
create_material("shape_material", gizmo_color);
|
|
const float gizmo_value = gizmo_color.get_v();
|
|
const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65);
|
|
create_material("shape_material_disabled", gizmo_color_disabled);
|
|
}
|
|
|
|
bool RayCast3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<RayCast3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String RayCast3DGizmoPlugin::get_gizmo_name() const {
|
|
return "RayCast3D";
|
|
}
|
|
|
|
int RayCast3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
void RayCast3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
RayCast3D *raycast = Object::cast_to<RayCast3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
const Ref<StandardMaterial3D> material = raycast->is_enabled() ? raycast->get_debug_material() : get_material("shape_material_disabled");
|
|
|
|
p_gizmo->add_lines(raycast->get_debug_line_vertices(), material);
|
|
|
|
if (raycast->get_debug_shape_thickness() > 1) {
|
|
p_gizmo->add_vertices(raycast->get_debug_shape_vertices(), material, Mesh::PRIMITIVE_TRIANGLE_STRIP);
|
|
}
|
|
|
|
p_gizmo->add_collision_segments(raycast->get_debug_line_vertices());
|
|
}
|
|
|
|
/////
|
|
|
|
void SpringArm3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
SpringArm3D *spring_arm = Object::cast_to<SpringArm3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Vector<Vector3> lines;
|
|
|
|
lines.push_back(Vector3());
|
|
lines.push_back(Vector3(0, 0, 1.0) * spring_arm->get_length());
|
|
|
|
Ref<StandardMaterial3D> material = get_material("shape_material", p_gizmo);
|
|
|
|
p_gizmo->add_lines(lines, material);
|
|
p_gizmo->add_collision_segments(lines);
|
|
}
|
|
|
|
SpringArm3DGizmoPlugin::SpringArm3DGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
|
|
create_material("shape_material", gizmo_color);
|
|
}
|
|
|
|
bool SpringArm3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<SpringArm3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String SpringArm3DGizmoPlugin::get_gizmo_name() const {
|
|
return "SpringArm3D";
|
|
}
|
|
|
|
int SpringArm3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
/////
|
|
|
|
VehicleWheel3DGizmoPlugin::VehicleWheel3DGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
|
|
create_material("shape_material", gizmo_color);
|
|
}
|
|
|
|
bool VehicleWheel3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<VehicleWheel3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String VehicleWheel3DGizmoPlugin::get_gizmo_name() const {
|
|
return "VehicleWheel3D";
|
|
}
|
|
|
|
int VehicleWheel3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
void VehicleWheel3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
VehicleWheel3D *car_wheel = Object::cast_to<VehicleWheel3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Vector<Vector3> points;
|
|
|
|
float r = car_wheel->get_radius();
|
|
const int skip = 10;
|
|
for (int i = 0; i <= 360; i += skip) {
|
|
float ra = Math::deg2rad((float)i);
|
|
float rb = Math::deg2rad((float)i + skip);
|
|
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
|
|
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
|
|
|
|
points.push_back(Vector3(0, a.x, a.y));
|
|
points.push_back(Vector3(0, b.x, b.y));
|
|
|
|
const int springsec = 4;
|
|
|
|
for (int j = 0; j < springsec; j++) {
|
|
float t = car_wheel->get_suspension_rest_length() * 5;
|
|
points.push_back(Vector3(a.x, i / 360.0 * t / springsec + j * (t / springsec), a.y) * 0.2);
|
|
points.push_back(Vector3(b.x, (i + skip) / 360.0 * t / springsec + j * (t / springsec), b.y) * 0.2);
|
|
}
|
|
}
|
|
|
|
//travel
|
|
points.push_back(Vector3(0, 0, 0));
|
|
points.push_back(Vector3(0, car_wheel->get_suspension_rest_length(), 0));
|
|
|
|
//axis
|
|
points.push_back(Vector3(r * 0.2, car_wheel->get_suspension_rest_length(), 0));
|
|
points.push_back(Vector3(-r * 0.2, car_wheel->get_suspension_rest_length(), 0));
|
|
//axis
|
|
points.push_back(Vector3(r * 0.2, 0, 0));
|
|
points.push_back(Vector3(-r * 0.2, 0, 0));
|
|
|
|
//forward line
|
|
points.push_back(Vector3(0, -r, 0));
|
|
points.push_back(Vector3(0, -r, r * 2));
|
|
points.push_back(Vector3(0, -r, r * 2));
|
|
points.push_back(Vector3(r * 2 * 0.2, -r, r * 2 * 0.8));
|
|
points.push_back(Vector3(0, -r, r * 2));
|
|
points.push_back(Vector3(-r * 2 * 0.2, -r, r * 2 * 0.8));
|
|
|
|
Ref<Material> material = get_material("shape_material", p_gizmo);
|
|
|
|
p_gizmo->add_lines(points, material);
|
|
p_gizmo->add_collision_segments(points);
|
|
}
|
|
|
|
///////////
|
|
|
|
SoftBody3DGizmoPlugin::SoftBody3DGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
|
|
create_material("shape_material", gizmo_color);
|
|
create_handle_material("handles");
|
|
}
|
|
|
|
bool SoftBody3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<SoftBody3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String SoftBody3DGizmoPlugin::get_gizmo_name() const {
|
|
return "SoftBody3D";
|
|
}
|
|
|
|
int SoftBody3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
bool SoftBody3DGizmoPlugin::is_selectable_when_hidden() const {
|
|
return true;
|
|
}
|
|
|
|
void SoftBody3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
if (!soft_body || soft_body->get_mesh().is_null()) {
|
|
return;
|
|
}
|
|
|
|
// find mesh
|
|
|
|
Vector<Vector3> lines;
|
|
|
|
soft_body->get_mesh()->generate_debug_mesh_lines(lines);
|
|
|
|
if (!lines.size()) {
|
|
return;
|
|
}
|
|
|
|
Ref<TriangleMesh> tm = soft_body->get_mesh()->generate_triangle_mesh();
|
|
|
|
Vector<Vector3> points;
|
|
for (int i = 0; i < soft_body->get_mesh()->get_surface_count(); i++) {
|
|
Array arrays = soft_body->get_mesh()->surface_get_arrays(i);
|
|
ERR_CONTINUE(arrays.is_empty());
|
|
|
|
const Vector<Vector3> &vertices = arrays[Mesh::ARRAY_VERTEX];
|
|
points.append_array(vertices);
|
|
}
|
|
|
|
Ref<Material> material = get_material("shape_material", p_gizmo);
|
|
|
|
p_gizmo->add_lines(lines, material);
|
|
p_gizmo->add_handles(points, get_material("handles"));
|
|
p_gizmo->add_collision_triangles(tm);
|
|
}
|
|
|
|
String SoftBody3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
return "SoftBody3D pin point";
|
|
}
|
|
|
|
Variant SoftBody3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node());
|
|
return Variant(soft_body->is_point_pinned(p_id));
|
|
}
|
|
|
|
void SoftBody3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node());
|
|
soft_body->pin_point_toggle(p_id);
|
|
}
|
|
|
|
bool SoftBody3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node());
|
|
return soft_body->is_point_pinned(p_id);
|
|
}
|
|
|
|
///////////
|
|
|
|
VisibleOnScreenNotifier3DGizmoPlugin::VisibleOnScreenNotifier3DGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/visibility_notifier", Color(0.8, 0.5, 0.7));
|
|
create_material("visibility_notifier_material", gizmo_color);
|
|
gizmo_color.a = 0.1;
|
|
create_material("visibility_notifier_solid_material", gizmo_color);
|
|
create_handle_material("handles");
|
|
}
|
|
|
|
bool VisibleOnScreenNotifier3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<VisibleOnScreenNotifier3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String VisibleOnScreenNotifier3DGizmoPlugin::get_gizmo_name() const {
|
|
return "VisibleOnScreenNotifier3D";
|
|
}
|
|
|
|
int VisibleOnScreenNotifier3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
String VisibleOnScreenNotifier3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
switch (p_id) {
|
|
case 0:
|
|
return "Size X";
|
|
case 1:
|
|
return "Size Y";
|
|
case 2:
|
|
return "Size Z";
|
|
case 3:
|
|
return "Pos X";
|
|
case 4:
|
|
return "Pos Y";
|
|
case 5:
|
|
return "Pos Z";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
Variant VisibleOnScreenNotifier3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node());
|
|
return notifier->get_aabb();
|
|
}
|
|
|
|
void VisibleOnScreenNotifier3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node());
|
|
|
|
Transform3D gt = notifier->get_global_transform();
|
|
|
|
Transform3D gi = gt.affine_inverse();
|
|
|
|
bool move = p_id >= 3;
|
|
p_id = p_id % 3;
|
|
|
|
AABB aabb = notifier->get_aabb();
|
|
Vector3 ray_from = p_camera->project_ray_origin(p_point);
|
|
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
|
|
|
|
Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
|
|
|
|
Vector3 ofs = aabb.position + aabb.size * 0.5;
|
|
|
|
Vector3 axis;
|
|
axis[p_id] = 1.0;
|
|
|
|
if (move) {
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(ofs - axis * 4096, ofs + axis * 4096, sg[0], sg[1], ra, rb);
|
|
|
|
float d = ra[p_id];
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
aabb.position[p_id] = d - 1.0 - aabb.size[p_id] * 0.5;
|
|
notifier->set_aabb(aabb);
|
|
|
|
} else {
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(ofs, ofs + axis * 4096, sg[0], sg[1], ra, rb);
|
|
|
|
float d = ra[p_id] - ofs[p_id];
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
if (d < 0.001) {
|
|
d = 0.001;
|
|
}
|
|
//resize
|
|
aabb.position[p_id] = (aabb.position[p_id] + aabb.size[p_id] * 0.5) - d;
|
|
aabb.size[p_id] = d * 2;
|
|
notifier->set_aabb(aabb);
|
|
}
|
|
}
|
|
|
|
void VisibleOnScreenNotifier3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node());
|
|
|
|
if (p_cancel) {
|
|
notifier->set_aabb(p_restore);
|
|
return;
|
|
}
|
|
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change Notifier AABB"));
|
|
ur->add_do_method(notifier, "set_aabb", notifier->get_aabb());
|
|
ur->add_undo_method(notifier, "set_aabb", p_restore);
|
|
ur->commit_action();
|
|
}
|
|
|
|
void VisibleOnScreenNotifier3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Vector<Vector3> lines;
|
|
AABB aabb = notifier->get_aabb();
|
|
|
|
for (int i = 0; i < 12; i++) {
|
|
Vector3 a, b;
|
|
aabb.get_edge(i, a, b);
|
|
lines.push_back(a);
|
|
lines.push_back(b);
|
|
}
|
|
|
|
Vector<Vector3> handles;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
Vector3 ax;
|
|
ax[i] = aabb.position[i] + aabb.size[i];
|
|
ax[(i + 1) % 3] = aabb.position[(i + 1) % 3] + aabb.size[(i + 1) % 3] * 0.5;
|
|
ax[(i + 2) % 3] = aabb.position[(i + 2) % 3] + aabb.size[(i + 2) % 3] * 0.5;
|
|
handles.push_back(ax);
|
|
}
|
|
|
|
Vector3 center = aabb.position + aabb.size * 0.5;
|
|
for (int i = 0; i < 3; i++) {
|
|
Vector3 ax;
|
|
ax[i] = 1.0;
|
|
handles.push_back(center + ax);
|
|
lines.push_back(center);
|
|
lines.push_back(center + ax);
|
|
}
|
|
|
|
Ref<Material> material = get_material("visibility_notifier_material", p_gizmo);
|
|
|
|
p_gizmo->add_lines(lines, material);
|
|
p_gizmo->add_collision_segments(lines);
|
|
|
|
if (p_gizmo->is_selected()) {
|
|
Ref<Material> solid_material = get_material("visibility_notifier_solid_material", p_gizmo);
|
|
p_gizmo->add_solid_box(solid_material, aabb.get_size(), aabb.get_position() + aabb.get_size() / 2.0);
|
|
}
|
|
|
|
p_gizmo->add_handles(handles, get_material("handles"));
|
|
}
|
|
|
|
////
|
|
|
|
CPUParticles3DGizmoPlugin::CPUParticles3DGizmoPlugin() {
|
|
create_icon_material("particles_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoCPUParticles3D"), SNAME("EditorIcons")));
|
|
}
|
|
|
|
bool CPUParticles3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<CPUParticles3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String CPUParticles3DGizmoPlugin::get_gizmo_name() const {
|
|
return "CPUParticles3D";
|
|
}
|
|
|
|
int CPUParticles3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
bool CPUParticles3DGizmoPlugin::is_selectable_when_hidden() const {
|
|
return true;
|
|
}
|
|
|
|
void CPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
Ref<Material> icon = get_material("particles_icon", p_gizmo);
|
|
p_gizmo->add_unscaled_billboard(icon, 0.05);
|
|
}
|
|
|
|
////
|
|
|
|
GPUParticles3DGizmoPlugin::GPUParticles3DGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particles", Color(0.8, 0.7, 0.4));
|
|
create_material("particles_material", gizmo_color);
|
|
gizmo_color.a = 0.1;
|
|
create_material("particles_solid_material", gizmo_color);
|
|
create_icon_material("particles_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoGPUParticles3D"), SNAME("EditorIcons")));
|
|
create_handle_material("handles");
|
|
}
|
|
|
|
bool GPUParticles3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<GPUParticles3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String GPUParticles3DGizmoPlugin::get_gizmo_name() const {
|
|
return "GPUParticles3D";
|
|
}
|
|
|
|
int GPUParticles3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
bool GPUParticles3DGizmoPlugin::is_selectable_when_hidden() const {
|
|
return true;
|
|
}
|
|
|
|
String GPUParticles3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
switch (p_id) {
|
|
case 0:
|
|
return "Size X";
|
|
case 1:
|
|
return "Size Y";
|
|
case 2:
|
|
return "Size Z";
|
|
case 3:
|
|
return "Pos X";
|
|
case 4:
|
|
return "Pos Y";
|
|
case 5:
|
|
return "Pos Z";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
Variant GPUParticles3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node());
|
|
return particles->get_visibility_aabb();
|
|
}
|
|
|
|
void GPUParticles3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node());
|
|
|
|
Transform3D gt = particles->get_global_transform();
|
|
Transform3D gi = gt.affine_inverse();
|
|
|
|
bool move = p_id >= 3;
|
|
p_id = p_id % 3;
|
|
|
|
AABB aabb = particles->get_visibility_aabb();
|
|
Vector3 ray_from = p_camera->project_ray_origin(p_point);
|
|
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
|
|
|
|
Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
|
|
|
|
Vector3 ofs = aabb.position + aabb.size * 0.5;
|
|
|
|
Vector3 axis;
|
|
axis[p_id] = 1.0;
|
|
|
|
if (move) {
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(ofs - axis * 4096, ofs + axis * 4096, sg[0], sg[1], ra, rb);
|
|
|
|
float d = ra[p_id];
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
aabb.position[p_id] = d - 1.0 - aabb.size[p_id] * 0.5;
|
|
particles->set_visibility_aabb(aabb);
|
|
|
|
} else {
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(ofs, ofs + axis * 4096, sg[0], sg[1], ra, rb);
|
|
|
|
float d = ra[p_id] - ofs[p_id];
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
if (d < 0.001) {
|
|
d = 0.001;
|
|
}
|
|
//resize
|
|
aabb.position[p_id] = (aabb.position[p_id] + aabb.size[p_id] * 0.5) - d;
|
|
aabb.size[p_id] = d * 2;
|
|
particles->set_visibility_aabb(aabb);
|
|
}
|
|
}
|
|
|
|
void GPUParticles3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node());
|
|
|
|
if (p_cancel) {
|
|
particles->set_visibility_aabb(p_restore);
|
|
return;
|
|
}
|
|
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change Particles AABB"));
|
|
ur->add_do_method(particles, "set_visibility_aabb", particles->get_visibility_aabb());
|
|
ur->add_undo_method(particles, "set_visibility_aabb", p_restore);
|
|
ur->commit_action();
|
|
}
|
|
|
|
void GPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Vector<Vector3> lines;
|
|
AABB aabb = particles->get_visibility_aabb();
|
|
|
|
for (int i = 0; i < 12; i++) {
|
|
Vector3 a, b;
|
|
aabb.get_edge(i, a, b);
|
|
lines.push_back(a);
|
|
lines.push_back(b);
|
|
}
|
|
|
|
Vector<Vector3> handles;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
Vector3 ax;
|
|
ax[i] = aabb.position[i] + aabb.size[i];
|
|
ax[(i + 1) % 3] = aabb.position[(i + 1) % 3] + aabb.size[(i + 1) % 3] * 0.5;
|
|
ax[(i + 2) % 3] = aabb.position[(i + 2) % 3] + aabb.size[(i + 2) % 3] * 0.5;
|
|
handles.push_back(ax);
|
|
}
|
|
|
|
Vector3 center = aabb.position + aabb.size * 0.5;
|
|
for (int i = 0; i < 3; i++) {
|
|
Vector3 ax;
|
|
ax[i] = 1.0;
|
|
handles.push_back(center + ax);
|
|
lines.push_back(center);
|
|
lines.push_back(center + ax);
|
|
}
|
|
|
|
Ref<Material> material = get_material("particles_material", p_gizmo);
|
|
Ref<Material> icon = get_material("particles_icon", p_gizmo);
|
|
|
|
p_gizmo->add_lines(lines, material);
|
|
|
|
if (p_gizmo->is_selected()) {
|
|
Ref<Material> solid_material = get_material("particles_solid_material", p_gizmo);
|
|
p_gizmo->add_solid_box(solid_material, aabb.get_size(), aabb.get_position() + aabb.get_size() / 2.0);
|
|
}
|
|
|
|
p_gizmo->add_handles(handles, get_material("handles"));
|
|
p_gizmo->add_unscaled_billboard(icon, 0.05);
|
|
}
|
|
|
|
////
|
|
|
|
GPUParticlesCollision3DGizmoPlugin::GPUParticlesCollision3DGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particle_collision", Color(0.5, 0.7, 1));
|
|
create_material("shape_material", gizmo_color);
|
|
gizmo_color.a = 0.15;
|
|
create_material("shape_material_internal", gizmo_color);
|
|
|
|
create_handle_material("handles");
|
|
}
|
|
|
|
bool GPUParticlesCollision3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return (Object::cast_to<GPUParticlesCollision3D>(p_spatial) != nullptr) || (Object::cast_to<GPUParticlesAttractor3D>(p_spatial) != nullptr);
|
|
}
|
|
|
|
String GPUParticlesCollision3DGizmoPlugin::get_gizmo_name() const {
|
|
return "GPUParticlesCollision3D";
|
|
}
|
|
|
|
int GPUParticlesCollision3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
const Node3D *cs = p_gizmo->get_spatial_node();
|
|
|
|
if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) {
|
|
return "Radius";
|
|
}
|
|
|
|
if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) {
|
|
return "Extents";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
Variant GPUParticlesCollision3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
const Node3D *cs = p_gizmo->get_spatial_node();
|
|
|
|
if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) {
|
|
return p_gizmo->get_spatial_node()->call("get_radius");
|
|
}
|
|
|
|
if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) {
|
|
return Vector3(p_gizmo->get_spatial_node()->call("get_extents"));
|
|
}
|
|
|
|
return Variant();
|
|
}
|
|
|
|
void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
Node3D *sn = p_gizmo->get_spatial_node();
|
|
|
|
Transform3D gt = sn->get_global_transform();
|
|
Transform3D gi = gt.affine_inverse();
|
|
|
|
Vector3 ray_from = p_camera->project_ray_origin(p_point);
|
|
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
|
|
|
|
Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
|
|
|
|
if (Object::cast_to<GPUParticlesCollisionSphere>(sn) || Object::cast_to<GPUParticlesAttractorSphere>(sn)) {
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb);
|
|
float d = ra.x;
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
if (d < 0.001) {
|
|
d = 0.001;
|
|
}
|
|
|
|
sn->call("set_radius", d);
|
|
}
|
|
|
|
if (Object::cast_to<GPUParticlesCollisionBox>(sn) || Object::cast_to<GPUParticlesAttractorBox>(sn) || Object::cast_to<GPUParticlesAttractorVectorField>(sn) || Object::cast_to<GPUParticlesCollisionSDF>(sn) || Object::cast_to<GPUParticlesCollisionHeightField>(sn)) {
|
|
Vector3 axis;
|
|
axis[p_id] = 1.0;
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
|
|
float d = ra[p_id];
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
if (d < 0.001) {
|
|
d = 0.001;
|
|
}
|
|
|
|
Vector3 he = sn->call("get_extents");
|
|
he[p_id] = d;
|
|
sn->call("set_extents", he);
|
|
}
|
|
}
|
|
|
|
void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
Node3D *sn = p_gizmo->get_spatial_node();
|
|
|
|
if (Object::cast_to<GPUParticlesCollisionSphere>(sn) || Object::cast_to<GPUParticlesAttractorSphere>(sn)) {
|
|
if (p_cancel) {
|
|
sn->call("set_radius", p_restore);
|
|
return;
|
|
}
|
|
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change Radius"));
|
|
ur->add_do_method(sn, "set_radius", sn->call("get_radius"));
|
|
ur->add_undo_method(sn, "set_radius", p_restore);
|
|
ur->commit_action();
|
|
}
|
|
|
|
if (Object::cast_to<GPUParticlesCollisionBox>(sn) || Object::cast_to<GPUParticlesAttractorBox>(sn) || Object::cast_to<GPUParticlesAttractorVectorField>(sn) || Object::cast_to<GPUParticlesCollisionSDF>(sn) || Object::cast_to<GPUParticlesCollisionHeightField>(sn)) {
|
|
if (p_cancel) {
|
|
sn->call("set_extents", p_restore);
|
|
return;
|
|
}
|
|
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change Box Shape Extents"));
|
|
ur->add_do_method(sn, "set_extents", sn->call("get_extents"));
|
|
ur->add_undo_method(sn, "set_extents", p_restore);
|
|
ur->commit_action();
|
|
}
|
|
}
|
|
|
|
void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
Node3D *cs = p_gizmo->get_spatial_node();
|
|
|
|
print_line("redraw request " + itos(cs != nullptr));
|
|
p_gizmo->clear();
|
|
|
|
const Ref<Material> material =
|
|
get_material("shape_material", p_gizmo);
|
|
const Ref<Material> material_internal =
|
|
get_material("shape_material_internal", p_gizmo);
|
|
|
|
Ref<Material> handles_material = get_material("handles");
|
|
|
|
if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) {
|
|
float r = cs->call("get_radius");
|
|
|
|
Vector<Vector3> points;
|
|
|
|
for (int i = 0; i <= 360; i++) {
|
|
float ra = Math::deg2rad((float)i);
|
|
float rb = Math::deg2rad((float)i + 1);
|
|
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
|
|
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
|
|
|
|
points.push_back(Vector3(a.x, 0, a.y));
|
|
points.push_back(Vector3(b.x, 0, b.y));
|
|
points.push_back(Vector3(0, a.x, a.y));
|
|
points.push_back(Vector3(0, b.x, b.y));
|
|
points.push_back(Vector3(a.x, a.y, 0));
|
|
points.push_back(Vector3(b.x, b.y, 0));
|
|
}
|
|
|
|
Vector<Vector3> collision_segments;
|
|
|
|
for (int i = 0; i < 64; i++) {
|
|
float ra = i * (Math_TAU / 64.0);
|
|
float rb = (i + 1) * (Math_TAU / 64.0);
|
|
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
|
|
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
|
|
|
|
collision_segments.push_back(Vector3(a.x, 0, a.y));
|
|
collision_segments.push_back(Vector3(b.x, 0, b.y));
|
|
collision_segments.push_back(Vector3(0, a.x, a.y));
|
|
collision_segments.push_back(Vector3(0, b.x, b.y));
|
|
collision_segments.push_back(Vector3(a.x, a.y, 0));
|
|
collision_segments.push_back(Vector3(b.x, b.y, 0));
|
|
}
|
|
|
|
p_gizmo->add_lines(points, material);
|
|
p_gizmo->add_collision_segments(collision_segments);
|
|
Vector<Vector3> handles;
|
|
handles.push_back(Vector3(r, 0, 0));
|
|
p_gizmo->add_handles(handles, handles_material);
|
|
}
|
|
|
|
if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) {
|
|
Vector<Vector3> lines;
|
|
AABB aabb;
|
|
aabb.position = -cs->call("get_extents").operator Vector3();
|
|
aabb.size = aabb.position * -2;
|
|
|
|
for (int i = 0; i < 12; i++) {
|
|
Vector3 a, b;
|
|
aabb.get_edge(i, a, b);
|
|
lines.push_back(a);
|
|
lines.push_back(b);
|
|
}
|
|
|
|
Vector<Vector3> handles;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
Vector3 ax;
|
|
ax[i] = cs->call("get_extents").operator Vector3()[i];
|
|
handles.push_back(ax);
|
|
}
|
|
|
|
p_gizmo->add_lines(lines, material);
|
|
p_gizmo->add_collision_segments(lines);
|
|
p_gizmo->add_handles(handles, handles_material);
|
|
|
|
GPUParticlesCollisionSDF *col_sdf = Object::cast_to<GPUParticlesCollisionSDF>(cs);
|
|
if (col_sdf) {
|
|
static const int subdivs[GPUParticlesCollisionSDF::RESOLUTION_MAX] = { 16, 32, 64, 128, 256, 512 };
|
|
int subdiv = subdivs[col_sdf->get_resolution()];
|
|
float cell_size = aabb.get_longest_axis_size() / subdiv;
|
|
|
|
lines.clear();
|
|
|
|
for (int i = 1; i < subdiv; i++) {
|
|
for (int j = 0; j < 3; j++) {
|
|
if (cell_size * i > aabb.size[j]) {
|
|
continue;
|
|
}
|
|
|
|
Vector2 dir;
|
|
dir[j] = 1.0;
|
|
Vector2 ta, tb;
|
|
int j_n1 = (j + 1) % 3;
|
|
int j_n2 = (j + 2) % 3;
|
|
ta[j_n1] = 1.0;
|
|
tb[j_n2] = 1.0;
|
|
|
|
for (int k = 0; k < 4; k++) {
|
|
Vector3 from = aabb.position, to = aabb.position;
|
|
from[j] += cell_size * i;
|
|
to[j] += cell_size * i;
|
|
|
|
if (k & 1) {
|
|
to[j_n1] += aabb.size[j_n1];
|
|
} else {
|
|
to[j_n2] += aabb.size[j_n2];
|
|
}
|
|
|
|
if (k & 2) {
|
|
from[j_n1] += aabb.size[j_n1];
|
|
from[j_n2] += aabb.size[j_n2];
|
|
}
|
|
|
|
lines.push_back(from);
|
|
lines.push_back(to);
|
|
}
|
|
}
|
|
}
|
|
|
|
p_gizmo->add_lines(lines, material_internal);
|
|
}
|
|
}
|
|
}
|
|
|
|
/////
|
|
|
|
////
|
|
|
|
ReflectionProbeGizmoPlugin::ReflectionProbeGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/reflection_probe", Color(0.6, 1, 0.5));
|
|
|
|
create_material("reflection_probe_material", gizmo_color);
|
|
|
|
gizmo_color.a = 0.5;
|
|
create_material("reflection_internal_material", gizmo_color);
|
|
|
|
gizmo_color.a = 0.1;
|
|
create_material("reflection_probe_solid_material", gizmo_color);
|
|
|
|
create_icon_material("reflection_probe_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoReflectionProbe"), SNAME("EditorIcons")));
|
|
create_handle_material("handles");
|
|
}
|
|
|
|
bool ReflectionProbeGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<ReflectionProbe>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String ReflectionProbeGizmoPlugin::get_gizmo_name() const {
|
|
return "ReflectionProbe";
|
|
}
|
|
|
|
int ReflectionProbeGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
String ReflectionProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
switch (p_id) {
|
|
case 0:
|
|
return "Extents X";
|
|
case 1:
|
|
return "Extents Y";
|
|
case 2:
|
|
return "Extents Z";
|
|
case 3:
|
|
return "Origin X";
|
|
case 4:
|
|
return "Origin Y";
|
|
case 5:
|
|
return "Origin Z";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
Variant ReflectionProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node());
|
|
return AABB(probe->get_extents(), probe->get_origin_offset());
|
|
}
|
|
|
|
void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node());
|
|
Transform3D gt = probe->get_global_transform();
|
|
|
|
Transform3D gi = gt.affine_inverse();
|
|
|
|
if (p_id < 3) {
|
|
Vector3 extents = probe->get_extents();
|
|
|
|
Vector3 ray_from = p_camera->project_ray_origin(p_point);
|
|
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
|
|
|
|
Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
|
|
|
|
Vector3 axis;
|
|
axis[p_id] = 1.0;
|
|
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
|
|
float d = ra[p_id];
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
if (d < 0.001) {
|
|
d = 0.001;
|
|
}
|
|
|
|
extents[p_id] = d;
|
|
probe->set_extents(extents);
|
|
} else {
|
|
p_id -= 3;
|
|
|
|
Vector3 origin = probe->get_origin_offset();
|
|
origin[p_id] = 0;
|
|
|
|
Vector3 ray_from = p_camera->project_ray_origin(p_point);
|
|
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
|
|
|
|
Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
|
|
|
|
Vector3 axis;
|
|
axis[p_id] = 1.0;
|
|
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(origin - axis * 16384, origin + axis * 16384, sg[0], sg[1], ra, rb);
|
|
// Adjust the actual position to account for the gizmo handle position
|
|
float d = ra[p_id] + 0.25;
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
origin[p_id] = d;
|
|
probe->set_origin_offset(origin);
|
|
}
|
|
}
|
|
|
|
void ReflectionProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node());
|
|
|
|
AABB restore = p_restore;
|
|
|
|
if (p_cancel) {
|
|
probe->set_extents(restore.position);
|
|
probe->set_origin_offset(restore.size);
|
|
return;
|
|
}
|
|
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change Probe Extents"));
|
|
ur->add_do_method(probe, "set_extents", probe->get_extents());
|
|
ur->add_do_method(probe, "set_origin_offset", probe->get_origin_offset());
|
|
ur->add_undo_method(probe, "set_extents", restore.position);
|
|
ur->add_undo_method(probe, "set_origin_offset", restore.size);
|
|
ur->commit_action();
|
|
}
|
|
|
|
void ReflectionProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Vector<Vector3> lines;
|
|
Vector<Vector3> internal_lines;
|
|
Vector3 extents = probe->get_extents();
|
|
|
|
AABB aabb;
|
|
aabb.position = -extents;
|
|
aabb.size = extents * 2;
|
|
|
|
for (int i = 0; i < 12; i++) {
|
|
Vector3 a, b;
|
|
aabb.get_edge(i, a, b);
|
|
lines.push_back(a);
|
|
lines.push_back(b);
|
|
}
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
Vector3 ep = aabb.get_endpoint(i);
|
|
internal_lines.push_back(probe->get_origin_offset());
|
|
internal_lines.push_back(ep);
|
|
}
|
|
|
|
Vector<Vector3> handles;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
Vector3 ax;
|
|
ax[i] = aabb.position[i] + aabb.size[i];
|
|
handles.push_back(ax);
|
|
}
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
Vector3 orig_handle = probe->get_origin_offset();
|
|
orig_handle[i] -= 0.25;
|
|
lines.push_back(orig_handle);
|
|
handles.push_back(orig_handle);
|
|
|
|
orig_handle[i] += 0.5;
|
|
lines.push_back(orig_handle);
|
|
}
|
|
|
|
Ref<Material> material = get_material("reflection_probe_material", p_gizmo);
|
|
Ref<Material> material_internal = get_material("reflection_internal_material", p_gizmo);
|
|
Ref<Material> icon = get_material("reflection_probe_icon", p_gizmo);
|
|
|
|
p_gizmo->add_lines(lines, material);
|
|
p_gizmo->add_lines(internal_lines, material_internal);
|
|
|
|
if (p_gizmo->is_selected()) {
|
|
Ref<Material> solid_material = get_material("reflection_probe_solid_material", p_gizmo);
|
|
p_gizmo->add_solid_box(solid_material, probe->get_extents() * 2.0);
|
|
}
|
|
|
|
p_gizmo->add_unscaled_billboard(icon, 0.05);
|
|
p_gizmo->add_handles(handles, get_material("handles"));
|
|
}
|
|
|
|
///////////////////////////////
|
|
|
|
////
|
|
|
|
DecalGizmoPlugin::DecalGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/decal", Color(0.6, 0.5, 1.0));
|
|
|
|
create_material("decal_material", gizmo_color);
|
|
|
|
create_handle_material("handles");
|
|
}
|
|
|
|
bool DecalGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<Decal>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String DecalGizmoPlugin::get_gizmo_name() const {
|
|
return "Decal";
|
|
}
|
|
|
|
int DecalGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
String DecalGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
switch (p_id) {
|
|
case 0:
|
|
return "Extents X";
|
|
case 1:
|
|
return "Extents Y";
|
|
case 2:
|
|
return "Extents Z";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
Variant DecalGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
|
|
return decal->get_extents();
|
|
}
|
|
|
|
void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
|
|
Transform3D gt = decal->get_global_transform();
|
|
|
|
Transform3D gi = gt.affine_inverse();
|
|
|
|
Vector3 extents = decal->get_extents();
|
|
|
|
Vector3 ray_from = p_camera->project_ray_origin(p_point);
|
|
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
|
|
|
|
Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
|
|
|
|
Vector3 axis;
|
|
axis[p_id] = 1.0;
|
|
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
|
|
float d = ra[p_id];
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
if (d < 0.001) {
|
|
d = 0.001;
|
|
}
|
|
|
|
extents[p_id] = d;
|
|
decal->set_extents(extents);
|
|
}
|
|
|
|
void DecalGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
|
|
|
|
Vector3 restore = p_restore;
|
|
|
|
if (p_cancel) {
|
|
decal->set_extents(restore);
|
|
return;
|
|
}
|
|
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change Decal Extents"));
|
|
ur->add_do_method(decal, "set_extents", decal->get_extents());
|
|
ur->add_undo_method(decal, "set_extents", restore);
|
|
ur->commit_action();
|
|
}
|
|
|
|
void DecalGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Vector<Vector3> lines;
|
|
Vector3 extents = decal->get_extents();
|
|
|
|
AABB aabb;
|
|
aabb.position = -extents;
|
|
aabb.size = extents * 2;
|
|
|
|
for (int i = 0; i < 12; i++) {
|
|
Vector3 a, b;
|
|
aabb.get_edge(i, a, b);
|
|
if (a.y == b.y) {
|
|
lines.push_back(a);
|
|
lines.push_back(b);
|
|
} else {
|
|
Vector3 ah = a.lerp(b, 0.2);
|
|
lines.push_back(a);
|
|
lines.push_back(ah);
|
|
Vector3 bh = b.lerp(a, 0.2);
|
|
lines.push_back(b);
|
|
lines.push_back(bh);
|
|
}
|
|
}
|
|
|
|
lines.push_back(Vector3(0, extents.y, 0));
|
|
lines.push_back(Vector3(0, extents.y * 1.2, 0));
|
|
|
|
Vector<Vector3> handles;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
Vector3 ax;
|
|
ax[i] = aabb.position[i] + aabb.size[i];
|
|
handles.push_back(ax);
|
|
}
|
|
|
|
Ref<Material> material = get_material("decal_material", p_gizmo);
|
|
|
|
p_gizmo->add_lines(lines, material);
|
|
p_gizmo->add_handles(handles, get_material("handles"));
|
|
}
|
|
|
|
///////////////////////////////
|
|
VoxelGIGizmoPlugin::VoxelGIGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/voxel_gi", Color(0.5, 1, 0.6));
|
|
|
|
create_material("voxel_gi_material", gizmo_color);
|
|
|
|
// This gizmo draws a lot of lines. Use a low opacity to make it not too intrusive.
|
|
gizmo_color.a = 0.1;
|
|
create_material("voxel_gi_internal_material", gizmo_color);
|
|
|
|
gizmo_color.a = 0.05;
|
|
create_material("voxel_gi_solid_material", gizmo_color);
|
|
|
|
create_icon_material("voxel_gi_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoVoxelGI"), SNAME("EditorIcons")));
|
|
create_handle_material("handles");
|
|
}
|
|
|
|
bool VoxelGIGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<VoxelGI>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String VoxelGIGizmoPlugin::get_gizmo_name() const {
|
|
return "VoxelGI";
|
|
}
|
|
|
|
int VoxelGIGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
String VoxelGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
switch (p_id) {
|
|
case 0:
|
|
return "Extents X";
|
|
case 1:
|
|
return "Extents Y";
|
|
case 2:
|
|
return "Extents Z";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
Variant VoxelGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node());
|
|
return probe->get_extents();
|
|
}
|
|
|
|
void VoxelGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node());
|
|
|
|
Transform3D gt = probe->get_global_transform();
|
|
Transform3D gi = gt.affine_inverse();
|
|
|
|
Vector3 extents = probe->get_extents();
|
|
|
|
Vector3 ray_from = p_camera->project_ray_origin(p_point);
|
|
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
|
|
|
|
Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
|
|
|
|
Vector3 axis;
|
|
axis[p_id] = 1.0;
|
|
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
|
|
float d = ra[p_id];
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
if (d < 0.001) {
|
|
d = 0.001;
|
|
}
|
|
|
|
extents[p_id] = d;
|
|
probe->set_extents(extents);
|
|
}
|
|
|
|
void VoxelGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node());
|
|
|
|
Vector3 restore = p_restore;
|
|
|
|
if (p_cancel) {
|
|
probe->set_extents(restore);
|
|
return;
|
|
}
|
|
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change Probe Extents"));
|
|
ur->add_do_method(probe, "set_extents", probe->get_extents());
|
|
ur->add_undo_method(probe, "set_extents", restore);
|
|
ur->commit_action();
|
|
}
|
|
|
|
void VoxelGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node());
|
|
|
|
Ref<Material> material = get_material("voxel_gi_material", p_gizmo);
|
|
Ref<Material> icon = get_material("voxel_gi_icon", p_gizmo);
|
|
Ref<Material> material_internal = get_material("voxel_gi_internal_material", p_gizmo);
|
|
|
|
p_gizmo->clear();
|
|
|
|
Vector<Vector3> lines;
|
|
Vector3 extents = probe->get_extents();
|
|
|
|
static const int subdivs[VoxelGI::SUBDIV_MAX] = { 64, 128, 256, 512 };
|
|
|
|
AABB aabb = AABB(-extents, extents * 2);
|
|
int subdiv = subdivs[probe->get_subdiv()];
|
|
float cell_size = aabb.get_longest_axis_size() / subdiv;
|
|
|
|
for (int i = 0; i < 12; i++) {
|
|
Vector3 a, b;
|
|
aabb.get_edge(i, a, b);
|
|
lines.push_back(a);
|
|
lines.push_back(b);
|
|
}
|
|
|
|
p_gizmo->add_lines(lines, material);
|
|
|
|
lines.clear();
|
|
|
|
for (int i = 1; i < subdiv; i++) {
|
|
for (int j = 0; j < 3; j++) {
|
|
if (cell_size * i > aabb.size[j]) {
|
|
continue;
|
|
}
|
|
|
|
Vector2 dir;
|
|
dir[j] = 1.0;
|
|
Vector2 ta, tb;
|
|
int j_n1 = (j + 1) % 3;
|
|
int j_n2 = (j + 2) % 3;
|
|
ta[j_n1] = 1.0;
|
|
tb[j_n2] = 1.0;
|
|
|
|
for (int k = 0; k < 4; k++) {
|
|
Vector3 from = aabb.position, to = aabb.position;
|
|
from[j] += cell_size * i;
|
|
to[j] += cell_size * i;
|
|
|
|
if (k & 1) {
|
|
to[j_n1] += aabb.size[j_n1];
|
|
} else {
|
|
to[j_n2] += aabb.size[j_n2];
|
|
}
|
|
|
|
if (k & 2) {
|
|
from[j_n1] += aabb.size[j_n1];
|
|
from[j_n2] += aabb.size[j_n2];
|
|
}
|
|
|
|
lines.push_back(from);
|
|
lines.push_back(to);
|
|
}
|
|
}
|
|
}
|
|
|
|
p_gizmo->add_lines(lines, material_internal);
|
|
|
|
Vector<Vector3> handles;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
Vector3 ax;
|
|
ax[i] = aabb.position[i] + aabb.size[i];
|
|
handles.push_back(ax);
|
|
}
|
|
|
|
if (p_gizmo->is_selected()) {
|
|
Ref<Material> solid_material = get_material("voxel_gi_solid_material", p_gizmo);
|
|
p_gizmo->add_solid_box(solid_material, aabb.get_size());
|
|
}
|
|
|
|
p_gizmo->add_unscaled_billboard(icon, 0.05);
|
|
p_gizmo->add_handles(handles, get_material("handles"));
|
|
}
|
|
|
|
////
|
|
|
|
LightmapGIGizmoPlugin::LightmapGIGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/lightmap_lines", Color(0.5, 0.6, 1));
|
|
|
|
gizmo_color.a = 0.1;
|
|
create_material("lightmap_lines", gizmo_color);
|
|
|
|
Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
|
|
mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
|
|
mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
|
|
mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
|
|
mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, false);
|
|
|
|
add_material("lightmap_probe_material", mat);
|
|
|
|
create_icon_material("baked_indirect_light_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoLightmapGI"), SNAME("EditorIcons")));
|
|
}
|
|
|
|
String LightmapGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
return "";
|
|
}
|
|
|
|
Variant LightmapGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
return Variant();
|
|
}
|
|
|
|
void LightmapGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
}
|
|
|
|
void LightmapGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
}
|
|
|
|
bool LightmapGIGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<LightmapGI>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String LightmapGIGizmoPlugin::get_gizmo_name() const {
|
|
return "LightmapGI";
|
|
}
|
|
|
|
int LightmapGIGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
void LightmapGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
Ref<Material> icon = get_material("baked_indirect_light_icon", p_gizmo);
|
|
LightmapGI *baker = Object::cast_to<LightmapGI>(p_gizmo->get_spatial_node());
|
|
Ref<LightmapGIData> data = baker->get_light_data();
|
|
|
|
p_gizmo->add_unscaled_billboard(icon, 0.05);
|
|
|
|
if (data.is_null()) {
|
|
return;
|
|
}
|
|
|
|
Ref<Material> material_lines = get_material("lightmap_lines", p_gizmo);
|
|
Ref<Material> material_probes = get_material("lightmap_probe_material", p_gizmo);
|
|
|
|
p_gizmo->clear();
|
|
|
|
Vector<Vector3> lines;
|
|
Set<Vector2i> lines_found;
|
|
|
|
Vector<Vector3> points = data->get_capture_points();
|
|
if (points.size() == 0) {
|
|
return;
|
|
}
|
|
Vector<Color> sh = data->get_capture_sh();
|
|
if (sh.size() != points.size() * 9) {
|
|
return;
|
|
}
|
|
|
|
Vector<int> tetrahedrons = data->get_capture_tetrahedra();
|
|
|
|
for (int i = 0; i < tetrahedrons.size(); i += 4) {
|
|
for (int j = 0; j < 4; j++) {
|
|
for (int k = j + 1; k < 4; k++) {
|
|
Vector2i pair;
|
|
pair.x = tetrahedrons[i + j];
|
|
pair.y = tetrahedrons[i + k];
|
|
|
|
if (pair.y < pair.x) {
|
|
SWAP(pair.x, pair.y);
|
|
}
|
|
if (lines_found.has(pair)) {
|
|
continue;
|
|
}
|
|
lines_found.insert(pair);
|
|
lines.push_back(points[pair.x]);
|
|
lines.push_back(points[pair.y]);
|
|
}
|
|
}
|
|
}
|
|
|
|
p_gizmo->add_lines(lines, material_lines);
|
|
|
|
int stack_count = 8;
|
|
int sector_count = 16;
|
|
|
|
float sector_step = (Math_PI * 2.0) / sector_count;
|
|
float stack_step = Math_PI / stack_count;
|
|
|
|
Vector<Vector3> vertices;
|
|
Vector<Color> colors;
|
|
Vector<int> indices;
|
|
float radius = 0.3;
|
|
|
|
for (int p = 0; p < points.size(); p++) {
|
|
int vertex_base = vertices.size();
|
|
Vector3 sh_col[9];
|
|
for (int i = 0; i < 9; i++) {
|
|
sh_col[i].x = sh[p * 9 + i].r;
|
|
sh_col[i].y = sh[p * 9 + i].g;
|
|
sh_col[i].z = sh[p * 9 + i].b;
|
|
}
|
|
|
|
for (int i = 0; i <= stack_count; ++i) {
|
|
float stack_angle = Math_PI / 2 - i * stack_step; // starting from pi/2 to -pi/2
|
|
float xy = radius * Math::cos(stack_angle); // r * cos(u)
|
|
float z = radius * Math::sin(stack_angle); // r * sin(u)
|
|
|
|
// add (sector_count+1) vertices per stack
|
|
// the first and last vertices have same position and normal, but different tex coords
|
|
for (int j = 0; j <= sector_count; ++j) {
|
|
float sector_angle = j * sector_step; // starting from 0 to 2pi
|
|
|
|
// vertex position (x, y, z)
|
|
float x = xy * Math::cos(sector_angle); // r * cos(u) * cos(v)
|
|
float y = xy * Math::sin(sector_angle); // r * cos(u) * sin(v)
|
|
|
|
Vector3 n = Vector3(x, z, y);
|
|
vertices.push_back(points[p] + n);
|
|
n.normalize();
|
|
|
|
const float c1 = 0.429043;
|
|
const float c2 = 0.511664;
|
|
const float c3 = 0.743125;
|
|
const float c4 = 0.886227;
|
|
const float c5 = 0.247708;
|
|
Vector3 light = (c1 * sh_col[8] * (n.x * n.x - n.y * n.y) +
|
|
c3 * sh_col[6] * n.z * n.z +
|
|
c4 * sh_col[0] -
|
|
c5 * sh_col[6] +
|
|
2.0 * c1 * sh_col[4] * n.x * n.y +
|
|
2.0 * c1 * sh_col[7] * n.x * n.z +
|
|
2.0 * c1 * sh_col[5] * n.y * n.z +
|
|
2.0 * c2 * sh_col[3] * n.x +
|
|
2.0 * c2 * sh_col[1] * n.y +
|
|
2.0 * c2 * sh_col[2] * n.z);
|
|
|
|
colors.push_back(Color(light.x, light.y, light.z, 1));
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < stack_count; ++i) {
|
|
int k1 = i * (sector_count + 1); // beginning of current stack
|
|
int k2 = k1 + sector_count + 1; // beginning of next stack
|
|
|
|
for (int j = 0; j < sector_count; ++j, ++k1, ++k2) {
|
|
// 2 triangles per sector excluding first and last stacks
|
|
// k1 => k2 => k1+1
|
|
if (i != 0) {
|
|
indices.push_back(vertex_base + k1);
|
|
indices.push_back(vertex_base + k2);
|
|
indices.push_back(vertex_base + k1 + 1);
|
|
}
|
|
|
|
// k1+1 => k2 => k2+1
|
|
if (i != (stack_count - 1)) {
|
|
indices.push_back(vertex_base + k1 + 1);
|
|
indices.push_back(vertex_base + k2);
|
|
indices.push_back(vertex_base + k2 + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Array array;
|
|
array.resize(RS::ARRAY_MAX);
|
|
array[RS::ARRAY_VERTEX] = vertices;
|
|
array[RS::ARRAY_INDEX] = indices;
|
|
array[RS::ARRAY_COLOR] = colors;
|
|
|
|
Ref<ArrayMesh> mesh;
|
|
mesh.instantiate();
|
|
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array, Array(), Dictionary(), 0); //no compression
|
|
mesh->surface_set_material(0, material_probes);
|
|
|
|
p_gizmo->add_mesh(mesh);
|
|
}
|
|
|
|
/////////
|
|
|
|
LightmapProbeGizmoPlugin::LightmapProbeGizmoPlugin() {
|
|
Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/lightprobe_lines", Color(0.5, 0.6, 1));
|
|
|
|
gizmo_color.a = 0.3;
|
|
create_material("lightprobe_lines", gizmo_color);
|
|
}
|
|
|
|
String LightmapProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
return "";
|
|
}
|
|
|
|
Variant LightmapProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
return Variant();
|
|
}
|
|
|
|
void LightmapProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
}
|
|
|
|
void LightmapProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
}
|
|
|
|
bool LightmapProbeGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<LightmapProbe>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String LightmapProbeGizmoPlugin::get_gizmo_name() const {
|
|
return "LightmapProbe";
|
|
}
|
|
|
|
int LightmapProbeGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
void LightmapProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
Ref<Material> material_lines = get_material("lightprobe_lines", p_gizmo);
|
|
|
|
p_gizmo->clear();
|
|
|
|
Vector<Vector3> lines;
|
|
|
|
int stack_count = 8;
|
|
int sector_count = 16;
|
|
|
|
float sector_step = (Math_PI * 2.0) / sector_count;
|
|
float stack_step = Math_PI / stack_count;
|
|
|
|
Vector<Vector3> vertices;
|
|
float radius = 0.2;
|
|
|
|
for (int i = 0; i <= stack_count; ++i) {
|
|
float stack_angle = Math_PI / 2 - i * stack_step; // starting from pi/2 to -pi/2
|
|
float xy = radius * Math::cos(stack_angle); // r * cos(u)
|
|
float z = radius * Math::sin(stack_angle); // r * sin(u)
|
|
|
|
// add (sector_count+1) vertices per stack
|
|
// the first and last vertices have same position and normal, but different tex coords
|
|
for (int j = 0; j <= sector_count; ++j) {
|
|
float sector_angle = j * sector_step; // starting from 0 to 2pi
|
|
|
|
// vertex position (x, y, z)
|
|
float x = xy * Math::cos(sector_angle); // r * cos(u) * cos(v)
|
|
float y = xy * Math::sin(sector_angle); // r * cos(u) * sin(v)
|
|
|
|
Vector3 n = Vector3(x, z, y);
|
|
vertices.push_back(n);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < stack_count; ++i) {
|
|
int k1 = i * (sector_count + 1); // beginning of current stack
|
|
int k2 = k1 + sector_count + 1; // beginning of next stack
|
|
|
|
for (int j = 0; j < sector_count; ++j, ++k1, ++k2) {
|
|
// 2 triangles per sector excluding first and last stacks
|
|
// k1 => k2 => k1+1
|
|
if (i != 0) {
|
|
lines.push_back(vertices[k1]);
|
|
lines.push_back(vertices[k2]);
|
|
lines.push_back(vertices[k1]);
|
|
lines.push_back(vertices[k1 + 1]);
|
|
}
|
|
|
|
if (i != (stack_count - 1)) {
|
|
lines.push_back(vertices[k1 + 1]);
|
|
lines.push_back(vertices[k2]);
|
|
lines.push_back(vertices[k2]);
|
|
lines.push_back(vertices[k2 + 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
p_gizmo->add_lines(lines, material_lines);
|
|
}
|
|
|
|
////
|
|
|
|
CollisionObject3DGizmoPlugin::CollisionObject3DGizmoPlugin() {
|
|
const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
|
|
create_material("shape_material", gizmo_color);
|
|
const float gizmo_value = gizmo_color.get_v();
|
|
const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65);
|
|
create_material("shape_material_disabled", gizmo_color_disabled);
|
|
}
|
|
|
|
bool CollisionObject3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<CollisionObject3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String CollisionObject3DGizmoPlugin::get_gizmo_name() const {
|
|
return "CollisionObject3D";
|
|
}
|
|
|
|
int CollisionObject3DGizmoPlugin::get_priority() const {
|
|
return -2;
|
|
}
|
|
|
|
void CollisionObject3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
List<uint32_t> owners;
|
|
co->get_shape_owners(&owners);
|
|
for (uint32_t &owner_id : owners) {
|
|
Transform3D xform = co->shape_owner_get_transform(owner_id);
|
|
Object *owner = co->shape_owner_get_owner(owner_id);
|
|
// Exclude CollisionShape3D and CollisionPolygon3D as they have their gizmo.
|
|
if (!Object::cast_to<CollisionShape3D>(owner) && !Object::cast_to<CollisionPolygon3D>(owner)) {
|
|
Ref<Material> material = get_material(!co->is_shape_owner_disabled(owner_id) ? "shape_material" : "shape_material_disabled", p_gizmo);
|
|
for (int shape_id = 0; shape_id < co->shape_owner_get_shape_count(owner_id); shape_id++) {
|
|
Ref<Shape3D> s = co->shape_owner_get_shape(owner_id, shape_id);
|
|
if (s.is_null()) {
|
|
continue;
|
|
}
|
|
SurfaceTool st;
|
|
st.append_from(s->get_debug_mesh(), 0, xform);
|
|
|
|
p_gizmo->add_mesh(st.commit(), material);
|
|
p_gizmo->add_collision_segments(s->get_debug_mesh_lines());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////
|
|
|
|
CollisionShape3DGizmoPlugin::CollisionShape3DGizmoPlugin() {
|
|
const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
|
|
create_material("shape_material", gizmo_color);
|
|
const float gizmo_value = gizmo_color.get_v();
|
|
const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65);
|
|
create_material("shape_material_disabled", gizmo_color_disabled);
|
|
create_handle_material("handles");
|
|
}
|
|
|
|
bool CollisionShape3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<CollisionShape3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String CollisionShape3DGizmoPlugin::get_gizmo_name() const {
|
|
return "CollisionShape3D";
|
|
}
|
|
|
|
int CollisionShape3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
const CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
|
|
|
|
Ref<Shape3D> s = cs->get_shape();
|
|
if (s.is_null()) {
|
|
return "";
|
|
}
|
|
|
|
if (Object::cast_to<SphereShape3D>(*s)) {
|
|
return "Radius";
|
|
}
|
|
|
|
if (Object::cast_to<BoxShape3D>(*s)) {
|
|
return "Size";
|
|
}
|
|
|
|
if (Object::cast_to<CapsuleShape3D>(*s)) {
|
|
return p_id == 0 ? "Radius" : "Height";
|
|
}
|
|
|
|
if (Object::cast_to<CylinderShape3D>(*s)) {
|
|
return p_id == 0 ? "Radius" : "Height";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
|
|
CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
|
|
|
|
Ref<Shape3D> s = cs->get_shape();
|
|
if (s.is_null()) {
|
|
return Variant();
|
|
}
|
|
|
|
if (Object::cast_to<SphereShape3D>(*s)) {
|
|
Ref<SphereShape3D> ss = s;
|
|
return ss->get_radius();
|
|
}
|
|
|
|
if (Object::cast_to<BoxShape3D>(*s)) {
|
|
Ref<BoxShape3D> bs = s;
|
|
return bs->get_size();
|
|
}
|
|
|
|
if (Object::cast_to<CapsuleShape3D>(*s)) {
|
|
Ref<CapsuleShape3D> cs2 = s;
|
|
return p_id == 0 ? cs2->get_radius() : cs2->get_height();
|
|
}
|
|
|
|
if (Object::cast_to<CylinderShape3D>(*s)) {
|
|
Ref<CylinderShape3D> cs2 = s;
|
|
return p_id == 0 ? cs2->get_radius() : cs2->get_height();
|
|
}
|
|
|
|
return Variant();
|
|
}
|
|
|
|
void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) {
|
|
CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
|
|
|
|
Ref<Shape3D> s = cs->get_shape();
|
|
if (s.is_null()) {
|
|
return;
|
|
}
|
|
|
|
Transform3D gt = cs->get_global_transform();
|
|
Transform3D gi = gt.affine_inverse();
|
|
|
|
Vector3 ray_from = p_camera->project_ray_origin(p_point);
|
|
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
|
|
|
|
Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
|
|
|
|
if (Object::cast_to<SphereShape3D>(*s)) {
|
|
Ref<SphereShape3D> ss = s;
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb);
|
|
float d = ra.x;
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
if (d < 0.001) {
|
|
d = 0.001;
|
|
}
|
|
|
|
ss->set_radius(d);
|
|
}
|
|
|
|
if (Object::cast_to<BoxShape3D>(*s)) {
|
|
Vector3 axis;
|
|
axis[p_id] = 1.0;
|
|
Ref<BoxShape3D> bs = s;
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
|
|
float d = ra[p_id];
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
if (d < 0.001) {
|
|
d = 0.001;
|
|
}
|
|
|
|
Vector3 he = bs->get_size();
|
|
he[p_id] = d * 2;
|
|
bs->set_size(he);
|
|
}
|
|
|
|
if (Object::cast_to<CapsuleShape3D>(*s)) {
|
|
Vector3 axis;
|
|
axis[p_id == 0 ? 0 : 1] = 1.0;
|
|
Ref<CapsuleShape3D> cs2 = s;
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
|
|
float d = axis.dot(ra);
|
|
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
if (d < 0.001) {
|
|
d = 0.001;
|
|
}
|
|
|
|
if (p_id == 0) {
|
|
cs2->set_radius(d);
|
|
} else if (p_id == 1) {
|
|
cs2->set_height(d * 2.0);
|
|
}
|
|
}
|
|
|
|
if (Object::cast_to<CylinderShape3D>(*s)) {
|
|
Vector3 axis;
|
|
axis[p_id == 0 ? 0 : 1] = 1.0;
|
|
Ref<CylinderShape3D> cs2 = s;
|
|
Vector3 ra, rb;
|
|
Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
|
|
float d = axis.dot(ra);
|
|
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
|
|
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
|
|
}
|
|
|
|
if (d < 0.001) {
|
|
d = 0.001;
|
|
}
|
|
|
|
if (p_id == 0) {
|
|
cs2->set_radius(d);
|
|
} else if (p_id == 1) {
|
|
cs2->set_height(d * 2.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) {
|
|
CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
|
|
|
|
Ref<Shape3D> s = cs->get_shape();
|
|
if (s.is_null()) {
|
|
return;
|
|
}
|
|
|
|
if (Object::cast_to<SphereShape3D>(*s)) {
|
|
Ref<SphereShape3D> ss = s;
|
|
if (p_cancel) {
|
|
ss->set_radius(p_restore);
|
|
return;
|
|
}
|
|
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change Sphere Shape Radius"));
|
|
ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius());
|
|
ur->add_undo_method(ss.ptr(), "set_radius", p_restore);
|
|
ur->commit_action();
|
|
}
|
|
|
|
if (Object::cast_to<BoxShape3D>(*s)) {
|
|
Ref<BoxShape3D> ss = s;
|
|
if (p_cancel) {
|
|
ss->set_size(p_restore);
|
|
return;
|
|
}
|
|
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
ur->create_action(TTR("Change Box Shape Size"));
|
|
ur->add_do_method(ss.ptr(), "set_size", ss->get_size());
|
|
ur->add_undo_method(ss.ptr(), "set_size", p_restore);
|
|
ur->commit_action();
|
|
}
|
|
|
|
if (Object::cast_to<CapsuleShape3D>(*s)) {
|
|
Ref<CapsuleShape3D> ss = s;
|
|
if (p_cancel) {
|
|
if (p_id == 0) {
|
|
ss->set_radius(p_restore);
|
|
} else {
|
|
ss->set_height(p_restore);
|
|
}
|
|
return;
|
|
}
|
|
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
if (p_id == 0) {
|
|
ur->create_action(TTR("Change Capsule Shape Radius"));
|
|
ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius());
|
|
ur->add_undo_method(ss.ptr(), "set_radius", p_restore);
|
|
} else {
|
|
ur->create_action(TTR("Change Capsule Shape Height"));
|
|
ur->add_do_method(ss.ptr(), "set_height", ss->get_height());
|
|
ur->add_undo_method(ss.ptr(), "set_height", p_restore);
|
|
}
|
|
|
|
ur->commit_action();
|
|
}
|
|
|
|
if (Object::cast_to<CylinderShape3D>(*s)) {
|
|
Ref<CylinderShape3D> ss = s;
|
|
if (p_cancel) {
|
|
if (p_id == 0) {
|
|
ss->set_radius(p_restore);
|
|
} else {
|
|
ss->set_height(p_restore);
|
|
}
|
|
return;
|
|
}
|
|
|
|
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
|
|
if (p_id == 0) {
|
|
ur->create_action(TTR("Change Cylinder Shape Radius"));
|
|
ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius());
|
|
ur->add_undo_method(ss.ptr(), "set_radius", p_restore);
|
|
} else {
|
|
ur->create_action(
|
|
///
|
|
|
|
////////
|
|
TTR("Change Cylinder Shape Height"));
|
|
ur->add_do_method(ss.ptr(), "set_height", ss->get_height());
|
|
ur->add_undo_method(ss.ptr(), "set_height", p_restore);
|
|
}
|
|
|
|
ur->commit_action();
|
|
}
|
|
}
|
|
|
|
void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Ref<Shape3D> s = cs->get_shape();
|
|
if (s.is_null()) {
|
|
return;
|
|
}
|
|
|
|
const Ref<Material> material =
|
|
get_material(!cs->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo);
|
|
Ref<Material> handles_material = get_material("handles");
|
|
|
|
if (Object::cast_to<SphereShape3D>(*s)) {
|
|
Ref<SphereShape3D> sp = s;
|
|
float r = sp->get_radius();
|
|
|
|
Vector<Vector3> points;
|
|
|
|
for (int i = 0; i <= 360; i++) {
|
|
float ra = Math::deg2rad((float)i);
|
|
float rb = Math::deg2rad((float)i + 1);
|
|
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
|
|
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
|
|
|
|
points.push_back(Vector3(a.x, 0, a.y));
|
|
points.push_back(Vector3(b.x, 0, b.y));
|
|
points.push_back(Vector3(0, a.x, a.y));
|
|
points.push_back(Vector3(0, b.x, b.y));
|
|
points.push_back(Vector3(a.x, a.y, 0));
|
|
points.push_back(Vector3(b.x, b.y, 0));
|
|
}
|
|
|
|
Vector<Vector3> collision_segments;
|
|
|
|
for (int i = 0; i < 64; i++) {
|
|
float ra = i * (Math_TAU / 64.0);
|
|
float rb = (i + 1) * (Math_TAU / 64.0);
|
|
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
|
|
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
|
|
|
|
collision_segments.push_back(Vector3(a.x, 0, a.y));
|
|
collision_segments.push_back(Vector3(b.x, 0, b.y));
|
|
collision_segments.push_back(Vector3(0, a.x, a.y));
|
|
collision_segments.push_back(Vector3(0, b.x, b.y));
|
|
collision_segments.push_back(Vector3(a.x, a.y, 0));
|
|
collision_segments.push_back(Vector3(b.x, b.y, 0));
|
|
}
|
|
|
|
p_gizmo->add_lines(points, material);
|
|
p_gizmo->add_collision_segments(collision_segments);
|
|
Vector<Vector3> handles;
|
|
handles.push_back(Vector3(r, 0, 0));
|
|
p_gizmo->add_handles(handles, handles_material);
|
|
}
|
|
|
|
if (Object::cast_to<BoxShape3D>(*s)) {
|
|
Ref<BoxShape3D> bs = s;
|
|
Vector<Vector3> lines;
|
|
AABB aabb;
|
|
aabb.position = -bs->get_size() / 2;
|
|
aabb.size = bs->get_size();
|
|
|
|
for (int i = 0; i < 12; i++) {
|
|
Vector3 a, b;
|
|
aabb.get_edge(i, a, b);
|
|
lines.push_back(a);
|
|
lines.push_back(b);
|
|
}
|
|
|
|
Vector<Vector3> handles;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
Vector3 ax;
|
|
ax[i] = bs->get_size()[i] / 2;
|
|
handles.push_back(ax);
|
|
}
|
|
|
|
p_gizmo->add_lines(lines, material);
|
|
p_gizmo->add_collision_segments(lines);
|
|
p_gizmo->add_handles(handles, handles_material);
|
|
}
|
|
|
|
if (Object::cast_to<CapsuleShape3D>(*s)) {
|
|
Ref<CapsuleShape3D> cs2 = s;
|
|
float radius = cs2->get_radius();
|
|
float height = cs2->get_height();
|
|
|
|
Vector<Vector3> points;
|
|
|
|
Vector3 d(0, height * 0.5 - radius, 0);
|
|
for (int i = 0; i < 360; i++) {
|
|
float ra = Math::deg2rad((float)i);
|
|
float rb = Math::deg2rad((float)i + 1);
|
|
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius;
|
|
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius;
|
|
|
|
points.push_back(Vector3(a.x, 0, a.y) + d);
|
|
points.push_back(Vector3(b.x, 0, b.y) + d);
|
|
|
|
points.push_back(Vector3(a.x, 0, a.y) - d);
|
|
points.push_back(Vector3(b.x, 0, b.y) - d);
|
|
|
|
if (i % 90 == 0) {
|
|
points.push_back(Vector3(a.x, 0, a.y) + d);
|
|
points.push_back(Vector3(a.x, 0, a.y) - d);
|
|
}
|
|
|
|
Vector3 dud = i < 180 ? d : -d;
|
|
|
|
points.push_back(Vector3(0, a.x, a.y) + dud);
|
|
points.push_back(Vector3(0, b.x, b.y) + dud);
|
|
points.push_back(Vector3(a.y, a.x, 0) + dud);
|
|
points.push_back(Vector3(b.y, b.x, 0) + dud);
|
|
}
|
|
|
|
p_gizmo->add_lines(points, material);
|
|
|
|
Vector<Vector3> collision_segments;
|
|
|
|
for (int i = 0; i < 64; i++) {
|
|
float ra = i * (Math_TAU / 64.0);
|
|
float rb = (i + 1) * (Math_TAU / 64.0);
|
|
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius;
|
|
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius;
|
|
|
|
collision_segments.push_back(Vector3(a.x, 0, a.y) + d);
|
|
collision_segments.push_back(Vector3(b.x, 0, b.y) + d);
|
|
|
|
collision_segments.push_back(Vector3(a.x, 0, a.y) - d);
|
|
collision_segments.push_back(Vector3(b.x, 0, b.y) - d);
|
|
|
|
if (i % 16 == 0) {
|
|
collision_segments.push_back(Vector3(a.x, 0, a.y) + d);
|
|
collision_segments.push_back(Vector3(a.x, 0, a.y) - d);
|
|
}
|
|
|
|
Vector3 dud = i < 32 ? d : -d;
|
|
|
|
collision_segments.push_back(Vector3(0, a.x, a.y) + dud);
|
|
collision_segments.push_back(Vector3(0, b.x, b.y) + dud);
|
|
collision_segments.push_back(Vector3(a.y, a.x, 0) + dud);
|
|
collision_segments.push_back(Vector3(b.y, b.x, 0) + dud);
|
|
}
|
|
|
|
p_gizmo->add_collision_segments(collision_segments);
|
|
|
|
Vector<Vector3> handles;
|
|
handles.push_back(Vector3(cs2->get_radius(), 0, 0));
|
|
handles.push_back(Vector3(0, cs2->get_height() * 0.5, 0));
|
|
p_gizmo->add_handles(handles, handles_material);
|
|
}
|
|
|
|
if (Object::cast_to<CylinderShape3D>(*s)) {
|
|
Ref<CylinderShape3D> cs2 = s;
|
|
float radius = cs2->get_radius();
|
|
float height = cs2->get_height();
|
|
|
|
Vector<Vector3> points;
|
|
|
|
Vector3 d(0, height * 0.5, 0);
|
|
for (int i = 0; i < 360; i++) {
|
|
float ra = Math::deg2rad((float)i);
|
|
float rb = Math::deg2rad((float)i + 1);
|
|
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius;
|
|
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius;
|
|
|
|
points.push_back(Vector3(a.x, 0, a.y) + d);
|
|
points.push_back(Vector3(b.x, 0, b.y) + d);
|
|
|
|
points.push_back(Vector3(a.x, 0, a.y) - d);
|
|
points.push_back(Vector3(b.x, 0, b.y) - d);
|
|
|
|
if (i % 90 == 0) {
|
|
points.push_back(Vector3(a.x, 0, a.y) + d);
|
|
points.push_back(Vector3(a.x, 0, a.y) - d);
|
|
}
|
|
}
|
|
|
|
p_gizmo->add_lines(points, material);
|
|
|
|
Vector<Vector3> collision_segments;
|
|
|
|
for (int i = 0; i < 64; i++) {
|
|
float ra = i * (Math_TAU / 64.0);
|
|
float rb = (i + 1) * (Math_TAU / 64.0);
|
|
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius;
|
|
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius;
|
|
|
|
collision_segments.push_back(Vector3(a.x, 0, a.y) + d);
|
|
collision_segments.push_back(Vector3(b.x, 0, b.y) + d);
|
|
|
|
collision_segments.push_back(Vector3(a.x, 0, a.y) - d);
|
|
collision_segments.push_back(Vector3(b.x, 0, b.y) - d);
|
|
|
|
if (i % 16 == 0) {
|
|
collision_segments.push_back(Vector3(a.x, 0, a.y) + d);
|
|
collision_segments.push_back(Vector3(a.x, 0, a.y) - d);
|
|
}
|
|
}
|
|
|
|
p_gizmo->add_collision_segments(collision_segments);
|
|
|
|
Vector<Vector3> handles;
|
|
handles.push_back(Vector3(cs2->get_radius(), 0, 0));
|
|
handles.push_back(Vector3(0, cs2->get_height() * 0.5, 0));
|
|
p_gizmo->add_handles(handles, handles_material);
|
|
}
|
|
|
|
if (Object::cast_to<WorldMarginShape3D>(*s)) {
|
|
Ref<WorldMarginShape3D> ps = s;
|
|
Plane p = ps->get_plane();
|
|
Vector<Vector3> points;
|
|
|
|
Vector3 n1 = p.get_any_perpendicular_normal();
|
|
Vector3 n2 = p.normal.cross(n1).normalized();
|
|
|
|
Vector3 pface[4] = {
|
|
p.normal * p.d + n1 * 10.0 + n2 * 10.0,
|
|
p.normal * p.d + n1 * 10.0 + n2 * -10.0,
|
|
p.normal * p.d + n1 * -10.0 + n2 * -10.0,
|
|
p.normal * p.d + n1 * -10.0 + n2 * 10.0,
|
|
};
|
|
|
|
points.push_back(pface[0]);
|
|
points.push_back(pface[1]);
|
|
points.push_back(pface[1]);
|
|
points.push_back(pface[2]);
|
|
points.push_back(pface[2]);
|
|
points.push_back(pface[3]);
|
|
points.push_back(pface[3]);
|
|
points.push_back(pface[0]);
|
|
points.push_back(p.normal * p.d);
|
|
points.push_back(p.normal * p.d + p.normal * 3);
|
|
|
|
p_gizmo->add_lines(points, material);
|
|
p_gizmo->add_collision_segments(points);
|
|
}
|
|
|
|
if (Object::cast_to<ConvexPolygonShape3D>(*s)) {
|
|
Vector<Vector3> points = Object::cast_to<ConvexPolygonShape3D>(*s)->get_points();
|
|
|
|
if (points.size() > 3) {
|
|
Vector<Vector3> varr = Variant(points);
|
|
Geometry3D::MeshData md;
|
|
Error err = ConvexHullComputer::convex_hull(varr, md);
|
|
if (err == OK) {
|
|
Vector<Vector3> points2;
|
|
points2.resize(md.edges.size() * 2);
|
|
for (int i = 0; i < md.edges.size(); i++) {
|
|
points2.write[i * 2 + 0] = md.vertices[md.edges[i].a];
|
|
points2.write[i * 2 + 1] = md.vertices[md.edges[i].b];
|
|
}
|
|
|
|
p_gizmo->add_lines(points2, material);
|
|
p_gizmo->add_collision_segments(points2);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Object::cast_to<ConcavePolygonShape3D>(*s)) {
|
|
Ref<ConcavePolygonShape3D> cs2 = s;
|
|
Ref<ArrayMesh> mesh = cs2->get_debug_mesh();
|
|
p_gizmo->add_mesh(mesh, material);
|
|
p_gizmo->add_collision_segments(cs2->get_debug_mesh_lines());
|
|
}
|
|
|
|
if (Object::cast_to<HeightMapShape3D>(*s)) {
|
|
Ref<HeightMapShape3D> hms = s;
|
|
|
|
Ref<ArrayMesh> mesh = hms->get_debug_mesh();
|
|
p_gizmo->add_mesh(mesh, material);
|
|
}
|
|
}
|
|
|
|
/////
|
|
|
|
CollisionPolygon3DGizmoPlugin::CollisionPolygon3DGizmoPlugin() {
|
|
const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
|
|
create_material("shape_material", gizmo_color);
|
|
const float gizmo_value = gizmo_color.get_v();
|
|
const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65);
|
|
create_material("shape_material_disabled", gizmo_color_disabled);
|
|
}
|
|
|
|
bool CollisionPolygon3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<CollisionPolygon3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String CollisionPolygon3DGizmoPlugin::get_gizmo_name() const {
|
|
return "CollisionPolygon3D";
|
|
}
|
|
|
|
int CollisionPolygon3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
void CollisionPolygon3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
CollisionPolygon3D *polygon = Object::cast_to<CollisionPolygon3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Vector<Vector2> points = polygon->get_polygon();
|
|
float depth = polygon->get_depth() * 0.5;
|
|
|
|
Vector<Vector3> lines;
|
|
for (int i = 0; i < points.size(); i++) {
|
|
int n = (i + 1) % points.size();
|
|
lines.push_back(Vector3(points[i].x, points[i].y, depth));
|
|
lines.push_back(Vector3(points[n].x, points[n].y, depth));
|
|
lines.push_back(Vector3(points[i].x, points[i].y, -depth));
|
|
lines.push_back(Vector3(points[n].x, points[n].y, -depth));
|
|
lines.push_back(Vector3(points[i].x, points[i].y, depth));
|
|
lines.push_back(Vector3(points[i].x, points[i].y, -depth));
|
|
}
|
|
|
|
const Ref<Material> material =
|
|
get_material(!polygon->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo);
|
|
|
|
p_gizmo->add_lines(lines, material);
|
|
p_gizmo->add_collision_segments(lines);
|
|
}
|
|
|
|
////
|
|
|
|
NavigationRegion3DGizmoPlugin::NavigationRegion3DGizmoPlugin() {
|
|
create_material("navigation_edge_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_edge", Color(0.5, 1, 1)));
|
|
create_material("navigation_edge_material_disabled", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_edge_disabled", Color(0.7, 0.7, 0.7)));
|
|
create_material("navigation_solid_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_solid", Color(0.5, 1, 1, 0.4)));
|
|
create_material("navigation_solid_material_disabled", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_solid_disabled", Color(0.7, 0.7, 0.7, 0.4)));
|
|
}
|
|
|
|
bool NavigationRegion3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<NavigationRegion3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String NavigationRegion3DGizmoPlugin::get_gizmo_name() const {
|
|
return "NavigationRegion3D";
|
|
}
|
|
|
|
int NavigationRegion3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
NavigationRegion3D *navmesh = Object::cast_to<NavigationRegion3D>(p_gizmo->get_spatial_node());
|
|
|
|
Ref<Material> edge_material = get_material("navigation_edge_material", p_gizmo);
|
|
Ref<Material> edge_material_disabled = get_material("navigation_edge_material_disabled", p_gizmo);
|
|
Ref<Material> solid_material = get_material("navigation_solid_material", p_gizmo);
|
|
Ref<Material> solid_material_disabled = get_material("navigation_solid_material_disabled", p_gizmo);
|
|
|
|
p_gizmo->clear();
|
|
Ref<NavigationMesh> navmeshie = navmesh->get_navigation_mesh();
|
|
if (navmeshie.is_null()) {
|
|
return;
|
|
}
|
|
|
|
Vector<Vector3> vertices = navmeshie->get_vertices();
|
|
const Vector3 *vr = vertices.ptr();
|
|
List<Face3> faces;
|
|
for (int i = 0; i < navmeshie->get_polygon_count(); i++) {
|
|
Vector<int> p = navmeshie->get_polygon(i);
|
|
|
|
for (int j = 2; j < p.size(); j++) {
|
|
Face3 f;
|
|
f.vertex[0] = vr[p[0]];
|
|
f.vertex[1] = vr[p[j - 1]];
|
|
f.vertex[2] = vr[p[j]];
|
|
|
|
faces.push_back(f);
|
|
}
|
|
}
|
|
|
|
if (faces.is_empty()) {
|
|
return;
|
|
}
|
|
|
|
Map<_EdgeKey, bool> edge_map;
|
|
Vector<Vector3> tmeshfaces;
|
|
tmeshfaces.resize(faces.size() * 3);
|
|
|
|
{
|
|
Vector3 *tw = tmeshfaces.ptrw();
|
|
int tidx = 0;
|
|
|
|
for (const Face3 &f : faces) {
|
|
for (int j = 0; j < 3; j++) {
|
|
tw[tidx++] = f.vertex[j];
|
|
_EdgeKey ek;
|
|
ek.from = f.vertex[j].snapped(Vector3(CMP_EPSILON, CMP_EPSILON, CMP_EPSILON));
|
|
ek.to = f.vertex[(j + 1) % 3].snapped(Vector3(CMP_EPSILON, CMP_EPSILON, CMP_EPSILON));
|
|
if (ek.from < ek.to) {
|
|
SWAP(ek.from, ek.to);
|
|
}
|
|
|
|
Map<_EdgeKey, bool>::Element *F = edge_map.find(ek);
|
|
|
|
if (F) {
|
|
F->get() = false;
|
|
|
|
} else {
|
|
edge_map[ek] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Vector<Vector3> lines;
|
|
|
|
for (Map<_EdgeKey, bool>::Element *E = edge_map.front(); E; E = E->next()) {
|
|
if (E->get()) {
|
|
lines.push_back(E->key().from);
|
|
lines.push_back(E->key().to);
|
|
}
|
|
}
|
|
|
|
Ref<TriangleMesh> tmesh = memnew(TriangleMesh);
|
|
tmesh->create(tmeshfaces);
|
|
|
|
if (lines.size()) {
|
|
p_gizmo->add_lines(lines, navmesh->is_enabled() ? edge_material : edge_material_disabled);
|
|
}
|
|
p_gizmo->add_collision_triangles(tmesh);
|
|
Ref<ArrayMesh> m = memnew(ArrayMesh);
|
|
Array a;
|
|
a.resize(Mesh::ARRAY_MAX);
|
|
a[0] = tmeshfaces;
|
|
m->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a);
|
|
m->surface_set_material(0, navmesh->is_enabled() ? solid_material : solid_material_disabled);
|
|
p_gizmo->add_mesh(m);
|
|
p_gizmo->add_collision_segments(lines);
|
|
}
|
|
|
|
//////
|
|
|
|
#define BODY_A_RADIUS 0.25
|
|
#define BODY_B_RADIUS 0.27
|
|
|
|
Basis JointGizmosDrawer::look_body(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
|
|
const Vector3 &p_eye(p_joint_transform.origin);
|
|
const Vector3 &p_target(p_body_transform.origin);
|
|
|
|
Vector3 v_x, v_y, v_z;
|
|
|
|
// Look the body with X
|
|
v_x = p_target - p_eye;
|
|
v_x.normalize();
|
|
|
|
v_z = v_x.cross(Vector3(0, 1, 0));
|
|
v_z.normalize();
|
|
|
|
v_y = v_z.cross(v_x);
|
|
v_y.normalize();
|
|
|
|
Basis base;
|
|
base.set(v_x, v_y, v_z);
|
|
|
|
// Absorb current joint transform
|
|
base = p_joint_transform.basis.inverse() * base;
|
|
|
|
return base;
|
|
}
|
|
|
|
Basis JointGizmosDrawer::look_body_toward(Vector3::Axis p_axis, const Transform3D &joint_transform, const Transform3D &body_transform) {
|
|
switch (p_axis) {
|
|
case Vector3::AXIS_X:
|
|
return look_body_toward_x(joint_transform, body_transform);
|
|
case Vector3::AXIS_Y:
|
|
return look_body_toward_y(joint_transform, body_transform);
|
|
case Vector3::AXIS_Z:
|
|
return look_body_toward_z(joint_transform, body_transform);
|
|
default:
|
|
return Basis();
|
|
}
|
|
}
|
|
|
|
Basis JointGizmosDrawer::look_body_toward_x(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
|
|
const Vector3 &p_eye(p_joint_transform.origin);
|
|
const Vector3 &p_target(p_body_transform.origin);
|
|
|
|
const Vector3 p_front(p_joint_transform.basis.get_axis(0));
|
|
|
|
Vector3 v_x, v_y, v_z;
|
|
|
|
// Look the body with X
|
|
v_x = p_target - p_eye;
|
|
v_x.normalize();
|
|
|
|
v_y = p_front.cross(v_x);
|
|
v_y.normalize();
|
|
|
|
v_z = v_y.cross(p_front);
|
|
v_z.normalize();
|
|
|
|
// Clamp X to FRONT axis
|
|
v_x = p_front;
|
|
v_x.normalize();
|
|
|
|
Basis base;
|
|
base.set(v_x, v_y, v_z);
|
|
|
|
// Absorb current joint transform
|
|
base = p_joint_transform.basis.inverse() * base;
|
|
|
|
return base;
|
|
}
|
|
|
|
Basis JointGizmosDrawer::look_body_toward_y(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
|
|
const Vector3 &p_eye(p_joint_transform.origin);
|
|
const Vector3 &p_target(p_body_transform.origin);
|
|
|
|
const Vector3 p_up(p_joint_transform.basis.get_axis(1));
|
|
|
|
Vector3 v_x, v_y, v_z;
|
|
|
|
// Look the body with X
|
|
v_x = p_target - p_eye;
|
|
v_x.normalize();
|
|
|
|
v_z = v_x.cross(p_up);
|
|
v_z.normalize();
|
|
|
|
v_x = p_up.cross(v_z);
|
|
v_x.normalize();
|
|
|
|
// Clamp Y to UP axis
|
|
v_y = p_up;
|
|
v_y.normalize();
|
|
|
|
Basis base;
|
|
base.set(v_x, v_y, v_z);
|
|
|
|
// Absorb current joint transform
|
|
base = p_joint_transform.basis.inverse() * base;
|
|
|
|
return base;
|
|
}
|
|
|
|
Basis JointGizmosDrawer::look_body_toward_z(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
|
|
const Vector3 &p_eye(p_joint_transform.origin);
|
|
const Vector3 &p_target(p_body_transform.origin);
|
|
|
|
const Vector3 p_lateral(p_joint_transform.basis.get_axis(2));
|
|
|
|
Vector3 v_x, v_y, v_z;
|
|
|
|
// Look the body with X
|
|
v_x = p_target - p_eye;
|
|
v_x.normalize();
|
|
|
|
v_z = p_lateral;
|
|
v_z.normalize();
|
|
|
|
v_y = v_z.cross(v_x);
|
|
v_y.normalize();
|
|
|
|
// Clamp X to Z axis
|
|
v_x = v_y.cross(v_z);
|
|
v_x.normalize();
|
|
|
|
Basis base;
|
|
base.set(v_x, v_y, v_z);
|
|
|
|
// Absorb current joint transform
|
|
base = p_joint_transform.basis.inverse() * base;
|
|
|
|
return base;
|
|
}
|
|
|
|
void JointGizmosDrawer::draw_circle(Vector3::Axis p_axis, real_t p_radius, const Transform3D &p_offset, const Basis &p_base, real_t p_limit_lower, real_t p_limit_upper, Vector<Vector3> &r_points, bool p_inverse) {
|
|
if (p_limit_lower == p_limit_upper) {
|
|
r_points.push_back(p_offset.translated(Vector3()).origin);
|
|
r_points.push_back(p_offset.translated(p_base.xform(Vector3(0.5, 0, 0))).origin);
|
|
|
|
} else {
|
|
if (p_limit_lower > p_limit_upper) {
|
|
p_limit_lower = -Math_PI;
|
|
p_limit_upper = Math_PI;
|
|
}
|
|
|
|
const int points = 32;
|
|
|
|
for (int i = 0; i < points; i++) {
|
|
real_t s = p_limit_lower + i * (p_limit_upper - p_limit_lower) / points;
|
|
real_t n = p_limit_lower + (i + 1) * (p_limit_upper - p_limit_lower) / points;
|
|
|
|
Vector3 from;
|
|
Vector3 to;
|
|
switch (p_axis) {
|
|
case Vector3::AXIS_X:
|
|
if (p_inverse) {
|
|
from = p_base.xform(Vector3(0, Math::sin(s), Math::cos(s))) * p_radius;
|
|
to = p_base.xform(Vector3(0, Math::sin(n), Math::cos(n))) * p_radius;
|
|
} else {
|
|
from = p_base.xform(Vector3(0, -Math::sin(s), Math::cos(s))) * p_radius;
|
|
to = p_base.xform(Vector3(0, -Math::sin(n), Math::cos(n))) * p_radius;
|
|
}
|
|
break;
|
|
case Vector3::AXIS_Y:
|
|
if (p_inverse) {
|
|
from = p_base.xform(Vector3(Math::cos(s), 0, -Math::sin(s))) * p_radius;
|
|
to = p_base.xform(Vector3(Math::cos(n), 0, -Math::sin(n))) * p_radius;
|
|
} else {
|
|
from = p_base.xform(Vector3(Math::cos(s), 0, Math::sin(s))) * p_radius;
|
|
to = p_base.xform(Vector3(Math::cos(n), 0, Math::sin(n))) * p_radius;
|
|
}
|
|
break;
|
|
case Vector3::AXIS_Z:
|
|
from = p_base.xform(Vector3(Math::cos(s), Math::sin(s), 0)) * p_radius;
|
|
to = p_base.xform(Vector3(Math::cos(n), Math::sin(n), 0)) * p_radius;
|
|
break;
|
|
}
|
|
|
|
if (i == points - 1) {
|
|
r_points.push_back(p_offset.translated(to).origin);
|
|
r_points.push_back(p_offset.translated(Vector3()).origin);
|
|
}
|
|
if (i == 0) {
|
|
r_points.push_back(p_offset.translated(from).origin);
|
|
r_points.push_back(p_offset.translated(Vector3()).origin);
|
|
}
|
|
|
|
r_points.push_back(p_offset.translated(from).origin);
|
|
r_points.push_back(p_offset.translated(to).origin);
|
|
}
|
|
|
|
r_points.push_back(p_offset.translated(Vector3(0, p_radius * 1.5, 0)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3()).origin);
|
|
}
|
|
}
|
|
|
|
void JointGizmosDrawer::draw_cone(const Transform3D &p_offset, const Basis &p_base, real_t p_swing, real_t p_twist, Vector<Vector3> &r_points) {
|
|
float r = 1.0;
|
|
float w = r * Math::sin(p_swing);
|
|
float d = r * Math::cos(p_swing);
|
|
|
|
//swing
|
|
for (int i = 0; i < 360; i += 10) {
|
|
float ra = Math::deg2rad((float)i);
|
|
float rb = Math::deg2rad((float)i + 10);
|
|
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w;
|
|
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w;
|
|
|
|
r_points.push_back(p_offset.translated(p_base.xform(Vector3(d, a.x, a.y))).origin);
|
|
r_points.push_back(p_offset.translated(p_base.xform(Vector3(d, b.x, b.y))).origin);
|
|
|
|
if (i % 90 == 0) {
|
|
r_points.push_back(p_offset.translated(p_base.xform(Vector3(d, a.x, a.y))).origin);
|
|
r_points.push_back(p_offset.translated(p_base.xform(Vector3())).origin);
|
|
}
|
|
}
|
|
|
|
r_points.push_back(p_offset.translated(p_base.xform(Vector3())).origin);
|
|
r_points.push_back(p_offset.translated(p_base.xform(Vector3(1, 0, 0))).origin);
|
|
|
|
/// Twist
|
|
float ts = Math::rad2deg(p_twist);
|
|
ts = MIN(ts, 720);
|
|
|
|
for (int i = 0; i < int(ts); i += 5) {
|
|
float ra = Math::deg2rad((float)i);
|
|
float rb = Math::deg2rad((float)i + 5);
|
|
float c = i / 720.0;
|
|
float cn = (i + 5) / 720.0;
|
|
Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w * c;
|
|
Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w * cn;
|
|
|
|
r_points.push_back(p_offset.translated(p_base.xform(Vector3(c, a.x, a.y))).origin);
|
|
r_points.push_back(p_offset.translated(p_base.xform(Vector3(cn, b.x, b.y))).origin);
|
|
}
|
|
}
|
|
|
|
////
|
|
|
|
Joint3DGizmoPlugin::Joint3DGizmoPlugin() {
|
|
create_material("joint_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1)));
|
|
create_material("joint_body_a_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint_body_a", Color(0.6, 0.8, 1)));
|
|
create_material("joint_body_b_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint_body_b", Color(0.6, 0.9, 1)));
|
|
|
|
update_timer = memnew(Timer);
|
|
update_timer->set_name("JointGizmoUpdateTimer");
|
|
update_timer->set_wait_time(1.0 / 120.0);
|
|
update_timer->connect("timeout", callable_mp(this, &Joint3DGizmoPlugin::incremental_update_gizmos));
|
|
update_timer->set_autostart(true);
|
|
EditorNode::get_singleton()->call_deferred(SNAME("add_child"), update_timer);
|
|
}
|
|
|
|
void Joint3DGizmoPlugin::incremental_update_gizmos() {
|
|
if (!current_gizmos.is_empty()) {
|
|
update_idx++;
|
|
update_idx = update_idx % current_gizmos.size();
|
|
redraw(current_gizmos[update_idx]);
|
|
}
|
|
}
|
|
|
|
bool Joint3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
|
|
return Object::cast_to<Joint3D>(p_spatial) != nullptr;
|
|
}
|
|
|
|
String Joint3DGizmoPlugin::get_gizmo_name() const {
|
|
return "Joint3D";
|
|
}
|
|
|
|
int Joint3DGizmoPlugin::get_priority() const {
|
|
return -1;
|
|
}
|
|
|
|
void Joint3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
|
|
Joint3D *joint = Object::cast_to<Joint3D>(p_gizmo->get_spatial_node());
|
|
|
|
p_gizmo->clear();
|
|
|
|
Node3D *node_body_a = nullptr;
|
|
if (!joint->get_node_a().is_empty()) {
|
|
node_body_a = Object::cast_to<Node3D>(joint->get_node(joint->get_node_a()));
|
|
}
|
|
|
|
Node3D *node_body_b = nullptr;
|
|
if (!joint->get_node_b().is_empty()) {
|
|
node_body_b = Object::cast_to<Node3D>(joint->get_node(joint->get_node_b()));
|
|
}
|
|
|
|
if (!node_body_a && !node_body_b) {
|
|
return;
|
|
}
|
|
|
|
Ref<Material> common_material = get_material("joint_material", p_gizmo);
|
|
Ref<Material> body_a_material = get_material("joint_body_a_material", p_gizmo);
|
|
Ref<Material> body_b_material = get_material("joint_body_b_material", p_gizmo);
|
|
|
|
Vector<Vector3> points;
|
|
Vector<Vector3> body_a_points;
|
|
Vector<Vector3> body_b_points;
|
|
|
|
if (Object::cast_to<PinJoint3D>(joint)) {
|
|
CreatePinJointGizmo(Transform3D(), points);
|
|
p_gizmo->add_collision_segments(points);
|
|
p_gizmo->add_lines(points, common_material);
|
|
}
|
|
|
|
HingeJoint3D *hinge = Object::cast_to<HingeJoint3D>(joint);
|
|
if (hinge) {
|
|
CreateHingeJointGizmo(
|
|
Transform3D(),
|
|
hinge->get_global_transform(),
|
|
node_body_a ? node_body_a->get_global_transform() : Transform3D(),
|
|
node_body_b ? node_body_b->get_global_transform() : Transform3D(),
|
|
hinge->get_param(HingeJoint3D::PARAM_LIMIT_LOWER),
|
|
hinge->get_param(HingeJoint3D::PARAM_LIMIT_UPPER),
|
|
hinge->get_flag(HingeJoint3D::FLAG_USE_LIMIT),
|
|
points,
|
|
node_body_a ? &body_a_points : nullptr,
|
|
node_body_b ? &body_b_points : nullptr);
|
|
|
|
p_gizmo->add_collision_segments(points);
|
|
p_gizmo->add_collision_segments(body_a_points);
|
|
p_gizmo->add_collision_segments(body_b_points);
|
|
|
|
p_gizmo->add_lines(points, common_material);
|
|
p_gizmo->add_lines(body_a_points, body_a_material);
|
|
p_gizmo->add_lines(body_b_points, body_b_material);
|
|
}
|
|
|
|
SliderJoint3D *slider = Object::cast_to<SliderJoint3D>(joint);
|
|
if (slider) {
|
|
CreateSliderJointGizmo(
|
|
Transform3D(),
|
|
slider->get_global_transform(),
|
|
node_body_a ? node_body_a->get_global_transform() : Transform3D(),
|
|
node_body_b ? node_body_b->get_global_transform() : Transform3D(),
|
|
slider->get_param(SliderJoint3D::PARAM_ANGULAR_LIMIT_LOWER),
|
|
slider->get_param(SliderJoint3D::PARAM_ANGULAR_LIMIT_UPPER),
|
|
slider->get_param(SliderJoint3D::PARAM_LINEAR_LIMIT_LOWER),
|
|
slider->get_param(SliderJoint3D::PARAM_LINEAR_LIMIT_UPPER),
|
|
points,
|
|
node_body_a ? &body_a_points : nullptr,
|
|
node_body_b ? &body_b_points : nullptr);
|
|
|
|
p_gizmo->add_collision_segments(points);
|
|
p_gizmo->add_collision_segments(body_a_points);
|
|
p_gizmo->add_collision_segments(body_b_points);
|
|
|
|
p_gizmo->add_lines(points, common_material);
|
|
p_gizmo->add_lines(body_a_points, body_a_material);
|
|
p_gizmo->add_lines(body_b_points, body_b_material);
|
|
}
|
|
|
|
ConeTwistJoint3D *cone = Object::cast_to<ConeTwistJoint3D>(joint);
|
|
if (cone) {
|
|
CreateConeTwistJointGizmo(
|
|
Transform3D(),
|
|
cone->get_global_transform(),
|
|
node_body_a ? node_body_a->get_global_transform() : Transform3D(),
|
|
node_body_b ? node_body_b->get_global_transform() : Transform3D(),
|
|
cone->get_param(ConeTwistJoint3D::PARAM_SWING_SPAN),
|
|
cone->get_param(ConeTwistJoint3D::PARAM_TWIST_SPAN),
|
|
node_body_a ? &body_a_points : nullptr,
|
|
node_body_b ? &body_b_points : nullptr);
|
|
|
|
p_gizmo->add_collision_segments(body_a_points);
|
|
p_gizmo->add_collision_segments(body_b_points);
|
|
|
|
p_gizmo->add_lines(body_a_points, body_a_material);
|
|
p_gizmo->add_lines(body_b_points, body_b_material);
|
|
}
|
|
|
|
Generic6DOFJoint3D *gen = Object::cast_to<Generic6DOFJoint3D>(joint);
|
|
if (gen) {
|
|
CreateGeneric6DOFJointGizmo(
|
|
Transform3D(),
|
|
gen->get_global_transform(),
|
|
node_body_a ? node_body_a->get_global_transform() : Transform3D(),
|
|
node_body_b ? node_body_b->get_global_transform() : Transform3D(),
|
|
|
|
gen->get_param_x(Generic6DOFJoint3D::PARAM_ANGULAR_LOWER_LIMIT),
|
|
gen->get_param_x(Generic6DOFJoint3D::PARAM_ANGULAR_UPPER_LIMIT),
|
|
gen->get_param_x(Generic6DOFJoint3D::PARAM_LINEAR_LOWER_LIMIT),
|
|
gen->get_param_x(Generic6DOFJoint3D::PARAM_LINEAR_UPPER_LIMIT),
|
|
gen->get_flag_x(Generic6DOFJoint3D::FLAG_ENABLE_ANGULAR_LIMIT),
|
|
gen->get_flag_x(Generic6DOFJoint3D::FLAG_ENABLE_LINEAR_LIMIT),
|
|
|
|
gen->get_param_y(Generic6DOFJoint3D::PARAM_ANGULAR_LOWER_LIMIT),
|
|
gen->get_param_y(Generic6DOFJoint3D::PARAM_ANGULAR_UPPER_LIMIT),
|
|
gen->get_param_y(Generic6DOFJoint3D::PARAM_LINEAR_LOWER_LIMIT),
|
|
gen->get_param_y(Generic6DOFJoint3D::PARAM_LINEAR_UPPER_LIMIT),
|
|
gen->get_flag_y(Generic6DOFJoint3D::FLAG_ENABLE_ANGULAR_LIMIT),
|
|
gen->get_flag_y(Generic6DOFJoint3D::FLAG_ENABLE_LINEAR_LIMIT),
|
|
|
|
gen->get_param_z(Generic6DOFJoint3D::PARAM_ANGULAR_LOWER_LIMIT),
|
|
gen->get_param_z(Generic6DOFJoint3D::PARAM_ANGULAR_UPPER_LIMIT),
|
|
gen->get_param_z(Generic6DOFJoint3D::PARAM_LINEAR_LOWER_LIMIT),
|
|
gen->get_param_z(Generic6DOFJoint3D::PARAM_LINEAR_UPPER_LIMIT),
|
|
gen->get_flag_z(Generic6DOFJoint3D::FLAG_ENABLE_ANGULAR_LIMIT),
|
|
gen->get_flag_z(Generic6DOFJoint3D::FLAG_ENABLE_LINEAR_LIMIT),
|
|
|
|
points,
|
|
node_body_a ? &body_a_points : nullptr,
|
|
node_body_a ? &body_b_points : nullptr);
|
|
|
|
p_gizmo->add_collision_segments(points);
|
|
p_gizmo->add_collision_segments(body_a_points);
|
|
p_gizmo->add_collision_segments(body_b_points);
|
|
|
|
p_gizmo->add_lines(points, common_material);
|
|
p_gizmo->add_lines(body_a_points, body_a_material);
|
|
p_gizmo->add_lines(body_b_points, body_b_material);
|
|
}
|
|
}
|
|
|
|
void Joint3DGizmoPlugin::CreatePinJointGizmo(const Transform3D &p_offset, Vector<Vector3> &r_cursor_points) {
|
|
float cs = 0.25;
|
|
|
|
r_cursor_points.push_back(p_offset.translated(Vector3(+cs, 0, 0)).origin);
|
|
r_cursor_points.push_back(p_offset.translated(Vector3(-cs, 0, 0)).origin);
|
|
r_cursor_points.push_back(p_offset.translated(Vector3(0, +cs, 0)).origin);
|
|
r_cursor_points.push_back(p_offset.translated(Vector3(0, -cs, 0)).origin);
|
|
r_cursor_points.push_back(p_offset.translated(Vector3(0, 0, +cs)).origin);
|
|
r_cursor_points.push_back(p_offset.translated(Vector3(0, 0, -cs)).origin);
|
|
}
|
|
|
|
void Joint3DGizmoPlugin::CreateHingeJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_limit_lower, real_t p_limit_upper, bool p_use_limit, Vector<Vector3> &r_common_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points) {
|
|
r_common_points.push_back(p_offset.translated(Vector3(0, 0, 0.5)).origin);
|
|
r_common_points.push_back(p_offset.translated(Vector3(0, 0, -0.5)).origin);
|
|
|
|
if (!p_use_limit) {
|
|
p_limit_upper = -1;
|
|
p_limit_lower = 0;
|
|
}
|
|
|
|
if (r_body_a_points) {
|
|
JointGizmosDrawer::draw_circle(Vector3::AXIS_Z,
|
|
BODY_A_RADIUS,
|
|
p_offset,
|
|
JointGizmosDrawer::look_body_toward_z(p_trs_joint, p_trs_body_a),
|
|
p_limit_lower,
|
|
p_limit_upper,
|
|
*r_body_a_points);
|
|
}
|
|
|
|
if (r_body_b_points) {
|
|
JointGizmosDrawer::draw_circle(Vector3::AXIS_Z,
|
|
BODY_B_RADIUS,
|
|
p_offset,
|
|
JointGizmosDrawer::look_body_toward_z(p_trs_joint, p_trs_body_b),
|
|
p_limit_lower,
|
|
p_limit_upper,
|
|
*r_body_b_points);
|
|
}
|
|
}
|
|
|
|
void Joint3DGizmoPlugin::CreateSliderJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_angular_limit_lower, real_t p_angular_limit_upper, real_t p_linear_limit_lower, real_t p_linear_limit_upper, Vector<Vector3> &r_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points) {
|
|
p_linear_limit_lower = -p_linear_limit_lower;
|
|
p_linear_limit_upper = -p_linear_limit_upper;
|
|
|
|
float cs = 0.25;
|
|
r_points.push_back(p_offset.translated(Vector3(0, 0, 0.5)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(0, 0, -0.5)).origin);
|
|
|
|
if (p_linear_limit_lower >= p_linear_limit_upper) {
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, 0, 0)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, 0, 0)).origin);
|
|
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, -cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, -cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, -cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, -cs)).origin);
|
|
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, -cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, -cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, -cs)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, -cs)).origin);
|
|
|
|
} else {
|
|
r_points.push_back(p_offset.translated(Vector3(+cs * 2, 0, 0)).origin);
|
|
r_points.push_back(p_offset.translated(Vector3(-cs * 2, 0, 0)).origin);
|
|
}
|
|
|
|
if (r_body_a_points) {
|
|
JointGizmosDrawer::draw_circle(
|
|
Vector3::AXIS_X,
|
|
BODY_A_RADIUS,
|
|
p_offset,
|
|
JointGizmosDrawer::look_body_toward(Vector3::AXIS_X, p_trs_joint, p_trs_body_a),
|
|
p_angular_limit_lower,
|
|
p_angular_limit_upper,
|
|
*r_body_a_points);
|
|
}
|
|
|
|
if (r_body_b_points) {
|
|
JointGizmosDrawer::draw_circle(
|
|
Vector3::AXIS_X,
|
|
BODY_B_RADIUS,
|
|
p_offset,
|
|
JointGizmosDrawer::look_body_toward(Vector3::AXIS_X, p_trs_joint, p_trs_body_b),
|
|
p_angular_limit_lower,
|
|
p_angular_limit_upper,
|
|
*r_body_b_points,
|
|
true);
|
|
}
|
|
}
|
|
|
|
void Joint3DGizmoPlugin::CreateConeTwistJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_swing, real_t p_twist, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points) {
|
|
if (r_body_a_points) {
|
|
JointGizmosDrawer::draw_cone(
|
|
p_offset,
|
|
JointGizmosDrawer::look_body(p_trs_joint, p_trs_body_a),
|
|
p_swing,
|
|
p_twist,
|
|
*r_body_a_points);
|
|
}
|
|
|
|
if (r_body_b_points) {
|
|
JointGizmosDrawer::draw_cone(
|
|
p_offset,
|
|
JointGizmosDrawer::look_body(p_trs_joint, p_trs_body_b),
|
|
p_swing,
|
|
p_twist,
|
|
*r_body_b_points);
|
|
}
|
|
}
|
|
|
|
void Joint3DGizmoPlugin::CreateGeneric6DOFJointGizmo(
|
|
const Transform3D &p_offset,
|
|
const Transform3D &p_trs_joint,
|
|
const Transform3D &p_trs_body_a,
|
|
const Transform3D &p_trs_body_b,
|
|
real_t p_angular_limit_lower_x,
|
|
real_t p_angular_limit_upper_x,
|
|
real_t p_linear_limit_lower_x,
|
|
real_t p_linear_limit_upper_x,
|
|
bool p_enable_angular_limit_x,
|
|
bool p_enable_linear_limit_x,
|
|
real_t p_angular_limit_lower_y,
|
|
real_t p_angular_limit_upper_y,
|
|
real_t p_linear_limit_lower_y,
|
|
real_t p_linear_limit_upper_y,
|
|
bool p_enable_angular_limit_y,
|
|
bool p_enable_linear_limit_y,
|
|
real_t p_angular_limit_lower_z,
|
|
real_t p_angular_limit_upper_z,
|
|
real_t p_linear_limit_lower_z,
|
|
real_t p_linear_limit_upper_z,
|
|
bool p_enable_angular_limit_z,
|
|
bool p_enable_linear_limit_z,
|
|
Vector<Vector3> &r_points,
|
|
Vector<Vector3> *r_body_a_points,
|
|
Vector<Vector3> *r_body_b_points) {
|
|
float cs = 0.25;
|
|
|
|
for (int ax = 0; ax < 3; ax++) {
|
|
float ll = 0;
|
|
float ul = 0;
|
|
float lll = 0;
|
|
float lul = 0;
|
|
|
|
int a1 = 0;
|
|
int a2 = 0;
|
|
int a3 = 0;
|
|
bool enable_ang = false;
|
|
bool enable_lin = false;
|
|
|
|
switch (ax) {
|
|
case 0:
|
|
ll = p_angular_limit_lower_x;
|
|
ul = p_angular_limit_upper_x;
|
|
lll = -p_linear_limit_lower_x;
|
|
lul = -p_linear_limit_upper_x;
|
|
enable_ang = p_enable_angular_limit_x;
|
|
enable_lin = p_enable_linear_limit_x;
|
|
a1 = 0;
|
|
a2 = 1;
|
|
a3 = 2;
|
|
break;
|
|
case 1:
|
|
ll = p_angular_limit_lower_y;
|
|
ul = p_angular_limit_upper_y;
|
|
lll = -p_linear_limit_lower_y;
|
|
lul = -p_linear_limit_upper_y;
|
|
enable_ang = p_enable_angular_limit_y;
|
|
enable_lin = p_enable_linear_limit_y;
|
|
a1 = 1;
|
|
a2 = 2;
|
|
a3 = 0;
|
|
break;
|
|
case 2:
|
|
ll = p_angular_limit_lower_z;
|
|
ul = p_angular_limit_upper_z;
|
|
lll = -p_linear_limit_lower_z;
|
|
lul = -p_linear_limit_upper_z;
|
|
enable_ang = p_enable_angular_limit_z;
|
|
enable_lin = p_enable_linear_limit_z;
|
|
a1 = 2;
|
|
a2 = 0;
|
|
a3 = 1;
|
|
break;
|
|
}
|
|
|
|
#define ADD_VTX(x, y, z) \
|
|
{ \
|
|
Vector3 v; \
|
|
v[a1] = (x); \
|
|
v[a2] = (y); \
|
|
v[a3] = (z); \
|
|
r_points.push_back(p_offset.translated(v).origin); \
|
|
}
|
|
|
|
if (enable_lin && lll >= lul) {
|
|
ADD_VTX(lul, 0, 0);
|
|
ADD_VTX(lll, 0, 0);
|
|
|
|
ADD_VTX(lul, -cs, -cs);
|
|
ADD_VTX(lul, -cs, cs);
|
|
ADD_VTX(lul, -cs, cs);
|
|
ADD_VTX(lul, cs, cs);
|
|
ADD_VTX(lul, cs, cs);
|
|
ADD_VTX(lul, cs, -cs);
|
|
ADD_VTX(lul, cs, -cs);
|
|
ADD_VTX(lul, -cs, -cs);
|
|
|
|
ADD_VTX(lll, -cs, -cs);
|
|
ADD_VTX(lll, -cs, cs);
|
|
ADD_VTX(lll, -cs, cs);
|
|
ADD_VTX(lll, cs, cs);
|
|
ADD_VTX(lll, cs, cs);
|
|
ADD_VTX(lll, cs, -cs);
|
|
ADD_VTX(lll, cs, -cs);
|
|
ADD_VTX(lll, -cs, -cs);
|
|
|
|
} else {
|
|
ADD_VTX(+cs * 2, 0, 0);
|
|
ADD_VTX(-cs * 2, 0, 0);
|
|
}
|
|
|
|
if (!enable_ang) {
|
|
ll = 0;
|
|
ul = -1;
|
|
}
|
|
|
|
if (r_body_a_points) {
|
|
JointGizmosDrawer::draw_circle(
|
|
static_cast<Vector3::Axis>(ax),
|
|
BODY_A_RADIUS,
|
|
p_offset,
|
|
JointGizmosDrawer::look_body_toward(static_cast<Vector3::Axis>(ax), p_trs_joint, p_trs_body_a),
|
|
ll,
|
|
ul,
|
|
*r_body_a_points,
|
|
true);
|
|
}
|
|
|
|
if (r_body_b_points) {
|
|
JointGizmosDrawer::draw_circle(
|
|
static_cast<Vector3::Axis>(ax),
|
|
BODY_B_RADIUS,
|
|
p_offset,
|
|
JointGizmosDrawer::look_body_toward(static_cast<Vector3::Axis>(ax), p_trs_joint, p_trs_body_b),
|
|
ll,
|
|
ul,
|
|
*r_body_b_points);
|
|
}
|
|
}
|
|
|
|
#undef ADD_VTX
|
|
}
|