Beispiel #1
0
def res_rotate_inst(inst: Entity, res: Property) -> None:
    """Rotate the instance around an axis.

    If `axis` is specified, it should be a normal vector and the instance will
    be rotated `angle` degrees around it.
    Otherwise, `angle` is a pitch-yaw-roll angle which is applied.
    `around` can be a point (local, pre-rotation) which is used as the origin.
    """
    angles = Angle.from_str(inst['angles'])
    if 'axis' in res:
        orient = Matrix.axis_angle(
            Vec.from_str(inst.fixup.substitute(res['axis'])),
            conv_float(inst.fixup.substitute(res['angle'])),
        )
    else:
        orient = Matrix.from_angle(
            Angle.from_str(inst.fixup.substitute(res['angle'])))

    try:
        offset = Vec.from_str(inst.fixup.substitute(res['around']))
    except NoKeyError:
        pass
    else:
        origin = Vec.from_str(inst['origin'])
        inst['origin'] = origin + (-offset @ orient + offset) @ angles

    inst['angles'] = (orient @ angles).to_angle()
Beispiel #2
0
def test_old_rotation(py_c_vec) -> None:
    """Verify that the code matches the results from the earlier Vec.rotate code."""
    Vec, Angle, Matrix, parse_vec_str = py_c_vec

    for pitch in range(0, 360, 15):
        for yaw in range(0, 360, 15):
            for roll in range(0, 360, 15):
                ang = Angle(pitch, yaw, roll)
                mat = Matrix.from_angle(ang)

                # Construct a matrix directly from 3 vector rotations.
                old_mat = Matrix()
                old_mat[0, 0], old_mat[0, 1], old_mat[0, 2] = old_rotate(
                    Vec(x=1), pitch, yaw, roll)
                old_mat[1, 0], old_mat[1, 1], old_mat[1, 2] = old_rotate(
                    Vec(y=1), pitch, yaw, roll)
                old_mat[2, 0], old_mat[2, 1], old_mat[2, 2] = old_rotate(
                    Vec(z=1), pitch, yaw, roll)

                assert_rot(mat, old_mat, ang)
                old = old_rotate(Vec(128, 0, 0), pitch, yaw, roll)

                by_ang = Vec(128, 0, 0) @ ang
                by_mat = Vec(128, 0, 0) @ mat
                assert_vec(by_ang, old.x, old.y, old.z, ang, tol=1e-1)
                assert_vec(by_mat, old.x, old.y, old.z, ang, tol=1e-1)
Beispiel #3
0
def compute_orients(nodes: Iterable[Node]) -> None:
    """Compute the appropriate orientation for each node."""
    # This is based on the info at:
    # https://janakiev.com/blog/framing-parametric-curves/
    tangents: Dict[Node, Vec] = {}
    all_nodes: Set[Node] = set()
    for node in nodes:
        if node.prev is node.next is None:
            continue
        node_prev = node.prev if node.prev is not None else node
        node_next = node.next if node.next is not None else node
        tangents[node] = (node_next.pos - node_prev.pos).norm()
        all_nodes.add(node)

    while all_nodes:
        node1 = all_nodes.pop()
        node1 = node1.find_start()
        tanj1 = tangents[node1]
        # Start with an arbitrary roll for the first orientation.
        node1.orient = Matrix.from_angle(tanj1.to_angle())
        while node1.next is not None:
            node2 = node1.next
            all_nodes.discard(node2)
            tanj1 = tangents[node1]
            tanj2 = tangents[node2]
            b = Vec.cross(tanj1, tanj2)
            if b.mag_sq() < 0.001:
                node2.orient = node1.orient.copy()
            else:
                b = b.norm()
                phi = math.acos(Vec.dot(tanj1, tanj2))
                up = node1.orient.up() @ Matrix.axis_angle(
                    b, math.degrees(phi))
                node2.orient = Matrix.from_basis(x=tanj2, z=up)
            node1 = node2
