예제 #1
0
    def __init__(
        self,
        position: Vector3 = None,
        bone_weights: VertexBoneWeights = None,
        bone_indices: VertexBoneIndices = None,
        normal: Vector3 = None,
        normal_w: int = None,
        uvs: list[Vector3] = None,
        tangents: list[Vector4] = None,
        bitangent: Vector4 = None,
        colors: list[ColorRGBA] = None,
    ):
        self.position = Vector3.zero() if position is None else position
        self.bone_weights = VertexBoneWeights(
        ) if bone_weights is None else bone_weights
        self.bone_indices = VertexBoneIndices(
        ) if bone_indices is None else bone_indices
        self.normal = Vector3.zero() if normal is None else normal
        self.normal_w = 0 if normal_w is None else normal_w
        self.uvs = [] if uvs is None else uvs
        self.tangents = [] if tangents is None else tangents
        self.bitangent = Vector4.zero() if bitangent is None else bitangent
        self.colors = [] if colors is None else colors

        self.raw = b""

        self.uv_queue = []  # type: list[Vector3]
        self.tangent_queue = []  # type: list[Vector4]
        self.color_queue = []  # type: list[ColorRGBA]
예제 #2
0
 def __init__(self, source=None, **kwargs):
     """Most of these floats have been mapped out."""
     self._wind_vector_min = Vector3.zero()
     self._wind_vector_max = Vector3.zero()
     self._wind_swing_cycles = [0.0, 0.0, 0.0, 0.0]
     self._wind_swing_powers = [0.0, 0.0, 0.0, 0.0]
     super().__init__(source=source, **kwargs)
예제 #3
0
class MSBEntryEntityCoordinates(MSBEntryEntity, abc.ABC):
    """Subclass of MSBEntryEntity with `translate` and `rotate` fields, and `rotate_in_world` method.

    Inherited by both `MSBPart` and `MSBRegion`).
    """

    FIELD_INFO = MSBEntryEntity.FIELD_INFO | {
        "translate": MapFieldInfo(
            "Translate",
            Vector3,
            Vector3.zero(),
            "3D coordinates of the part's position. Note that the anchor of the part is usually at its base.",
        ),
        "rotate": MapFieldInfo(
            "Rotate",
            Vector3,
            Vector3.zero(),
            "Euler angles for part rotation around its local X, Y, and Z axes.",
        ),
    }

    translate: Vector3
    rotate: Vector3

    def __init__(self, source=None, **kwargs):
        self._translate = Vector3.zero()
        self._rotate = Vector3.zero()
        super().__init__(source=source, **kwargs)

    def apply_rotation(
        self, rotation: tp.Union[Matrix3, Vector3, list, tuple, int, float], pivot_point=(0, 0, 0), radians=False,
    ):
        """Modify entity `translate` and `rotate` by rotating entity around some `pivot_point` in world coordinates.

        Default `pivot_point` is the world origin (0, 0, 0). Default rotation units are degrees.
        """
        rotation = resolve_rotation(rotation, radians)
        pivot_point = Vector3(pivot_point)
        self._rotate = (rotation @ Matrix3.from_euler_angles(self.rotate)).to_euler_angles()
        self._translate = (rotation @ (self.translate - pivot_point)) + pivot_point

    @property
    def translate(self):
        return self._translate

    @translate.setter
    def translate(self, value):
        self._translate = Vector3(value)

    @property
    def rotate(self):
        return self._rotate

    @rotate.setter
    def rotate(self, value):
        if isinstance(value, (int, float)):
            self._rotate = Vector3(0, value, 0)
        else:
            self._rotate = Vector3(value)
예제 #4
0
    def __init__(self):
        """Subclass of MSBEntryEntity with `translate` and `rotate` fields, and `rotate_in_world` method.

        Inherited by both `MSBPart` and `MSBRegion`).
        """
        super().__init__()
        self._translate = Vector3.zero()
        self._rotate = Vector3.zero()
예제 #5
0
 def __init__(self):
     self.position = Vector3.zero()
     self.bone_weights = VertexBoneWeights()
     self.bone_indices = VertexBoneIndices()
     self.normal = Vector3.zero()
     self.normal_w = 0
     self.uvs = []  # type: list[Vector3]
     self.tangents = []  # type: list[Vector4]
     self.bitangent = Vector4.zero()
     self.colors = []  # type: list[Color]
