Portals - add gizmo handles for editing portals and rooms

Gizmo handles are added for much more user friendly editing of portals and room bounds.
This commit is contained in:
lawnjelly 2021-08-04 11:23:04 +01:00
parent 93ff6e790b
commit 770d9f8220
8 changed files with 518 additions and 211 deletions

View file

@ -12,6 +12,14 @@
<tutorials>
</tutorials>
<methods>
<method name="set_point">
<return type="void" />
<argument index="0" name="index" type="int" />
<argument index="1" name="position" type="Vector2" />
<description>
Sets individual points. Primarily for use by the editor.
</description>
</method>
</methods>
<members>
<member name="linked_room" type="NodePath" setter="set_linked_room" getter="get_linked_room" default="NodePath(&quot;&quot;)">

View file

@ -13,6 +13,14 @@
<tutorials>
</tutorials>
<methods>
<method name="set_point">
<return type="void" />
<argument index="0" name="index" type="int" />
<argument index="1" name="position" type="Vector3" />
<description>
Sets individual points. Primarily for use by the editor.
</description>
</method>
</methods>
<members>
<member name="points" type="PoolVector3Array" setter="set_points" getter="get_points" default="PoolVector3Array( )">

View file

@ -4441,6 +4441,19 @@ RoomGizmoPlugin::RoomGizmoPlugin() {
create_material("room", color_room, false, true, false);
create_material("room_overlap", color_overlap, false, false, false);
create_handle_material("room_handle");
}
Ref<EditorSpatialGizmo> RoomGizmoPlugin::create_gizmo(Spatial *p_spatial) {
Ref<RoomSpatialGizmo> ref;
Room *room = Object::cast_to<Room>(p_spatial);
if (room) {
ref = Ref<RoomSpatialGizmo>(memnew(RoomSpatialGizmo(room)));
}
return ref;
}
bool RoomGizmoPlugin::has_gizmo(Spatial *p_spatial) {
@ -4459,22 +4472,110 @@ int RoomGizmoPlugin::get_priority() const {
return -1;
}
void RoomGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
p_gizmo->clear();
//////////////////////
Room *room = Object::cast_to<Room>(p_gizmo->get_spatial_node());
String RoomSpatialGizmo::get_handle_name(int p_idx) const {
return "Point " + itos(p_idx);
}
if (room) {
const Geometry::MeshData &md = room->_bound_mesh_data;
Variant RoomSpatialGizmo::get_handle_value(int p_idx) {
if (!_room) {
return Vector3(0, 0, 0);
}
int num_points = _room->_bound_pts.size();
if (p_idx >= num_points) {
return Vector3(0, 0, 0);
}
return _room->_bound_pts[p_idx];
}
void RoomSpatialGizmo::set_handle(int p_idx, Camera *p_camera, const Point2 &p_point) {
if (!_room || (p_idx >= _room->_bound_pts.size())) {
return;
}
Transform tr = _room->get_global_transform();
Transform tr_inv = tr.affine_inverse();
Vector3 pt_world = _room->_bound_pts[p_idx];
pt_world = tr.xform(pt_world);
Vector3 ray_from = p_camera->project_ray_origin(p_point);
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
Vector3 camera_dir = p_camera->get_transform().basis.get_axis(2);
// find the smallest camera axis, we will only transform the handles on 2 axes max,
// to try and make things more user friendly (it is confusing trying to change 3d position
// from a 2d view)
int biggest_axis = 0;
real_t biggest = 0.0;
for (int n = 0; n < 3; n++) {
real_t val = Math::abs(camera_dir.get_axis(n));
if (val > biggest) {
biggest = val;
biggest_axis = n;
}
}
{
Plane plane(pt_world, camera_dir);
Vector3 inters;
if (plane.intersects_ray(ray_from, ray_dir, &inters)) {
if (SpatialEditor::get_singleton()->is_snap_enabled()) {
float snap = SpatialEditor::get_singleton()->get_translate_snap();
inters.snap(Vector3(snap, snap, snap));
}
for (int n = 0; n < 3; n++) {
if (n != biggest_axis) {
pt_world.set_axis(n, inters.get_axis(n));
}
}
Vector3 pt_local = tr_inv.xform(pt_world);
_room->set_point(p_idx, pt_local);
}
return;
}
}
void RoomSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_cancel) {
if (!_room || (p_idx >= _room->_bound_pts.size())) {
return;
}
UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo();
ur->create_action(TTR("Set Room Point Position"));
ur->add_do_method(_room, "set_point", p_idx, _room->_bound_pts[p_idx]);
ur->add_undo_method(_room, "set_point", p_idx, p_restore);
ur->commit_action();
_room->property_list_changed_notify();
}
void RoomSpatialGizmo::redraw() {
clear();
if (!_room) {
return;
}
const Geometry::MeshData &md = _room->_bound_mesh_data;
if (!md.edges.size())
return;
Vector<Vector3> lines;
Transform tr = room->get_global_transform();
tr.affine_invert();
Transform tr = _room->get_global_transform();
Transform tr_inv = tr.affine_inverse();
Ref<Material> material = get_material("room", p_gizmo);
Ref<Material> material_overlap = get_material("room_overlap", p_gizmo);
Ref<Material> material = gizmo_plugin->get_material("room", this);
Ref<Material> material_overlap = gizmo_plugin->get_material("room_overlap", this);
Color color(1, 1, 1, 1);
for (int n = 0; n < md.edges.size(); n++) {
@ -4482,27 +4583,29 @@ void RoomGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
Vector3 b = md.vertices[md.edges[n].b];
// xform
a = tr.xform(a);
b = tr.xform(b);
a = tr_inv.xform(a);
b = tr_inv.xform(b);
lines.push_back(a);
lines.push_back(b);
}
p_gizmo->add_lines(lines, material, false, color);
if (lines.size()) {
add_lines(lines, material, false, color);
}
// overlap zones
for (int z = 0; z < room->_gizmo_overlap_zones.size(); z++) {
const Geometry::MeshData &md_overlap = room->_gizmo_overlap_zones[z];
for (int z = 0; z < _room->_gizmo_overlap_zones.size(); z++) {
const Geometry::MeshData &md_overlap = _room->_gizmo_overlap_zones[z];
Vector<Vector3> pts;
for (int f = 0; f < md_overlap.faces.size(); f++) {
const Geometry::MeshData::Face &face = md_overlap.faces[f];
for (int c = 0; c < face.indices.size() - 2; c++) {
pts.push_back(tr.xform(md_overlap.vertices[face.indices[0]]));
pts.push_back(tr.xform(md_overlap.vertices[face.indices[c + 1]]));
pts.push_back(tr.xform(md_overlap.vertices[face.indices[c + 2]]));
pts.push_back(tr_inv.xform(md_overlap.vertices[face.indices[0]]));
pts.push_back(tr_inv.xform(md_overlap.vertices[face.indices[c + 1]]));
pts.push_back(tr_inv.xform(md_overlap.vertices[face.indices[c + 2]]));
}
}
@ -4511,16 +4614,31 @@ void RoomGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
array.resize(Mesh::ARRAY_MAX);
array[Mesh::ARRAY_VERTEX] = pts;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array);
p_gizmo->add_mesh(mesh, false, Ref<SkinReference>(), material_overlap);
add_mesh(mesh, false, Ref<SkinReference>(), material_overlap);
}
Vector<Vector3> handles;
// draw the handles separately because these must correspond to the raw points
// for editing
for (int n = 0; n < _room->_bound_pts.size(); n++) {
handles.push_back(_room->_bound_pts[n]);
}
// handles
if (handles.size()) {
Ref<Material> material_handle = gizmo_plugin->get_material("room_handle", this);
add_handles(handles, material_handle);
}
}
RoomSpatialGizmo::RoomSpatialGizmo(Room *p_room) {
_room = p_room;
set_spatial_node(p_room);
}
////
PortalGizmoPlugin::PortalGizmoPlugin() {
_color_portal_front = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/portal_front", Color(0.05, 0.05, 1.0, 0.3));
_color_portal_back = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/portal_back", Color(1.0, 1.0, 0.0, 0.15));
Color color_portal_margin = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/portal_margin", Color(1.0, 0.1, 0.1, 0.3));
Color color_portal_edge = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/portal_edge", Color(0.0, 0.0, 0.0, 0.3));
Color color_portal_arrow = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/portal_arrow", Color(1.0, 1.0, 1.0, 1.0));
@ -4530,6 +4648,19 @@ PortalGizmoPlugin::PortalGizmoPlugin() {
create_material("portal_margin", color_portal_margin, false, false, false);
create_material("portal_edge", color_portal_edge, false, false, false);
create_material("portal_arrow", color_portal_arrow, false, false, false);
create_handle_material("portal_handle");
}
Ref<EditorSpatialGizmo> PortalGizmoPlugin::create_gizmo(Spatial *p_spatial) {
Ref<PortalSpatialGizmo> ref;
Portal *portal = Object::cast_to<Portal>(p_spatial);
if (portal) {
ref = Ref<PortalSpatialGizmo>(memnew(PortalSpatialGizmo(portal)));
}
return ref;
}
bool PortalGizmoPlugin::has_gizmo(Spatial *p_spatial) {
@ -4548,31 +4679,106 @@ int PortalGizmoPlugin::get_priority() const {
return -1;
}
void PortalGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
p_gizmo->clear();
//////////////////////
Portal *portal = Object::cast_to<Portal>(p_gizmo->get_spatial_node());
String PortalSpatialGizmo::get_handle_name(int p_idx) const {
return "Point " + itos(p_idx);
}
if (portal) {
// warnings
if (portal->_warning_outside_room_aabb || portal->_warning_facing_wrong_way || portal->_warning_autolink_failed) {
Ref<Material> icon = get_material("portal_icon", p_gizmo);
p_gizmo->add_unscaled_billboard(icon, 0.05);
Variant PortalSpatialGizmo::get_handle_value(int p_idx) {
if (!_portal) {
return Vector2(0, 0);
}
Transform tr = portal->get_global_transform();
tr.affine_invert();
int num_points = _portal->_pts_local_raw.size();
if (p_idx >= num_points) {
return Vector2(0, 0);
}
Ref<Material> material_portal = get_material("portal", p_gizmo);
Ref<Material> material_margin = get_material("portal_margin", p_gizmo);
Ref<Material> material_edge = get_material("portal_edge", p_gizmo);
Ref<Material> material_arrow = get_material("portal_arrow", p_gizmo);
return _portal->_pts_local_raw[p_idx];
}
void PortalSpatialGizmo::set_handle(int p_idx, Camera *p_camera, const Point2 &p_point) {
if (!_portal || (p_idx >= _portal->_pts_local_raw.size())) {
return;
}
Transform tr = _portal->get_global_transform();
Transform tr_inv = tr.affine_inverse();
Vector3 pt_local = Portal::_vec2to3(_portal->_pts_local_raw[p_idx]);
Vector3 pt_world = tr.xform(pt_local);
Vector3 ray_from = p_camera->project_ray_origin(p_point);
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
// get a normal from the global transform
Plane plane(Vector3(0, 0, 0), Vector3(0, 0, 1));
plane = tr.xform(plane);
// construct the plane that the 2d portal is defined in
plane = Plane(pt_world, plane.normal);
Vector3 inters;
if (plane.intersects_ray(ray_from, ray_dir, &inters)) {
// back calculate from the 3d intersection to the 2d portal plane
inters = tr_inv.xform(inters);
// snapping will be in 2d for portals, and the scale may make less sense,
// but better to offer at least some functionality
if (SpatialEditor::get_singleton()->is_snap_enabled()) {
float snap = SpatialEditor::get_singleton()->get_translate_snap();
inters.snap(Vector3(snap, snap, snap));
}
_portal->set_point(p_idx, Vector2(inters.x, inters.y));
return;
}
}
void PortalSpatialGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_cancel) {
if (!_portal || (p_idx >= _portal->_pts_local_raw.size())) {
return;
}
UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo();
ur->create_action(TTR("Set Portal Point Position"));
ur->add_do_method(_portal, "set_point", p_idx, _portal->_pts_local_raw[p_idx]);
ur->add_undo_method(_portal, "set_point", p_idx, p_restore);
ur->commit_action();
_portal->property_list_changed_notify();
}
void PortalSpatialGizmo::redraw() {
clear();
if (!_portal) {
return;
}
// warnings
if (_portal->_warning_outside_room_aabb || _portal->_warning_facing_wrong_way || _portal->_warning_autolink_failed) {
Ref<Material> icon = gizmo_plugin->get_material("portal_icon", this);
add_unscaled_billboard(icon, 0.05);
}
Transform tr = _portal->get_global_transform();
Transform tr_inv = tr.affine_inverse();
Ref<Material> material_portal = gizmo_plugin->get_material("portal", this);
Ref<Material> material_margin = gizmo_plugin->get_material("portal_margin", this);
Ref<Material> material_edge = gizmo_plugin->get_material("portal_edge", this);
Ref<Material> material_arrow = gizmo_plugin->get_material("portal_arrow", this);
Color color(1, 1, 1, 1);
// make sure world points are up to date
portal->portal_update();
_portal->portal_update();
int num_points = portal->_pts_world.size();
int num_points = _portal->_pts_world.size();
// prevent compiler warnings later on
if (num_points < 3) {
@ -4580,7 +4786,7 @@ void PortalGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
}
// margins
real_t margin = portal->get_active_portal_margin();
real_t margin = _portal->get_active_portal_margin();
bool show_margins = Portal::_settings_gizmo_show_margins;
if (margin < 0.05f) {
@ -4592,27 +4798,29 @@ void PortalGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
PoolVector<Vector3> pts_margin;
Vector<Vector3> edge_pts;
Vector3 portal_normal_world_space = portal->_plane.normal;
Vector<Vector3> handles;
Vector3 portal_normal_world_space = _portal->_plane.normal;
portal_normal_world_space *= margin;
// this may not be necessary, dealing with non uniform scales,
// possible the affine_invert dealt with this earlier .. but it's just for
// the editor so not performance critical
Basis normal_basis = tr.basis;
Basis normal_basis = tr_inv.basis;
Vector3 portal_normal = normal_basis.xform(portal_normal_world_space);
Vector3 pt_portal_first = tr.xform(portal->_pts_world[0]);
Vector3 pt_portal_first = tr_inv.xform(_portal->_pts_world[0]);
for (int n = 0; n < num_points; n++) {
Vector3 pt = portal->_pts_world[n];
pt = tr.xform(pt);
Vector3 pt = _portal->_pts_world[n];
pt = tr_inv.xform(pt);
// CI for visual studio can't seem to get around the possibility
// that this could cause a divide by zero, so using a local to preclude the
// possibility of aliasing from another thread
int m = (n + 1) % num_points;
Vector3 pt_next = portal->_pts_world[m];
pt_next = tr.xform(pt_next);
Vector3 pt_next = _portal->_pts_world[m];
pt_next = tr_inv.xform(pt_next);
// don't need the first and last triangles
if ((n != 0) && (n != (num_points - 1))) {
@ -4652,6 +4860,13 @@ void PortalGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
}
}
// draw the handles separately because these must correspond to the raw points
// for editing
for (int n = 0; n < _portal->_pts_local_raw.size(); n++) {
Vector3 pt = Portal::_vec2to3(_portal->_pts_local_raw[n]);
handles.push_back(pt);
}
// portal itself
{
Ref<ArrayMesh> mesh = memnew(ArrayMesh);
@ -4660,7 +4875,11 @@ void PortalGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
array[Mesh::ARRAY_VERTEX] = pts_portal;
array[Mesh::ARRAY_COLOR] = cols_portal;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array);
p_gizmo->add_mesh(mesh, false, Ref<SkinReference>(), material_portal);
add_mesh(mesh, false, Ref<SkinReference>(), material_portal);
// handles
Ref<Material> material_handle = gizmo_plugin->get_material("portal_handle", this);
add_handles(handles, material_handle);
}
if (show_margins) {
@ -4669,10 +4888,10 @@ void PortalGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
array.resize(Mesh::ARRAY_MAX);
array[Mesh::ARRAY_VERTEX] = pts_margin;
mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array);
p_gizmo->add_mesh(mesh, false, Ref<SkinReference>(), material_margin);
add_mesh(mesh, false, Ref<SkinReference>(), material_margin);
// lines around the outside of mesh
p_gizmo->add_lines(edge_pts, material_edge, false, color);
add_lines(edge_pts, material_edge, false, color);
} // only if the margin is sufficient to be worth drawing
// arrow
@ -4708,8 +4927,14 @@ void PortalGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
}
}
p_gizmo->add_lines(lines, material_arrow, false, color);
add_lines(lines, material_arrow, false, color);
}
} // was portal
}
PortalSpatialGizmo::PortalSpatialGizmo(Portal *p_portal) {
_portal = p_portal;
set_spatial_node(p_portal);
_color_portal_front = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/portal_front", Color(0.05, 0.05, 1.0, 0.3));
_color_portal_back = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/portal_back", Color(1.0, 1.0, 0.0, 0.15));
}

