Godot 4 Source Code Analysis- Practice- Harmonious Coexistence

I saw a WeChat video today, harmonious symbiosis, the approximate effect is as follows

https://live.csdn.net/v/306826

After studying Godot for such a long time, try to see if the above effect can be achieved today

At a glance, this effect is achieved in several steps:

1. Draw a circle and determine the regularity of the positions of multiple circles

2. Moving points, and determine the movement law of each moving point

3. Comprehensive debugging

1. Draw a circle

This should be relatively simple, and the intuitive feeling is to draw it. Directly create a new project draw, the root scene is Node2D, named Circle, and bind the script circle.gd. Come down and deal directly with circle.gd

From the perspective of learning and understanding, it should be to rewrite the _draw() function and try to call the draw_circle function directly

func _draw():	
	draw_circle(Vector2(300, 300), 150, Color.WHITE);

The result came out, it seems not difficult. 

But it seems that you need to draw a hollow circle, but looking at the Godot source code, the draw_circle function has only three parameters, which are the coordinates of the center point, the length of the radius, and the color.

void CanvasItem::draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color) {
	ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal.");

	RenderingServer::get_singleton()->canvas_item_add_circle(canvas_item, p_pos, p_radius, p_color);
}

 Track source canvas_item_add_circle

void RendererCanvasCull::canvas_item_add_circle(RID p_item, const Point2 &p_pos, float p_radius, const Color &p_color) {
	Item *canvas_item = canvas_item_owner.get_or_null(p_item);
	ERR_FAIL_COND(!canvas_item);

	Item::CommandPolygon *circle = canvas_item->alloc_command<Item::CommandPolygon>();
	ERR_FAIL_COND(!circle);

	circle->primitive = RS::PRIMITIVE_TRIANGLES;

	Vector<int> indices;
	Vector<Vector2> points;

	static const int circle_points = 64;

	points.resize(circle_points);
	Vector2 *points_ptr = points.ptrw();
	const real_t circle_point_step = Math_TAU / circle_points;

	for (int i = 0; i < circle_points; i++) {
		float angle = i * circle_point_step;
		points_ptr[i].x = Math::cos(angle) * p_radius;
		points_ptr[i].y = Math::sin(angle) * p_radius;
		points_ptr[i] += p_pos;
	}

	indices.resize((circle_points - 2) * 3);
	int *indices_ptr = indices.ptrw();

	for (int i = 0; i < circle_points - 2; i++) {
		indices_ptr[i * 3 + 0] = 0;
		indices_ptr[i * 3 + 1] = i + 1;
		indices_ptr[i * 3 + 2] = i + 2;
	}

	Vector<Color> color;
	color.push_back(p_color);
	circle->polygon.create(indices, points, color);
}

I figured it out, drawing a circle is actually using polygons internally. Feel

circle->primitive = RS::PRIMITIVE_TRIANGLES;

 It controls the effect, directly look at the definition:

	enum PrimitiveType {
		PRIMITIVE_POINTS,
		PRIMITIVE_LINES,
		PRIMITIVE_LINE_STRIP,
		PRIMITIVE_TRIANGLES,
		PRIMITIVE_TRIANGLE_STRIP,
		PRIMITIVE_MAX,
	};

Then test one by one, the first 5 respectively correspond to the following effects

It doesn't feel like the desired effect. Looking at these enumerations again, I feel a little familiar, as if there is a similar definition in OpenGL. Doesn't Godot have a way to draw hollow circles.

Of course not, one way is to draw a solid circle, and then draw a solid circle with a smaller radius and use the background color, and finally feel like a hollow circle. for example

	draw_circle(Vector2(300, 300), 150, Color.WHITE);
	draw_circle(Vector2(300, 300), 149, Color.BLACK);

result 

In order to be more similar, first draw the background in black

	draw_rect(Rect2(0, 0, get_viewport_rect().size.x, get_viewport_rect().size.y), Color.BLACK)
	draw_circle(Vector2(300, 300), 150, Color.WHITE);
	draw_circle(Vector2(300, 300), 149, Color.BLACK);

When the result is obtained, it feels like a hollow circle.

 

But this method is a pseudo-hollow circle. If you draw two partially overlapping circles, you will find the problem.

	draw_rect(Rect2(0, 0, get_viewport_rect().size.x, get_viewport_rect().size.y), Color.BLACK)
	draw_circle(Vector2(300, 300), 150, Color.WHITE);
	draw_circle(Vector2(300, 300), 149, Color.BLACK);
	draw_circle(Vector2(450, 300), 150, Color.WHITE);
	draw_circle(Vector2(450, 300), 149, Color.BLACK);

 So this approach won't work.