예제 #6
0
    def __init__(self):
        self.position = Vector3.zero()
        self.bone_weights = VertexBoneWeights()
        self.bone_indices = VertexBoneIndices()
        self.normal = Vector3.zero()
        self.normal_w = 0
        self.uvs = []  # type: tp.List[Vector3]
        self.tangents = []  # type: tp.List[Vector4]
        self.bitangent = Vector4.zero()
        self.colors = []  # type: tp.List[ColorRGBA]

        self.raw = b""

        self.uv_queue = []  # type: tp.List[Vector3]
        self.tangent_queue = []  # type: tp.List[Vector4]
        self.color_queue = []  # type: tp.List[ColorRGBA]
예제 #7
0
파일: bone.py 프로젝트: Grimrukh/soulstruct
 def get_absolute_translate(self, bones: list[Bone]) -> Vector3:
     """Accumulates parents' translates and rotates."""
     absolute_translate = Vector3.zero()
     rotate = Matrix3.identity()
     indent = ""
     for bone in self.get_all_parents(bones):
         absolute_translate += rotate @ bone.translate
         rotate @= Matrix3.from_euler_angles(bone.rotate, radians=True)
         indent += "    "
     return absolute_translate
예제 #8
0
class MSBMapOffsetEvent(MSBEvent):
    ENTRY_SUBTYPE = MSBEventSubtype.MapOffset
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("translate", "3f"),
        ("rotate_y", "f"),
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "translate":
        MapFieldInfo(
            "Translate",
            Vector3,
            Vector3.zero(),
            "Vector of (x, y, z) coordinates of map offset.",
        ),
        "rotate_y":
        MapFieldInfo(
            "Y Rotation",
            float,
            0.0,
            "Euler angle of rotation around the Y (vertical) axis.",
        ),
    }

    FIELD_ORDER = (
        "translate",
        "rotate_y",
    )

    translate: Vector3
    rotate_y: float

    def __init__(self, source=None, **kwargs):
        self._translate = Vector3.zero()
        super().__init__(source=source, **kwargs)

    @property
    def translate(self):
        return self._translate

    @translate.setter
    def translate(self, value):
        self._translate = Vector3(value)
예제 #9
0
 def __init__(self, source=None, **kwargs):
     self._translate = Vector3.zero()
     self._rotate = Vector3.zero()
     super().__init__(source=source, **kwargs)
예제 #10
0
class MSBRegion(MSBEntryEntityCoordinates, abc.ABC):

    ENTRY_SUBTYPE: MSBRegionSubtype = None
    REGION_STRUCT: BinaryStruct = None
    REGION_TYPE_DATA_STRUCT: BinaryStruct = None
    NAME_ENCODING = ""
    UNKNOWN_DATA_SIZE = -1

    FIELD_INFO = MSBEntryEntityCoordinates.FIELD_INFO | {
        "translate":
        MapFieldInfo(
            "Translate",
            Vector3,
            Vector3.zero(),
            "3D coordinates of the region's position. Note that this is the middle of the bottom face for box "
            "regions.",
        ),
        "rotate":
        MapFieldInfo(
            "Rotate",
            Vector3,
            Vector3.zero(),
            "Euler angles for region rotation around its local X, Y, and Z axes.",
        ),
    }

    translate: Vector3
    rotate: Vector3

    def __init__(self, source=None, **kwargs):
        self._region_index = None  # Final automatic assignment done on `MSB.pack()`.
        super().__init__(source=source, **kwargs)

    def unpack(self, msb_buffer):
        region_offset = msb_buffer.tell()
        base_data = self.REGION_STRUCT.unpack(msb_buffer)
        self.name = read_chars_from_buffer(
            msb_buffer,
            offset=region_offset + base_data["name_offset"],
            encoding=self.NAME_ENCODING,
        )
        self._region_index = base_data["__region_index"]
        self.translate = Vector3(base_data["translate"])
        self.rotate = Vector3(base_data["rotate"])
        self.check_null_field(msb_buffer,
                              region_offset + base_data["unknown_offset_1"])
        self.check_null_field(msb_buffer,
                              region_offset + base_data["unknown_offset_2"])

        if base_data["type_data_offset"] != 0:
            msb_buffer.seek(region_offset + base_data["type_data_offset"])
            self.unpack_type_data(msb_buffer)

        msb_buffer.seek(region_offset + base_data["entity_id_offset"])
        self.entity_id = struct.unpack("i", msb_buffer.read(4))[0]

        return region_offset + base_data["entity_id_offset"]

    def pack(self, region_index=0):
        name_offset = self.REGION_STRUCT.size
        packed_name = pad_chars(self.get_name_to_pack(),
                                encoding=self.NAME_ENCODING,
                                pad_to_multiple_of=4)
        unknown_offset_1 = name_offset + len(packed_name)
        unknown_offset_2 = unknown_offset_1 + 4
        packed_type_data = self.pack_type_data()
        if packed_type_data:
            type_data_offset = unknown_offset_2 + 4
            entity_id_offset = type_data_offset + len(packed_type_data)
        else:
            type_data_offset = 0
            entity_id_offset = unknown_offset_2 + 4
        packed_base_data = self.REGION_STRUCT.pack(
            name_offset=name_offset,
            __region_index=region_index,
            region_type=self.ENTRY_SUBTYPE,
            translate=list(self.translate),
            rotate=list(self.rotate),
            unknown_offset_1=unknown_offset_1,
            unknown_offset_2=unknown_offset_2,
            type_data_offset=type_data_offset,
            entity_id_offset=entity_id_offset,
        )
        packed_entity_id = struct.pack("i", self.entity_id)
        return packed_base_data + packed_name + b"\0\0\0\0" * 2 + packed_type_data + packed_entity_id

    def unpack_type_data(self, msb_buffer):
        self.set(**self.REGION_TYPE_DATA_STRUCT.unpack(msb_buffer))

    def pack_type_data(self):
        return self.REGION_TYPE_DATA_STRUCT.pack(self)

    def set_indices(self, region_index):
        self._region_index = region_index

    @classmethod
    def check_null_field(cls, msb_buffer, offset_to_null):
        msb_buffer.seek(offset_to_null)
        zero = msb_buffer.read(cls.UNKNOWN_DATA_SIZE)
        if zero != b"\0" * cls.UNKNOWN_DATA_SIZE:
            _LOGGER.warning(
                f"Null data entry in `{cls.__name__}` was not zero: {zero}.")
