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 __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 gravity(self, value): try: self._gravity = Vec2(*value) except TypeError: self._gravity = Vec2(0, -value) self.flags |= flags.owns_gravity
def from_pos(self, x, y): w, h = self._shape x0, y0 = self._origin return Vec2(x0 + a * w + x, y0 + b * h + y)
def apply_accel(self, a, dt): '''Aplica uma aceleração linear durante um intervalo de tempo dt. Tem efeito em objetos cinemáticos. Observations ------------ Implementa a integração de Velocity-Verlet para o sistema. Este integrador é superior ao Euler por dois motivos: primeiro, trata-se de um integrador de ordem superior (O(dt^4) vs O(dt^2)). Além disto, ele possui a propriedade simplética, o que implica que o erro da energia não tende a divergir, mas sim oscilar ora positivamente ora negativamente em torno de zero. Isto é extremamente desejável para simulações de física que parecam realistas. A integração de Euler seria implementada como: x(t + dt) = x(t) + v(t) * dt + a(t) * dt**2 / 2 v(t + dt) = v(t) + a(t) * dt Em código Python >>> self.move(self.vel * dt + a * (dt**2/2)) # doctest: +SKIP >>> self.boost(a * dt) # doctest: +SKIP Este método simples e intuitivo sofre com o efeito da "deriva de energia". Devido aos erros de truncamento, o valor da energia da solução numérica flutua com relação ao valor exato. Na grande maioria dos sistemas, esssa flutuação ocorre com mais probabilidade para a região de maior energia e portanto a energia tende a crescer continuamente, estragando a credibilidade da simulação. Velocity-Verlet está numa classe de métodos numéricos que não sofrem com esse problema. A principal desvantagem, no entanto, é que devemos manter uma variável adicional com o último valor conhecido da aceleração. Esta pequena complicação é mais que compensada pelo ganho em precisão numérica. O algorítmo consiste em: x(t + dt) = x(t) + v(t) * dt + a(t) * dt**2 / 2 v(t + dt) = v(t) + [(a(t) + a(t + dt)) / 2] * dt O termo a(t + dt) normalemente só pode ser calculado se soubermos como obter as acelerações como função das posições x(t + dt). Na prática, cada iteração de .apply_accel() calcula o valor da posição em x(t + dt) e da velocidade no passo anterior v(t). Calcular v(t + dt) requer uma avaliação de a(t + dt), que só estará disponível na iteração seguinte. A próxima iteração segue então para calcular v(t + dt) e x(t + 2*dt), e assim sucessivamente. A ordem de acurácia de cada passo do algoritmo Velocity-Verlet é de O(dt^4) para uma força que dependa exclusivamente da posição e tempo. Caso haja dependência na velocidade, a acurácia reduz e ficaríamos sujeitos aos efeitos da deriva de energia. Normalmente as forças físicas que dependem da velocidade são dissipativas e tendem a reduzir a energia total do sistema muito mais rapidamente que a deriva de energia tende a fornecer energia espúria ao sistema. Deste modo, a acurácia ficaria reduzida, mas a simulação ainda manteria alguma credibilidade. ''' # TODO: reimplementar algoritmo correto a = Vec2.from_seq(a) self.move(self._vel * dt + a * (dt**2 / 2.0)) self.boost(a * dt)
def pos_prop(self): w, h = self._shape x0, y0 = self._origin return Vec2(x0 + a * w, y0 + b * h)
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)
# -*- coding: utf8 -*- ''' ======== Tutorial ======== Exemplo básico ============== Este tutorial explica como utilizar a FGAme para a criação de jogos ou simulações de física simples. A FGAme é um motor de jogos com ênfase na simulação de física. Todos os objetos, portanto, possuem propriedades físicas bem definidas como massa, momento de inércia, velocidade, etc. A simulação da física é feita, em grande parte, de modo automático. O primeiro passo é definir o palco que os objetos habitarão. Isto pode ser feito criando um objeto da classe World(). >>> world = World() A partir daí, podemos criar objetos e inserí-los na simulação >>> obj1 = Circle(50) >>> obj2 = Circle(50, color='red') >>> world.add([obj1, obj2]) Para modificar as propriedades físicas dos objetos basta modificar diretamente os atributos correspondentes. Uma lista completa de atributos pode ser encontrada no módulo `FGAme.objects`:module:. >>> obj1.mass = 10
def __set__(self, obj, value): if not isinstance(value, Vec2): value = Vec2(value) setter(obj, value)
# -*- coding: utf8 -*- from FGAme.mathutils import Vec2, dot, area, center_of_mass, clip, pi from FGAme.mathutils import shadow_x, shadow_y from FGAme.physics.collision import Collision from FGAme.physics import Circle, AABB, Poly, Rectangle from FGAme.util import multifunction u_x = Vec2(1, 0) DEFAULT_DIRECTIONS = [ u_x.rotate(n * pi / 12) for n in [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11] ] class CollisionError(Exception): '''Declara que não existem colisões disponíveis para os dois tipos de objetos''' pass def get_collision_generic(A, B): '''Retorna uma colisão genérica definido pela CBB dos objetos A e B''' rA = A.cbb_radius rB = B.cbb_radius delta = B._pos - A._pos if delta.norm() < rA + rB: n = delta.normalize() D = rA + rB - delta.norm() pos = A._pos + (rA - D / 2) * n
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_sw(self): return Vec2(self.xmin, self.ymin)
def pos_se(self): return Vec2(self.xmax, self.ymin)
def pos_nw(self): return Vec2(self.xmin, self.ymax)
def F(R): Dx = x0 - R.x Dy = y0 - R.y return Vec2(kx * Dx + kxy * Dy, ky * Dy + kxy * Dx)
def pos_ne(self): return Vec2(self.xmax, self.ymax)
def apply_accel(self, a, dt): '''Aplica uma aceleração linear durante um intervalo de tempo dt. Tem efeito em objetos cinemáticos. Observations ------------ Implementa a integração de Velocity-Verlet para o sistema. Este integrador é superior ao Euler por dois motivos: primeiro, trata-se de um integrador de ordem superior (O(dt^4) vs O(dt^2)). Além disto, ele possui a propriedade simplética, o que implica que o erro da energia não tende a divergir, mas sim oscilar ora positivamente ora negativamente em torno de zero. Isto é extremamente desejável para simulações de física que parecam realistas. A integração de Euler seria implementada como: x(t + dt) = x(t) + v(t) * dt + a(t) * dt**2 / 2 v(t + dt) = v(t) + a(t) * dt Em código Python >>> self.move(self.vel * dt + a * (dt**2/2)) # doctest: +SKIP >>> self.boost(a * dt) # doctest: +SKIP Este método simples e intuitivo sofre com o efeito da "deriva de energia". Devido aos erros de truncamento, o valor da energia da solução numérica flutua com relação ao valor exato. Na grande maioria dos sistemas, esssa flutuação ocorre com mais probabilidade para a região de maior energia e portanto a energia tende a crescer continuamente, estragando a credibilidade da simulação. Velocity-Verlet está numa classe de métodos numéricos que não sofrem com esse problema. A principal desvantagem, no entanto, é que devemos manter uma variável adicional com o último valor conhecido da aceleração. Esta pequena complicação é mais que compensada pelo ganho em precisão numérica. O algorítmo consiste em: x(t + dt) = x(t) + v(t) * dt + a(t) * dt**2 / 2 v(t + dt) = v(t) + [(a(t) + a(t + dt)) / 2] * dt O termo a(t + dt) normalemente só pode ser calculado se soubermos como obter as acelerações como função das posições x(t + dt). Na prática, cada iteração de .apply_accel() calcula o valor da posição em x(t + dt) e da velocidade no passo anterior v(t). Calcular v(t + dt) requer uma avaliação de a(t + dt), que só estará disponível na iteração seguinte. A próxima iteração segue então para calcular v(t + dt) e x(t + 2*dt), e assim sucessivamente. A ordem de acurácia de cada passo do algoritmo Velocity-Verlet é de O(dt^4) para uma força que dependa exclusivamente da posição e tempo. Caso haja dependência na velocidade, a acurácia reduz e ficaríamos sujeitos aos efeitos da deriva de energia. Normalmente as forças físicas que dependem da velocidade são dissipativas e tendem a reduzir a energia total do sistema muito mais rapidamente que a deriva de energia tende a fornecer energia espúria ao sistema. Deste modo, a acurácia ficaria reduzida, mas a simulação ainda manteria alguma credibilidade. ''' # TODO: reimplementar algoritmo correto a = Vec2.from_seq(a) self.move(self._vel * dt + a * (dt ** 2 / 2.0)) self.boost(a * dt)
def _random(self, scale, angle): pass def random(self, angle=None): return self._random(self.fair, angle) def random_fast(self, angle=None): return self._random(self.slow, angle) def random_slow(self, angle=None): return self._random(self.slow, angle) # Velocidades em direções específicas ######################################### _speeds = dict( up=Vec2(0, 1), down=Vec2(0, -1), right=Vec2(1, 0), left=Vec2(-1, 0), ne=Vec2(1, 1).normalize(), nw=Vec2(-1, 1).normalize(), se=Vec2(1, -1).normalize(), sw=Vec2(-1, -1).normalize(), ) def speed_prop(name, scale, vec): def prop(self): return getattr(self, scale) * vec prop.__name__ = name