I am making a simulation where you create different balls of certain mass, connected by springs which you can define (in the program below all springs have natural length L and spring constant k). How I do it is I created a function accel(b,BALLS), (note b is the specific ball and BALLS are all of the ball objects in various stages of update) which gets me acceleration on this one ball from calculating all the forces acting on it (tensions from ball the springs connected to it and gravity) and I would think this function is definitely correct and problems lie elsewhere in the while loop. I then use the RK4 method described on this website: http://spiff.rit.edu/richmond/nbody/OrbitRungeKutta4.pdf in the while loop to update velocity and position of each ball. To test my understanding of the method I first made a simulation where only two balls and one spring is involved on Desmos: https://www.desmos.com/calculator/4ag5gkerag
I allowed for energy display and saw that indeed RK4 is much better than Euler method. Now I made it in python in the hope that it should work with arbitrary config of balls and springs, but energy isn't even conserved when I have two balls and one spring! I couldn't see what I did differently, at least when two balls on involved. And when I introduce a third ball and a second spring to the system, energy increases by the hundreds every second. This is my first time coding a simulation with RK4, and I expect you guys can find mistakes in it. I have an idea that maybe the problem is caused by because there are multiple bodies and difficulties arises when I update their kas or kvs at the same time but then again I can't spot any difference between what this code is doing when simulating two balls and my method used in the Desmos file. Here is my code in python:
import pygame
import sys
import math
import numpy as np
pygame.init()
width = 1200
height = 900
SCREEN = pygame.display.set_mode((width, height))
font = pygame.font.Font(None, 25)
TIME = pygame.time.Clock()
dampwall = 1
dt = 0.003
g = 20
k=10
L=200
def dist(a, b):
return math.sqrt((a[0] - b[0])*(a[0] - b[0]) + (a[1] - b[1])*(a[1] - b[1]))
def mag(a):
return dist(a, [0, 0])
def dp(a, b):
return a[0]*b[0]+a[1]*b[1]
def norm(a):
return list(np.array(a)/mag(a))
def reflect(a, b):
return norm([2*a[1]*b[0]*b[1]+a[0]*(b[0]**2 - b[1]**2), 2*a[0]*b[0]*b[1]+a[1]*(-b[0]**2 + b[1]**2)])
class ball:
def __init__(self, x, y, vx, vy, mass,spr,index,ka,kv):
self.r = [x, y]
self.v = [vx, vy]
self.radius = 5
self.mass = mass
self.spr=spr
self.index = index
self.ka=ka
self.kv=kv
def detectbounce(self,width,height):
if self.r[0] + self.radius > width/2 and self.r[0]+self.v[0] > self.r[0] or self.r[0] - self.radius < -width/2 and self.r[0]+self.v[0] < self.r[0] or self.r[1] + self.radius > height/2 and self.r[1]+self.v[1] > self.r[1] or self.r[1] - self.radius < -height/2 and self.r[1]+self.v[1] < self.r[1]:
return True
def bounce_walls(self, width, height):
if self.r[0] + self.radius > width/2 and self.r[0]+self.v[0] > self.r[0]:
self.v[0] *= -dampwall
if self.r[0] - self.radius < -width/2 and self.r[0]+self.v[0] < self.r[0]:
self.v[0] *= -dampwall
if self.r[1] + self.radius > height/2 and self.r[1]+self.v[1] > self.r[1]:
self.v[1] *= -dampwall
if self.r[1] - self.radius < -height/2 and self.r[1]+self.v[1] < self.r[1]:
self.v[1] *= -dampwall
def update_r(self,v, h):
self.r[0] += v[0] * h
self.r[1] += v[1] * h
def un_update_r(self,v, h):
self.r[0] += -v[0] * h
self.r[1] += -v[1] * h
def KE(self):
return 0.5 * self.mass * mag(self.v)**2
def GPE(self):
return self.mass * g * (-self.r[1] + height)
def draw(self, screen, width, height):
pygame.draw.circle(screen, (0, 0, 255), (self.r[0] +
width / 2, self.r[1] + height / 2), self.radius)
#(self, x, y, vx, vy, mass,spr,index,ka,kv):
# balls = [ball(1, 19, 0, 0,5,[1],0,[0,0,0,0],[0,0,0,0]), ball(250, 20, 0,0,1,[0],1,[0,0,0,0],[0,0,0,0])]
# springs = [[0, 1]]
balls = [ball(1, 19, 0, 0,5,[1,3],0,[0,0,0,0],[0,0,0,0]), ball(250, 20, 0,0,2,[0,2,3],1,[0,0,0,0],[0,0,0,0]),ball(450, 0, 0,0,2,[1,3],1,[0,0,0,0],[0,0,0,0]),ball(250, -60, 0,0,2,[0,1,2],1,[0,0,0,0],[0,0,0,0])]
springs = [[0, 1],[1,2],[0,3],[1,3],[2,3]]
def accel(b,BALLS):
A=[0,g]
for i in range(0,len(b.spr)):
ball1=b
ball2=BALLS[b.spr[i]]
r1 = norm(list(np.array(ball2.r) - np.array(ball1.r)))
lnow = dist(ball1.r, ball2.r)
force = k * (lnow - L)
A[0]+=force/ball1.mass*r1[0]
A[1]+=force/ball1.mass*r1[1]
return A
initE=0
while True:
TIME.tick(200)
SCREEN.fill((0, 0, 0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#compute k1a and k1v for all balls
for ball in balls:
ball.ka[0]=accel(ball,balls)
ball.kv[0]=ball.v
#create newb1 based on 'updated' position of all balls with their own k1v
newb=[]
for ball in balls:
ball.update_r(ball.kv[0], dt/2)
newb.append(ball)
ball.un_update_r(ball.kv[0], dt/2)
#compute k2a and k2v for all balls based on newb1
for ball in balls:
ball.update_r(ball.kv[0], dt/2)
ball.ka[1]=accel(ball,newb)
ball.un_update_r(ball.kv[0], dt/2)
ball.kv[1]=[ball.v[0]+0.5*dt*ball.ka[0][0],ball.v[1]+0.5*dt*ball.ka[0][1]]
#create newb2 based on 'updated' position of all balls with their own k2v
newb=[]
for ball in balls:
ball.update_r(ball.kv[1], dt/2)
newb.append(ball)
ball.un_update_r(ball.kv[1], dt/2)
#compute k3a and k3v for all balls
for ball in balls:
ball.update_r(ball.kv[1], dt/2)
ball.ka[2]=accel(ball,newb)
ball.un_update_r(ball.kv[1], dt/2)
ball.kv[2]=[ball.v[0]+0.5*dt*ball.ka[1][0],ball.v[1]+0.5*dt*ball.ka[1][1]]
newb=[]
for ball in balls:
ball.update_r(ball.kv[2], dt)
newb.append(ball)
ball.un_update_r(ball.kv[2], dt)
#compute k4a and k4v for all balls
for ball in balls:
ball.update_r(ball.kv[2], dt)
ball.ka[3]=accel(ball,newb)
ball.un_update_r(ball.kv[2], dt)
ball.kv[3]=[ball.v[0]+dt*ball.ka[2][0],ball.v[1]+dt*ball.ka[2][1]]
#final stage of update
for ball in balls:
if ball.detectbounce(width,height)==True:
ball.bounce_walls(width, height)
else:
ball.v=[ball.v[0]+dt*(ball.ka[0][0]+2*ball.ka[1][0]+2*ball.ka[2][0]+ball.ka[3][0])/6, ball.v[1]+dt*(ball.ka[0][1]+2*ball.ka[1][1]+2*ball.ka[2][1]+ball.ka[3][1])/6]
ball.r=[ball.r[0]+dt*(ball.kv[0][0]+2*ball.kv[1][0]+2*ball.kv[2][0]+ball.kv[3][0])/6, ball.r[1]+dt*(ball.kv[0][1]+2*ball.kv[1][1]+2*ball.kv[2][1]+ball.kv[3][1])/6]
for ball in balls:
ball.draw(SCREEN, width, height)
for i in range(0,len(ball.spr)):
ball1=ball
ball2=balls[ball.spr[i]]
pygame.draw.line(SCREEN, (0, 0, 155), (
ball1.r[0]+width/2, ball1.r[1]+height/2), (ball2.r[0]+width/2, ball2.r[1]+height/2))
#check for energy
KE = 0
EPE = 0
GPE = 0
for i in range(0, len(springs)):
EPE += 1/2 * k * \
(L - dist(balls[springs[i][0]].r,
balls[springs[i][1]].r))**2
for i in range(0, len(balls)):
KE += balls[i].KE()
GPE += balls[i].GPE()
if initE == 0:
initE += KE+EPE+GPE
text = font.render('init Energy: ' + str(round(initE,1))+' '+'KE: ' + str(round(KE, 1)) + ' '+'EPE: ' + str(round(EPE, 1))+' ' + 'GPE: ' + str(round(GPE, 1)) + ' ' + 'Total: ' + str(round(KE+EPE+GPE, 1)) + ' ' + 'Diff: ' + str(round((KE+EPE+GPE-initE), 1)),
True, (255, 255, 255))
textRect = text.get_rect()
textRect.center = (370, 70)
SCREEN.blit(text, textRect)
pygame.display.flip()
This is the edited, corrected by Lutz Lehmann and with some extra improvements:
import pygame
import sys
import math
import numpy as np
pygame.init()
width = 1200
height = 900
SCREEN = pygame.display.set_mode((width, height))
font = pygame.font.Font(None, 25)
TIME = pygame.time.Clock()
dampwall = 1
dt = 0.003
g = 5
k = 10
L = 200
digits = 6
def dist(a, b):
return math.sqrt((a[0] - b[0])*(a[0] - b[0]) + (a[1] - b[1])*(a[1] - b[1]))
def mag(a):
return dist(a, [0, 0])
def dp(a, b):
return a[0]*b[0]+a[1]*b[1]
def norm(a):
return list(np.array(a)/mag(a))
def reflect(a, b):
return norm([2*a[1]*b[0]*b[1]+a[0]*(b[0]**2 - b[1]**2), 2*a[0]*b[0]*b[1]+a[1]*(-b[0]**2 + b[1]**2)])
class Ball:
def __init__(self, x, y, vx, vy, mass, spr, index, ka, kv):
self.r = [x, y]
self.v = [vx, vy]
self.radius = 5
self.mass = mass
self.spr = spr
self.index = index
self.ka = ka
self.kv = kv
def copy(self):
return Ball(self.r[0], self.r[1], self.v[0], self.v[1], self.mass, self.spr, self.index, self.ka, self.kv)
def detectbounce(self, width, height):
if self.r[0] + self.radius > width/2 and self.r[0]+self.v[0] > self.r[0] or self.r[0] - self.radius < -width/2 and self.r[0]+self.v[0] < self.r[0] or self.r[1] + self.radius > height/2 and self.r[1]+self.v[1] > self.r[1] or self.r[1] - self.radius < -height/2 and self.r[1]+self.v[1] < self.r[1]:
return True
def bounce_walls(self, width, height):
if self.r[0] + self.radius > width/2 and self.r[0]+self.v[0] > self.r[0]:
self.v[0] *= -dampwall
if self.r[0] - self.radius < -width/2 and self.r[0]+self.v[0] < self.r[0]:
self.v[0] *= -dampwall
if self.r[1] + self.radius > height/2 and self.r[1]+self.v[1] > self.r[1]:
self.v[1] *= -dampwall
if self.r[1] - self.radius < -height/2 and self.r[1]+self.v[1] < self.r[1]:
self.v[1] *= -dampwall
def update_r(self, v, h):
self.r[0] += v[0] * h
self.r[1] += v[1] * h
def un_update_r(self, v, h):
self.r[0] += -v[0] * h
self.r[1] += -v[1] * h
def KE(self):
return 0.5 * self.mass * mag(self.v)**2
def GPE(self):
return self.mass * g * (-self.r[1] + height)
def draw(self, screen, width, height):
pygame.draw.circle(screen, (0, 0, 255), (self.r[0] +
width / 2, self.r[1] + height / 2), self.radius)
# (self, x, y, vx, vy, mass,spr,index,ka,kv):
# balls = [Ball(1, 19, 0, 0, 1, [1], 0, [0, 0, 0, 0], [0, 0, 0, 0]),
# Ball(250, 20, 0, 0, 1, [0], 1, [0, 0, 0, 0], [0, 0, 0, 0])]
# springs = [[0, 1]]
balls = [Ball(1, 19, 0, 0,5,[1,3],0,[0,0,0,0],[0,0,0,0]), Ball(250, 20, 0,0,2,[0,2,3],1,[0,0,0,0],[0,0,0,0]),Ball(450, 0, 0,0,2,[1,3],1,[0,0,0,0],[0,0,0,0]),Ball(250, -60, 0,0,2,[0,1,2],1,[0,0,0,0],[0,0,0,0])]
# n=5
# resprings=[]
# for i in range(0,n):
# for j in range(0,n):
# if i==0 and j==0:
# resprings.append([1,2,n,n+1,2*n])
# if i==n and j==0:
# resprings.apend([n*(n-1)+1,n*(n-1)+2,n*(n-2),n*(n-3),n*(n-2)+1])
# if j==0 and i!=0 or i!=n:
# resprings.append([(i-1)*n+1,(i-1)*n+2,(i-2)*n,(i-2)*n+1,(i)*n,(i)*n+1])
def getsprings(B):
S=[]
for i in range(0,len(B)):
theball=B[i]
for j in range(len(theball.spr)):
spring=sorted([i,theball.spr[j]])
if spring not in S:
S.append(spring)
return S
springs = getsprings(balls)
def accel(b, BALLS):
A = [0, g]
for i in range(0, len(b.spr)):
ball1 = b
ball2 = BALLS[b.spr[i]]
r1 = norm(list(np.array(ball2.r) - np.array(ball1.r)))
lnow = dist(ball1.r, ball2.r)
force = k * (lnow - L)
A[0] += force/ball1.mass*r1[0]
A[1] += force/ball1.mass*r1[1]
return A
initE = 0
while True:
TIME.tick(200)
SCREEN.fill((0, 0, 0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
for ball in balls:
ball.bounce_walls(width, height)
# compute k1a and k1v for all balls
for ball in balls:
ball.ka[0] = accel(ball, balls)
ball.kv[0] = ball.v
# create newb1 based on 'updated' position of all balls with their own k1v
newb = []
for ball in balls:
ball.update_r(ball.kv[0], dt/2)
newb.append(ball.copy())
ball.un_update_r(ball.kv[0], dt/2)
# compute k2a and k2v for all balls based on newb1
for ball in balls:
ball.update_r(ball.kv[0], dt/2)
ball.ka[1] = accel(ball, newb)
ball.un_update_r(ball.kv[0], dt/2)
ball.kv[1] = [ball.v[0]+0.5*dt*ball.ka[0]
[0], ball.v[1]+0.5*dt*ball.ka[0][1]]
# create newb2 based on 'updated' position of all balls with their own k2v
newb = []
for ball in balls:
ball.update_r(ball.kv[1], dt/2)
newb.append(ball.copy())
ball.un_update_r(ball.kv[1], dt/2)
# compute k3a and k3v for all balls
for ball in balls:
ball.update_r(ball.kv[1], dt/2)
ball.ka[2] = accel(ball, newb)
ball.un_update_r(ball.kv[1], dt/2)
ball.kv[2] = [ball.v[0]+0.5*dt*ball.ka[1]
[0], ball.v[1]+0.5*dt*ball.ka[1][1]]
newb = []
for ball in balls:
ball.update_r(ball.kv[2], dt)
newb.append(ball.copy())
ball.un_update_r(ball.kv[2], dt)
# compute k4a and k4v for all balls
for ball in balls:
ball.update_r(ball.kv[2], dt)
ball.ka[3] = accel(ball, newb)
ball.un_update_r(ball.kv[2], dt)
ball.kv[3] = [ball.v[0]+dt*ball.ka[2][0], ball.v[1]+dt*ball.ka[2][1]]
# final stage of update
for ball in balls:
ball.v = [ball.v[0]+dt*(ball.ka[0][0]+2*ball.ka[1][0]+2*ball.ka[2][0]+ball.ka[3][0])/6,
ball.v[1]+dt*(ball.ka[0][1]+2*ball.ka[1][1]+2*ball.ka[2][1]+ball.ka[3][1])/6]
ball.r = [ball.r[0]+dt*(ball.kv[0][0]+2*ball.kv[1][0]+2*ball.kv[2][0]+ball.kv[3][0])/6,
ball.r[1]+dt*(ball.kv[0][1]+2*ball.kv[1][1]+2*ball.kv[2][1]+ball.kv[3][1])/6]
for ball in balls:
ball.draw(SCREEN, width, height)
for i in range(0, len(ball.spr)):
ball1 = ball
ball2 = balls[ball.spr[i]]
pygame.draw.line(SCREEN, (0, 0, 155), (
ball1.r[0]+width/2, ball1.r[1]+height/2), (ball2.r[0]+width/2, ball2.r[1]+height/2))
# check for energy
KE = 0
EPE = 0
GPE = 0
for i in range(0, len(springs)):
EPE += 1/2 * k * \
(L - dist(balls[springs[i][0]].r,
balls[springs[i][1]].r))**2
for i in range(0, len(balls)):
KE += balls[i].KE()
GPE += balls[i].GPE()
if initE == 0:
initE += KE+EPE+GPE
text1 = font.render(f"initial energy: {str(round(initE, digits))}", True, (255, 255, 255))
text2 = font.render(f"kinetic energy: {str(round(KE, digits))}", True, (255, 255, 255))
text3 = font.render(f"elastic potential energy: {str(round(EPE, digits))}", True, (255, 255, 255))
text4 = font.render(f"gravitational energy: {str(round(GPE, digits))}", True, (255, 255, 255))
text5 = font.render(f"total energy: {str(round(KE + EPE + GPE, digits))}", True, (255, 255, 255))
text6 = font.render(f"change in energy: {str(round(KE + EPE + GPE - initE, digits))}", True, (255, 255, 255))
SCREEN.blit(text1, (10, 10))
SCREEN.blit(text2, (10, 60))
SCREEN.blit(text3, (10, 110))
SCREEN.blit(text4, (10, 160))
SCREEN.blit(text5, (10, 210))
SCREEN.blit(text6, (10, 260))
pygame.display.flip()
The immediate error seems to be this
for ball in balls:
...
newb1.append(ball)
...
as ball is just a reference to the class ball instance, thus newb1 is a list of references to the objects in balls, it makes no difference if you manipulate the one or the other, it is always the same data records that get changed.
You need to apply a copy mechanism, as you have lists of lists, you need a deep copy, or a dedicated copy member method, else you just copy the array references in the ball instances, so you get different instances, but pointing to the same arrays.
It is probably not an error but still a bad idea to have the class name also as variable name in the same scope.
I'm new to Psychtoolbox and am using it to create a reversing checkerboard pattern on the screen. The checkerboard has been coloured such that it is more like vertical stripes rather than checks. I have two contrasting conditions a regular black/white striped pattern and then a light grey/dark grey striped pattern. I have managed to do everything except I am really struggling with timing.
At first, I was struggling with the timing aspect of things and was not able to get the screen to switch from the grey screen to black and white screen. The waitframes fixed this. However, now that I have each screen appearing for a set duration, the alternation of the pattern has stopped for each texture. Does anybody have advice on how to fix this? What is missing in my code currently that's stopping the reversing of the stripes??
Can anyone provide advice? My code is below.
% Clear the workspace and the screen
sca;
close all;
clearvars;
PsychDefaultSetup(2);
screens = Screen('Screens');
screenNumber = max(screens);
white = WhiteIndex(screenNumber);
black = BlackIndex(screenNumber);
grey = white / 2;
% Open an on screen window
[window, windowRect] = PsychImaging('OpenWindow', screenNumber, grey, [0 0 400 400]);
ifi = Screen('GetFlipInterval', window);
% Query the maximum priority level
topPriorityLevel = MaxPriority(window);
[screenXpixels, screenYpixels] = Screen('WindowSize', window);
%Stripe information Get the centre coordinate of the window
[xCenter, yCenter] = RectCenter(windowRect);
cx = (screenXpixels/2);
cy = (screenYpixels/2);
% Make a base Rect of 200 by 200 pixels
dimx = cx;
dimy = cy;
baseRect = [0 0 dimx dimy];
pos = [cx- dimx/2 ,cy - dimy/2,cx+ dimx/2 ,cy + dimy/2];
[xPos, yPos] = meshgrid(-2:0.5:2, -2:0.5:2);
% Calculate the number of squares and reshape the matrices of coordinates
% into a vector
[s1, s2] = size(xPos);
numSquares = s1 * s2;
xPos = reshape(xPos, 1, numSquares);
yPos = reshape(yPos, 1, numSquares);
% Set the colors of each of our squares
%grey colours
bwColors = repmat([0.55 0.46; 0.55 0.46], 5, 5);
bwColors = bwColors(1:end-1, 1:end-1);
bwColors = reshape(bwColors, 1, numSquares);
bwColors = repmat(bwColors, 3, 1);
multiColors = repmat([0.45 0.58; 0.45 0.58], 5, 5);
multiColors = multiColors(1:end-1, 1:end-1);
multiColors = reshape( multiColors, 1, numSquares);
multiColors = repmat( multiColors, 3, 1);
%black and white colours
board3 = repmat([1 0; 1 0], 5, 5);
board3 = board3(1:end-1, 1:end-1);
board3 = reshape(board3, 1, numSquares);
board3 = repmat(board3, 3, 1);
board4 = repmat([0 1; 0 1], 5, 5);
board4 = board4(1:end-1, 1:end-1);
board4 = reshape( board4, 1, numSquares);
board4 = repmat( board4, 3, 1);
% Texture cue that determines which texture we will show
textureCue = [1 2];
% Sync us to the vertical retrace
vbl = Screen('Flip', window);
%Timing
% Time we want to wait before reversing the contrast of the checkerboard
checkFlipTimeSecs = 0.5;
checkFlipTimeFrames = round(checkFlipTimeSecs / ifi);
frameCounter = 0;
flipSecs = 12;
waitframes = round(flipSecs / ifi);
% Keybpard setup
spaceKey = KbName('space');
escapeKey = KbName('ESCAPE');
RestrictKeysForKbCheck([spaceKey escapeKey]);
%Experimental loop
% Start screen
DrawFormattedText(window, 'Press Space To Begin', 'center', 'center', black);
Screen('Flip', window);
KbWait;
numTrials = 10;
for trial=1:numTrials
textureCue = [1 2];
tex(1)= Screen('MakeTexture',window,multiColors);
tex(2)= Screen('MakeTexture',window,bwColors);
tex2(1)=Screen('MakeTexture',window,board3);
tex2(2)=Screen('MakeTexture',window,board4);
Screen('FillRect', window, grey);
Screen('Flip', window);
% This is our drawing loop
Priority(topPriorityLevel);
while ~KbCheck
% Draw the textures or a blank frame
frameCounter = frameCounter + 1;
if frameCounter == checkFlipTimeFrames
textureCue = fliplr(textureCue);
frameCounter = 0;
end
Screen('DrawTexture', window, tex(textureCue(1)), [],pos);
vbl = Screen('Flip', window, vbl + (waitframes - 0.5) * ifi);
frameCounter = frameCounter + 1;
if frameCounter == checkFlipTimeFrames
textureCue = fliplr(textureCue);
frameCounter = 0;
end
Screen('DrawTexture', window, tex2(textureCue(1)), [],pos);
vbl = Screen('Flip', window, vbl + (waitframes - 0.5) * ifi);
% Poll the keyboard for the space key
[keyIsDown, secs, keyCode] = KbCheck(-1);
if keyCode(KbName('space')) == 1
respMade = 1;
elseif keyCode(KbName('ESCAPE')) == 1
sca;
disp('*** Experiment terminated ***');
return
end
end
end
Screen('Close', tex);
Screen('Close', tex2);
sca;
I want to move a star marker along hexagon trajectory similar to "Circle trajectory" that I have added at the end of my question. Thanks.
This is the source code to that I have written yet for creating concentric hegzagons but I don't know how to move a star marker which traverses the concentric hexagons, I had written a similar simulation code for circle trajectory but I couldn't do it for hexagon.
%clc; % Clear the command window.
%close all; % Close all figures (except those of imtool.)
%clear; % Erase all existing variables. Or clearvars if you want.
workspace; % Make sure the workspace panel is showing.
format long g;
format compact;
fontSize = 20;
angles = linspace(0, 360, 7);
radii = [20, 35, 50,70];
% First create and draw the hexagons.
numberOfHexagons = 4;
% Define x and y arrays. Each row is one hexagon.
% Columns are the vertices.
x1=radii(1) * cosd(angles)+50;
y1 = radii(1) * sind(angles)+50;
x2=radii(2) * cosd(angles)+50;
y2 = radii(2) * sind(angles)+50;
x3=radii(3) * cosd(angles)+50;
y3 = radii(3) * sind(angles)+50;
x4=radii(4) * cosd(angles)+50;
y4 = radii(4) * sind(angles)+50;
plot(x1 , y1, 'b');
hold on
plot(x2, y2, 'b');
hold on
plot(x3, y3, 'b');
hold on
plot(x4, y4, 'b');
hold on
% Connecting Line:
plot([70 100], [50 50],'color','b')
axis([0 100 0 100])
hold on
Circle trajectory:
% Initialization steps.
format long g;
format compact;
fontSize = 20;
r1 = 50;
r2 = 35;
r3= 20;
xc = 50;
yc = 50;
% Since arclength = radius * (angle in radians),
% (angle in radians) = arclength / radius = 5 / radius.
deltaAngle1 = 5 / r1;
deltaAngle2 = 5 / r2;
deltaAngle3 = 5 / r3;
theta1 = 0 : deltaAngle1 : (2 * pi);
theta2 = 0 : deltaAngle2 : (2 * pi);
theta3 = 0 : deltaAngle3 : (2 * pi);
x1 = r1*cos(theta1) + xc;
y1 = r1*sin(theta1) + yc;
x2 = r2*cos(theta2) + xc;
y2 = r2*sin(theta2) + yc;
x3 = r3*cos(theta3) + xc;
y3 = r3*sin(theta3) + yc;
plot(x1,y1,'color',[1 0.5 0])
hold on
plot(x2,y2,'color',[1 0.5 0])
hold on
plot(x3,y3,'color',[1 0.5 0])
hold on
% Connecting Line:
plot([70 100], [50 50],'color',[1 0.5 0])
% Set up figure properties:
% Enlarge figure to full screen.
set(gcf, 'Units', 'Normalized', 'OuterPosition', [0, 0, 1, 1]);
drawnow;
axis square;
for i = 1 : length(theta1)
plot(x1(i),y1(i),'r*')
pause(0.1)
end
for i = 1 : length(theta2)
plot(x2(i),y2(i),'r*')
pause(0.1)
end
for i = 1 : length(theta3)
plot(x3(i),y3(i),'r*')
pause(0.1)
end
I would generalize your problem with parametric function for the trajectory. In it use rotation kernel which you want here few examples in C++/VCL/GDI (sorry I am not Matlab friendly but the equations should be the same in Matlab too) for circle,square and hexagon rotation kernels:
void getpnt_circle(double &x,double &y,double &pi2,double r,double t) // (x,y) = circle(r,t) t=<0,1>
{
pi2=2.0*M_PI; // circumference(r=1) 6.283185307179586476925286766559
t*=pi2;
x=r*cos(t);
y=r*sin(t);
}
//---------------------------------------------------------------------------
void getpnt_square(double &x,double &y,double &pi2,double r,double t) // (x,y) = square(r,t) t=<0,1>
{
pi2=8.0; // circumference(r=1)
// kernel
const int n=4; // sides
const double x0[n]={+1.0,+1.0,-1.0,-1.0}; // side start point
const double y0[n]={-1.0,+1.0,+1.0,-1.0};
const double dx[n]={ 0.0,-2.0, 0.0,+2.0}; // side tangent
const double dy[n]={+2.0, 0.0,-2.0, 0.0};
int ix;
t-=floor(t); // t = <0, 1.0)
t*=n; // t = <0,n)
ix=floor(t); // side of square
t-=ix; // distance from side start
x=r*(x0[ix]+t*dx[ix]);
y=r*(y0[ix]+t*dy[ix]);
}
//---------------------------------------------------------------------------
void getpnt_hexagon(double &x,double &y,double &pi2,double r,double t) // (x,y) = square(r,t) t=<0,1>
{
pi2=6.0; // circumference(r=1)
// kernel
const int n=6; // sides
const double c60=cos(60.0*M_PI/180.0);
const double s60=sin(60.0*M_PI/180.0);
const double x0[n]={+1.0,+c60,-c60,-1.0,-c60,+c60}; // side start point
const double y0[n]={ 0.0,+s60,+s60, 0.0,-s60,-s60};
const double dx[n]={-c60,-1.0,-c60,+c60,+1.0,+c60}; // side tangent
const double dy[n]={+s60, 0.0,-s60,-s60, 0.0,+s60};
int ix;
t-=floor(t); // t = <0, 1.0)
t*=n; // t = <0,n)
ix=floor(t); // side of square
t-=ix; // distance from side start
x=r*(x0[ix]+t*dx[ix]);
y=r*(y0[ix]+t*dy[ix]);
}
//---------------------------------------------------------------------------
void TMain::draw()
{
if (!_redraw) return;
// clear buffer
bmp->Canvas->Brush->Color=clBlack;
bmp->Canvas->FillRect(TRect(0,0,xs,ys));
int e;
double r,t,x,y,c,dr=15.0,dl=15.0;
int xx,yy,rr=3;
bmp->Canvas->MoveTo(xs2,ys2);
bmp->Canvas->Pen->Color=clAqua;
bmp->Canvas->Brush->Color=clBlue;
for (r=dr,t=0.0;;)
{
// get point from selected kernel
// getpnt_circle (x,y,c,r,t);
// getpnt_square (x,y,c,r,t);
getpnt_hexagon(x,y,c,r,t);
// render it
xx=xs2+x;
yy=ys2+y;
bmp->Canvas->LineTo(xx,yy);
bmp->Canvas->Ellipse(xx-rr,yy-rr,xx+rr,yy+rr);
// update position
r+=dr*dr/(r*c);
t+=dl/(r*c); t-=floor(t);
if (r>=xs2) break;
if (r>=ys2) break;
}
// render backbuffer
Main->Canvas->Draw(0,0,bmp);
_redraw=false;
}
//---------------------------------------------------------------------------
You can ignore the VCL/GDI rendering stuff.
xs,ys is full and xs2,ys2 is half resolution of window to scale the plot properly...
dl is distance between markers [pixels]
dr is distance between spiral screws [pixels]
the spiral is sampled with r,t step depending on the actual circumference (that is what the pi2 or c is for). The getpnt_xxxxx function will return x,y coordinate of your shape from parameter t=<0,1> and actual radius r. It also returns the actual value of circumference/r ratio called pi2
Here preview of the 3 kernels used for spiral ...