Study the source code, the various API functions of drawing

	void draw_dashed_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = -1.0, real_t p_dash = 2.0, bool p_aligned = true);
	void draw_line(const Point2 &p_from, const Point2 &p_to, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
	void draw_polyline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
	void draw_polyline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = -1.0, bool p_antialiased = false);
	void draw_arc(const Vector2 &p_center, real_t p_radius, real_t p_start_angle, real_t p_end_angle, int p_point_count, const Color &p_color, real_t p_width = -1.0, bool p_antialiased = false);
	void draw_multiline(const Vector<Point2> &p_points, const Color &p_color, real_t p_width = -1.0);
	void draw_multiline_colors(const Vector<Point2> &p_points, const Vector<Color> &p_colors, real_t p_width = -1.0);
	void draw_rect(const Rect2 &p_rect, const Color &p_color, bool p_filled = true, real_t p_width = -1.0);
	void draw_circle(const Point2 &p_pos, real_t p_radius, const Color &p_color);
	void draw_texture(const Ref<Texture2D> &p_texture, const Point2 &p_pos, const Color &p_modulate = Color(1, 1, 1, 1));
	void draw_texture_rect(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, bool p_tile = false, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false);
	void draw_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), bool p_transpose = false, bool p_clip_uv = false);
	void draw_msdf_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1), double p_outline = 0.0, double p_pixel_range = 4.0, double p_scale = 1.0);
	void draw_lcd_texture_rect_region(const Ref<Texture2D> &p_texture, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate = Color(1, 1, 1));
	void draw_style_box(const Ref<StyleBox> &p_style_box, const Rect2 &p_rect);
	void draw_primitive(const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs, Ref<Texture2D> p_texture = Ref<Texture2D>());
	void draw_polygon(const Vector<Point2> &p_points, const Vector<Color> &p_colors, const Vector<Point2> &p_uvs = Vector<Point2>(), Ref<Texture2D> p_texture = Ref<Texture2D>());
	void draw_colored_polygon(const Vector<Point2> &p_points, const Color &p_color, const Vector<Point2> &p_uvs = Vector<Point2>(), Ref<Texture2D> p_texture = Ref<Texture2D>());

	void draw_mesh(const Ref<Mesh> &p_mesh, const Ref<Texture2D> &p_texture, const Transform2D &p_transform = Transform2D(), const Color &p_modulate = Color(1, 1, 1));
	void draw_multimesh(const Ref<MultiMesh> &p_multimesh, const Ref<Texture2D> &p_texture);

	void draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const;
	void draw_multiline_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_max_lines = -1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::LineBreakFlag> p_brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const;

	void draw_string_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_size = 1, int p_font_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const;
	void draw_multiline_string_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, HorizontalAlignment p_alignment = HORIZONTAL_ALIGNMENT_LEFT, float p_width = -1, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_max_lines = -1, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0), BitField<TextServer::LineBreakFlag> p_brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND, TextServer::Direction p_direction = TextServer::DIRECTION_AUTO, TextServer::Orientation p_orientation = TextServer::ORIENTATION_HORIZONTAL) const;

	void draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size = Font::DEFAULT_FONT_SIZE, const Color &p_modulate = Color(1.0, 1.0, 1.0)) const;
	void draw_char_outline(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, int p_font_size = Font::DEFAULT_FONT_SIZE, int p_size = 1, const Color &p_modulate = Color(1.0, 1.0, 1.0)) const;

	void draw_set_transform(const Point2 &p_offset, real_t p_rot = 0.0, const Size2 &p_scale = Size2(1.0, 1.0));
	void draw_set_transform_matrix(const Transform2D &p_matrix);
	void draw_animation_slice(double p_animation_length, double p_slice_begin, double p_slice_end, double p_offset = 0);
	void draw_end_animation();

I feel that draw_arc may be useful, try it

	draw_rect(Rect2(0, 0, get_viewport_rect().size.x, get_viewport_rect().size.y), Color.BLACK)
	draw_arc(Vector2(300, 300), 150, 0, PI * 2, 200, Color.WHITE, -1, true);
	draw_arc(Vector2(450, 300), 150, 0, PI * 2, 200, Color.WHITE, -1, true);

result

feasible.

Then unify the transformation. Because it is possible to draw a solid circle, it is also possible to draw a hollow circle, so implement the function

func drawCircle(pos, radius, color, line_width = -1, filled = false):
	if filled :
		draw_circle(pos, radius, color);
	else:
		draw_arc(pos, radius, 0, PI * 2, 200, color, line_width, true);	

 Therefore, with a simple modification, you can draw a hollow circle + solid point

	draw_rect(Rect2(0, 0, get_viewport_rect().size.x, get_viewport_rect().size.y), Color.BLACK)
	drawCircle(Vector2(300, 300), radius, lineColor);
	drawCircle(Vector2(450, 300), 2, Color.WHITE, -1, true);

2. Moving point

Move it to see the effect

From a theoretical point of view, the moving point rotates counterclockwise along the circumference, and there is an angle theta between it and the center of the circle, which is controlled by time t. Of course, the most precise time control is in the _physics_process function, you can define a global angle variable deltaAngle = 0, update deltaAngle in _physics_process and force refresh

func _physics_process(delta):
	deltaAngle -= 0.125 / PI;
	queue_redraw();

func _draw():	
	draw_rect(Rect2(0, 0, get_viewport_rect().size.x, get_viewport_rect().size.y), Color.BLACK)
	drawCircle(Vector2(300, 300), radius, lineColor);
	drawCircle(Vector2(300 + cos(deltaAngle) * 150, 300 + sin(deltaAngle) * 150), 2, Color.WHITE, -1, true);

move now 

3. Distribution law

The next step is to look at the law of the position of the circle and the angle of the moving point. This process will not be analyzed. Anyway, I will directly propose the rules after a simple analysis, and write codes to test and verify.

For better presentation, add level control

func _draw():	
	draw_rect(Rect2(0, 0, get_viewport_rect().size.x, get_viewport_rect().size.y), Color.BLACK)	
	drawCircleAtAngle(0, PI + deltaAngle);
	drawCircleAtAngle(PI, PI + deltaAngle - PI);
	
	var step = 2;
	while step <= pow(2, level):
		for i in step:
			drawCircleAtAngle(PI / step + i * 2 * PI / step, PI + deltaAngle - PI / step - i * 2 * PI / step);
		step *= 2;
	pass

and then the final effect

http://42.192.128.33:1880/hx.mp4

reach the initial goal.

Guess you like

Origin blog.csdn.net/drgraph/article/details/131366992