Beispiel #4
0
 def test(axis, equiv_ang: Py_Angle):
     for ang in range(0, 360, 15):
         assert_rot(Matrix.axis_angle(axis, ang),
                    Matrix.from_angle(ang * equiv_ang),
                    f'{axis} * {ang} != {equiv_ang}')
         # Inverse axis = reversed rotation.
         assert_rot(Matrix.axis_angle(-axis, ang),
                    Matrix.from_angle(-ang * equiv_ang),
                    f'{-axis} * {ang} != {equiv_ang}')
Beispiel #5
0
def test_matrix_roundtrip_pitch(py_c_vec):
    """Check converting to and from a Matrix does not change values."""
    Vec, Angle, Matrix, parse_vec_str = py_c_vec

    # We can't directly check the resulted value, some of these produce
    # gimbal lock and can't be recovered.
    # So instead check the rotation matrix is the same.
    for pitch in range(0, 360, 45):
        old_ang = Angle(pitch, 0, 0)
        new_ang = Matrix.from_pitch(pitch).to_angle()
        assert_rot(
            Matrix.from_angle(old_ang),
            Matrix.from_angle(new_ang),
            (old_ang, new_ang),
        )
Beispiel #6
0
def res_add_placement_helper(inst: Entity, res: Property):
    """Add a placement helper to a specific tile.

    `Offset` and `normal` specify the position and direction out of the surface
    the helper should be added to. If `upDir` is specified, this is the
    direction of the top of the portal.
    """
    orient = Matrix.from_angle(Angle.from_str(inst['angles']))

    pos = conditions.resolve_offset(inst, res['offset', '0 0 0'], zoff=-64)
    normal = res.vec('normal', 0, 0, 1) @ orient

    up_dir: Optional[Vec]
    try:
        up_dir = Vec.from_str(res['upDir']) @ orient
    except LookupError:
        up_dir = None

    try:
        tile = tiling.TILES[(pos - 64 * normal).as_tuple(), normal.as_tuple()]
    except KeyError:
        LOGGER.warning('No tile at {} @ {}', pos, normal)
        return

    tile.add_portal_helper(up_dir)
Beispiel #7
0
def res_set_marker(inst: Entity, res: Property) -> None:
    """Set a marker at a specific position.

    Parameters:
        * `global`: If true, the position is an absolute position, ignoring this instance.
        * `name`: A name to store to identify this marker/item.
        * `pos`: The position or offset to use for the marker.
    """
    origin = Vec.from_str(inst['origin'])
    orient = Matrix.from_angle(Angle.from_str(inst['angles']))

    try:
        is_global = srctools.conv_bool(
            inst.fixup.substitute(res['global'], allow_invert=True))
    except LookupError:
        is_global = False

    name = inst.fixup.substitute(res['name']).casefold()
    pos = Vec.from_str(inst.fixup.substitute(res['pos']))
    if not is_global:
        pos = pos @ orient + origin

    mark = Marker(pos, name, inst)
    MARKERS.append(mark)
    LOGGER.debug('Marker added: {}', mark)
Beispiel #8
0
def make_corner(
    vmf: VMF,
    origin: Vec,
    start_dir: Vec,
    end_dir: Vec,
    size: int,
    config: Config,
) -> None:
    """Place a corner."""
    angles = Matrix.from_basis(z=start_dir, x=end_dir)
    conditions.add_inst(
        vmf,
        origin=origin,
        angles=angles,
        file=config.inst_corner[int(size)],
    )

    temp, visgroups = config.temp_corner[int(size)]
    if temp is not None:
        temp_solids = template_brush.import_template(
            vmf,
            temp,
            additional_visgroups=visgroups,
            origin=origin,
            angles=angles,
            force_type=template_brush.TEMP_TYPES.world,
            add_to_map=False,
        ).world
        motion_trigger(vmf, *temp_solids)
