Beispiel #1
0
    def draw_gradient_grid(self, sampler, filename=None):
        """draw_gradient_grid"""
        gradient_grid = self.get_gradient_grid(sampler)

        fig, ax = plt.subplots()
        ax.set_xlim([-1.2, 1.2])
        ax.set_ylim([-1.2, 1.2])
        ax.imshow(utils.torch_img_to_np_img(
            self.input[0]), extent=[-1, 1, -1, 1])

        total_angle = 0
        target_mo = self.target_mo[0].cpu().numpy()
        for gradient in gradient_grid:
            base_loc = gradient['motion'][0].neg().cpu().numpy()
            gradient = gradient['gradient'][0].neg().cpu().numpy()
            gradient_dir = utils.unit_vector(gradient)
            gt_dir = utils.unit_vector(target_mo - base_loc)
            angle = utils.angle_between(gradient_dir, gt_dir)

            total_angle += angle
            try:
                cur_color = utils.angle_to_color(angle)
            except ValueError:
                cur_color = [0., 0., 0.]

            ax.arrow(*base_loc, *(gradient_dir/10),
                     head_width=0.05, head_length=0.1, color=cur_color)
        # plt.show()
        if filename is None:
            filename = sampler.sampling_mode
        print(filename, 'mean error angle:', total_angle/len(gradient_grid))
        plt.savefig('./%s.png' % filename)
        plt.close(fig)
Beispiel #2
0
    def __init__(self, lookfrom, lookat, vup, vfov, aspect, aperture, focus_dist):
        """
        Initialize camera position, orientation, field of view and aperture.

        Args:
            lookfrom: camera location within scene
            lookat: coordinates towards which camera is oriented
            vup: "up" direction
            vfov: "field of view" angle, in rad
            aspect: aspect ratio (width / height) of focus window
            aperture: aperture (diameter) of camera lense
            focus_dist: distance of focus plane from camera
        """
        self._lens_radius = aperture / 2
        # vfov is top to bottom in rad
        half_height = np.tan(vfov/2)
        half_width = aspect * half_height
        self._origin = lookfrom
        # orthonormal basis
        self._w = unit_vector(lookfrom - lookat)
        self._u = unit_vector(np.cross(vup, self._w))
        self._v = np.cross(self._w, self._u)
        # define the focus plane window
        self._lower_left_corner = self._origin \
            - half_width *focus_dist*self._u   \
            - half_height*focus_dist*self._v   \
            -             focus_dist*self._w
        self._horizontal = 2*half_width *focus_dist*self._u
        self._vertical   = 2*half_height*focus_dist*self._v
 def __init__(self, lookfrom, lookat, vup, vfov, aspect, aperture,
              focus_dist):
     self.lens_radius = aperture / 2
     theta = vfov * math.pi / 180
     half_height = math.tan(theta / 2)
     half_width = aspect * half_height
     self.origin = lookfrom
     self.w = unit_vector(lookfrom - lookat)
     self.u = unit_vector(np.cross(vup, self.w))
     self.v = np.cross(self.w, self.u)
     self.lower_left_corner = self.origin \
                             - half_width * self.u * focus_dist \
                             - half_height * self.v * focus_dist \
                             - self.w * focus_dist
     self.horizontal = 2 * half_width * self.u * focus_dist
     self.vertical = 2 * half_height * self.v * focus_dist
Beispiel #4
0
def ray_color(ray, scene, depth):
    """
    Perform ray tracing and return color of ray.

    Args:
        ray: to-be traced ray
        scene: geometric scene
        depth: how often the ray is allowed to scatter

    Returns:
        numpy.ndarray: ray color as RGB values
    """
    rec, _ = scene.hit(ray, 0.001, 1e6)
    if rec is not None:
        scattered, attenuation = rec.material.scatter(ray, rec)
        if depth > 0 and scattered is not None:
            # pointwise multiplication between attenuation and
            # return value of recursive function call
            return attenuation * ray_color(scattered, scene, depth - 1)
        else:
            return np.zeros(3)
    else:
        # blue background sky
        unitdir = unit_vector(ray.direction)
        t = 0.5*(unitdir[1] + 1)
        return (1 - t)*np.array([1.0, 1.0, 1.0]) + t*np.array([0.5, 0.7, 1.0])
