반응형

베지어 스플라인 곡선을 그려보자.

 

Node2D를 생성하고 스크립트를 추가한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using Godot;
 
public partial class test : Node2D
{    
    private Curve2D pos;
    
    private Vector2 position0;
    private Vector2 position1;
    private Vector2 position2;
    private Vector2 position3;
    private Vector2 position4;
 
    private Vector2 control0;
    private Vector2 control1;
 
    public override void _Ready()
    {
        pos = new Curve2D();
 
        position0 = new Vector2(200200);
        position1 = new Vector2(400400);
        position2 = new Vector2(600200);
        position3 = new Vector2(800400);
        position4 = new Vector2(1000200);
 
        control0 = new Vector2(-1000);
        control1 = new Vector2(1000);
 
        BezierSpline(position0, position1, position2, position3,
            position4, control0, control1);
    }
 
    public override void _Draw()
    {
        //DrawPolyline(pos.GetBakedPoints(), Colors.Red, 5);
        DrawPolyline(pos.Tessellate(), Colors.Red, 5);
    }
 
    private void BezierSpline(Vector2 p0, Vector2 p1, Vector2 p2,
        Vector2 p3, Vector2 p4, Vector2 c0, Vector2 c1)
    {
        pos.AddPoint(p0, c0, c1);
        pos.AddPoint(p1, c0, c1);
        pos.AddPoint(p2, c0, c1);
        pos.AddPoint(p3, c0, c1);
        pos.AddPoint(p4, c0, c1);
    }
}
 

 

 

위와 같이 코드를 작성하고 실행한다.

_Draw()는 처음 한 번만 호출된다. 스플라인이 변경되거나 _Draw()를 다시 호출할 필요가 있다면 QueueRedraw()를 사용한다.

 

각 포인트에서 컨트롤이 적용된 스플라인이 표시된다.

 

※ 참고

Custom drawing in 2D

Beziers, curves, and paths

About Spline Interpolation

 

반응형
Posted by J-sean
:
반응형

 

2023.10.14 - [Godot] - [Godot] 2D Splash Dynamic Wave 자연스러운 물결 파동 1. 준비

 

위 링크를 참고해 준비가 끝난 씬에 Polygon2D를 이용해 물 모양을 추가해 보자.

 

WaterBody에 Polygon2D를 추가한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
using Godot;
using System.Collections.Generic;
 
public partial class WaterBody : Node2D
{
    public float k;
    public float d;
    public float spread;
    public int passes;
    public List<spring> springs;
    public int spring_number;
    public int distance_between_springs;
    public int depth;
    public int target_height;
    public int bottom;
    public PackedScene scene;
    public Polygon2D water_polygon;
 
    public override void _Ready()
    {
        k = 0.015f;
        d = 0.05f;
        spread = 0.0002f;
        passes = 8;
        depth = 1000;
        // 물의 깊이
        bottom = (int)GlobalPosition.Y + depth;
        // 물의 바닥 위치
        water_polygon = GetNode<Polygon2D>("Polygon2D");
        // Polygon2D 노드를 가져온다.
 
        springs = new List<spring>();
        spring_number = 10;
        distance_between_springs = 100;
        scene = ResourceLoader.Load<PackedScene>("res://spring.tscn");
        for (int i = 0; i < spring_number; i++)
        {            
            Node2D s = scene.Instantiate<Node2D>();            
            AddChild(s);            
            springs.Add(s as spring);
 
            int x_position = distance_between_springs * i + 100;
            (s as spring).Initialize(x_position);
        }
    }
 