Beispiel #9
0
def build_collision(qc: QC, prop: PropPos, ref_mesh: Mesh) -> Optional[Mesh]:
    """Get the correct collision mesh for this model."""
    if prop.solidity is CollType.NONE:  # Non-solid
        return None
    elif prop.solidity is CollType.VPHYS or prop.solidity is CollType.BSP:
        if qc.phy_smd is None:
            return None
        try:
            return _coll_cache[qc.phy_smd]
        except KeyError:
            LOGGER.info('Parsing coll "{}"', qc.phy_smd)
            with open(qc.phy_smd, 'rb') as fb:
                coll = Mesh.parse_smd(fb)

            rot = Matrix.from_yaw(90)
            for tri in coll.triangles:
                for vert in tri:
                    vert.pos @= rot
                    vert.norm @= rot

            _coll_cache[qc.phy_smd] = coll
            return coll
    # Else, it's one of the three bounding box types.
    # We don't really care about which.
    bbox_min, bbox_max = Vec.bbox(vert.pos for tri in ref_mesh.triangles
                                  for vert in tri)
    return Mesh.build_bbox('static_prop', 'phy', bbox_min, bbox_max)
Beispiel #10
0
def make_corner(
    vmf: VMF,
    origin: Vec,
    start_dir: Vec,
    end_dir: Vec,
    size: int,
    config: Config,
) -> None:
    angles = Matrix.from_basis(z=start_dir, x=end_dir).to_angle()
    vmf.create_ent(
        classname='func_instance',
        origin=origin,
        angles=angles,
        file=config.inst_corner[size],
    )

    temp = config.temp_corner[size]
    if temp:
        temp_solids = template_brush.import_template(
            vmf,
            temp,
            origin=origin,
            angles=angles,
            force_type=template_brush.TEMP_TYPES.world,
            add_to_map=False,
        ).world
        motion_trigger(vmf, *temp_solids)
Beispiel #11
0
 def add_item_coll(self, item: Item, inst: Entity) -> None:
     """Add the default collisions from an item definition for this instance."""
     origin = Vec.from_str(inst['origin'])
     orient = Matrix.from_angle(Angle.from_str(inst['angles']))
     for coll in item.collisions:
         self.add(
             (coll @ orient + origin).with_attrs(name=inst['targetname']))
Beispiel #12
0
def save_connectionpoint(item: Item, vmf: VMF) -> None:
    """Write connectionpoints to a VMF."""
    for side, points in item.antline_points.items():
        yaw = side.yaw
        inv_orient = Matrix.from_yaw(-yaw)
        for point in points:
            ant_pos = Vec(point.pos.x, -point.pos.y, -64)
            sign_pos = Vec(point.sign_off.x, -point.sign_off.y, -64)

            offset = (ant_pos - sign_pos) @ inv_orient
            try:
                skin = CONN_OFFSET_TO_SKIN[offset.as_tuple()]
            except KeyError:
                LOGGER.warning(
                    'Pos=({}), Sign=({}) -> ({}) is not a valid offset for signs!',
                    point.pos, point.sign_off, offset)
                continue
            pos: Vec = round((ant_pos + sign_pos) / 2.0 * 16.0, 0)

            vmf.create_ent(
                'bee2_editor_connectionpoint',
                origin=Vec(pos.x - 56, pos.y + 56, -64),
                angles=f'0 {yaw} 0',
                skin=skin,
                priority=point.priority,
                group_id='' if point.group is None else point.group,
            )
Beispiel #13
0
 def __init__(self, inst: Entity, conf: Config, size: int) -> None:
     self.ent = inst
     self.conf = conf
     self.next = None
     self.no_prev = True
     self.size = size
     self.orient = Matrix.from_angle(Angle.from_str(inst['angles']))
Beispiel #14
0
class Node(Generic[ConfT]):
    """Represents a single node in the chain."""
    item: Item = attr.ib(init=True)
    conf: ConfT = attr.ib(init=True)

    # Origin and angles of the instance.
    pos = attr.ib(init=False,
                  default=attr.Factory(
                      lambda self: Vec.from_str(self.item.inst['origin']),
                      takes_self=True,
                  ))
    orient = attr.ib(init=False,
                     default=attr.Factory(
                         lambda self: Matrix.from_angle(
                             Angle.from_str(self.item.inst['angles'])),
                         takes_self=True,
                     ))

    # The links between nodes
    prev: Optional[Node[ConfT]] = attr.ib(default=None, init=False)
    next: Optional[Node[ConfT]] = attr.ib(default=None, init=False)

    @property
    def inst(self) -> Entity:
        """Return the relevant instance."""
        return self.item.inst

    @classmethod
    def from_inst(cls, inst: Entity, conf: ConfT) -> Node[ConfT]:
        """Find the item for this instance, and return the node."""
        name = inst['targetname']
        try:
            return Node(connections.ITEMS[name], conf)
        except KeyError:
            raise ValueError('No item for "{}"?'.format(name)) from None