Beispiel #5
0
    def test_dielectric_scatter(self):

        dielmat = Dielectric(0.2)

        # ray specified by origin and direction
        ray = Ray(np.array([0.2, 0., 0.3]), np.array([0.1, -0.05, -1.1]))

        # hit record
        point = np.array([0.3, 0.4, -0.5])
        normal = unit_vector(np.array([0.2, 0.9, -0.1]))
        rec = HitRecord(point, normal, dielmat)

        scattered, att = dielmat.scatter(ray, rec)

        self.assertEqual(
            np.linalg.norm(scattered.origin - point),
            0,
            msg='origin of scattered ray must agree with hit record point')

        # reference directions of scattered ray (can be either reflected or refracted)
        reflect_ref = np.array(
            [0.054686541501393009, -0.20612619488986597, -0.97699609720757918])
        refract_ref = np.array(
            [0.22585143494322246, 0.92588833091527412, -0.30285628276298776])
        err_reflect = np.linalg.norm(scattered.direction - reflect_ref)
        err_refract = np.linalg.norm(scattered.direction - refract_ref)
        self.assertAlmostEqual(
            min(err_reflect, err_refract),
            0,
            delta=1e-14,
            msg='direction of scattered ray must agree with reference')
Beispiel #6
0
    def __init__(self, lookfrom, lookat, vup, vfov, aspect, aperture,
                 focus_dist):
        self.lens_radius = aperture / 2.0
        theta = vfov * math.pi / 180
        half_height = math.tan(theta * 0.5)
        half_width = aspect * half_height

        self.origin = lookfrom
        w = unit_vector(lookfrom - lookat)
        u = unit_vector(np.cross(vup, w))
        v = np.cross(w, u)
        self.low_left_corner = lookfrom - half_width * u * focus_dist - half_height * v * focus_dist - w * focus_dist
        self.horizontal = 2 * half_width * u * focus_dist
        self.vertical = 2 * half_height * v * focus_dist
        self.u = u
        self.v = v
        self.w = w
Beispiel #7
0
 def __init__(self, master, pos, direction, color=None):
     self.pos = np.array(pos)
     if color is None:
         color = [255 for i in range(3)]
     self.color = color
     self.master = master
     self.radius = 10
     self.dir = utils.unit_vector(direction)
     self.speed = 0
def refract(v, n, ni_over_nt, refracted):
    uv = unit_vector(v)
    dt = np.dot(uv, n)
    discriminant = 1.0 - ni_over_nt * ni_over_nt * (1 - dt**2)
    if discriminant > 0:
        refracted = ni_over_nt * (uv - n * dt) - n * math.sqrt(discriminant)
        return True, refracted
    else:
        return False, refracted
Beispiel #9
0
 def scatter(self, ray, rec):
     nraydir = unit_vector(ray.direction)
     reflected = reflect(nraydir, rec.normal)
     scattered = Ray(rec.point.copy(),
                     reflected + self._fuzz * random_in_unit_sphere())
     if np.dot(scattered.direction, rec.normal) > 0:
         return (scattered, self._albedo)
     else:
         return (None, self._albedo)