    public override void _PhysicsProcess(double delta)
    {
        if (Input.IsActionJustPressed("ui_accept"))
            splash(520);
 
        foreach (spring item in springs)
        {
            item.water_update(k, d);
        }
 
        List<float> left_deltas = new List<float>();
        List<float> right_deltas = new List<float>();
 
        for(int i = 0; i < springs.Count; i++)
        {
            left_deltas.Add(0.0f);
            right_deltas.Add(0.0f);
        }
 
        for (int p = 0; p < passes; p++)
        {
            for (int i = 0; i < springs.Count; i++)
            {
                if (i > 0)
                {
                    left_deltas[i] = spread * (springs[i].height - springs[i-1].height);
                    springs[i-1].velocity += left_deltas[i];
                }
                if (i < springs.Count - 1)
                {
                    right_deltas[i] = spread * (springs[i].height - springs[i+1].height);
                    springs[i+1].velocity += right_deltas[i];
                }
            }
        }
 
        draw_waterbody();
        // 물 그리는 함수 호출.
    }
 
    public void splash(int index, float speed)
    {
        if(index >= 0 && index < springs.Count)
        {
            springs[index].velocity += speed;
        }
    }
 
    public void draw_waterbody()
    {
        List<Vector2> surface_points = new List<Vector2>();
        foreach (spring item in springs)
        {
            surface_points.Add(item.Position);
        }
        // 모든 스프링의 위치를 리스트에 추가한다.
 
        int first_index = 0;
        int last_index = surface_points.Count - 1;
 
        surface_points.Add(new Vector2(surface_points[last_index].X, bottom));
        surface_points.Add(new Vector2(surface_points[first_index].X, bottom));
        // 폴리곤을 사각형으로 만들기 위해 리스트 마지막에 사각형 밑변을 구성할 포인트를
        // 두 개 추가한다.
 
        water_polygon.Polygon = surface_points.ToArray();
        // 폴리곤을 구성하는 포인트 배열을 지정한다.
    }
}
 

 

 

WaterBody.cs 파일은 위와 같이 수정한다.

 

게임을 실행하고 엔터키를 누르면 폴리곤이 그려지고 스프링과 함께 물결처럼 운동한다.

 

좀 더 부드러운 물결을 만들어 보자.

 

WaterBody 씬에서 Polygon2D - Inspector - Visibility - Show Behind Parent 옵션을 체크한다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
using Godot;
using System.Collections.Generic;
 
public partial class WaterBody : Node2D
{
    public float k;
    public float d;
    public float spread;
    public int passes;
    public List<spring> springs;
    public int spring_number;
    public int distance_between_springs;
    public int depth;
    public int target_height;
    public int bottom;
    public PackedScene scene;
    public Polygon2D water_polygon;
    public Curve2D wave_position;
    // 부드러운 물결 곡선의 포인트를 저장할 Curve2D 변수
 
    public override void _Ready()
    {
        k = 0.015f;
        d = 0.05f;
        spread = 0.0002f;
        passes = 8;
        depth = 1000;
        bottom = (int)GlobalPosition.Y + depth;
        water_polygon = GetNode<Polygon2D>("Polygon2D");
 
        springs= new List<spring>();
        spring_number = 10;
        distance_between_springs = 100;
        scene = ResourceLoader.Load<PackedScene>("res://spring.tscn");
        for (int i = 0; i < spring_number; i++)
        {            
            Node2D s = scene.Instantiate<Node2D>();            
            AddChild(s);            
            springs.Add(s as spring);
 
            int x_position = distance_between_springs * i + 100;
            (s as spring).Initialize(x_position);
        }
 
        wave_position = new Curve2D();
        // 물결 곡선 Curve2D 초기화
    }
 
    public override void _PhysicsProcess(double delta)
    {
        if (Input.IsActionJustPressed("ui_accept"))
            splash(520);
 
        foreach (spring item in springs)
        {
            item.water_update(k, d);
        }
 
        List<float> left_deltas = new List<float>();
        List<float> right_deltas = new List<float>();
 
        for(int i = 0; i < springs.Count; i++)
        {
            left_deltas.Add(0.0f);
            right_deltas.Add(0.0f);
        }
 
        for (int p = 0; p < passes; p++)
        {
            for (int i = 0; i < springs.Count; i++)
            {
                if (i > 0)
                {
                    left_deltas[i] = spread * (springs[i].height - springs[i-1].height);
                    springs[i-1].velocity += left_deltas[i];
                }
                if (i < springs.Count - 1)
                {
                    right_deltas[i] = spread * (springs[i].height - springs[i+1].height);
                    springs[i+1].velocity += right_deltas[i];
                }
            }
        }
 
        draw_waterbody();
        draw_wave();
        // 부드러운 물결 그리기 함수 호출.
    }
 