Beispiel #15
0
    def __init__(self, ent: Entity) -> None:
        self.origin = Vec.from_str(ent['origin'])
        self.matrix = Matrix.from_angle(Angle.from_str(ent['angles']))
        self.ent = ent

        self.has_input = False  # We verify every node has an input if used.
        # DestType -> output.
        self.outputs: Dict[DestType, Optional[Node]] = dict.fromkeys(
            self.out_types, None)
        # Outputs fired when cubes reach this point.
        pass_outputs = [
            out for out in ent.outputs
            if out.output.casefold() == self.pass_out_name
        ]
        self.has_pass = bool(pass_outputs)
        if self.has_pass:
            for out in pass_outputs:
                out.output = 'On' + PASS_OUT
            if ent['classname'].startswith('comp_'):
                # Remove the extra keyvalues we use.
                ent.keys = {
                    'classname': 'info_target',
                    'targetname': ent['targetname'],
                    'origin': ent['origin'],
                    'angles': ent['angles'],
                }
            ent.make_unique('_vac_node')
        elif not self.keep_ent:
            ent.remove()
Beispiel #16
0
def test_matrix_roundtrip_yaw(py_c_vec):
    """Check converting to and from a Matrix does not change values."""
    Vec, Angle, Matrix, parse_vec_str = py_c_vec

    for yaw in range(0, 360, 45):
        mat = Matrix.from_yaw(yaw)
        assert_ang(mat.to_angle(), 0, yaw, 0)
Beispiel #17
0
def _fill_norm_rotations() -> Dict[Tuple[Tuple[float, float, float], Tuple[
    float, float, float]], Matrix, ]:
    """Given a norm->norm rotation, return the angles producing that."""
    rotations = {}
    for norm_ax in 'xyz':
        for norm_mag in [-1, +1]:
            norm = Vec.with_axes(norm_ax, norm_mag)
            for angle_ax in ('pitch', 'yaw', 'roll'):
                for angle_mag in (-90, 90):
                    angle = Matrix.from_angle(
                        Angle.with_axes(angle_ax, angle_mag))
                    new_norm = norm @ angle
                    if new_norm != norm:
                        rotations[norm.as_tuple(), new_norm.as_tuple()] = angle
            # Assign a null rotation as well.
            rotations[norm.as_tuple(), norm.as_tuple()] = Matrix()
            rotations[norm.as_tuple(), (-norm).as_tuple()] = Matrix()
    return rotations
Beispiel #18
0
def test_gen_check(py_c_vec) -> None:
    """Do an exhaustive check on all rotation math using data from the engine."""
    Vec, Angle, Matrix, parse_vec_str = py_c_vec

    X = Vec(x=1)
    Y = Vec(y=1)
    Z = Vec(z=1)
    with open('rotation.txt') as f:
        for line_num, line in enumerate(f, start=1):
            if not line.startswith('|'):
                # Skip other junk in the log.
                continue

            (pit, yaw, roll, for_x, for_y, for_z, left_x, left_y, left_z, up_x,
             up_y, up_z) = map(float, line[1:].split())

            mat = Matrix.from_angle(Angle(pit, yaw, roll))

            # Then check rotating vectors works correctly.
            # The engine actually gave us a right vector, so we need to flip that.
            assert_vec(X @ mat, for_x, for_y, for_z)
            assert_vec(Y @ mat, -left_x, -left_y, -left_z)
            assert_vec(Z @ mat, up_x, up_y, up_z)

            assert math.isclose(for_x, mat[0, 0], abs_tol=EPSILON)
            assert math.isclose(for_y, mat[0, 1], abs_tol=EPSILON)
            assert math.isclose(for_z, mat[0, 2], abs_tol=EPSILON)

            assert math.isclose(-left_x, mat[1, 0], abs_tol=EPSILON)
            assert math.isclose(-left_y, mat[1, 1], abs_tol=EPSILON)
            assert math.isclose(-left_z, mat[1, 2], abs_tol=EPSILON)

            assert math.isclose(up_x, mat[2, 0], abs_tol=EPSILON)
            assert math.isclose(up_y, mat[2, 1], abs_tol=EPSILON)
            assert math.isclose(up_z, mat[2, 2], abs_tol=EPSILON)

            # Also test Matrix.from_basis().
            x = Vec(for_x, for_y, for_z)
            y = -Vec(left_x, left_y, left_z)
            z = Vec(up_x, up_y, up_z)
            assert_rot(Matrix.from_basis(x=x, y=y, z=z), mat)
            assert_rot(Matrix.from_basis(x=x, y=y), mat)
            assert_rot(Matrix.from_basis(y=y, z=z), mat)
            assert_rot(Matrix.from_basis(x=x, z=z), mat)