예제 #11
0
class MSBWindEvent(MSBEvent):
    ENTRY_SUBTYPE = MSBEventSubtype.Wind
    EVENT_TYPE_DATA_STRUCT = BinaryStruct(
        ("wind_vector_min", "3f"),
        ("unk_x04_x08", "f"),
        ("wind_vector_max", "3f"),
        ("unk_x0c_x10", "f"),
        ("wind_swing_cycles", "4f"),
        ("wind_swing_powers", "4f"),
    )

    FIELD_INFO = MSBEvent.FIELD_INFO | {
        "wind_vector_min":
        MapFieldInfo(
            "Wind Vector Min",
            Vector3,
            Vector3.zero(),
            "Wind vector minimum.",
        ),
        "unk_x04_x08":
        MapFieldInfo(
            "Unknown [04-08]",
            float,
            0.0,
            "Unknown Wind parameter (floating-point number).",
        ),
        "wind_vector_max":
        MapFieldInfo(
            "Wind Vector Max",
            Vector3,
            Vector3.zero(),
            "Wind vector maximum.",
        ),
        "unk_x0c_x10":
        MapFieldInfo(
            "Unknown [0c-10]",
            float,
            0.0,
            "Unknown Wind parameter (floating-point number).",
        ),
        "wind_swing_cycles":
        MapFieldInfo(
            "Wind Swing Cycles",
            list,
            [0.0, 0.0, 0.0, 0.0],
            "Wind swing cycles (four values).",
        ),
        "wind_swing_powers":
        MapFieldInfo(
            "Wind Swing Powers",
            list,
            [0.0, 0.0, 0.0, 0.0],
            "Wind swing powers (four values).",
        ),
    }

    FIELD_ORDER = (
        "wind_vector_min",
        "unk_x04_x08",
        "wind_vector_max",
        "unk_x0c_x10",
        "wind_swing_cycles",
        "wind_swing_powers",
    )

    unk_x04_x08: float
    unk_x0c_x10: float

    def __init__(self, source=None, **kwargs):
        """Most of these floats have been mapped out."""
        self._wind_vector_min = Vector3.zero()
        self._wind_vector_max = Vector3.zero()
        self._wind_swing_cycles = [0.0, 0.0, 0.0, 0.0]
        self._wind_swing_powers = [0.0, 0.0, 0.0, 0.0]
        super().__init__(source=source, **kwargs)

    @property
    def wind_vector_min(self):
        return self._wind_vector_min

    @wind_vector_min.setter
    def wind_vector_min(self, value):
        self._wind_vector_min = Vector3(value)

    @property
    def wind_vector_max(self):
        return self._wind_vector_max

    @wind_vector_max.setter
    def wind_vector_max(self, value):
        self._wind_vector_max = Vector3(value)

    @property
    def wind_swing_cycles(self):
        return self._wind_swing_cycles

    @wind_swing_cycles.setter
    def wind_swing_cycles(self, value):
        try:
            value = list(value)
            if not len(value) == 4 or not all(
                    isinstance(v, (int, float)) for v in value):
                raise ValueError
        except (TypeError, ValueError):
            raise ValueError(
                f"`wind_swing_cycles` must be a sequence of four numbers.")
        self._wind_swing_cycles = value

    @property
    def wind_swing_powers(self):
        return self._wind_swing_powers

    @wind_swing_powers.setter
    def wind_swing_powers(self, value):
        try:
            value = list(value)
            if not len(value) == 4 or not all(
                    isinstance(v, (int, float)) for v in value):
                raise ValueError
        except (TypeError, ValueError):
            raise ValueError(
                f"`wind_swing_powers` must be a sequence of four numbers.")
        self._wind_swing_powers = value