    public void splash(int index, float speed)
    {
        if(index >= 0 && index < springs.Count)
        {
            springs[index].velocity += speed;
        }
    }
 
    public void draw_waterbody()
    {
        List<Vector2> surface_points = new List<Vector2>();
        foreach (spring item in springs)
        {
            surface_points.Add(item.Position);
        }
 
        int first_index = 0;
        int last_index = surface_points.Count - 1;
 
        surface_points.Add(new Vector2(surface_points[last_index].X, bottom));
        surface_points.Add(new Vector2(surface_points[first_index].X, bottom));
        
        water_polygon.Polygon = surface_points.ToArray();
    }
 
    public override void _Draw()
    {
        DrawPolyline(wave_position.Tessellate(), Colors.Blue, 5);
        // 물결 곡선을 그린다.
    }
 
    public void draw_wave()
    {
        wave_position.ClearPoints();
        // 매번 곡선의 포인트를 초기화 한다.
 
        Vector2 control0 = new Vector2(-500);
        Vector2 control1 = new Vector2(500);
        // 수평 컨트롤
 
        foreach (spring item in springs)
        {
            wave_position.AddPoint(item.Position, control0, control1);
            // 스프링 위치와 컨트롤을 곡선 포인트로 추가한다.
        }
 
        QueueRedraw();
        // _Draw() 호출
    }
}
 

 

 

WaterBody.cs 는 위와같이 변경한다.

 

spring 씬에서 Sprite2D - Inspector - Visibility - Visible 옵션을 해제한다.

 

Main 씬에서 실행한다.

 

폴리곤 위에 파란 물결이 부드러운 곡선으로 표시된다.

 

 

하지만 자세히 보면 폴리곤은 여전히 거칠다.

 

폴리곤을 부드럽게 바꿔보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
using Godot;
using System.Collections.Generic;
 
public partial class WaterBody : Node2D
{
    public float k;
    public float d;
    public float spread;
    public int passes;
    public List<spring> springs;
    public int spring_number;
    public int distance_between_springs;
    public int depth;
    public int target_height;
    public int bottom;
    public PackedScene scene;
    public Polygon2D water_polygon;
    public Curve2D wave_position;
 
    public override void _Ready()
    {
        k = 0.015f;
        d = 0.05f;
        spread = 0.0002f;
        passes = 8;
        depth = 1000;
        bottom = (int)GlobalPosition.Y + depth;
        water_polygon = GetNode<Polygon2D>("Polygon2D");
 
        springs= new List<spring>();
        spring_number = 10;
        distance_between_springs = 100;
        scene = ResourceLoader.Load<PackedScene>("res://spring.tscn");
        for (int i = 0; i < spring_number; i++)
        {            
            Node2D s = scene.Instantiate<Node2D>();            
            AddChild(s);            
            springs.Add(s as spring);
 
            int x_position = distance_between_springs * i + 100;
            (s as spring).Initialize(x_position);
        }
 
        wave_position = new Curve2D();
    }
 
    public override void _PhysicsProcess(double delta)
    {
        if (Input.IsActionJustPressed("ui_accept"))
            splash(520);
 
        foreach (spring item in springs)
        {
            item.water_update(k, d);
        }
 
        List<float> left_deltas = new List<float>();
        List<float> right_deltas = new List<float>();
 
        for(int i = 0; i < springs.Count; i++)
        {
            left_deltas.Add(0.0f);
            right_deltas.Add(0.0f);
        }
 
        for (int p = 0; p < passes; p++)
        {
            for (int i = 0; i < springs.Count; i++)
            {
                if (i > 0)
                {
                    left_deltas[i] = spread * (springs[i].height - springs[i-1].height);
                    springs[i-1].velocity += left_deltas[i];
                }
                if (i < springs.Count - 1)
                {
                    right_deltas[i] = spread * (springs[i].height - springs[i+1].height);
                    springs[i+1].velocity += right_deltas[i];
                }
            }
        }
 
        draw_wave();
        draw_waterbody();
        // draw_wave()가 먼저 호출되어야 한다.
    }
 