View file

@ -429,6 +429,23 @@ public:
JointSpatialGizmoPlugin();
};
class Room;
class RoomSpatialGizmo : public EditorSpatialGizmo {
GDCLASS(RoomSpatialGizmo, EditorSpatialGizmo);
Room *_room = nullptr;
public:
virtual String get_handle_name(int p_idx) const;
virtual Variant get_handle_value(int p_idx);
virtual void set_handle(int p_idx, Camera *p_camera, const Point2 &p_point);
virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel = false);
virtual void redraw();
RoomSpatialGizmo(Room *p_room = nullptr);
};
class RoomGizmoPlugin : public EditorSpatialGizmoPlugin {
GDCLASS(RoomGizmoPlugin, EditorSpatialGizmoPlugin);
@ -436,12 +453,31 @@ protected:
virtual bool has_gizmo(Spatial *p_spatial);
String get_name() const;
int get_priority() const;
void redraw(EditorSpatialGizmo *p_gizmo);
Ref<EditorSpatialGizmo> create_gizmo(Spatial *p_spatial);
public:
RoomGizmoPlugin();
};
class Portal;
class PortalSpatialGizmo : public EditorSpatialGizmo {
GDCLASS(PortalSpatialGizmo, EditorSpatialGizmo);
Portal *_portal = nullptr;
Color _color_portal_front;
Color _color_portal_back;
public:
virtual String get_handle_name(int p_idx) const;
virtual Variant get_handle_value(int p_idx);
virtual void set_handle(int p_idx, Camera *p_camera, const Point2 &p_point);
virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel = false);
virtual void redraw();
PortalSpatialGizmo(Portal *p_portal = nullptr);
};
class PortalGizmoPlugin : public EditorSpatialGizmoPlugin {
GDCLASS(PortalGizmoPlugin, EditorSpatialGizmoPlugin);
@ -449,11 +485,7 @@ protected:
virtual bool has_gizmo(Spatial *p_spatial);
String get_name() const;
int get_priority() const;
void redraw(EditorSpatialGizmo *p_gizmo);
private:
Color _color_portal_front;
Color _color_portal_back;
Ref<EditorSpatialGizmo> create_gizmo(Spatial *p_spatial);
public:
PortalGizmoPlugin();

View file

@ -114,6 +114,16 @@ String Portal::get_configuration_warning() const {
return warning;
}
void Portal::set_point(int p_idx, const Vector2 &p_point) {
if (p_idx >= _pts_local_raw.size()) {
return;
}
_pts_local_raw.set(p_idx, p_point);
_sanitize_points();
update_gizmo();
}
void Portal::set_points(const PoolVector<Vector2> &p_points) {
_pts_local_raw = p_points;
_sanitize_points();
@ -670,6 +680,8 @@ void Portal::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_points", "points"), &Portal::set_points);
ClassDB::bind_method(D_METHOD("get_points"), &Portal::get_points);
ClassDB::bind_method(D_METHOD("set_point", "index", "position"), &Portal::set_point);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "portal_active"), "set_portal_active", "get_portal_active");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "two_way"), "set_two_way", "is_two_way");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "linked_room", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Room"), "set_linked_room", "get_linked_room");

