Search code examples
pythoncenterpointscentroidorbit

Orbiting four objects around centroid in an elliptic shape, Python


Okay... I'm not so good at maths (didn't even finish our version of high school) and is really tired right now, but I need to orbit four objects elliptically around the centroid and have been stuck for a while, but it's not like I haven't tried or come anywhere by myself. This is what I've come up with so far:

from math import cos,sin,pi,sqrt,atan2
def orbit(p, deg, pivot=(0.32563325, 0.123498),ellipse=(0.5, 0.743992)): 
    # p = current (x,y)-position of current object,
    # Pivot = (x,y)-position of point to orbit around,
    #   retrieved by centroid((x1,y1),(x2,y2),(x3,y3),(x4,y4))
    # Ellipse = (width,height) of ellipse to rotate around
    #   retrieved by ellipse((x1,y1),(x2,y2),(x3,y3),(x4,y4))
    px,py = pivot
    if ellipse == (0.0,0.0):
        o = polar(p[0]-px,p[1]-py)[0]
        xr,yr = 1.0,1.0
    else:
        ew,eh = ellipse
        if ew < eh: 
            o = eh/2            # Distance to the point most far away from the middle of the ellipse (Outer radius)
            xr = ew/eh          # Horizontal ratio of ellipse so we can move it back properly into ellipse after performing circular orbit
            yr = 1.0            # Verical movement will not be affected because it's on the large axis
        else: 
            o = ew/2
            xr = 1.0
            yr = eh/ew
    x,y = p[0]-px,p[1]-py       # Subtract pivot's position (that's the the point I want to orbit around)
    d,a = polar(x,y)            # Get angle
    x,y = rect(o,a+deg)         # Move point as far away from middle as it will be as most and make circular orbit around pivot by deg degrees 
    x,y = x*xr,y*yr             # Move points back to elliptic shape by multiplying positions with according ratio <--- I guess it's here something goes wrong
    x,y = x+px,y+py             # Move point back to original position by adding pivot's positions
    return x,y

# Other functions
def centroid(*points):
    x,y = izip(*points)
    return (sum(x) / len(points), sum(y) / len(points))
def ellipse(*points):
    x,y = izip(*points)
    xd,yd = max(x)-min(x),max(y)-min(y)
    return (xd,yd)
def polar(x,y):
    d = sqrt(x**2 + y**2)
    a = atan2(y,x) * (180.0/pi)
    return d, a
def rect(d,a):
    x = d * cos(a*pi/180.0)
    y = d * sin(a*pi/180.0)
    return x, y

If i use ellipse=(0.0,0.0) and orbit everything in an "ordinary" circle instead of an elliptic shape it works just fine, so I guess it's when I try to multiply the x/y-position with the ratio ew/eh or eh/ew I do something wrong but I can't figure out what at the moment.

I am a bit tired and will try and get some sleep now and see if I can solve it tomorrow but some helpt would really be appriciated here.


Solution

  • This encapsulates the math for each Ellipse, and shows sample usage; you will have to add code for whatever output you want.

    from math import sin, cos, pi
    
    class Ellipse(object):
        @classmethod
        def fromBox(cls, p1=(-1.,-1.), p2=(1.,1.), period=1., offs=0., clockwise=False):
            """
            Construct an Ellipse from an axis-aligned bounding box
            p1, p2      diagonally-opposed corners of the bounding box
            period      time for a complete orbit
            offs        offset into initial rotation
            clockwise   direction of rotation
            """
            x1,y1 = p1
            x2,y2 = p2
            # find center point
            cx, cy = (x1 + x2)*0.5, (y1 + y2)*0.5
            # find major and minor semi-axes and corresponding theta
            a,b = abs(x2 - x1)*0.5, abs(y2 - y1)*0.5
            if a >= b:
                theta = 0.0
            else:
                a,b = b,a
                theta = pi/2
            # return an Ellipse object
            return cls(cx, cy, a, b, theta, period, offs, clockwise)
    
        def __init__(self, x=0., y=0., a=1., b=1., theta=0., period=1., offs=0., clockwise=False):
            """
            Create  an ellipse
            x,y         center point
            a           semi-major axis
            b           semi-minor axis
            theta       major axis inclination (in radians)
            period      time for a complete orbit
            offs        offset into initial rotation
            clockwise   direction of rotation
            """
            self.x = x
            self.y = y
            self.period = -period if clockwise else period
            self._freq = self.period / (2. * pi)
            self.offs = offs
    
            s_th = sin(theta)
            c_th = cos(theta)
            self._ast = a * s_th
            self._act = a * c_th
            self._bst = b * s_th
            self._bct = b * c_th
    
        def at(self, t):
            """
            Evaluate the ellipse at time t
            """
            _t = (t - self.offs) * self._freq
            st = sin(_t)
            ct = cos(_t)
            return self.x + self._act*ct - self._bst*st, self.y + self._act*st + self._bst*ct
    
    def main():
        a = Ellipse.fromBox((-0.67436675, -0.376502), (1.32563325, 0.623498))
        b = Ellipse(0.32563325, 0.123498, 0.9, 0.6, pi/6)
        c = Ellipse(0.32563325, 0.123498, 1.1, 0.5, pi/4)
        d = Ellipse(0.32563325, 0.123498, 1.0, 0.5, pi/2)
    
        t_step = 0.03
        for t in xrange(200):
            drawCircle(a.at(t*t_step), "red")
            drawCircle(b.at(t*t_step), "blue")
            drawCircle(c.at(t*t_step), "green")
            drawCircle(d.at(t*t_step), "yellow")
    
    if __name__=="__main__":
        main()