Beispiel #10
0
	def inertial2orbital(self):
		"""
		Takes the intertial positions and velocity and turns that into the LVLH distances and relative speeds.
		
		This gives excellent estimations of the horizontal (ie vbar) distance, but fails in the rbar direction.		
		Issue with linearisation of the fact that the trajectory is a curve?
		
		.. note:: This *does work* when the chaser and target are very close
		
		:return: delta r, delta v (both 3d vector)
		
		.. note:: this method is currently unused...
		"""
		
		Rchaser = np.array([0,self.chaser.x, self.chaser.y]).reshape((1,3))
		Rtarget = np.array([0,self.target.x, self.target.y]).reshape((1,3))
		deltaR = (Rchaser - Rtarget)
		
		Vchaser = np.array([0,self.chaser.u, self.chaser.v]).reshape((1,3))
		Vtarget = np.array([0,self.target.u,  self.target.v]).reshape((1,3))
		deltaV = (Vchaser - Vtarget)
		
		Ht = np.cross(Rtarget, Vtarget)
		
		Roi = np.zeros([3,3])
		
		Roi[:,0] = utils.unit_vector(np.cross(Ht,Rtarget))
		Roi[:,1] = -utils.unit_vector(Ht)
		Roi[:,2] = -utils.unit_vector(Rtarget)
		
		omegaI = np.cross(Rtarget, Vtarget) / np.linalg.norm(Rtarget) / np.linalg.norm(Rtarget)
		
		deltaro = np.matmul(deltaR, Roi)

		
		OmegaR = np.cross(omegaI, deltaR)
		
		deltaVo = np.matmul(deltaV, Roi) - np.matmul(OmegaR, Roi)
		
		return deltaro, deltaVo
Beispiel #11
0
    def __init__(self):

        # Initialise pygame
        os.environ["SDL_VIDEO_CENTERED"] = "1"
        pygame.init()

        s_width = 1600
        s_height = 800
        # Set up the display
        self.screen = pygame.display.set_mode((s_width, s_height))

        self.clock = pygame.time.Clock()

        # define cornerpoints
        wall_thickness = 20.0
        c_depth = 250.0
        p0 = (wall_thickness, wall_thickness)
        p1 = (s_width - wall_thickness, wall_thickness)
        p2 = (s_width - wall_thickness, s_height - wall_thickness)
        p3 = (wall_thickness, s_height - wall_thickness)
        self.walls = [
            RectWall((p0, p1), (0.0, 0.0), (wall_thickness, s_height)),
            RectWall((p1, p2), (s_width - wall_thickness, 0.0),
                     (wall_thickness, s_height)),
            RectWall((p2, p3), (0.0, s_height - wall_thickness),
                     (s_width, wall_thickness)),
            RectWall((p3, p0), (0.0, 0.0), (s_width, wall_thickness)),
            PolyWall(((0, c_depth), (c_depth, 0.0), (0.0, 0.0))),
            PolyWall(((s_width - c_depth, 0.0), (s_width, c_depth), (s_width,
                                                                     0.0))),
            PolyWall(((s_width, s_height - c_depth),
                      (s_width - c_depth, s_height), (s_width, s_height))),
            PolyWall(((0, s_height - c_depth), (c_depth, s_height),
                      (0.0, s_height)))
        ]

        self.orbits = [
            orbit.Orbit((500, 600), 100, [40 for i in range(3)]),
            orbit.Orbit((900, 500), 100, [40 for i in range(3)]),
            orbit.Orbit((200, 400), 100, [40 for i in range(3)])
        ]

        self.orbs = []

        self.players = [
            head.Head(self, (400.0, 400.0), utils.unit_vector((-1, -1.01)))
        ]
        # self.players = [head.Head(self, (random.randrange(200.0, 1400.0), random.randrange(200.0, 600.0)), utils.unit_vector((random.random()*2 - 1, random.random() * 2 - 1))) for i in range(10)]
        # self.players = [orb.Orb(self, (701.0, 200.0), utils.unit_vector((-1, 0))), orb.Orb(self, (100.0, 259.0), utils.unit_vector((1, 0)))]

        self.tail = False
Beispiel #12
0
def color(r, world, depth):
    boolean, rec = world.hit(r, 0.001, float('inf'), hit_record())
    if boolean:
        flag, attenuation, scattered = rec.mat_ptr.scatter(
            r, rec, np.array([0.0, 0.0, 0.0], dtype=float))
        if depth < 50 and flag:
            return attenuation * color(scattered, world, depth + 1)
        else:
            return np.zeros([3], dtype=float)
    else:
        unit_direction = unit_vector(r.direction())
        t = 0.5 * (unit_direction[1] + 1.0)
        return (1.0 - t) * np.array([1.0, 1.0, 1.0]) + t * np.array(
            [0.5, 0.7, 1.0])