예제 #12
0
파일: msb.py 프로젝트: wrekklol/soulstruct
    def move_map(
        self,
        start_translate: tp.Union[Vector3, list, tuple] = None,
        end_translate: tp.Union[Vector3, list, tuple] = None,
        start_rotate: tp.Union[Vector3, list, tuple, int, float, None] = None,
        end_rotate: tp.Union[Vector3, list, tuple, int, float, None] = None,
        selected_entries=None,
    ):
        """Rotate and then translate entire map so that an entity with a translate of `start_translate` and rotate of
        `start_rotate` ends up with a translate of `end_translate` and a rotate of `end_rotate`.

        Optionally, move only a subset of entry names given in `selected_entry_names`.
        """
        if selected_entries is not None:
            selected_entries = list(
                selected_entries
            )  # so strings can be replaced with part instances
            for i, entry in enumerate(selected_entries):
                if isinstance(entry, str):
                    try:
                        selected_part = self.parts.get_entry_by_name(entry)
                    except KeyError:
                        selected_part = None
                    try:
                        selected_region = self.regions.get_entry_by_name(entry)
                    except KeyError:
                        selected_region = None
                    if selected_part and selected_region:
                        raise ValueError(
                            f"Found both a Part and Region with name '{entry}'. You must pass the desired entry "
                            f"instance directly.")
                    elif selected_part:
                        selected_entries[i] = selected_part
                    elif selected_region:
                        selected_entries[i] = selected_region
                    else:
                        raise ValueError(
                            f"Could not find a Part or Region named '{entry}' in MSB."
                        )
                elif not isinstance(entry, MSBEntry):
                    raise TypeError(
                        "`selected_entries` should contain only `MSBEntry` instances or Part names."
                    )

        if start_translate is None:
            start_translate = Vector3.zero()
        elif not isinstance(start_translate, Vector3):
            start_translate = Vector3(start_translate)
        if end_translate is None:
            end_translate = Vector3.zero()
        elif not isinstance(end_translate, Vector3):
            end_translate = Vector3(end_translate)
        if start_rotate is None:
            start_rotate = Vector3.zero()
        elif isinstance(start_rotate, (int, float)):
            start_rotate = Vector3(0, start_rotate, 0)
        else:
            start_rotate = Vector3(start_rotate)
        if end_rotate is None:
            end_rotate = Vector3.zero()
        elif isinstance(end_rotate, (int, float)):
            end_rotate = Vector3(0, end_rotate, 0)
        else:
            end_rotate = Vector3(end_rotate)

        # Compute global rotation matrix required to get from `start_rotate` to `end_rotate`.
        m_start_rotate = Matrix3.from_euler_angles(start_rotate)
        m_end_rotate = Matrix3.from_euler_angles(end_rotate)
        m_world_rotate = m_end_rotate @ m_start_rotate.T
        # Apply global rotation to start point to determine required global translation.
        translation = end_translate - (m_world_rotate @ start_translate
                                       )  # type: Vector3

        self.rotate_all_in_world(m_world_rotate,
                                 selected_entries=selected_entries)
        self.translate_all(translation, selected_entries=selected_entries)