Ejemplo n.º 1
0
def strafe_time(x, speedxi, K):
    """Compute the time it takes to strafe for the given distance and initial
    speed.

    Always returns positive times.

    >>> vix = 400
    >>> x = 1000
    >>> K = strafe_K(30, 0.01, 320, 10)
    >>> tx = strafe_time(x, vix, K)
    >>> math.isclose(x, strafe_distance(tx, vix, K))
    True
    """
    if K < 0:
        raise ValueError('K must be > 0')
    speedxi = math.fabs(speedxi)
    x = math.fabs(x)
    if common.float_zero(x):
        return 0.0
    if common.float_zero(K):
        try:
            return x / speedxi
        except ZeroDivisionError:
            return math.inf
    sq = speedxi * speedxi
    ret = ((sq * speedxi + 1.5 * K * x)**(2 / 3) - sq) / K
    # ret < 0 can occur from the subtraction with small x and big speedxi
    return max(ret, 0.0)
Ejemplo n.º 2
0
def gravity_speediz_distance_time(t, z, g):
    """Compute the initial speed needed to travel to the given ``z`` position.

    z can be negative.
    """
    if common.float_zero(t) and common.float_zero(z):
        raise ValueError('indeterminate')
    try:
        return (0.5 * g * t * t + z) / t
    except ZeroDivisionError:
        return math.copysign(math.inf, z)
Ejemplo n.º 3
0
def strafe_solve_speedxi(speedzi, K, x, z, g):
    """Compute the initial horizontal speed needed to reach the final position.

    z can be negative.

    Automatically handles both cases where the final velocity is positive or
    negative.

    If the time it takes to reach the vertical position is longer or equal to
    the *maximum* time it takes the reach the horizontal position (calculated
    by setting the initial horizontal speed to zero), the function will simply
    return 0.0, indicating that strafing from zero speed is enough to reach
    the final horizontal position *sooner than required*. In such a case, the
    user needs to manually "slow down" the strafing, by taking a longer path
    in 3D space, stop strafing altogether at some point, or "backpedalling" in
    air, so that both the horizontal and vertical positions can hit the final
    position exactly at the same time. In other words, the user must "wait"
    for the vertical position to move up to the final position before the
    horizontal position should hit it.

    The result is computed using the ``brentq`` function provided by scipy.
    """
    if K < 0:
        raise ValueError('K must be > 0')

    sqrt_tmp = math.sqrt(speedzi * speedzi - 2 * g * z)
    tz = speedzi - sqrt_tmp
    if tz < 0:
        tz = speedzi + sqrt_tmp
    if tz < 0:
        return math.nan
    tz /= g

    x = math.fabs(x)
    txmax = (1.5 * x)**(2 / 3) * K**(-1 / 3)
    if common.float_zero(txmax):
        return 0.0

    if common.float_equal(txmax, tz) or txmax < tz:
        return 0.0
    elif common.float_zero(tz):
        return math.inf

    # The upper bound of x / tz is the minimum _constant_ speed needed
    tmp = 1.5 * K * x
    return opt.brentq(lambda v: ((v**3 + tmp)**(2 / 3) - v**2) / K - tz, 0,
                      x / tz)
Ejemplo n.º 4
0
def climb_velocity(n, f, s, F, S):
    """Compute the climbing velocity given unit acceleration vectors.

    Parameters *n*, *f*, and *s* are 3D vectors. *n* is the unit normal of the
    climbing surface. There is no restriction on the directionality of *n*. *f*
    is the unit forward acceleration of the player, while *s* is the unit side
    acceleration of the player.

    *F* and *S* can be either positive, negative, or zero. Only the sign is
    considered. Positive *F* means ``+forward`` is held down, while negative *F*
    means ``+back`` is held down. Positive *S* means ``+moveright`` is held
    down, while negative *S* means ``+moveleft`` is held down.

    Return the player climbing velocity in 3D.
    """
    if not common.float_equal(common.vec_dot(n, n, 3), 1):
        raise ValueError('n must be a unit vector')
    if not common.float_equal(common.vec_dot(f, f, 3), 1):
        raise ValueError('f must be a unit vector')
    if not common.float_equal(common.vec_dot(s, s, 3), 1):
        raise ValueError('s must be a unit vector')

    f, s = f[:], s[:]
    common.vec_mul(f, 0 if common.float_zero(F) else math.copysign(200, F))
    common.vec_mul(s, 0 if common.float_zero(S) else math.copysign(200, S))

    u = [0.0, 0.0, 0.0]
    common.vec_add(u, f)
    common.vec_add(u, s)
    cross = common.vec_cross([0, 0, 1], n)
    cross_norm = common.vec_dot(cross, cross)
    if common.float_zero(cross_norm):
        fac = [0.0, 0.0, 0.0]
    else:
        fac = common.vec_cross(n, cross)
        common.vec_mul(fac, 1 / cross_norm)
    common.vec_add(fac, n)
    common.vec_mul(fac, common.vec_dot(u, n))
    common.vec_sub(u, fac)
    return u
