Wednesday, November 15, 2017

Trying to understand the maths behind the movements of a two-legged air dancer (aka skydancer, tube man)

I am trying to understand the maths behind the movements of a two-legged air dancer, aka skydancer aka tube man. Well, I mean these cheery friends here:

 

(This is a mirror of my question on this topic at MSE) The following discrete time algorithm is my devised solution, but I am sure that other much better approaches are possible. Indeed it is not very realistic and I am not very happy. I would like to learn other solutions more closer to reality and related with dynamical systems, complex dynamics or fluid dynamics:

1. Take for instance a unit square, same width and height. Then generate two partially (explanation of the "partially" wording is done below) random walks with these rules:

2. The first partially random walk $R1$ starts at $(0,0)$ and finishes at $(1,1)$

3. The second random walk $R2$ starts at $(0,1)$ and finishes at $(1,0)$

4. We will generate $n$ random points sequentially for $R1$ as follows. The first point is $(x_0,y_0)=(0,0)$, then we will take a random point from the intervals $([0,1],[0,1])$, it will be $(x_1,y_1)$. Then $(x_2,y_2)$ will be a random point from the intervals $([x_2,1],[y_2,1])$, then next point $([x_3,1],[y_3,1])$, etc. So we are approaching $(1,1)$ on each iteration. Usually $n=20$ iterations is good enough to be visually very close to $(1,1)$ so the simulation is good enough. The reason of the "partially" wording is that on every iteration we reduce the intervals, so it is not always a random point of the square unit, so it is not totally random.

5. In the same fashion we will generate points for $R2$ but the intervals will approach on each iteration to $(1,0)$.

6. Now we will create segments joining the sequential points of $R1$ and same for $R2$.

7. As both paths are crossing completely each diagonal of the square unit, there is an intersection point between them. Mark with a disk the intersection of both random paths and display the unit square including the segments and the intersection.

8. Execution: the algorithm can be looped for an infinite time of $t$ units in a loop, and each iteration $t$ will generate the aspect of the air dancer for that unit of time. So in my case the algorithm makes a kind of discrete system.

And this is how looks my air dancer simulation:



And another one with two dancers:



Some facts and trivia:

1. Due to the way that the segments are generated, the lengths of them are not totally random, see the square line picking problem.

2. The above solution might be defined as a kind of discrete map where each unit of time $t$ is independent of the status of the previous unit of time (but this can be modified to make movements depend on the previous unit of time at some extent so it would be a standard discrete-time map).

3. The algorithm works for any square, not only the unit square, it is just an example. They can be any desired width and height.

4. The legs of the air dancers are usually fixed to the ground, but not the arms, and that is a part of my simulation that is not very realistic (apart from others) although I think that it would be more or less easy to enhance this point specifically.

I would like to simulate this kind of objects with a more realistic approach, but initially I did not find a reference on Internet. My guessing is that it must be very related to dynamical systems and more specifically to fluid dynamics, so it might be possible to express the movement by a discrete (map) or continuous (based on derivatives) dynamical-system.

Below is the Python code for my simulation (also available at my account at repl.it). It will work in, for instance, a personal computer using Python Anaconda if the pygame library is downloaded as usual. Feel free to use and modify it and enjoy!

#!/usr/bin/env python
import pygame
import random
from collections import defaultdict
from time import sleep

# This script aims to simulate a tube man aka air dancer aka skydancer
# https://en.wikipedia.org/wiki/Tube_man

# The code will generate a TOTWIDTHxTOTHEIGHT pixels screen
# where HOBJECTSxVOBJECTS skydancers are generated
# each one with a similar WIDTHxHEIGHT
# the body of each skydancer is a disk of radius = RBIG pixels
# the simulation runs until ESC is pressed and each
# simulation time unit is shown during DELAYSEC seconds
# then the screen is wiped and the next time unit image is calculated