Beispiel #19
0
def test_vec_identities(py_c_vec) -> None:
    """Check that vectors in the same axis as the rotation don't get spun."""
    Vec, Angle, Matrix, parse_vec_str = py_c_vec

    for ang in range(0, 360, 13):
        # Check the two constructors match.
        assert_rot(Matrix.from_pitch(ang), Matrix.from_angle(Angle(pitch=ang)))
        assert_rot(Matrix.from_yaw(ang), Matrix.from_angle(Angle(yaw=ang)))
        assert_rot(Matrix.from_roll(ang), Matrix.from_angle(Angle(roll=ang)))

        # Various magnitudes to test
        for mag in (-250, -1, 0, 1, 250):
            assert_vec(Vec(y=mag) @ Matrix.from_pitch(ang), 0, mag, 0)
            assert_vec(Vec(z=mag) @ Matrix.from_yaw(ang), 0, 0, mag)
            assert_vec(Vec(x=mag) @ Matrix.from_roll(ang), mag, 0, 0)
Beispiel #20
0
def test_matrix_roundtrip_roll(py_c_vec):
    """Check converting to and from a Matrix does not change values."""
    Vec, Angle, Matrix, parse_vec_str = py_c_vec

    for roll in range(0, 360, 45):
        if roll in (90, -90):
            # Don't test gimbal lock.
            continue
        mat = Matrix.from_roll(roll)
        assert_ang(mat.to_angle(), 0, 0, roll)
Beispiel #21
0
def test_bad_from_basis(py_c_vec) -> None:
    """Test invalid arguments to Matrix.from_basis()"""
    Vec, Angle, Matrix, parse_vec_str = py_c_vec
    v = Vec(0, 1, 0)
    with pytest.raises(TypeError):
        Matrix.from_basis()
    with pytest.raises(TypeError):
        Matrix.from_basis(x=v)
    with pytest.raises(TypeError):
        Matrix.from_basis(y=v)
    with pytest.raises(TypeError):
        Matrix.from_basis(z=v)
Beispiel #22
0
 def __init__(self, pos: Vec, config: Config, radius: float) -> None:
     self.config = config
     self.prev: Optional[Node] = None
     self.next: Optional[Node] = None
     self.pos = pos
     self.radius = radius
     # Orientation of the segment up to the next.
     self.orient = Matrix()
     # The points for the cylinder, on these sides.
     self.points_prev: List[Vertex] = []
     self.points_next: List[Vertex] = []
Beispiel #23
0
def res_alt_orientation(res: Property) -> Callable[[Entity], None]:
    """Apply an alternate orientation.

    "wall" makes the attaching surface in the -X direction, making obs rooms,
    corridors etc easier to build. The Z axis points in the former +X direction.
    "ceiling" flips the instance, making items such as droppers easier to build.
    The X axis remains unchanged.
    """
    val = res.value.casefold()
    if val == 'wall':
        pose = Matrix.from_angle(-90, 180, 0)
    elif val in ('ceil', 'ceiling'):
        pose = Matrix.from_roll(180)
    else:
        raise ValueError(f'Unknown orientation type "{res.value}"!')

    def swap_orient(inst: Entity) -> None:
        """Apply the new orientation."""
        inst['angles'] = pose @ Angle.from_str(inst['angles'])
    return swap_orient
