def __init__(self, pos=null2D, vel=null2D, mass=1.0, gravity=None, damping=None, adamping=None, restitution=None, friction=None, simulation=None, col_layer=0, col_group=0, flags=DEFAULT_FLAGS): self._simulation = simulation # Object flags self.flags = flags # State variables self._pos = asvector(pos) self._vel = asvector(vel) self._acceleration = null2D self._invmass = 1 / mass # Control object-local physical parameters self._gravity = null2D self._damping = 0.0 self._friction = 0.0 self._restitution = 1.0 if damping is not None: self.damping = damping if gravity is not None: self.gravity = gravity if restitution is not None: self.restitution = restitution if friction is not None: self.friction = friction # Collision filters self.collisions = [] if col_layer: if isinstance(col_layer, int): self._col_layer_mask = 1 << col_layer else: mask = 0 for n in col_layer: mask |= 1 << n self._col_layer_mask = mask else: self._col_layer_mask = 0 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 # World if simulation is not None: self._simulation.add(self)
def __init__(self, A, B, normal=None, pos=None, delta=0.0): super(Collision, self).__init__(A, B) self.normal = normal = asvector(normal) self.pos = pos = asvector(pos) self.delta = delta = float(delta) self.restitution = sqrt(A.restitution * B.restitution) self.friction = sqrt(A.friction * B.friction) self.active = True
def __init_image(self, image, image_offset=(0, 0), image_reference=None, **kwargs): self._image = None self._drawshape = None if image is not None: # Get all image parameters img_kwargs = {} for k, v in kwargs.items(): if k.startswith('image_'): img_kwargs[k[6:]] = v if isinstance(image, str): image = Image(image, self.pos, **img_kwargs) else: raise NotImplementedError self._image = image offset = asvector(image_offset) if image_reference in ['pos_ne', 'pos_nw', 'pos_se', 'pos_sw', 'pos_left', 'pos_right', 'pos_up', 'pos_down']: pos_ref = getattr(self, image_reference) pos_img_ref = getattr(image, image_reference) offset += pos_ref - pos_img_ref elif image_reference not in ['middle', None]: raise ValueError( 'invalid image reference: %r' % image_reference) ImageChops.offset(image.texture._pil, int(offset[0]), int(offset[1])) else: self._drawshape = self._init_drawshape(color=self.color or black, linecolor=self.linecolor, linewidth=self.linewidth)
def tileinit(self, tilesize, coords=None, tileorigin=(0, 0)): '''Inicializa os valores de tilesize e tileorigin''' self.tileorigin = asvector(tileorigin) self.tilesize = tilesize if coords is not None: self.coords = coords
def __rescale(self, scale, resample): '''Rescale image to the given scale factor *inplace*''' if scale == 1: return default = 'nearest' if scale == int(scale) else 'linear' try: resample = dict( default=default, fastest=PIL.Image.NEAREST, nearest=PIL.Image.NEAREST, linear=PIL.Image.BILINEAR, bilinear=PIL.Image.BILINEAR, cubic=PIL.Image.BICUBIC, bicubic=PIL.Image.BICUBIC, lanczos=PIL.Image.LANCZOS, best=PIL.Image.LANCZOS, )[resample] except KeyError: raise ValueError('invalid sampling method: %r' % resample) shape = (self.texture._pil.width, self.texture._pil.height) shape = map(int, scale * asvector(shape)) self.texture._pil = self.texture._pil.resize(shape, resample)
def __init__(self, canvas, displacement=(0, 0), scale=1, rotation=0): if scale != 1 or rotation != 0: raise ValueError('rotations and rescaling are not supported') self.displacement = asvector(displacement) self.canvas = canvas self._passthru = True
def apply_force(self, force, dt, method=None): """ Apply a linear force during interval dt. This has no effect on kinematic objects. """ self.apply_accel(asvector(force) * self._invmass, dt, method)
def apply_impulse_at_relative(self, J, pos): """ Like .apply_impulse_at(), but consider pos to be relative to the center of mass. """ self.apply_impulse(J) if self.invinertia: self.apply_aimpulse(asvector(pos).cross(J))
def fast_func(t): F = null2D for k, func in nmuls: Fi = asvector(func(t)) try: F += Fi * k except TypeError: x, y = Fi F += Vec2(k * x, k * y) return F
def apply_accel(self, a, dt, method=None): """ Apply linear acceleration during interval dt. """ a = asvector(a) if method is None or method == 'euler-semi-implicit': self.boost(a * dt) self.move(self._vel * dt + a * (0.5 * dt * dt)) elif method == 'verlet': raise NotImplementedError elif method == 'euler': self.move(self._vel * dt + a * (0.5 * dt * dt)) self.boost(a * dt) else: raise ValueError('invalid method: %r' % method)
def vpoint(self, pos_or_x, y=None, relative=False): '''Retorna a velocidade linear de um ponto preso rigidamente ao objeto. Se o parâmetro `relative` for verdadeiro, o vetor de entrada é interpretado como a posição relativa ao centro de massa. O padrão é considerá-lo como a posição absoluta no sistema de coordenadas do mundo. Example ------- Criamos um objeto inicialmente sem rotação >>> b1 = Body(pos=(1, 1), vel=(1, 1), mass=1, inertia=1) Neste caso, a velocidade relativa a qualquer ponto corresponde à velocidade do centro de massa >>> b1.vpoint(0, 0), b1.vpoint(1, 1) (Vec(1, 1), Vec(1, 1)) Quando aplicamos uma rotação, a velocidade em cada ponto assume uma componente rotacional. >>> b1.aboost(1.0) >>> b1.vpoint(0, 0), b1.vpoint(1, 1) (Vec(2, 0), Vec(1, 1)) Note que quando o parâmetro ``relative=True``, as posições são calculadas relativas ao centro de massa do objeto >>> b1.vpoint(0, 0, relative=True), b1.vpoint(1, 1, relative=True) (Vec(1, 1), Vec(0, 2)) ''' if y is None: pos = asvector(pos_or_x) else: pos = Vec2(pos_or_x, y) if not relative: pos -= self.pos return self._vel + pos.perp() * self._omega
def momentumL(self, pos_or_x=None, y=None): '''Momentum angular em torno do ponto dado (usa o centro de massa como padrão) Examples -------- Considere uma partícula no ponto (0, 1) com velocidade (1, 0). É fácil calcular o momentum linear resultante >>> b1 = Body(pos=(0, 1), vel=(1, 0), mass=2) >>> b1.momentumL(0, 0) -2.0 É lógico que este valor muda de acordo com o ponto de referência >>> b1.momentumL(0, 2) 2.0 Quando chamamos a função sem argumentos, o padrão é utilizar o centro de massa. Numa partícula pontual sem velocidade angular, este valor é sempre zero >>> b1.momentumL(b1.pos) 0.0 ''' if pos_or_x is None: momentumL = 0.0 elif y is None: delta_pos = self.pos - asvector(pos_or_x) momentumL = delta_pos.cross(self.momentumP()) else: delta_pos = self.pos - Vec2(pos_or_x, y) momentumL = delta_pos.cross(self.momentumP()) if self._invinertia: return momentumL + self.omega * self.inertia else: return momentumL
def apply_accel(self, a, dt, method=None): '''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 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 aproximação, 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. ''' a = asvector(a) if method is None or method == 'euler-semi-implicit': self.boost(a * dt) self.move(self._vel * dt + a * (0.5 * dt * dt)) elif method == 'verlet': raise NotImplementedError elif method == 'euler': self.move(self._vel * dt + a * (0.5 * dt * dt)) self.boost(a * dt) else: raise ValueError('invalid method: %r' % method)
def __init__(self, shape=(50, 50), origin=(0, 0)): self.shape = asvector(shape) self.origin = asvector(origin) self.tiles = [] self.specs = {} self.charspecs = {}
def get_reference_point(self, ref): # TODO: imove to HasAABB or something more generic point = getattr(self, REFERENCE_NAMES.get(ref, ref)) return asvector(point)
def coords(self, value): self.pos_sw = asvector(value) * self.tilesize + self.tileorigin
def __set__(self, obj, value): if not isinstance(value, Vec2): value = asvector(value) setter(obj, value)
def apply_impulse(self, impulse_or_x, y=None, pos=None, relative=False): '''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. Examples -------- Considere um objeto criado na origem e outro na posição (4, 3) >>> b1 = Body(pos=(2, 0), mass=1) >>> b2 = Body(pos=(-1, 0), mass=2) Criamos um impulso J e aplicamos o mesmo em b1 >>> J = Vec2(0, 2) >>> b1.apply_impulse(J) Note que isto afeta a velocidade do corpo de acordo com a fórmula $\delta v = J / m$: >>> b1.vel Vec(0, 2) Se aplicarmos um impulso oposto à b2, o resultado não deve alterar o momento linear, que permanece nulo >>> b2.apply_impulse(-J, pos=(0, 0)); b2.vel Vec(0, -1) >>> b1.momentumP() + b2.momentumP() Vec(0, 0) O exemplo anterior é bastante simples pois os dois objetos não possuem momentum de inércia. As mesmas leis de conservação valem na presença de rotações ou mesmo de velocidades iniciais >>> b1 = Body(pos=(0, 0), mass=1, inertia=1, vel=(2, 0), omega=1) >>> b2 = Body(pos=(4, 3), mass=2, inertia=2, vel=(-1, 1)) Primeiro calculamos os momentos iniciais >>> P0 = b1.momentumP() + b2.momentumP() Note que o momentum linear exige um ponto de referência para a realização do cálculo. Escolhemos o centro de massa, mas os resultados devem valer para qualquer escolha arbitrária. >>> from FGAme.physics import center_of_mass >>> Rcm = center_of_mass(b1, b2) >>> L0 = b1.momentumL(Rcm) + b2.momentumL(Rcm) Aplicamos momentos opostos respeitando a terceira lei de Newton. Note que no problema com rotações devemos tomar cuidado em aplicar o impulso no mesmo ponto nos dois objetos. Caso isto não ocorra, haverá a produção de um torque externo, violando a lei de conservação do momento angular. >>> b1.apply_impulse(J, pos=(4, 0)) >>> b2.apply_impulse(-J, pos=(4, 0)) E verificamos que os valores finais são iguais aos iniciais >>> b1.momentumP() + b2.momentumP() == P0 True >>> b1.momentumL(Rcm) + b2.momentumL(Rcm) == L0 True ''' if y is None: impulse = asvector(impulse_or_x) else: impulse = Vec2(impulse_or_x, y) self.boost(impulse * self._invmass) if pos is not None and self._invinertia: R = asvector(pos) R = R if relative else R - self.pos self.aboost(R.cross(impulse) * self._invinertia)
def move_to_vec(self, vec): self._pos = asvector(vec) self.flags |= flags.dirty_aabb return self
def __init__(self, pos=null2D, vel=null2D, theta=0.0, omega=0.0, mass=None, density=None, inertia=None, gravity=None, damping=None, adamping=None, restitution=None, friction=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 = asvector(pos) self._vel = asvector(vel) self._e_vel = null2D self._e_omega = 0.0 self._theta = float(theta) self._omega = float(omega) self._accel = null2D 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) try: density = mass / self.area() except ZeroDivisionError: density = float('inf') 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 = null2D self._damping = self._adamping = 0.0 self._friction = 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 friction is not None: self.friction = friction # 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 if col_layer: if isinstance(col_layer, int): self._col_layer_mask = 1 << col_layer else: mask = 0 for n in col_layer: mask |= 1 << n self._col_layer_mask = mask else: self._col_layer_mask = 0 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 # Presença em mundo if world is not None: self._world.add(self)
def get_reference_point(self, ref): #TODO: move to HasAABB or something more generic point = getattr(self, REFERENCE_NAMES.get(ref, ref)) return asvector(point)
def heading(self, value): vel = self.vel speed = vel.norm() heading = asvector(value).normalize() self.vel = heading * speed