Ejemplo n.º 5
0
def hpap_damage(hp, ap, dmg):
    """Compute the new HP and AP given damage.

    The damage can be negative.

    This function works for all damage types except ``DMG_FALL`` and
    ``DMG_DROWN``, both of which bypass the armour. The health loss due to these
    types of damage is simply the truncated damage value.

    >>> hpap_damage(100, 100, 100)
    (80, 60.0)
    >>> hpap_damage(100, 0, 50.5)
    (50, 0.0)

    Negative damages are accepted, such as the damage from the infinite health
    doors in the Half-Life main campaign:

    >>> hpap_damage(100, 0, -1)
    (101, 0.0)
    """
    new_ap = 0.0 if common.float_zero(ap) else max(0.0, ap - 0.4 * dmg)
    hp -= int(dmg - 2 * ap if common.float_zero(new_ap) else 0.2 * dmg)
    return hp, new_ap
Ejemplo n.º 6
0
def angles_to_vectors(pitch, yaw, dim):
    """Convert pitch and yaw to view vectors.

    Angles must be in radians, and the roll is assumed to be zero. *dim*
    specifies the dimensions of pitch and yaw, and can be either ``2`` or ``3``.
    In the 2D case, this function is equivalent to computing the 3D vectors,
    discarding the *z* component, and then normalising only the *x* and *y*
    components.

    Returns a 2-tuple (*fv*, *sv*), where *fv* is the unit forward view vector
    and *sv* is the unit side view vector.

    >>> fv, sv = angles_to_vectors(0, math.pi / 4, 2)
    >>> f'{fv[0]:.6g} {fv[1]:.6g}'
    '0.707107 0.707107'
    >>> f'{sv[0]:.6g} {sv[1]:.6g}'
    '0.707107 -0.707107'

    Note that the phenomenon of gimbal lock can occur in the two dimensional
    output when the pitch is 90 degrees up or down, thus losing directionality
    in the horizontal plane. A warning will be issued when this happens. This is
    because when the pitch is 90 degrees up or down, the *x* and *y* components
    of *fv* will both be zero, thus normalising them would be indeterminate. In
    the implementation of this function, the *fv* and *sv* vectors would still
    be computed as though the pitch is not vertical, but they may not behave
    correctly in game or other algorithms based on normalising the horizontal
    components of the 3D vectors.
    """
    syaw, cyaw = math.sin(yaw), math.cos(yaw)
    spitch, cpitch = math.sin(pitch), math.cos(pitch)
    if dim == 2:
        sv = [syaw, -cyaw]
        fv = [cyaw, syaw]
        if common.float_zero(cpitch):
            warnings.warn('gimbal lock due to pitch of +/- pi/2',
                          RuntimeWarning)
    elif dim == 3:
        sv = [syaw, -cyaw, 0.0]
        fv = [cyaw * cpitch, syaw * cpitch, -spitch]
    else:
        raise ValueError('dim must be either 2 or 3')
    return fv, sv
Ejemplo n.º 7
0
def strafe_fme_theta(v, theta, L, gamma1):
    r"""Perform a general strafe parameterised with theta.

    The convention is such that positive *theta* means strafing towards the
    left, and vice versa. *L* is typically :math:`\min(30, M)` for airstrafing and *M*
    for groundstrafing. *gamma1* is usually :math:`k_e \tau M A`.

    """
    speed = common.vec_length(v, 2)
    if common.float_zero(speed):
        raise ValueError('speed cannot be 0')
    vhat = v[:]
    common.vec_mul(vhat, 1 / speed, 2)
    ct = math.cos(theta)
    gamma2 = L - speed * ct
    if gamma2 <= 0.0:
        return
    st = math.sin(theta)
    mu = min(gamma1, gamma2)
    a = [vhat[0] * ct - vhat[1] * st, vhat[0] * st + vhat[1] * ct]
    common.vec_mul(a, mu, 2)
    common.vec_add(v, a, 2)
Ejemplo n.º 8
0
def gravity_time_speediz_z(speedzi, z, g):
    """Compute the time it takes to reach a height given initial vertical
    velocity.

    z can be negative.

    >>> viz = 200
    >>> z = 10
    >>> g = 800
    >>> t1, t2 = gravity_time_speediz_z(viz, z, g)
    >>> '{:.10g} {:.10g}'.format(t1, t2)
    '0.05635083269 0.4436491673'
    >>> math.isclose(z, viz * t1 - 0.5 * g * t1 * t1)
    True
    >>> math.isclose(z, viz * t2 - 0.5 * g * t2 * t2)
    True
    """
    if common.float_zero(g):
        t = z / speedzi
        return t, t
    sqrt_tmp = math.sqrt(speedzi * speedzi - 2 * g * z)
    t1 = (speedzi - sqrt_tmp) / g
    t2 = (speedzi + sqrt_tmp) / g
    return t1, t2
Ejemplo n.º 9
0
def test_float_zero():
    assert common.float_zero(1e-10)
    assert not common.float_zero(1e-4)