Beispiel #24
0
def res_sendificator(vmf: VMF, inst: Entity):
    """Implement Sendificators."""
    # For our version, we know which sendtor connects to what laser,
    # so we can couple the logic together (avoiding @sendtor_mutex).

    sendtor_name = inst['targetname']
    sendtor = connections.ITEMS[sendtor_name]

    sendtor.enable_cmd += (Output(
        '',
        f'@{sendtor_name}_las_relay_*',
        'Trigger',
        delay=0.01,
    ), )

    for ind, conn in enumerate(list(sendtor.outputs), start=1):
        las_item = conn.to_item
        conn.remove()
        try:
            targ_offset, targ_normal = SENDTOR_TARGETS[las_item.name]
        except KeyError:
            LOGGER.warning('"{}" is not a Sendificator target!', las_item.name)
            continue

        orient = Matrix.from_angle(Angle.from_str(las_item.inst['angles']))

        targ_offset = Vec.from_str(
            las_item.inst['origin']) + targ_offset @ orient
        targ_normal = targ_normal @ orient

        relay_name = f'@{sendtor_name}_las_relay_{ind}'

        relay = vmf.create_ent(
            'logic_relay',
            targetname=relay_name,
            origin=targ_offset,
            angles=targ_normal.to_angle(),
        )
        relay.add_out(
            Output('OnTrigger', '!self', 'RunScriptCode',
                   '::sendtor_source <- self;'),
            Output('OnTrigger', '@sendtor_fire', 'Trigger'),
        )
        if not las_item.inputs:
            # No other inputs, make it on always. PeTI automatically turns
            # it off when inputs are connected, which is annoying.
            las_item.inst.fixup['$start_enabled'] = '1'
            is_on = True
        else:
            is_on = las_item.inst.fixup.bool('$start_enabled')

        relay['StartDisabled'] = not is_on
        las_item.enable_cmd += (Output('', relay_name, 'Enable'), )
        las_item.disable_cmd += (Output('', relay_name, 'Disable'), )
Beispiel #25
0
def test_ang_matrix_roundtrip(py_c_vec):
    """Check converting to and from a Matrix does not change values."""
    Vec, Angle, Matrix, parse_vec_str = py_c_vec

    for p, y, r in iter_vec(range(0, 360, 90)):
        vert = Vec(x=1).rotate(p, y, r).z
        if vert < 0.99 or vert > 0.99:
            # If nearly vertical, gimbal lock prevents roundtrips.
            continue
        mat = Matrix.from_angle(Angle(p, y, r))
        assert_ang(mat.to_angle(), p, y, r)
