Ejemplo n.º 1
0
class NiZBufferProperty(NiProperty):

    # provide access to related enums
    TestFunction = TestFunction

    # flags access
    z_buffer_test = bool_property(mask=0x0001)
    z_buffer_write = bool_property(mask=0x0002)
    test_function = enum_property(TestFunction, mask=0x003C, pos=2)
    test_function_specified = bool_property(mask=0x0040)
Ejemplo n.º 2
0
class NiFltAnimationNode(NiSwitchNode):
    period: float32 = 0

    # flags access
    bounce = bool_property(mask=0x0040)

    def load(self, stream):
        super().load(stream)
        self.period = stream.read_float()

    def save(self, stream):
        super().save(stream)
        stream.write_float(self.period)
Ejemplo n.º 3
0
class NiSwitchNode(NiNode):
    active_index: uint32 = 0

    # flags access
    update_only_active = bool_property(mask=0x0020)

    def load(self, stream):
        super().load(stream)
        self.active_index = stream.read_uint()

    def save(self, stream):
        super().save(stream)
        stream.write_uint(self.active_index)
Ejemplo n.º 4
0
class NiAlphaProperty(NiProperty):
    test_ref: uint8 = 0  # [0, 255]

    # provide access to related enums
    AlphaBlendFunction = AlphaBlendFunction
    AlphaTestFunction = AlphaTestFunction

    # convenience properties
    alpha_blending = bool_property(mask=0x0001)
    src_blend_mode = enum_property(AlphaBlendFunction, mask=0x001E, pos=1)
    dst_blend_mode = enum_property(AlphaBlendFunction, mask=0x01E0, pos=5)

    alpha_testing = bool_property(mask=0x0200)
    test_mode = enum_property(AlphaTestFunction, mask=0x1C00, pos=10)

    no_sort = bool_property(mask=0x2000)

    def load(self, stream):
        super().load(stream)
        self.test_ref = stream.read_ubyte()

    def save(self, stream):
        super().save(stream)
        stream.write_ubyte(self.test_ref)
Ejemplo n.º 5
0
class NiLookAtController(NiTimeController):
    look_at: NiAVObject | None = None

    _ptrs = (*NiTimeController._ptrs, "look_at")

    # provide access to related enums
    Axis = Axis

    # convenience properties
    flip = bool_property(mask=0x0010)
    axis = enum_property(Axis, mask=0x0060, pos=5)

    def load(self, stream):
        super().load(stream)
        self.look_at = stream.read_link()

    def save(self, stream):
        super().save(stream)
        stream.write_link(self.look_at)
Ejemplo n.º 6
0
class NiTimeController(NiObject):
    next: NiTimeController | None = None
    flags: uint16 = 8
    frequency: float32 = 1.0
    phase: float32 = 0.0
    start_time: float32 = 0.0
    stop_time: float32 = 0.0
    target: NiObjectNET | None = None

    # provide access to related enums
    CycleType = CycleType

    # flags access
    cycle_type = enum_property(CycleType, mask=0x0006, pos=1)
    active = bool_property(mask=0x0008)

    _refs = (*NiObject._refs, "next")
    _ptrs = (*NiObject._ptrs, "target")

    def load(self, stream):
        self.next = stream.read_link()
        self.flags = stream.read_ushort()
        self.frequency = stream.read_float()
        self.phase = stream.read_float()
        self.start_time = stream.read_float()
        self.stop_time = stream.read_float()
        self.target = stream.read_link()

    def save(self, stream):
        stream.write_link(self.next)
        stream.write_ushort(self.flags)
        stream.write_float(self.frequency)
        stream.write_float(self.phase)
        stream.write_float(self.start_time)
        stream.write_float(self.stop_time)
        stream.write_link(self.target)

    def update_start_stop_times(self) -> tuple[int, int]:
        if self.data:
            self.start_time, self.stop_time = self.data.get_start_stop_times()
        else:
            self.start_time, self.stop_time = 0, 0