# Initialization constraints
HOBJECTS=2 #number of air dancers per line
VOBJECTS=1 #number of rows
WIDTH, HEIGHT = 200, 200
TOTWIDTH, TOTHEIGHT = WIDTH*HOBJECTS, HEIGHT*VOBJECTS
RBIG = 17#10#17
RSMALL = 8#8
DELAYSEC=0.3

def air_dancer_simulator():
    pygame.init()
    win=pygame.display.set_mode((TOTWIDTH, TOTHEIGHT))

    keysPressed = defaultdict(bool)

    def ScanKeyboard():
        while True:
            # Update the keysPressed state:
            evt = pygame.event.poll()
            if evt.type == pygame.NOEVENT:
                break
            elif evt.type in [pygame.KEYDOWN, pygame.KEYUP]:
                keysPressed[evt.key] = evt.type == pygame.KEYDOWN

    # detects the intersection point (x,y) of two lines, if exists.
    def line_intersection(line1, line2):
        xdiff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0])
        ydiff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1])

        def det(a, b):
            return a[0] * b[1] - a[1] * b[0]

        div = det(xdiff, ydiff)
        if div == 0:
            raise Exception('lines do not intersect')

        d = (det(*line1), det(*line2))
        x = det(d, xdiff) / div
        y = det(d, ydiff) / div
        return x, y
   
    bClearScreen = True
    pygame.display.set_caption('Skydancer aka tube man aka air dancer simulator')
    loop=0
   
    #Seed points of the air dancer sequential segments to be generated are (0,0) and (0,1)
    x_currpos_incdiag = 0
    y_currpos_incdiag = 0
    x_currpos_decdiag = 0
    y_currpos_decdiag = HEIGHT-1
   
    test_points_inc=[[x_currpos_incdiag,y_currpos_incdiag]]
    test_points_dec=[[x_currpos_decdiag,y_currpos_decdiag]]
   
    frames=0
    # the simulation will run infinite discrete units of time (ESC to finish the simulation)
    while True:
        # draw biash
        for biash in range(0,HOBJECTS):
            for biasv in range(0,VOBJECTS):
                loop=1
                while True:
                    # each dancer is finished in 20 iteracions
                    if loop%20==0:
                       
                        found=False
                        for i in range(0,len(test_points_inc)-2):
                            for j in range(0,len(test_points_dec)-2):
                                try:                       
                                    x,y=line_intersection([test_points_inc[i],test_points_inc[i+1]],[test_points_dec[j],test_points_dec[j+1]])
                                    if x>=0 and x<=WIDTH and y>=0 and y<=HEIGHT:
                                        if (test_points_inc[i][0]<=x) and (x<=test_points_inc[i+1][0]):
                                            if (test_points_inc[i][1]<=y) and (y<=test_points_inc[i+1][1]):
                                                if (test_points_dec[j][0]<=x) and (x<=test_points_dec[j+1][0]):
                                                    if (test_points_dec[j][1]>=y) and (y>=test_points_dec[j+1][1]):
                                                        # Detected interection of two segments, will be the body of the air dancer
                                                        pygame.draw.circle(win, (255,255,255),(int(x)+(biash*WIDTH),int(y)+(biasv*HEIGHT)),RBIG)       
                                                        found=True
                                                        break
                                except:
                                    pass
                                   
                            if found==True:
                                break
                       
                        #pygame.display.flip()
                        #sleep(5)
       
                        #initial positions
                        x_currpos_incdiag = 0
                        y_currpos_incdiag = 0
                        x_currpos_decdiag = 0
                        y_currpos_decdiag = HEIGHT-1
                        test_points_inc=[[x_currpos_incdiag,y_currpos_incdiag]]
                        test_points_dec=[[x_currpos_decdiag,y_currpos_decdiag]]
                        break
                       
                    x_newpos_incdiag = random.randint(x_currpos_incdiag,WIDTH-1)
                    y_newpos_incdiag = random.randint(y_currpos_incdiag,HEIGHT-1)
                    x_newpos_decdiag = random.randint(x_currpos_decdiag,WIDTH-1)
                    y_newpos_decdiag = random.randint(0,y_currpos_decdiag)
                   
                    test_points_inc.append([x_newpos_incdiag,y_newpos_incdiag])
                    test_points_dec.append([x_newpos_decdiag,y_newpos_decdiag])
               
                    pygame.draw.line(win, (150,200,255),(x_currpos_incdiag+(biash*WIDTH),y_currpos_incdiag+(biasv*HEIGHT)),(x_newpos_incdiag+(biash*WIDTH),y_newpos_incdiag+(biasv*HEIGHT)))
                    pygame.draw.line(win, (150,200,255),(x_currpos_decdiag+(biash*WIDTH),y_currpos_decdiag+(biasv*HEIGHT)),(x_newpos_decdiag+(biash*WIDTH),y_newpos_decdiag+(biasv*HEIGHT)))
                   
                    # Add horizontal and vertical lines too
                    #pygame.draw.line(win, (255,255,255),(x_currpos_incdiag+(biash*WIDTH),y_currpos_incdiag+(biasv*HEIGHT)),(x_currpos_incdiag+(biash*WIDTH),0+(biasv*HEIGHT)))
                    #pygame.draw.line(win, (255,255,255),(x_currpos_incdiag+(biash*WIDTH),y_currpos_incdiag+(biasv*HEIGHT)),(x_currpos_incdiag+(biash*WIDTH),HEIGHT+(biasv*HEIGHT)))
                    #pygame.draw.line(win, (255,255,255),(x_currpos_decdiag+(biash*WIDTH),y_currpos_decdiag+(biasv*HEIGHT)),(0+(biash*WIDTH),y_currpos_decdiag+(biasv*HEIGHT)))
                    #pygame.draw.line(win, (255,255,255),(x_currpos_decdiag+(biash*WIDTH),y_currpos_decdiag+(biasv*HEIGHT)),(WIDTH+(biash*WIDTH),y_currpos_decdiag+(biasv*HEIGHT)))
                   
                    # Add circles on each beginning and end of segment
                    #pygame.draw.circle(win, (150,200,255),(x_currpos_incdiag+(biash*WIDTH),y_currpos_incdiag+(biasv*HEIGHT)),RSMALL)
                    #pygame.draw.circle(win, (150,200,255),(x_currpos_decdiag+(biash*WIDTH),y_currpos_decdiag+(biasv*HEIGHT)),RSMALL)       
                       
                    x_currpos_incdiag = x_newpos_incdiag
                    y_currpos_incdiag = y_newpos_incdiag
                    x_currpos_decdiag = x_newpos_decdiag
                    y_currpos_decdiag = y_newpos_decdiag
               
                    win.lock()
                    win.unlock()
                    ScanKeyboard()

                    if keysPressed[pygame.K_ESCAPE]:
                        pygame.image.save(win, "intersections.png")
                        break
                   
                    loop = loop+1
                   
                    if keysPressed[pygame.K_ESCAPE]:
                        pygame.image.save(win, "intersections.png")
                        break
           
                if keysPressed[pygame.K_ESCAPE]:
                    pygame.image.save(win, "intersections.png")
                    break
           
            if keysPressed[pygame.K_ESCAPE]:
                pygame.image.save(win, "intersections.png")
                break
       
        if keysPressed[pygame.K_ESCAPE]:
            pygame.image.save(win, "intersections.png")
            break       
           
        pygame.display.flip()
        sleep(DELAYSEC)
       
        # Uncomment to save the frames, then you can create an animated gif or video with
        # your favorite program (e.g. VirtualDub)
        #pygame.image.save(win, "airdancer_" + str(frames) + ".png")
       
        frames=frames+1
       
        if bClearScreen:
            win.fill((0, 0, 0))
                   
air_dancer_simulator()

No comments:

Post a Comment