Beispiel #26
0
def res_antigel(inst: Entity) -> None:
    """Implement the Antigel marker."""
    inst.remove()
    origin = Vec.from_str(inst['origin'])
    orient = Matrix.from_angle(Angle.from_str(inst['angles']))

    pos = round(origin - 128 * orient.up(), 6)
    norm = round(orient.up(), 6)
    try:
        tiling.TILES[pos.as_tuple(), norm.as_tuple()].is_antigel = True
    except KeyError:
        LOGGER.warning('No tile to set antigel at {}, {}', pos, norm)
    texturing.ANTIGEL_LOCS.add((origin // 128).as_tuple())
Beispiel #27
0
def test_vec_basic_roll(py_c_vec):
    """Check each direction rotates appropriately in roll."""
    Vec, Angle, Matrix, parse_vec_str = py_c_vec

    assert_vec(Vec(0, 200, 0) @ Matrix.from_roll(0), 0, 200, 0)
    assert_vec(Vec(0, 0, 150) @ Matrix.from_roll(0), 0, 0, 150)

    assert_vec(Vec(0, 200, 0) @ Matrix.from_roll(90), 0, 0, 200)
    assert_vec(Vec(0, 0, 150) @ Matrix.from_roll(90), 0, -150, 0)

    assert_vec(Vec(0, 200, 0) @ Matrix.from_roll(180), 0, -200, 0)
    assert_vec(Vec(0, 0, 150) @ Matrix.from_roll(180), 0, 0, -150)

    assert_vec(Vec(0, 200, 0) @ Matrix.from_roll(270), 0, 0, -200)
    assert_vec(Vec(0, 0, 150) @ Matrix.from_roll(270), 0, 150, 0)
Beispiel #28
0
def test_vec_basic_pitch(py_c_vec) -> None:
    """Check each direction rotates appropriately in pitch."""
    Vec, Angle, Matrix, parse_vec_str = py_c_vec

    assert_vec(Vec(200, 0, 0) @ Matrix.from_pitch(0), 200, 0, 0)
    assert_vec(Vec(0, 0, 150) @ Matrix.from_pitch(0), 0, 0, 150)

    assert_vec(Vec(200, 0, 0) @ Matrix.from_pitch(90), 0, 0, -200)
    assert_vec(Vec(0, 0, 150) @ Matrix.from_pitch(90), 150, 0, 0)

    assert_vec(Vec(200, 0, 0) @ Matrix.from_pitch(180), -200, 0, 0)
    assert_vec(Vec(0, 0, 150) @ Matrix.from_pitch(180), 0, 0, -150)

    assert_vec(Vec(200, 0, 0) @ Matrix.from_pitch(270), 0, 0, 200)
    assert_vec(Vec(0, 0, 150) @ Matrix.from_pitch(270), -150, 0, 0)
Beispiel #29
0
def fix_single_straight(
    seg: Segment,
    over_name: str,
    join_points: dict[tuple[str, float, float, float], Segment],
    overlay_joins: dict[Segment, set[Segment]],
) -> None:
    """Figure out the correct rotation for 1-long straight antlines."""
    # Check the U and V axis, to see if there's another antline on both
    # sides. If there is that's the correct orientation.
    orient = Matrix.from_angle(seg.normal.to_angle())

    center = seg.start.copy()

    for off in [
        orient.left(-8.0),
        orient.left(+8.0),
        orient.up(-8.0),
        orient.up(+8.0),
    ]:
        try:
            neigh = join_points[(over_name, ) + (center + off).as_tuple()]
        except KeyError:
            continue

        overlay_joins[seg].add(neigh)
        overlay_joins[neigh].add(seg)

        off_min = center - abs(off)
        off_max = center + abs(off)

        # If corners are on both opposite sides, we can be fairly confident
        # that's the correct orientation. If we don't have that (end of trail),
        # settle for one side.
        if seg.start == seg.end:
            # No points found. This is our best guess.
            seg.start = off_min
            seg.end = off_max
        elif seg.start != off_min or seg.end != off_max:
            # The other side is also present. Only override if we are on both
            # sides.
            if (over_name, ) + (center - off).as_tuple() in join_points:
                seg.start = off_min
                seg.end = off_max
        # Else: Both equal, we're fine.
    if seg.start == seg.end:
        raise ValueError(
            'Cannot determine orientation '
            'for 1-wide straight '
            'antline at ({})!'.format(seg.start)
        )
Beispiel #30
0
def test_bbox_rotation(
    pitch: float, yaw: float, roll: float,
) -> None:
    """Test the rotation logic against the slow direct approach."""
    ang = Angle(pitch, yaw, roll)
    bb_start = BBox(100, 200, 300, 300, 450, 600, contents=CollideType.ANTLINES, tags='blah')
    # Directly compute, by rotating all the angles,
    points = [
        Vec(x, y, z)
        for x in [100, 300]
        for y in [200, 450]
        for z in [300, 600]
    ]
    result_ang = bb_start @ ang
    result_mat = bb_start @ Matrix.from_angle(ang)
    assert result_ang == result_mat

    bb_min, bb_max = Vec.bbox(
        point @ ang for point in points
    )
    assert_bbox(result_mat, round(bb_min, 0), round(bb_max, 0), CollideType.ANTLINES, {'blah'})