Beispiel #13
0
    def scatter(self, ray, rec):

        # normalized ray direction
        nraydir = unit_vector(ray.direction)

        reflected = reflect(nraydir, rec.normal)

        cosine = np.dot(nraydir, rec.normal)
        if cosine > 0:
            refracted = refract(nraydir, -rec.normal, self._ref_idx)
        else:
            refracted = refract(nraydir, rec.normal, 1.0 / self._ref_idx)
            cosine = -cosine

        if refracted is not None:
            reflect_prob = schlick(cosine, self._ref_idx)
        else:
            reflect_prob = 1.0

        # randomly choose between reflection or refraction
        if np.random.rand() < reflect_prob:
            return (Ray(rec.point.copy(), reflected), np.ones(3))
        else:
            return (Ray(rec.point.copy(), refracted), np.ones(3))
Beispiel #14
0
def sky(ray):
    unit_dir = unit_vector(ray.direction)
    t = 0.5 * (unit_dir[1] + 1.0)
    return (1.0 - t) * np.array([1.0, 1.0, 1.0]) + t * np.array(
        [0.5, 0.7, 1.0])
Beispiel #15
0
 def compute(self, pos, dir):
     v0 = pos - self.orbit_centre
     v1 = np.matmul(self.rot_matrix, v0)
     p1 = self.orbit_centre + v1
     return p1, utils.unit_vector(p1 - pos)
Beispiel #16
0
 def __init__(self, collision_vector, color=(255, 255, 255)):
     self.coll_v = np.array(collision_vector)
     self.coll_norm = utils.perp(
         utils.unit_vector(self.coll_v[1] - self.coll_v[0]))
     print(self.coll_v, self.coll_norm)
     self.color = color
Beispiel #17
0
def circle_vertices(pos, r, n=32):
    result = []
    a = 2 * pi / n
    for i in range(n):
        result.append(pos + r * unit_vector(a * i))
    return result
