draw fish bones for Path2D and Path3D

These fish bones are add to indicate the direction and local transforms alone the path.
This commit is contained in:
Yaohua Xiong 2022-11-13 08:24:54 +08:00
parent 63578e208c
commit 9bdc0cb16f
4 changed files with 116 additions and 35 deletions

View file

@ -240,38 +240,63 @@ void Path3DGizmo::redraw() {
return;
}
Vector<Vector3> v3a = c->tessellate();
//Vector<Vector3> v3a=c->get_baked_points();
real_t interval = 0.1;
const real_t length = c->get_baked_length();
int v3s = v3a.size();
if (v3s == 0) {
return;
}
Vector<Vector3> v3p;
const Vector3 *r = v3a.ptr();
// 1. Draw curve and bones.
if (length > CMP_EPSILON) {
const int sample_count = int(length / interval) + 2;
interval = length / (sample_count - 1); // Recalculate real interval length.
// BUG: the following won't work when v3s, avoid drawing as a temporary workaround.
for (int i = 0; i < v3s - 1; i++) {
v3p.push_back(r[i]);
v3p.push_back(r[i + 1]);
//v3p.push_back(r[i]);
//v3p.push_back(r[i]+Vector3(0,0.2,0));
}
Vector<Transform3D> frames;
frames.resize(sample_count);
{
Transform3D *w = frames.ptrw();
for (int i = 0; i < sample_count; i++) {
w[i] = c->sample_baked_with_rotation(i * interval, true, true);
}
}
const Transform3D *r = frames.ptr();
Vector<Vector3> v3p;
for (int i = 0; i < sample_count - 1; i++) {
const Vector3 p1 = r[i].origin;
const Vector3 p2 = r[i + 1].origin;
const Vector3 side = r[i].basis.get_column(0);
const Vector3 up = r[i].basis.get_column(1);
const Vector3 forward = r[i].basis.get_column(2);
// Curve segment.
v3p.push_back(p1);
v3p.push_back(p2);
// Fish Bone.
v3p.push_back(p1);
v3p.push_back(p1 + (side - forward) * 0.06);
v3p.push_back(p1);
v3p.push_back(p1 + (-side - forward) * 0.06);
v3p.push_back(p1);
v3p.push_back(p1 + up * 0.03);
}
if (v3p.size() > 1) {
add_lines(v3p, path_material);
add_collision_segments(v3p);
}
// 2. Draw handles.
if (Path3DEditorPlugin::singleton->get_edited_path() == path) {
v3p.clear();
Vector<Vector3> v3p;
Vector<Vector3> handle_points;
Vector<Vector3> sec_handle_points;
for (int i = 0; i < c->get_point_count(); i++) {
Vector3 p = c->get_point_position(i);
handle_points.push_back(p);
// push Out points first so they get selected if the In and Out points are on top of each other.
// Push out points first so they get selected if the In and Out points are on top of each other.
if (i < c->get_point_count() - 1) {
v3p.push_back(p);
v3p.push_back(p + c->get_point_out(i));

View file

@ -106,18 +106,57 @@ void Path2D::_notification(int p_what) {
#else
const real_t line_width = get_tree()->get_debug_paths_width();
#endif
_cached_draw_pts.resize(curve->get_point_count() * 8);
int count = 0;
real_t interval = 10;
const real_t length = curve->get_baked_length();
for (int i = 0; i < curve->get_point_count(); i++) {
for (int j = 0; j < 8; j++) {
real_t frac = j * (1.0 / 8.0);
Vector2 p = curve->sample(i, frac);
_cached_draw_pts.set(count++, p);
if (length > CMP_EPSILON) {
const int sample_count = int(length / interval) + 2;
interval = length / (sample_count - 1); // Recalculate real interval length.
Vector<Transform2D> frames;
frames.resize(sample_count);
{
Transform2D *w = frames.ptrw();
for (int i = 0; i < sample_count; i++) {
w[i] = curve->sample_baked_with_rotation(i * interval, true, true);
}
}
const Transform2D *r = frames.ptr();
// Draw curve segments
{
PackedVector2Array v2p;
v2p.resize(sample_count);
Vector2 *w = v2p.ptrw();
for (int i = 0; i < sample_count; i++) {
w[i] = r[i].get_origin();
}
draw_polyline(v2p, get_tree()->get_debug_paths_color(), line_width, false);
}
// Draw fish bones
{
PackedVector2Array v2p;
v2p.resize(3);
Vector2 *w = v2p.ptrw();
for (int i = 0; i < sample_count; i++) {
const Vector2 p = r[i].get_origin();
const Vector2 side = r[i].columns[0];
const Vector2 forward = r[i].columns[1];
// Fish Bone.
w[0] = p + (side - forward) * 5;
w[1] = p;
w[2] = p + (-side - forward) * 5;
draw_polyline(v2p, get_tree()->get_debug_paths_color(), line_width * 0.5, false);
}
}
}
draw_polyline(_cached_draw_pts, get_tree()->get_debug_paths_color(), line_width, true);
} break;
}
}

View file

@ -38,7 +38,6 @@ class Path2D : public Node2D {
GDCLASS(Path2D, Node2D);
Ref<Curve2D> curve;
Vector<Vector2> _cached_draw_pts;
void _curve_changed();

View file

@ -1075,18 +1075,36 @@ void RendererCanvasCull::canvas_item_add_polyline(RID p_item, const Vector<Point
void RendererCanvasCull::canvas_item_add_multiline(RID p_item, const Vector<Point2> &p_points, const Vector<Color> &p_colors, float p_width) {
ERR_FAIL_COND(p_points.size() < 2);
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_COND(!canvas_item);
Item::CommandPolygon *pline = canvas_item->alloc_command<Item::CommandPolygon>();
ERR_FAIL_COND(!pline);
if (true || p_width <= 1) {
#define TODO make thick lines possible
// TODO: `canvas_item_add_line`(`multiline`, `polyline`) share logic, should factor out.
if (p_width <= 1) {
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_COND(!canvas_item);
Item::CommandPolygon *pline = canvas_item->alloc_command<Item::CommandPolygon>();
ERR_FAIL_COND(!pline);
pline->primitive = RS::PRIMITIVE_LINES;
pline->polygon.create(Vector<int>(), p_points, p_colors);
} else {
if (p_colors.size() == 1) {
Color color = p_colors[0];
for (int i = 0; i < p_points.size() >> 1; i++) {
Vector2 from = p_points[i * 2 + 0];
Vector2 to = p_points[i * 2 + 1];
canvas_item_add_line(p_item, from, to, color, p_width);
}
} else if (p_colors.size() == p_points.size() >> 1) {
for (int i = 0; i < p_points.size() >> 1; i++) {
Color color = p_colors[i];
Vector2 from = p_points[i * 2 + 0];
Vector2 to = p_points[i * 2 + 1];
canvas_item_add_line(p_item, from, to, color, p_width);
}
} else {
ERR_FAIL_MSG("Length of p_colors is invalid.");
}
}
}