def find_target_position(origin_pos, initial_velocity, wanted_z=0): """ Find and return target ball position with a specified initial velocity and position. :param pygame.Vector3 origin_pos: initial ball position in world coordinates :param pygame.Vector3 initial_velocity: initial ball velocity in world coordinates :param float wanted_z: specify at which z value target position will be found :return: target ball position :rtype pygame.Vector3: """ # z_t z_t = wanted_z - origin_pos.z # u vector : unit vector in XY plane from origin to target position u = Vector3(initial_velocity) u.z = 0 u = u.normalize() if u.length_squared() != 0 else Vector3() # get final time t_t t_t = get_time_at_z(initial_velocity.z, origin_pos.z, wanted_z) # u_t u_t = t_t * initial_velocity.dot(u) return u_t * u + origin_pos + Vector3(0, 0, z_t)
def shade(self, pos): L = Vector3(self.x * sin(t), self.y, self.z * cos(t)) L = L.normalize() dt = 1e-6 current_val = surface_distance(pos) x = Vector3(pos.x + dt, pos.y, pos.z) dx = surface_distance(x) - current_val y = Vector3(pos.x, pos.y + dt, pos.z) dy = surface_distance(y) - current_val z = Vector3(pos.x, pos.y, pos.z + dt) dz = surface_distance(z) - current_val # normal N = Vector3( (dx - pos.x) / dt, (dy - pos.y) / dt, (dz - pos.z) / dt, ) if N.length() < 1e-9: return PIXELS[0] N = N.normalize() diffuse = L.x * N.x + L.y * N.y + L.z * N.z diffuse = (diffuse + 1) / 2 * len(PIXELS) return PIXELS[int(diffuse) % len(PIXELS)]
def test_world_to_pixel_coords(horizontal_camera, oblique_camera, not_centered_camera): w, h = (100, 100) for cam in (horizontal_camera, oblique_camera, not_centered_camera): # center center_pt = Vector3(cam.focus_point) u, v = cam.world_to_pixel_coords(center_pt, (w, h)) assert u == w / 2 or u == w / 2 - 1 # -1 because of imprecision due to continuous/discrete conversion assert v == h / 2 or v == h / 2 - 1 # same thing # point in 4 screen areas (top-left, top-right, bottom-left and bottom-right) tl_pt = Vector3(0, -1, 1) + cam.focus_point tr_pt = Vector3(0, 1, 1) + cam.focus_point bl_pt = Vector3(0, -1, -1) + cam.focus_point br_pt = Vector3(0, 1, -1) + cam.focus_point # test if points are in correct area u, v = cam.world_to_pixel_coords(tl_pt, (w, h)) assert u < w / 2 and v < h / 2 u, v = cam.world_to_pixel_coords(tr_pt, (w, h)) assert u > w / 2 and v < h / 2 u, v = cam.world_to_pixel_coords(bl_pt, (w, h)) assert u < w / 2 and v > h / 2 u, v = cam.world_to_pixel_coords(br_pt, (w, h)) assert u > w / 2 and v > h / 2
def calculatePerspective(self, location: pygame.Vector3): translation = pygame.Vector2(self.getLocation().x + self.getWidth() / 2, self.getLocation().y + self.getHeight() / 2) #ray = Ray(self.getFocus(), location) location = location - self.getLocation() ray = Ray(pygame.Vector3(0, 0, -self.__focus_distance), pygame.Vector3(location.dot(self.getHorisontal()), location.dot(self.getVertical()), location.dot(self.getFacing()))) return ray.projection(self.__focus_distance) + translation
def draw_sphere(center, radius, col=None): """ Draw a filled sphere on camera screen. :param pygame.Vector3 center: center of sphere :param float radius: radius (world scale, not in pixel) of sphere :param tuple(int, int, int) col: drawing color. default is :var DBG_COLOR_SPHERE: :return: rect bounding the changed pixels :rtype pygame.Rect: """ display_manager = DisplayManager.get_instance() if col is None: col = DBG_COLOR_SPHERE # process r_px : radius in pixel camera = display_manager.camera surface = display_manager.debug_3d.image surface_size = surface.get_size() c_pos = Vector3(center) if SIZE_INDEPENDENT_FROM_Y_POS: c_pos.y = camera.position.y if SIZE_INDEPENDENT_FROM_Z_POS: c_pos.z = camera.position.z r_px = int((Vector2( camera.world_to_pixel_coords(c_pos, surface_size) - Vector2( camera.world_to_pixel_coords(c_pos + radius * Vector3(0, 1, 0), surface_size))).magnitude())) return draw.circle(surface, col, camera.world_to_pixel_coords(center, surface_size), r_px)
def update_physics(self, dt): self.previous_position = Vector3(self.position) if not self.will_be_served: self.add_velocity(Vector3(0, 0, -0.001 * dt * G)) self.move_rel(0.001 * dt * self.velocity) else: self.velocity = Vector3()
def _set_n_debug_3d_points(self, n): """ Process n 3D-points in ball trajectory. :param int n: points count :return: n points in list :rtype list(Vector3): """ if self.origin_pos is None or self.target_pos is None or self.initial_velocity is None: self.debug_pts = [] else: assert n > 1 # u vector : unit vector in XY plane from origin to target position u = Vector3(self.initial_velocity) u.z = 0 u = u.normalize() if u.length_squared() != 0 else Vector3(0, 0, 0) # get t_t : final time t_t = self.get_final_time() dt = t_t / (n - 1) self.debug_pts = [] for i in range(n): t_i = i * dt u_i = t_i * self.initial_velocity.dot(u) z_i = -t_i**2 / 2 * G + t_i * self.initial_velocity.z self.debug_pts.append( Vector3(u_i * u + self.origin_pos + Vector3(0, 0, z_i)))
def explode(self): explode_force = randint(10, 30) for i in range(300): r = (random() * 360) * math.pi / 180 f = random() * explode_force + 1 self.particles.append( Particle(self.screen, False, Vector3(self.firework.pos), Vector3(math.sin(r) * f, math.cos(r) * f, 0)))
def __init__(self, location: pygame.Vector3 = pygame.Vector3(0, 0, 0), facing: pygame.Vector3 = pygame.Vector3(1, 0, 0), vertical: pygame.Vector3 = pygame.Vector3(0, 1, 0), **kwargs): super().__init__(**kwargs) self.__centre: pygame.Vector3 = location self.__facing: pygame.Vector3 = facing.normalize() self.__vertical: pygame.Vector3 = vertical.normalize()
def test_world_to_cam_3d_coords(horizontal_camera, oblique_camera, not_centered_camera): center_pt = Vector3(0, 0, 0) assert horizontal_camera.world_to_cam_3d_coords(center_pt) == Vector3( 0, 0, -5) assert oblique_camera.world_to_cam_3d_coords(center_pt) == Vector3( 0, 0, -5) assert not_centered_camera.world_to_cam_3d_coords( not_centered_camera.focus_point) == Vector3(0, 0, -5)
def __init__(self, screen, width, height, gravity): self.screen = screen self.width = width self.height = height self.gravity = gravity self.firework = Particle(screen, True, Vector3(randint(0, width), height, 0), Vector3(0, randint(-16, -10), 0)) self.exploded = False self.particles = []
def _rotate(vec, angle, axis): """Rotate vec around axis by the given angle in radians.""" # We are using the Pygame's vectors here # because pyglm documentation is SHIT and # I can't find a way do do the same SIMPLE thing. vec = Vector3(vec) axis = Vector3(axis) vec.rotate_ip(angle * 180 / pi, axis) return vec3(vec)
def __init__(self, pos, WW, WH): gluPerspective(50, (WW / WH), 0.1, 50) glTranslatef(pos[0], pos[1], pos[2]) glRotatef(0, 0, 0, 0) self.pos = Vector3(pos) self.rot = Vector3(0, 0, 0) self.rot_angle = 0 self.velocity = Vector3(0, 0, 0) self.rot_velocity = Vector3(0, 0, 0)
def _create_gradient_steps(self, *colors): current = Vector3(colors[0][:3]) self.gradient_steps = [] distance = 0 for color in colors[1:]: vector = Vector3(color) gradient_step = GradientStep(current, vector, distance) self.gradient_steps.append(gradient_step) distance += gradient_step.distance current = Vector3(vector) return distance
def __init__(self, screen, fire, pos=Vector3(0, 0, 0), vel=Vector3(0, 0, 0), col=white): self.screen = screen self.firework = fire self.pos = pos self.vel = vel self.acc = Vector3(0, 0, 0) self.col = col self.transparency = 255
def find_initial_velocity(origin_pos, target_pos, wanted_height): """ Return initial velocity to apply to a ball to reach a target from an origin position and a specified height. Process initial velocity in world coordinates to apply on a ball. The specified height is taken in account for the ball trajectory. If target and origin y sign values are different, the wanted height will be on y = 0 (net position). Else, the wanted height will be on the middle of trajectory, or at apogee trajectory during a vertical throw. :param pygame.Vector3 origin_pos: origin position, the point where the ball will be thrown from :param pygame.Vector3 target_pos: the desired target position the ball will reach :param float wanted_height: height desired on net place, middle of trajectory or at apogee :return: velocity to apply to the ball :rtype pygame.Vector3: """ assert wanted_height > origin_pos.z assert wanted_height > target_pos.z if target_pos.x == origin_pos.x and target_pos.y == origin_pos.y: # vertical throw zh = wanted_height - origin_pos.z vo_z = sqrt(2 * G * zh) return Vector3(0, 0, vo_z) else: # u vector : unit vector in XY plane from origin to target position u = Vector3(target_pos - origin_pos) u.z = 0 u = u.normalize() # ut, zt : coordinates of target point in (u, z) ref to_vect = (target_pos - origin_pos) to_vect.z = 0 ut = to_vect.length() zt = target_pos.z - origin_pos.z # uh, zh : coordinates of point above the net in (u, z) ref alpha = 0.5 if origin_pos.y * target_pos.y < 0: # if target and origin points are not in the same court side alpha = abs(origin_pos.y / (target_pos.y - origin_pos.y)) uh = alpha * ut zh = wanted_height - origin_pos.z # process initial velocity to apply in (u, z) ref : vo_u, vo_z # not trivial equations, from math and physics resolutions a = (ut / uh * zh - zt) c = G * ut / 2 * (uh - ut) delta = -4 * a * c vo_u = sqrt(delta) / (2 * a) vo_z = zh * (vo_u / uh) + uh / vo_u * G / 2 return Vector3(vo_u * u + Vector3(0, 0, vo_z))
def __init__(self, origin_pos=None, target_pos=None, initial_velocity=None): self.origin_pos = Vector3( origin_pos) if origin_pos is not None else None self.target_pos = Vector3( target_pos) if target_pos is not None else None self.initial_velocity = Vector3( initial_velocity) if initial_velocity is not None else None self.t0 = None self.debug_pts = None self._create()
def __init__(self): #self.modelcanon = pygame.image.load('Model/canon.png') self.vitesse = 0 self.position = Vector2(400, 450) self.direction = Vector2() self.couleur = Vector3() self.pointdevie = 3
def __init__(self, w, h, net_z1, net_z2): self.w = w self.h = h self.net_z1 = net_z1 self.net_z2 = net_z2 self.collider = AABBCollider(Vector3(0, 0, (net_z1 + net_z2) / 2), (h, 0, net_z2 - net_z1)) # sprite self.rects = [pg.Rect(0, 0, 0, 0) for _ in range(10)]
def test_move_time(character): dt = 10 origin_pos = Vector3() character.position = Vector3(origin_pos) n = 100 # move along +y axis for n frames for _ in range(n): character.move(Vector3(0, 1, 0), dt, free_displacement=True) assert n * dt / 1000 == pytest.approx( character.get_time_to_run_to(origin_pos), 0.01) # move along +x +y axis for n frames for _ in range(n): character.move(Vector3(0.7071, 0.7071, 0), dt, free_displacement=True) assert 2 * n * dt / 1000 == pytest.approx( character.get_time_to_run_to(origin_pos), 0.01)
def draw(self, surface, vertical): vector = Vector3(self.color) step = self.start_distance for i in range(self.distance): if vertical: surface.set_at((0, i + step), Color(*map(int, vector))) else: surface.set_at((i + step, 0), Color(*map(int, vector))) vector += self.step self.minmax_color_vector(vector)
def perspective_projection(pt): # Assuming camera has orientation along -z axis to easy calculations. # Thus the display surface is perpendicular to z-axis # display_surface_distance is the distance between camera and the display surface # https://en.wikipedia.org/wiki/3D_projection#Perspective_projection camera = Vector3(0, 0, 6) display_surface_distance = 0.2 projected_point = (pt - camera) * \ display_surface_distance / (camera.z - pt.z) # Return the point after normalizing return projected_point * camera.z / display_surface_distance
def draw_debug(self): prev_rect = self.rect prev_rect_shadow = self.rect_shadow self.rect = debug3D_utils.draw_horizontal_ellipse( Vector3(self.position[0], self.position[1], 0), self.radius) self.rect_shadow = self.collider.draw_debug() return [ prev_rect.union(self.rect), prev_rect_shadow.union(self.rect_shadow) ]
def __init__(self, position=None, radius=0.5, sprite_groups=[]): pg.sprite.DirtySprite.__init__(self, *sprite_groups) self.radius = radius self.acceleration = Vector3() self.velocity = Vector3() self._position = Vector3( position) if position is not None else Vector3() self.previous_position = Vector3(self._position) self.collider = SphereCollider(position, radius) self.is_colliding_ground = False self.is_colliding_net = False self.is_colliding_character = False self.will_be_served = False # sprite self.rect_shadow = pg.Rect(0, 0, 0, 0) self.rect = pg.Rect(0, 0, 0, 0) # game rules self._current_team_touches = []
def draw_horizontal_ellipse(center, radius): """ Draw a filled ellipse and a unfilled bound trapeze in XY 3D plane on camera screen. :param pygame.Vector3 center: center of ellipse :param float radius: radius (world scale, not in pixel) of ellipse :return: rect bounding the changed pixels :rtype pygame.Rect: """ display_manager = DisplayManager.get_instance() camera = display_manager.camera surface = display_manager.debug_3d.image surface_size = surface.get_size() # corners of shadow (trapeze) t_l = camera.world_to_pixel_coords( Vector3(center) + (-radius, -radius, 0), surface_size) t_r = camera.world_to_pixel_coords( Vector3(center) + (-radius, radius, 0), surface_size) b_l = camera.world_to_pixel_coords( Vector3(center) + (radius, -radius, 0), surface_size) b_r = camera.world_to_pixel_coords( Vector3(center) + (radius, radius, 0), surface_size) # approximate rect r_pos = [int(0.5 * (t_l[0] + b_l[0])), t_l[1]] r_w = int(0.5 * (t_r[0] + b_r[0]) - r_pos[0]) r_h = b_r[1] - r_pos[1] if r_h < 0: r_pos[1] += r_h r_h = -r_h rect = Rect(r_pos, (r_w, r_h)) if r_w >= r_h: draw.ellipse(surface, DBG_COLOR_HOR_ELLIPSE, rect) # ellipse is in polygon rect, not need to return both rects return draw.polygon(surface, DBG_COLOR_SHADOW_HOR_ELLIPSE_TRAPEZE, [t_l, t_r, b_r, b_l], 1)
def test_move_rel(character): assert character.position == Vector3() assert character.collider.center == Vector3( ) + character.collider_relative_position # move character.move_rel(Vector3(0, 1, 0.1), free_displacement=True) assert character.position == Vector3(0, 1, 0.1) assert character.collider.center == Vector3( 0, 1, 0.1) + character.collider_relative_position # move character.move_rel(Vector3(-0.2, 0, 0), free_displacement=True) assert character.position == Vector3(-0.2, 1, 0.1) assert character.collider.center == Vector3( -0.2, 1, 0.1) + character.collider_relative_position
def blend(self, *colors): if len(colors) < 2: return False vectors = [] for color in colors: vectors.append(Vector3(color[:3])) distance = self._create_gradient_steps(*vectors) self.surface = self._create_surface(distance) for gstep in self.gradient_steps: gstep.draw(self.surface, self.vertical) return True
def run(): global SPHERE_RADIUS global t pygame.font.init() clock = pygame.time.Clock() font = pygame.font.Font(None, 30) screen = pygame.display.set_mode((800, 800)) buffer = pygame.Surface((80, 40)) rect = buffer.get_rect() position = Vector3(0, 0, -2) step = .005 shader = Shader() running = True while running: elapsed = clock.tick(60) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.KEYDOWN: if event.key in (pygame.K_ESCAPE, pygame.K_q): pygame.event.post(pygame.event.Event(pygame.QUIT)) elif event.key == pygame.K_LEFT: SPHERE_RADIUS += step print(SPHERE_RADIUS) elif event.key == pygame.K_RIGHT: SPHERE_RADIUS -= step print(SPHERE_RADIUS) # pressed = pygame.key.get_pressed() if pressed[pygame.K_UP]: position.z += step print(position) elif pressed[pygame.K_DOWN]: position.z -= step print(position) elif pressed[pygame.K_a]: shader.x += 1 print(shader.x) elif pressed[pygame.K_z]: shader.x -= 1 print(shader.x) # buffer.fill((0, 0, 0)) draw(rect, buffer, position, shader) pygame.transform.scale(buffer, screen.get_rect().size, screen) img = font.render(f'{t:.04f}', True, (200, 200, 200)) screen.blit(img, (0, 0)) pygame.display.flip() # t = (t + elapsed / 1000 * 1) % tau
def test_change_collider(character): char_original_pos = Vector3(character.position) char_original_collider_rel_pos = Vector3( character.collider_relative_position) coll_original_size = Vector3(character.collider.size3) coll_original_center = Vector3(character.collider.center) # set default collider character.set_default_collider() assert character.collider.size3 == coll_original_size assert character.collider.center == coll_original_center assert character.position == char_original_pos assert character.collider_relative_position == char_original_collider_rel_pos # change and reset collider direction = Vector3(0, 1, 0) character.set_diving_collider(direction) character.set_default_collider() assert character.collider.size3 == coll_original_size assert character.collider.center == coll_original_center assert character.position == char_original_pos assert character.collider_relative_position == char_original_collider_rel_pos # move, change collider, move again and reset collider direction = Vector3(0, 1, 0) character.move(Vector3(1, 0, 0), 1000, free_displacement=True) character.set_diving_collider(direction) character.move(Vector3(0, 1, 0), 500, free_displacement=True) character.set_default_collider() assert character.collider.size3 == coll_original_size assert character.collider.center == coll_original_center + character.max_velocity * Vector3( 1, 0.5, 0) assert character.position == char_original_pos + character.max_velocity * Vector3( 1, 0.5, 0) assert character.collider_relative_position == char_original_collider_rel_pos
def draw(rect, surface, position, shader): for y in range(rect.height): for x in range(rect.width): pos = position target = Vector3(x / rect.width - .5, (y / rect.height - .5) * (rect.height / rect.width) * 1.5, -1.5) ray = target - pos ray.normalize() pixel = PIXELS[0] max_ = 9_999 for _ in range(15_000): if (fabs(pos.x) > max_ or fabs(pos.y) > max_ or fabs(pos.z) > max_): break dist = surface_distance(pos) if dist < 1e-1: pixel = shader.shade(pos) break pos = pos + ray * dist surface.set_at((x, y), pixel)