Beispiel #18
0
	def plot(self, time_ticks=1, save=True, highlight_new_orbit=True):
		"""
		A lot of things here could be computed only once
		"""
		
		t = np.arange(len(self.target.speed_x)) * self.dt / self.chaser.P
		vtgt = np.hypot(self.target.speed_x, self.target.speed_y) * 1e-3
		vcha = np.hypot(self.chaser.speed_x, self.chaser.speed_y) * 1e-3
		
		#################################

		chaserspeed = np.array([self.chaser.speed_x, self.chaser.speed_y]).T
			
		vch = np.hypot(chaserspeed[:,0], chaserspeed[:,1])# * 1e-3
		chxy = np.array([self.chaser.trajectory_x, np.array(self.chaser.trajectory_y)])
		
		r = u.unit_vector(chxy).T
		
		aalpha = u.vangle_between(chaserspeed, r)
		
		dxo = 3. * np.pi * (self.target.a - self.chaser.a)
		
		v_bar = np.zeros_like(aalpha)
		for jjk in range(len(chxy[0])):
			try:
				vb = (self.distances.trajectory_x[jjk-1] - self.distances.trajectory_x[jjk]) / (self.dt)
			except:
				vb = np.nan 
			if jjk == 0:
				vb = np.nan
			v_bar[jjk] = vb
			
				
		vz_bar = vch * np.cos(aalpha) * 1e-3
		#v_bar *= dxo / (np.nanmean((v_bar[:self.idch])) * 1e3 * self.chaser.P)
		
		##############################
		
		if self.chaser.hp == self.chaser.ha:
			txtch = r"$\mathrm{%s:\,%d\,km\,alt.}$" % (self.chaser.name, self.chaser.hp)
		else:
			txtch = r"$\mathrm{%s}:\,%d\times%d\mathrm{\,km\,alt.}$" % (self.chaser.name, self.chaser.hp, self.chaser.ha)
		txttgt = r"$\mathrm{%s:\,%d\,km\,alt.}$" % (self.target.name, self.target.hp)
		
		nimg = 0
		for ii in range(len(self.target.speed_x)-1):
			
			if not ii % time_ticks == 0: continue
			
			#if ii > len(self.target.speed_x): continue
	
			fig = plt.figure(figsize=(18,10))
			plt.subplots_adjust(wspace=0.)
			plt.subplots_adjust(bottom=0.01)
			plt.subplots_adjust(top=0.95)
			plt.subplots_adjust(right=0.98)
			plt.subplots_adjust(left=0.)
			
			gs = gridspec.GridSpec(4, 3)
			
			# This where we plot the Earth orbit at the right scale
			ax0 = fig.add_subplot(gs[:2,0])
			#ax0.plot([1,2,3,4,5], [10,5,10,5,10], 'r-')
			ax0.set_aspect("equal")
			
			circle1 = plt.Circle((0, 0), constants.radiusE, color='b', alpha=0.5)
			ax0.add_artist(circle1)
			ax0.plot(self.target.trajectory_x[:self.idtgt], -1 * np.array(self.target.trajectory_y[:self.idtgt]), c='k')
			ax0.plot(self.chaser.trajectory_x[:self.idch], -1 * np.array(self.chaser.trajectory_y[:self.idch]), c='r')
			ax0.scatter(self.target.trajectory_x[ii], -1 * np.array(self.target.trajectory_y[ii]), c='k')
			ax0.scatter(self.chaser.trajectory_x[ii], -1 * np.array(self.chaser.trajectory_y[ii]), c='r')
			ax0.set_xticks([])
			ax0.set_yticks([])
			ax0.axis('off')
			
			ax1 = fig.add_subplot(gs[0,1:])
			#t = np.arange(len(self.target.speed_x)) * self.dt / self.chaser.P
			#vtgt = np.hypot(self.target.speed_x, self.target.speed_y) * 1e-3
			#vcha = np.hypot(self.chaser.speed_x, self.chaser.speed_y) * 1e-3
			ax1.plot(t, vtgt, c='k', label=self.target.name)
			ax1.plot(t, vcha, c='r', label=self.chaser.name)
			xmin1, xmax1 = ax1.get_xlim()
			ymin1, ymax1 = ax1.get_ylim()
			ax1.scatter(t[ii], vtgt[ii], c='k')
			ax1.scatter(t[ii], vcha[ii], c='r')			
			#ax1.plot(t, vcha-vtgt, c='b', label=r"$\Delta v$")
			ax1.legend(fontsize=self.txtsize - 2)
			ax1.xaxis.set_ticklabels([])
			ax1.set_ylabel(r"$v\,\mathrm{[km/s]}$")
			ax1.grid()
			ax1.set_xlim([xmin1, xmax1])
			ax1.set_ylim([ymin1, ymax1])
			
			ax2 = fig.add_subplot(gs[2:,0])
			ax2.set_aspect("equal")
			
			r, theta = u.cart2polar(self.target.trajectory_x, self.target.trajectory_y)
			r -= constants.radiusE
			x, y = u.polar2cart(r, theta)
			ax2.plot(x[:self.idtgt] * 1e-3, -1e-3 * y[:self.idtgt], c='k')
			ax2.scatter(x[ii] * 1e-3, -1e-3 * y[ii], c='k')
			
			r, theta = u.cart2polar(self.chaser.trajectory_x, self.chaser.trajectory_y)
			r -= constants.radiusE
			x, y = u.polar2cart(r, theta)
			
			lim2 = 1.15 * np.amax([self.chaser.ha, self.target.ha])
			ax2.set_xlim([-lim2, lim2])
			ax2.set_ylim([-lim2, lim2])
			
			ax2.plot(x[:self.idtgt] * 1e-3, -1e-3 * y[:self.idtgt], c='r')
			ax2.scatter(x[ii] * 1e-3, -1e-3 * y[ii], c='r')
			ax2.set_xticklabels([])
			ax2.set_yticks([])
			ax2.axis('off')
			
			ax3 = fig.add_subplot(gs[1, 1:])#, sharex=ax1)
			ax3.set_xlabel("$\mathrm{Time\,[Orbits\,of\,chaser]}$")
			ax3.plot(t, v_bar*1e3, c='gold', label=r"$\bar{V}$")
			ax3.plot(t, vz_bar*1e3, c='g', label=r"$v_R$")
			ymin3, ymax3 = ax3.get_ylim()
			ax3.scatter(t[ii], v_bar[ii]*1e3, c='gold')
			ax3.scatter(t[ii], vz_bar[ii]*1e3, c='g')
			ax3.set_xlim([xmin1, xmax1])
			ax3.set_ylim([ymin3, ymax3])
			ax3.legend(fontsize=self.txtsize - 2)
			ax3.grid()
			ax3.axhline(0, c='k', ls='--')
			ax3.set_ylabel(r"$\bar{V},\,v_R\,\mathrm{[m/s]}$")
			
			ax4 = fig.add_subplot(gs[2:,1:])
			
			ax4.spines['left'].set_position('zero')
			ax4.spines['right'].set_color('none')
			ax4.spines['bottom'].set_position('zero')
			ax4.spines['top'].set_color('none')
			
			# remove the ticks from the top and right edges
			ax4.xaxis.set_ticks_position('bottom')
			ax4.yaxis.set_ticks_position('left')
			ax4.invert_xaxis()
			ax4.invert_yaxis()
			
			ax4.plot(-np.array(self.distances.trajectory_x), -np.array(self.distances.trajectory_y))
			ax4.scatter(-np.array(self.distances.trajectory_x)[ii], -np.array(self.distances.trajectory_y)[ii])
			
			# For correctly plotting the vbar axis
			ax4.plot([0], [0], c='k')
			
			xmin, xmax = ax4.get_xlim()
			ymin, ymax = ax4.get_ylim()
			
			if t[ii] > 1 and highlight_new_orbit:
				ax4.axvline(-self.distances.trajectory_x[0], c='k', ls='--')
				ax4.axvline(-self.distances.trajectory_x[int(np.ceil(self.chaser.P / self.dt))], c='k', ls='--')
				x4t = -(self.distances.trajectory_x[0] + self.distances.trajectory_x[int(np.ceil(self.chaser.P / self.dt))])/2.
				
				y4t = (ymax - ymin) * 0.5 + ymin
				y4tt = (ymax - ymin) * 0.51 + ymin

				ax4.plot([-self.distances.trajectory_x[0], -self.distances.trajectory_x[int(np.ceil(self.chaser.P / self.dt))]], [y4t, y4t], ls='--', c='k')
				ax4.annotate(r'$1\,\mathrm{orbit\,}\Delta x = 3\pi\Delta a\approx %3.0f\,\mathrm{km}$' % (dxo/1e3), xy=(x4t, y4tt), ha='center')
			
			xx = ((1. - xmax / (xmax - xmin)) * 1.005 * (xmax - xmin) + xmin)
			yy = ((1. - ymax / (ymax - ymin)) * 1.015 * (ymax - ymin) + ymin)
			ax4.annotate(r'$\bar{V}\,\mathrm{[km]}$', xy=((xmax - xmin) * 0.01 + xmin, yy))
			ax4.annotate(r'$\bar{R}\,\mathrm{[km]}$', xy=(xx, (ymax - ymin) * 0.02 + ymin))
			
			if self.ax4_xmin is None:
				self.ax4_xmin = xmin
				self.ax4_xmax = xmax
				self.ax4_ymin = ymin
				self.ax4_ymax = ymax
			gs.update(wspace=0.05, hspace=0.3)
			
			ax4.set_xlim([self.ax4_xmin, self.ax4_xmax])
			ax4.set_ylim([self.ax4_ymin, self.ax4_ymax])
			

			plt.suptitle(txtch + r"$\quad$--$\quad$" + txttgt + r"$\quad$--$\quad$" + r"Time {:03d} min".format(int(self.dt*ii/60.)))
			
			if not os.path.exists(self.outdir):
				os.mkdir(self.outdir)
				
			if save:
				fig.savefig(os.path.join(self.outdir, "img_{:05d}.png".format(nimg)), dpi=200)
				plt.close()
			else:
				plt.show()
			
			nimg += 1
 def scatter(self, r_in, rec, attenuation):
     reflected = reflect(unit_vector(r_in.direction()), rec.normal)
     scattered = ray(rec.p, reflected + self.fuzz * random_in_unit_sphere())
     attenuation = self.albedo
     return np.any(np.dot(scattered.direction(),
                          rec.normal)) > 0, attenuation, scattered
