1

I am currently attempting to make a simple string simulation. The purpose is to just look like string but mine looks a bit weird and I don't know what the solution is. Does anyone have any ideas, here is the code

from pygame.locals import *
from math import sqrt
import pygame
pygame.init()
screen = pygame.display.set_mode([500, 500])
clock = pygame.time.Clock()

class Node():
    def __init__(self, position, mass, initalVelocity):
        self.position = position[:]
        self.mass = mass
        self.velocity = initalVelocity

    def update(self):
        self.position[0] += self.velocity[0]
        self.position[1] += self.velocity[1]


class String():
    def __init__(self, nodes, gravity = .981 , springConstant = 10, iterations = 1):
        self.nodes = nodes[:]
        self.gravity = gravity
        self.springC = springConstant
        self.iterations = iterations
        self.setDistance = 1
    def calculateForces(self):
        for x in range(self.iterations):
            for i in range(1, len(self.nodes), 1):
                distance = sqrt((self.nodes[i].position[0] - self.nodes[i - 1].position[0]) ** 2 + (self.nodes[i].position[1] - self.nodes[i - 1].position[1]) ** 2)
                force = -self.springC * (distance - self.setDistance)
                force = force / self.nodes[i].mass
                nDistanceVector = [(self.nodes[i].position[0] - self.nodes[i - 1].position[0]) / distance, (self.nodes[i].position[1] - self.nodes[i - 1].position[1]) / distance]
                nDistanceVector = [nDistanceVector[0] * force, nDistanceVector[1] * force]
                self.nodes[i].velocity[0] = 0.71 * self.nodes[i].velocity[0] + nDistanceVector[0]
                self.nodes[i].velocity[1] = 0.71 * self.nodes[i].velocity[1] + (nDistanceVector[1] + self.gravity)

                self.nodes[i].update()

     pygame.draw.aalines(screen, [0, 0, 0], False, a)

a = [Node([250 + i * 10, 100], 100, [0, 1]) for i in range(25)]
s = String(a)
while 1:
    screen.fill([255, 255, 255])

    s.calculateForces()

    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()

    pygame.display.update()
    clock.tick(60)`

I'm using Hooke's law and each node acts as a spring as soon as it goes beyond a certain distance from the previous node, but it just looks clunky and unrealistic. What am i missing?

adammoyle
  • 199
  • 1
  • 13

1 Answers1

0

Use pygame.math.Vector2 to simplify the code. Divide the calculateForces into 2 loops. First calculate the new velocity, then move the positions:

import pygame

pygame.init()
window = pygame.display.set_mode([500, 500])
clock = pygame.time.Clock()

class Node():
    def __init__(self, position, mass, initial_velocity):
        self.position = pygame.math.Vector2(position)
        self.mass = mass
        self.velocity = pygame.math.Vector2(initial_velocity)

class String():
    def __init__(self, nodes, gravity = .981, spring_constant = 10):
        self.nodes = nodes[:]
        self.gravity = gravity
        self.spring_constant = spring_constant
        self.set_distance = 1

    def calculateForces(self):
        for i in range(1, len(self.nodes), 1):
            n1, n0 = self.nodes[i], self.nodes[i - 1]
            distance = n1.position.distance_to(n0.position)
            force = -self.spring_constant * (distance - self.set_distance) / n1.mass
            n_dist_v = (n1.position - n0.position) / distance * force
            n1.velocity = 0.71 * n1.velocity + n_dist_v + pygame.math.Vector2(0, self.gravity)
        for i in range(1, len(self.nodes), 1):
            self.nodes[i].position += self.nodes[i].velocity

    def draw(self, surf):
        pygame.draw.aalines(surf, [0, 0, 0], False, [node.position for node in self.nodes])

def createString():
    return String([Node([150 + i * 10, 100], 120, [0, 1]) for i in range(25)])
s = createString()

run = True
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
            s = createString()

    s.calculateForces()
    window.fill([255, 255, 255])
    s.draw(window)
    pygame.display.update()
    clock.tick(60)

pygame.quit()
exit()

Rabbid76
  • 202,892
  • 27
  • 131
  • 174