How can I connect two points with a series of circles?

Viewed 22

I am trying to make realistic water in pygame: This is till now my code:

from random import randint
import pygame

WIDTH = 700
HEIGHT = 500

win = pygame.display.set_mode((WIDTH, HEIGHT))

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
AQUA = 'aqua'

RADIUS = 1

x, y = 0, HEIGHT//2

K = 1

FORCE = 100

VELOCITY = 0.5

run = True

class Molecule:
    def __init__(self, x, y, radius, force, k):
        self.x = x
        self.y = y
        self.radius = radius
        self.force = force
        self.k = k
        self.max_amplitude = y + force/k
        self.min_amplitude = y - force/k
        self.up = False
        self.down = True
        self.restore = False

    def draw(self, win):
        pygame.draw.circle(win, BLACK, (self.x, self.y), self.radius)

    def oscillate(self):
        if self.y <= self.max_amplitude and self.down == True:
            self.y += VELOCITY

        if self.y == self.max_amplitude or self.up:
            self.up = True
            self.down = False
            self.y -= VELOCITY
        
        if self.y == self.min_amplitude:
            self.up = False 
            self.down = True
        

molecules = []

for i in range(100):
    FORCE = randint(10, 20)
    molecules.append(Molecule(x, y, RADIUS, FORCE, K))
    x += 10

while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
    
    win.fill(WHITE)

    for molecule in molecules:
        molecule.draw(win)
        molecule.oscillate()

    for i in range(len(molecules)):
        try:
            pygame.draw.line(win, BLACK, (molecules[i].x, molecules[i].y), (molecules[i+1].x, molecules[i+1].y))
            pygame.draw.line(win, AQUA, (molecules[i].x, molecules[i].y), (molecules[i+1].x, HEIGHT))
        except:
            pass

    pygame.display.flip()

pygame.quit()

But as may expected the water curve is not smooth: Look at it: Sample Img1

I want to connect the two randomly added wave points using a set of circles not line like in this one so that a smooth curve could occur. And in this way i could add the water color to it such that it will draw aqua lines or my desired color line from the point to the end of screen and all this will end up with smooth water flowing simulation. Now the question is how could i make the points connect together smoothly into a smooth curve by drawing point circles at relative points?

1 Answers

I suggest sticking the segments with a Bézier curves. Bézier curves can be drawn with pygame.gfxdraw.bezier

Calculate the slopes of the tangents to the points along the wavy waterline:

ts = []
for i in range(len(molecules)):
    pa = molecules[max(0, i-1)]
    pb = molecules[min(len(molecules)-1, i+1)]
    ts.append((pb.y-pa.y) / (pb.x-pa.x))

Use the the tangents to define 4 control points for each segment and draw the curve with pygame.gfxdraw.bezier:

pts = []
for i in range(len(molecules)-1):
    p0 = molecules[i].x, molecules[i].y
    p3 = molecules[i+1].x, molecules[i+1].y
    p1 = p0[0] + 10, p0[1] + 10 * ts[i]
    p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
    pygame.gfxdraw.bezier(win, [p0, p1, p2, p3], 4, BLACK)

Complete example:

from random import randint
import pygame
import pygame.gfxdraw

WIDTH = 700
HEIGHT = 500

win = pygame.display.set_mode((WIDTH, HEIGHT))

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
AQUA = 'aqua'
RADIUS = 1
x, y = 0, HEIGHT//2
K = 1
FORCE = 100
VELOCITY = 0.5

class Molecule:
    def __init__(self, x, y, radius, force, k):
        self.x = x
        self.y = y
        self.radius = radius
        self.force = force
        self.k = k
        self.max_amplitude = y + force/k
        self.min_amplitude = y - force/k
        self.up = False
        self.down = True
        self.restore = False

    def draw(self, win):
        pygame.draw.circle(win, BLACK, (self.x, self.y), self.radius)

    def oscillate(self):
        if self.y <= self.max_amplitude and self.down == True:
            self.y += VELOCITY

        if self.y == self.max_amplitude or self.up:
            self.up = True
            self.down = False
            self.y -= VELOCITY
        
        if self.y == self.min_amplitude:
            self.up = False 
            self.down = True
        

molecules = []

for i in range(50):
    FORCE = randint(10, 20)
    molecules.append(Molecule(x, y, RADIUS, FORCE, K))
    x += 20

clock = pygame.time.Clock()
run = True
while run:
    clock.tick(100)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
    
    win.fill(WHITE)

    for molecule in molecules:
        molecule.draw(win)
        molecule.oscillate()

    ts = []
    for i in range(len(molecules)):
        pa = molecules[max(0, i-1)]
        pb = molecules[min(len(molecules)-1, i+1)]
        ts.append((pb.y-pa.y) / (pb.x-pa.x))

    pts = []
    for i in range(len(molecules)-1):
        p0 = molecules[i].x, molecules[i].y
        p3 = molecules[i+1].x, molecules[i+1].y
        p1 = p0[0] + 10, p0[1] + 10 * ts[i]
        p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
        pygame.gfxdraw.bezier(win, [p0, p1, p2, p3], 4, BLACK)

    for i in range(len(molecules)-1):
        pygame.draw.line(win, AQUA, (molecules[i].x, molecules[i].y), (molecules[i].x, HEIGHT))

    pygame.display.flip()

pygame.quit()
Related