Beispiel #20
0
def update(refresh_rate):
    # Controls first, see if anything has happened

    # zoom functionality
    if controller[key.DOWN]:

        transform.zoom *= 0.975
        for body in bodies:
            body.zoom(0.975)

    if controller[key.UP]:
        transform.zoom *= 1.025
        for body in bodies:
            body.zoom(1.025)

    # Boosting
    if controller[key.W] and sol.m[0] > DRY_MASS:
        # perform boost in direction of spacecraft
        orientation = np.radians(player.rotation)
        boost = utils.unit_vector(orientation) * THRUST / sol.m[
            0]  # acceleration
        sol.v[0] = sol.v[0] + boost * VARS['dt']
        sol.m[0] = sol.m[0] - FLOW_RATE * VARS['dt']
        mass_label.text = f'Fuel: %.3E' % (sol.m[0] - DRY_MASS)
    elif sol.m[0] < DRY_MASS:
        mass_label.text = f'Fuel: Empty'
    # Ship rotation
    if controller[key.D]:
        # rotate clockwise
        player.rotation = utils.bound_angles(player.rotation + DTHETA)
    if controller[key.A]:
        # rotate counterclockwise
        player.rotation = utils.bound_angles(player.rotation - DTHETA)

    # Change simulation speed
    if controller[key.COMMA]:
        VARS['dt'] *= 0.98
        sim_label.text = 'Sim. Speed: %.3f' % (VARS['dt'])
    if controller[key.PERIOD]:
        VARS['dt'] *= 1.02
        sim_label.text = 'Sim. Speed: %.3f' % (VARS['dt'])

    VARS['time'] = time.time() - t_zero  #VARS['dt'] # update time
    time_label.text = 'Time: %.2f' % (VARS['time'])
    # this is where we update the window, and the game actually happens
    sol.update(VARS['dt'])  # update solar system motions with time step dt
    # then shift from solar system coordinates to screen coordinates
    screen_coordinates = transform(sol.r)
    # update the sprites (icons) accordingly
    background_sprite.update(
        sol.v[1] * VARS['dt'] * transform.scale /
        transform.zoom)  # update background to follow star
    for j, body in enumerate(bodies):
        body.update(screen_coordinates[j])

    # Collision detection + fuel conversion
    # Check if any object is close enough to be converted to fuel
    dist_to_player = np.linalg.norm(
        sol.r[1:], axis=1) - sol.radius[1:]  # distance to surface
    ################################# Add sol.r[0] - ... for different reference frames!
    close_enough = np.any(dist_to_player < EAT_DISTANCE)

    # Change player sprite to indicate that planet is in range
    if VARS['was_close'] == True and close_enough == False:
        player.image = player_icon
        VARS['was_close'] = False
    elif close_enough and VARS['was_close'] == True:
        too_close = dist_to_player < 0  # inside surface --> collision
        if np.any(too_close):
            player.image = explosion_animation
            #sys.exit() # if too close, game over

        if controller[key.SPACE]:
            object_id = np.argmin(dist_to_player) + 1
            consumed = sol.convert_to_fuel(
                object_id, DM)  # and remove object from calculations
            mass_label.text = f'Fuel: %.3E' % (sol.m[0] - DRY_MASS)
            if consumed:
                bodies[object_id].delete()  # remove sprite
                del (bodies[object_id])  # and list entry
                objects_consumed = n_bodies - len(bodies)
                progress_label.text = f'Objects Consumed: {objects_consumed}/{n_bodies - 1}'

    elif close_enough:
        VARS['was_close'] = True
        player.image = player_eat_icon