View file

@ -47,6 +47,7 @@ class Portal : public Spatial {
friend class RoomManager;
friend class PortalGizmoPlugin;
friend class PortalEditorPlugin;
friend class PortalSpatialGizmo;
public:
// ui interface .. will have no effect after room conversion
@ -83,6 +84,9 @@ public:
void set_points(const PoolVector<Vector2> &p_points);
PoolVector<Vector2> get_points() const;
// primarily for the gizmo
void set_point(int p_idx, const Vector2 &p_point);
String get_configuration_warning() const;
Portal();
@ -105,7 +109,7 @@ private:
void flip();
void _sanitize_points();
void _update_aabb();
Vector3 _vec2to3(const Vector2 &p_pt) const { return Vector3(p_pt.x, p_pt.y, 0.0); }
static Vector3 _vec2to3(const Vector2 &p_pt) { return Vector3(p_pt.x, p_pt.y, 0.0); }
void _sort_verts_clockwise(bool portal_plane_convention, Vector<Vector3> &r_verts);
Plane _plane_from_points_newell(const Vector<Vector3> &p_pts);
void resolve_links(const LocalVector<Room *, int32_t> &p_rooms, const RID &p_from_room_rid);

View file

@ -130,6 +130,18 @@ void Room::set_use_default_simplify(bool p_use) {
_use_default_simplify = p_use;
}
void Room::set_point(int p_idx, const Vector3 &p_point) {
if (p_idx >= _bound_pts.size()) {
return;
}
_bound_pts.set(p_idx, p_point);
#ifdef TOOLS_ENABLED
_changed(true);
#endif
}
void Room::set_points(const PoolVector<Vector3> &p_points) {
_bound_pts = p_points;
@ -273,6 +285,8 @@ void Room::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_points", "points"), &Room::set_points);
ClassDB::bind_method(D_METHOD("get_points"), &Room::get_points);
ClassDB::bind_method(D_METHOD("set_point", "index", "position"), &Room::set_point);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_default_simplify"), "set_use_default_simplify", "get_use_default_simplify");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "room_simplify", PROPERTY_HINT_RANGE, "0.0,1.0,0.005"), "set_room_simplify", "get_room_simplify");

View file

@ -45,6 +45,7 @@ class Room : public Spatial {
friend class Portal;
friend class RoomGizmoPlugin;
friend class RoomEditorPlugin;
friend class RoomSpatialGizmo;
RID _room_rid;
@ -71,6 +72,9 @@ public:
void set_points(const PoolVector<Vector3> &p_points);
PoolVector<Vector3> get_points() const;
// primarily for the gizmo
void set_point(int p_idx, const Vector3 &p_point);
// editor only
PoolVector<Vector3> generate_points();