Ejemplo n.º 7
0
class NiAVObject(NiObjectNET):
    flags: uint16 = 0
    translation: NiPoint3 = ZERO3
    rotation: NiMatrix3 = ID33
    scale: float32 = 1.0
    velocity: NiPoint3 = ZERO3
    properties: list[NiProperty | None] = []
    bounding_volume: NiBoundingVolume | None = None

    # TODO: remove
    children = []  # type: list[NiAVObject | None]

    # provide access to related enums
    PropagateMode = PropagateMode

    # flags access
    app_culled = bool_property(mask=0x0001)
    propagate_mode = enum_property(PropagateMode, mask=0x0006, pos=1)
    visual = bool_property(mask=0x0008)

    _refs = (*NiObjectNET._refs, "properties")

    def load(self, stream):
        super().load(stream)
        self.flags = stream.read_ushort()
        self.translation = stream.read_floats(3)
        self.rotation = stream.read_floats(3, 3)
        self.scale = stream.read_float()
        self.velocity = stream.read_floats(3)
        self.properties = stream.read_links()
        has_bounding_volume = stream.read_bool()
        if has_bounding_volume:
            self.bounding_volume = NiBoundingVolume.load(stream)

    def save(self, stream):
        super().save(stream)
        stream.write_ushort(self.flags)
        stream.write_floats(self.translation)
        stream.write_floats(self.rotation)
        stream.write_float(self.scale)
        stream.write_floats(self.velocity)
        stream.write_links(self.properties)
        stream.write_bool(self.bounding_volume)
        if self.bounding_volume:
            self.bounding_volume.save(stream)

    def sort(self, key=lambda prop: prop.type):
        super().sort()
        self.properties.sort(key=key)

    def apply_scale(self, scale: float):
        self.translation *= scale
        if self.bounding_volume:
            self.bounding_volume.apply_scale(scale)

    def get_property(self, property_type: type[T]) -> T:
        for prop in self.properties:
            if isinstance(prop, property_type):
                return prop

    @property
    def matrix(self) -> ndarray:
        return compose(self.translation, self.rotation, self.scale)

    @matrix.setter
    def matrix(self, value: ndarray):
        self.translation, self.rotation, self.scale = decompose_uniform(value)

    def matrix_relative_to(self, ancestor: NiAVObject) -> ndarray:
        path = reversed(list(self.find_path(ancestor)))
        return dotproduct([obj.matrix for obj in path])

    @property
    def is_biped(self) -> bool:
        return self.name.lower().startswith("bip01")

    @property
    def is_shadow(self) -> bool:
        return self.name.lower().startswith(("shadow", "tri shadow"))

    @property
    def is_bounding_box(self) -> bool:
        return bool(self.bounding_volume) and self.name.lower().startswith(
            "bounding box")

    def descendants(self, breadth_first=False) -> Iterator[NiAVObject]:
        queue = deque(filter(None, self.children))
        extend, iterator = (queue.extendleft, iter) if breadth_first else \
                           (queue.extend, reversed)
        while queue:
            node = queue.pop()
            yield node
            extend(child for child in iterator(node.children) if child)

    def descendants_pairs(
            self,
            breadth_first=False) -> Iterator[tuple[NiAVObject, NiAVObject]]:
        """Similar to descendants, but yielding pairs of (parent, node)."""

        queue = deque((self, child) for child in self.children if child)
        extend, iterator = (queue.extendleft, iter) if breadth_first else \
                           (queue.extend, reversed)
        while queue:
            parent, node = queue.pop()
            yield parent, node
            extend((node, child) for child in iterator(node.children) if child)

    def find_path(self, ancestor, breadth_first=True) -> Iterator[NiAVObject]:
        parents = {}
        for parent, node in ancestor.descendants_pairs(breadth_first):
            parents[node] = parent
            if node is self:
                break
        else:
            raise ValueError(
                f"find_path: no path from {self} to {ancestor} exists")

        while node is not ancestor:
            yield node
            node = parents[node]
Ejemplo n.º 8
0
class NiBSAnimationNode(NiNode):

    # flags access
    animated = bool_property(mask=0x0020)
    not_random = bool_property(mask=0x0040)
Ejemplo n.º 9
0
class NiBSParticleNode(NiBSAnimationNode):

    # flags access
    follow = bool_property(mask=0x0080)
Ejemplo n.º 10
0
class NiWireframeProperty(NiProperty):
    wireframe = bool_property(mask=0x0001)
Ejemplo n.º 11
0
class NiCollisionSwitch(NiNode):

    # flags access
    collidable = bool_property(mask=0x0020)
Ejemplo n.º 12
0
class NiGeometry(NiAVObject):
    data: NiGeometryData | None = None
    skin: NiSkinInstance | None = None

    # flags access
    compress_vertices = bool_property(mask=0x0008)
    compress_normals = bool_property(mask=0x0010)
    compress_uv_sets = bool_property(mask=0x0020)
    shadow = bool_property(mask=0x0040)

    _refs = (*NiAVObject._refs, "data", "skin")

    def load(self, stream):
        super().load(stream)
        self.data = stream.read_link()
        self.skin = stream.read_link()

    def save(self, stream):
        super().save(stream)
        stream.write_link(self.data)
        stream.write_link(self.skin)

    @property
    def bone_influences(self):
        try:
            skin = self.skin.root and self.skin
            assert len(skin.bones) == len(skin.data.bone_data)
        except (AttributeError, AssertionError):
            return ()
        return tuple(zip(skin.bones, skin.data.bone_data))

    @property
    def morph_targets(self):
        try:
            basis, *targets = self.controller.data.targets
            assert len(basis.vertices) == len(self.data.vertices)
        except (AttributeError, AssertionError, ValueError):
            return ()
        return tuple(targets)

    def vertex_weights(self):
        bone_influences = self.bone_influences
        vertex_weights = zeros(len(bone_influences), len(self.data.vertices))

        for i, (_, bone_data) in enumerate(bone_influences):
            indices = bone_data.vertex_weights["f0"]
            weights = bone_data.vertex_weights["f1"]
            vertex_weights[i, indices] = weights

        return vertex_weights

    def vertex_morphs(self):
        morph_targets = self.morph_targets
        vertex_morphs = zeros(len(morph_targets), len(self.data.vertices), 3)

        for i, target in enumerate(morph_targets):
            vertex_morphs[
                i] = self.data.vertices + target.vertices  # TODO not always relative!

        return vertex_morphs

    def apply_skin(self, keep_skin=False):
        data = self.data
        skin = self.skin
        if not keep_skin:
            self.skin = None

        deformed_verts = zeros(*data.vertices.shape)
        deformed_norms = zeros(*data.normals.shape)

        root_to_skin = skin.data.matrix
        for bone, bone_data in zip(skin.bones, skin.data.bone_data):
            skin_to_bone = bone_data.matrix

            bone_matrix = bone.matrix_relative_to(skin.root)
            bind_matrix = root_to_skin @ bone_matrix @ skin_to_bone

            location, rotation, scale = decompose_uniform(bind_matrix)

            # indices and weights
            i = bone_data.vertex_weights["f0"]
            w = bone_data.vertex_weights["f1"][:, None]

            if len(deformed_verts):
                deformed_verts[i] += w * (
                    data.vertices[i] @ rotation.T * scale + location.T)
            if len(deformed_norms):
                deformed_norms[i] += w * (data.normals[i] @ rotation.T)

        data.vertices = deformed_verts
        data.normals = deformed_norms  # TODO: normalize?