def init(self): '''Calcula propriedades da colisão''' A, B = self.A, self.B n = self.normal P = self.pos # Coeficiente de atrito e restituição self.mu = self.get_friction_coeff() self.e = self.get_restitution() # Pontos de contato self.rA = rA = P - A.pos self.rB = rB = P - B.pos self.rA_ortho = Vec2(-rA.y, rA.x) self.rB_ortho = Vec2(-rB.y, rB.x) # Massa efetiva eff_invmass = A._invmass + B._invmass if A._invinertia: eff_invmass += rA.cross(n) ** 2 * A._invinertia if B._invinertia: eff_invmass += rB.cross(n) ** 2 * B._invinertia self.effmass = 1.0 / eff_invmass # Viés na velocidade relativa para definir o coeficiente de restituição vrel = ((B.vel + B.omega * self.rB_ortho) - (A.vel + A.omega * self.rA_ortho)) vrel_normal = vrel.dot(n) if -vrel_normal > self.min_vel_bias: self.vel_bias = -self.e * vrel_normal
def collision_aabb(A, B): '''Retorna uma colisão com o objeto other considerando apenas a caixas de contorno alinhadas ao eixo.''' # Detecta colisão pelas sombras das caixas de contorno rx, ry = B._pos - A._pos shadowx = A._delta_x + B._delta_x - abs(rx) shadowy = A._delta_y + B._delta_y - abs(ry) if shadowx <= 0 or shadowy <= 0: return None # Calcula ponto de colisão x_col = max(A.xmin, B.xmin) + shadowx / 2. y_col = max(A.ymin, B.ymin) + shadowy / 2. pos_col = Vec2(x_col, y_col) # Define sinal dos vetores normais: colisões tipo PONG if shadowx > shadowy: n = Vec2(0, (1 if A.ymin < B.ymin else -1)) delta = shadowy else: n = Vec2((1 if A.xmin < B.xmin else -1), 0) delta = shadowx return Collision(A, B, pos=pos_col, normal=n, delta=delta)
def circle_aabb(A, B): # TODO: implementar direito, está utilizando AABBs r = A.cbb_radius x, y = A._pos Axmin, Axmax = x - r, x + r Aymin, Aymax = y - r, y + r x, y = B._pos dx, dy = B._delta_x, B._delta_y Bxmin, Bxmax = x - dx, x + dx Bymin, Bymax = y - dy, y + dy shadowx = min(Axmax, Bxmax) - max(Axmin, Bxmin) shadowy = min(Aymax, Bymax) - max(Aymin, Bymin) if shadowx < 0 or shadowy < 0: return None # Calcula ponto de colisão x_col = max(A.xmin, B.xmin) + shadowx / 2. y_col = max(A.ymin, B.ymin) + shadowy / 2. pos_col = Vec2(x_col, y_col) # Define sinal dos vetores normais: colisões tipo PONG if shadowx > shadowy: n = Vec2(0, (1 if A.ymin < B.ymin else -1)) else: n = Vec2((1 if A.xmin < B.xmin else -1), 0) return Collision(A, B, pos=pos_col, normal=n)
def _vertices(self, N, length, pos): self.length = length alpha = pi / N theta = 2 * alpha b = length / (2 * sin(alpha)) P0 = Vec2(b, 0) pos = Vec2(*pos) return [(P0.rotate(n * theta)) + pos for n in range(N)]
def gravity(self, value): owns_prop = BodyFlags.owns_gravity old = self._gravity try: gravity = self._gravity = Vec2(*value) except TypeError: gravity = self._gravity = Vec2(0, -value) for obj in self._objects: if not obj.flags & owns_prop: obj._gravity = gravity self.trigger('gravity-change', old, self._gravity)
def __call__(self, t): # Se o argumento for uma função, significa que estamos usando o # decorador para definir o valor da força if callable(t): func = t self.clear() self.add(func) return func # Caso contrário, assume que o argumento é numérico e executa t = float(t) if self._fast is not None: return Vec2(*self._fast(t)) else: return Vec2(0, 0)
def orientation(self, theta=0.0): '''Retorna um vetor unitário na direção em que o objeto está orientado. Pode aplicar um ângulo adicional a este vetor fornecendo o parâmetro _theta.''' theta += self._theta return Vec2(cos(theta), sin(theta))
def boost(self, delta_or_x, y=None): '''Adiciona um valor vetorial delta à velocidade linear''' if y is None: self._vel += delta_or_x else: self._vel += Vec2(delta_or_x, y)
def __init__(self, obj, k, r0=(0, 0)): kxy = 0 x0, y0 = self._r0 = Vec2(*r0) try: # Caso isotrópico kx = ky = k = self._k = float(k) except TypeError: # Caso anisotrópico k = self._k = tuple(map(float, k)) if len(k) == 2: kx, ky = k else: kx, ky, kxy = k # Define forças e potenciais def F(R): Dx = x0 - R.x Dy = y0 - R.y return Vec2(kx * Dx + kxy * Dy, ky * Dy + kxy * Dx) def U(R): Dx = x0 - R.x Dy = y0 - R.y return (kx * Dx**2 + ky * Dy**2 + 2 * kxy * Dx * Dy) / 2 super(SpringSF, self).__init__(obj, F, U)
def __init__(self, A, B, k, delta=(0, 0)): kxy = 0 dx, dy = delta = self._delta = Vec2(*delta) try: # Caso isotrópico kx = ky = k = self._k = float(k) except TypeError: # Caso anisotrópico k = self._k = tuple(map(float, k)) if len(k) == 2: kx, ky = k else: kx, ky, kxy = k # Define forças e potenciais def F(rA, rB): Dx = rB.x - rA.x + dx Dy = rB.y - rA.y + dy return Vec2(kx * Dx + kxy * Dy, +ky * Dy + kxy * Dx) def U(rA, rB): Dx = rB.x - rA.x + dx Dy = rB.y - rA.y + dy return (kx * Dx**2 + ky * Dy**2 + 2 * kxy * Dx * Dy) / 2 super(SpringF, self).__init__(A, B, F, U)
def get_tangent(self): # Vetor unitário tangente à colisão n = self.normal tangent = Vec2(-n.y, n.x) if tangent.dot(self.vrel) > 0: tangent *= -1 return tangent
def get_normal(self, i): '''Retorna a normal unitária associada ao i-ésimo segmento. Cada segmento é definido pela diferença entre o (i+1)-ésimo ponto e o i-ésimo ponto.''' points = self.vertices x, y = points[(i + 1) % self.num_sides] - points[i] return Vec2(y, -x).normalize()
def apply_impulse(self, impulse_or_x, y=None): '''Aplica um impulso linear ao objeto. Isto altera sua velocidade linear com relação ao centro de massa. Se for chamado com dois agumentos aplica o impulso em um ponto específico e também resolve a dinâmica angular. ''' self.boost(Vec2(impulse_or_x, y) * self._invmass)
def make_static_linear(self): '''Resgata os parâmetros dinâmicos lineares de um objeto estático ou cinemático paralizado pelos métodos `obj.make_static()` ou `obj.make_kinematic()`.''' self.make_kinematic_linear() if self._vel != nullvec2: self._old_vel = self._vel self._vel = Vec2(0, 0)
def __init__(self, vertices, pos=None, vel=(0, 0), theta=0.0, omega=0.0, mass=None, density=None, inertia=None, **kwds): vertices = [Vec2(*pt) for pt in vertices] pos_cm = center_of_mass(vertices) vertices = [v - pos_cm for v in vertices] self._vertices = vertices # Cache de vértices self._cache_theta = None self._cache_rvertices_last = None self._cache_rbbox_last = None self.cbb_radius = max(v.norm() for v in vertices) super(Poly, self).__init__(pos_cm, vel, theta, omega, mass=mass, density=density, inertia=inertia, **kwds) self.num_sides = len(vertices) self._normals_idxs = self.get_li_indexes() self.num_normals = len(self._normals_idxs or self.vertices) # Aceleramos um pouco o cálculo para o caso onde todas as normais são # LI. entre si. Isto é sinalizado por self._normals_idx = None, que # implica que todas as normais do polígono devem ser recalculadas a # cada frame if self.num_normals == self.num_sides: self._normals_idxs = None # Movemos para a posição especificada caso _pos seja fornecido if pos is not None: self._pos = Vec2(*pos)
def get_normals(self): '''Retorna uma lista com as normais linearmente independentes.''' if self._normals_idxs is None: N = self.num_sides points = self.vertices segmentos = (points[(i + 1) % N] - points[i] for i in range(N)) return [Vec2(y, -x).normalize() for (x, y) in segmentos] else: return [self.get_normal(i) for i in self._normals_idxs]
def __init__(self, A, B, world=None, pos=None, normal=None, delta=0.0): super(Collision, self).__init__(A, B) self.objects = A, B self.world = world self.is_active = True self.resolved = False self.vrel = nullvec2 if pos is None: raise ValueError(A, B) self.pos = Vec2(pos) self.normal = Vec2(normal) self.delta = delta self.Jn = 0.0 self.vel_bias = 0.0 self.init() # Certifica se normal foi bem escolhida if (self.rA.dot(self.normal) < 0): self.__init__(self.A, self.B, world=world, pos=pos, normal=-normal, delta=delta)
def vpoint(self, pos_or_x, y=None, relative=False): '''Retorna a velocidade linear de um ponto em _pos preso rigidamente ao objeto. Se o parâmetro `relative` for verdadeiro, o vetor `_pos` é interpretado como a posição relativa ao centro de massa. O padrão é considerá-lo como a posição absoluta no centro de coordenadas do mundo.''' if relative: if y is None: x, y = pos_or_x return self._vel + self._omega * Vec2(-y, x) else: return self._vel + self._omega * Vec2(-y, pos_or_x) else: if y is None: x, y = pos_or_x - self._pos return self._vel + self._omega * Vec2(-y, x) else: x = pos_or_x - self._pos.x y = y - self._pos.y return self._vel + self._omega * Vec2(-y, x)
def _make_fast_combined(self, combined): '''Cria função que retorna a força como a soma de todas as constantes k e forças f para cada elemento (k, f) em combined''' F = Vec2(0, 0) Fmul = F.__imul__ Fadd = F.__iadd__ def fast_func(t): Fmul(0) for k, func in combined: fi = func(t) Fadd((k * fi[0], k * fi[1])) return F return fast_func
def _make_fast_adds(self, adds): '''Cria função que retorna a força como a soma de todas as forças em adds''' # TODO: adaptar para vetores não-mutáveis? F = Vec2(0, 0) Fmul = F._imul # usamos estas funções para evitar problemas de Fadd = F._iadd # escopo no closure fa função fast_func def fast_func(t): Fmul(0) for func in adds: Fadd(func(t)) return F return fast_func
def __init__(self, obj, G, M=None, epsilon=0, r0=(0, 0)): if M is None: M = obj.mass M = self._M = float(M) self._epsilon = float(epsilon) self._G = float(G) r0 = self._r0 = Vec2(*r0) def F(R): R = r0 - R r = R.norm() r3 = (r + epsilon)**2 * r R *= G * obj.mass * M / r3 return R def U(R): R = (R - r0).norm() return -self._G * obj.mass * M / (R + self._epsilon) super(GravitySF, self).__init__(obj, F, U)
def gravity(self, value): try: self._gravity = Vec2(*value) except TypeError: self._gravity = Vec2(0, -value) self.flags |= flags.owns_gravity
def F(rA, rB): Dx = rB.x - rA.x + dx Dy = rB.y - rA.y + dy return Vec2(kx * Dx + kxy * Dy, +ky * Dy + kxy * Dx)
def __init__(self, pos=nullvec2, vel=nullvec2, theta=0.0, omega=0.0, mass=None, density=None, inertia=None, gravity=None, damping=None, adamping=None, restitution=None, sfriction=None, dfriction=None, baseshape=None, world=None, col_layer=0, col_group=0, flags=DEFAULT_FLAGS): self._world = world EventDispatcher.__init__(self) # Flags de objeto self.flags = flags # Variáveis de estado ################################################# self._pos = Vec2(pos) self._vel = Vec2(vel) self._e_vel = nullvec2 self._e_omega = 0.0 self._theta = float(theta) self._omega = float(omega) self._accel = nullvec2 self._alpha = 0.0 # Harmoniza massa, inércia e densidade ################################ self._baseshape = self._shape = baseshape self._aabb = getattr(baseshape, 'aabb', None) if density is not None: density = float(density) if mass is None: mass = density * self.area() else: mass = float(mass) if inertia is None: inertia = density * \ self.area() * self.ROG_sqr() / INERTIA_SCALE else: inertia = float(inertia) elif mass is not None: mass = float(mass) density = mass / self.area() if inertia is None: inertia = mass * self.ROG_sqr() / INERTIA_SCALE else: inertia = float(inertia) else: density = 1.0 mass = density * self.area() if inertia is None: inertia = density * \ self.area() * self.ROG_sqr() / INERTIA_SCALE else: inertia = float(inertia) self._invmass = 1.0 / mass self._invinertia = 1.0 / inertia self._density = float(density) # Controle de parâmetros físicos locais ############################### self._gravity = nullvec2 self._damping = self._adamping = 0.0 self._sfriction = self._dfriction = 0.0 self._restitution = 1.0 if damping is not None: self.damping = damping if adamping is not None: self.adamping = adamping if gravity is not None: self.gravity = gravity if restitution is not None: self.restitution = restitution if sfriction is not None: self.sfriction = sfriction if sfriction is not None: self.dfriction = dfriction # Vínculos e contatos ################################################# self._contacts = [] self._joints = [] # Filtros de colisões ################################################# # Colide se objetos estão em groupos diferentes (exceto os que estão # no grupo 0) e no mesmo layer self._col_layer = int(col_layer) if col_group: if isinstance(col_group, int): self._col_group_mask = 1 << (col_group - 1) else: mask = 0 for n in col_group: mask |= 1 << (n - 1) self._col_group_mask = mask else: self._col_group_mask = 0 # TODO: fundir col_layer com col_group_mask no mesmo inteiro? # (talvez 50 bits para grupos e 10 ==> 1024 layers diferentes) # Do jeito que está, podemos utilizar strings como identificadores # de layers (mas não para grupos). A classe mundo poderia fazer um # mapa entre strings > números nos dois casos. # Presença em mundo ################################################### if world is not None: self._world.add(self)
def F(R): Dx = x0 - R.x Dy = y0 - R.y return Vec2(kx * Dx + kxy * Dy, ky * Dy + kxy * Dx)
def pos_sw(self): return Vec2(self.xmin, self.ymin)
def pos_se(self): return Vec2(self.xmax, self.ymin)
def __init__(self, shape=(800, 600), pos=(0, 0), zoom=1, background=None): self.width, self.height = shape self.pos = Vec2(*pos) self.zoom = zoom self.background = background self._direct = True
def pos_nw(self): return Vec2(self.xmin, self.ymax)
def pos_ne(self): return Vec2(self.xmax, self.ymax)