    public void splash(int index, float speed)
    {
        if(index >= 0 && index < springs.Count)
        {
            springs[index].velocity += speed;
        }
    }
 
    public void draw_waterbody()
    {
        List<Vector2> surface_points = new List<Vector2>();
        //foreach (spring item in springs)
        //{
        //    surface_points.Add(item.Position);
        //}
        Vector2[] baked_points = wave_position.GetBakedPoints();
        for (int i = 0; i < baked_points.Length; i++)
        {
            surface_points.Add(baked_points[i]);
        }
        // wave_position으로 만든 곡선의 포인트를 폴리곤 포인트로 사용하기 위해
        // 위와 같이 수정한다.
 
        int first_index = 0;
        int last_index = surface_points.Count - 1;
 
        surface_points.Add(new Vector2(surface_points[last_index].X, bottom));
        surface_points.Add(new Vector2(surface_points[first_index].X, bottom));
        
        water_polygon.Polygon = surface_points.ToArray();
    }
 
    public override void _Draw()
    {
        DrawPolyline(wave_position.Tessellate(), Colors.Blue, 5);
    }
 
    public void draw_wave()
    {
        wave_position.ClearPoints();
 
        Vector2 control0 = new Vector2(-500);
        Vector2 control1 = new Vector2(500);
 
        foreach (spring item in springs)
        {
            wave_position.AddPoint(item.Position, control0, control1);
        }
 
        QueueRedraw();
    }
}
 

 

 

WaterBody.cs를 위와같이 변경한다.

 

폴리곤도 부드럽게 바뀌었다.

 

2024.02.11 - [Godot] - [Godot] 2D Splash Dynamic Wave 자연스러운 물결 파동 3. 응용

 

※ 참고

Beziers, curves, and paths

Make a Splash With Dynamic 2D Water Effects

 

반응형
Posted by J-sean
:
반응형

베지어 곡선을 계산하고 적용해 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#import numpy as np
import bezier
import pygame
 
pygame.init()
pygame.display.set_caption("Super fun game development")
screen = pygame.display.set_mode((640480))
clock = pygame.time.Clock()
running = True
 
#nodes = np.asfortranarray([
#    [0.0, 640.0*2/4, 640.0*3/4, 640.0],
#    [0.0, 960.0, -960.0, 960.0],
#    ])
#curve = bezier.Curve(nodes, degree=3)
 
#nodes = np.asfortranarray([
#    [0.0, 640.0*2/4, 640.0*3/4, 640.0],
#    [0.0, 960.0, -960.0, 960.0],
#    ])
 
#nodes = np.array([
#    [0.0, 640.0*2/4, 640.0*3/4, 640.0],
#    [0.0, 960.0, -960.0, 960.0],
#    ])
 
nodes = [
    [0.0640.0*2/4640.0*3/4640.0],
    [0.0960.0-960.0960.0],
    ]
# 곡선 위 노드 행렬. 행은 차원, 열은 노드의 좌표를 나타낸다.
# (0, 0), (640*2/4, 960), (640*3/4, -960), (640, 960)
 
curve = bezier.Curve.from_nodes(nodes)
# 베지어 곡선을 생성한다. 차수는 node로부터 계산한다.
 
index = 0.0
 
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            running = False
 
    screen.fill("black")
 
    index += 0.005
    if index > 1:
        index = 0.0
 
    coord = curve.evaluate(index)
    # 베지어 곡선 위 index 지점 좌표를 계산한다. index는 1을 넘을 수 있다.
    pygame.draw.circle(screen, "yellow", (coord[0][0], coord[1][0]), 100)
    pygame.display.flip()
 
    clock.tick(60)
 
pygame.quit()
 

 

 

베지어 곡선을 따라 원이 이동한다.

 

반응형
Posted by J-sean
: