예제 #1
0
class NiBillboardNode(NiNode):

    # provide access to related enums
    BillboardMode = BillboardMode

    # flags access
    billboard_mode = enum_property(BillboardMode, mask=0x0060, pos=5)
예제 #2
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)
예제 #3
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)
class NiMaterialColorController(NiTimeController):
    data: NiPosData | None = None

    # provide access to related enums
    ColorField = ColorField

    # convenience properties
    color_field = enum_property(ColorField, mask=0x0070, pos=4)

    _refs = (*NiTimeController._refs, "data")

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

    def save(self, stream):
        super().save(stream)
        stream.write_link(self.data)
예제 #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)
예제 #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
예제 #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]