Mercurial > touhou
changeset 608:725bd24235a2
Make ANM0 pure-python again, by moving the Cython-dependent ANM class into its own module.
| author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
|---|---|
| date | Wed, 26 Nov 2014 14:00:17 +0100 |
| parents | 9dbc234ea087 |
| children | 23b9418e4b2f |
| files | pytouhou/formats/animation.pxd pytouhou/formats/animation.pyx pytouhou/formats/anm0.pxd pytouhou/formats/anm0.py pytouhou/formats/anm0.pyx pytouhou/game/sprite.pxd setup.py |
| diffstat | 7 files changed, 272 insertions(+), 260 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pytouhou/formats/animation.pxd Wed Nov 26 14:00:17 2014 +0100 @@ -0,0 +1,7 @@ +cdef class Animation: + cdef public long version + cdef public unicode first_name, secondary_name + cdef public dict sprites, scripts + cdef public object texture + + cdef double size_inv[2]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pytouhou/formats/animation.pyx Wed Nov 26 14:00:17 2014 +0100 @@ -0,0 +1,27 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2014 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +cdef class Animation: + def __init__(self): + self.version = 0 + self.size_inv[:] = [0, 0] + self.first_name = None + self.secondary_name = None + self.sprites = {} + self.scripts = {} + + property size: + def __set__(self, tuple value): + width, height = value + self.size_inv[:] = [1. / <double>width, 1. / <double>height]
--- a/pytouhou/formats/anm0.pxd Wed Nov 26 13:37:07 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -cdef class ANM: - cdef public long version - cdef public unicode first_name, secondary_name - cdef public dict sprites, scripts - cdef public object texture - - cdef double size_inv[2]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pytouhou/formats/anm0.py Wed Nov 26 14:00:17 2014 +0100 @@ -0,0 +1,235 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com> +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 3 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +"""ANM0 files handling. + +This module provides classes for handling the ANM0 file format. +The ANM0 format is a format used in Touhou 6: EoSD to describe sprites +and animations. +Almost everything rendered in the game is described by an ANM0 file. +""" + +from struct import pack, unpack +from pytouhou.utils.helpers import read_string, get_logger + +from pytouhou.formats import WrongFormatError +from pytouhou.formats.animation import Animation +from pytouhou.formats.thtx import Texture + + +logger = get_logger(__name__) + +#TODO: refactor/clean up + + +class Script(list): + def __init__(self): + list.__init__(self) + self.interrupts = {} + + + +class ANM0(Animation): + _instructions = {0: {0: ('', 'delete'), + 1: ('I', 'set_sprite'), + 2: ('ff', 'set_scale'), + 3: ('I', 'set_alpha'), + 4: ('BBBx', 'set_color'), + 5: ('I', 'jump'), + 7: ('', 'toggle_mirrored'), + 9: ('fff', 'set_3d_rotations'), + 10: ('fff', 'set_3d_rotations_speed'), + 11: ('ff', 'set_scale_speed'), + 12: ('ii', 'fade'), + 13: ('', 'set_blendmode_add'), + 14: ('', 'set_blendmode_alphablend'), + 15: ('', 'keep_still'), + 16: ('ii', 'set_random_sprite'), + 17: ('fff', 'set_3d_translation'), + 18: ('fffi', 'move_to_linear'), + 19: ('fffi', 'move_to_decel'), + 20: ('fffi', 'move_to_accel'), + 21: ('', 'wait'), + 22: ('i', 'interrupt_label'), + 23: ('', 'set_corner_relative_placement'), + 24: ('', 'wait_ex'), + 25: ('i', 'set_allow_offset'), #TODO: better name + 26: ('i', 'set_automatic_orientation'), + 27: ('f', 'shift_texture_x'), + 28: ('f', 'shift_texture_y'), + 29: ('i', 'set_visible'), + 30: ('ffi', 'scale_in'), + 31: ('i', None)}, + + 2: {0: ('', 'noop'), + 1: ('', 'delete'), + 2: ('', 'keep_still'), + 3: ('I', 'set_sprite'), + 4: ('II', 'jump_bis'), + 5: ('III', 'jump_ex'), + 6: ('fff', 'set_3d_translation'), + 7: ('ff', 'set_scale'), + 8: ('I', 'set_alpha'), + 9: ('BBBx', 'set_color'), + 10: ('', 'toggle_mirrored'), + 12: ('fff', 'set_3d_rotations'), + 13: ('fff', 'set_3d_rotations_speed'), + 14: ('ff', 'set_scale_speed'), + 15: ('ii', 'fade'), + 16: ('I', 'set_blendmode'), + 17: ('fffi', 'move_to_linear'), + 18: ('fffi', 'move_to_decel'), + 19: ('fffi', 'move_to_accel'), + 20: ('', 'wait'), + 21: ('i', 'interrupt_label'), + 22: ('', 'set_corner_relative_placement'), + 23: ('', 'wait_ex'), + 24: ('i', 'set_allow_offset'), #TODO: better name + 25: ('i', 'set_automatic_orientation'), + 26: ('f', 'shift_texture_x'), + 27: ('f', 'shift_texture_y'), + 28: ('i', 'set_visible'), + 29: ('ffi', 'scale_in'), + 30: ('i', None), + 31: ('I', None), + 32: ('IIfff', 'move_in_linear_bis'), + 33: ('IIBBBx', 'change_color_in'), + 34: ('III', 'fade_bis'), + 35: ('IIfff', 'rotate_in_bis'), + 36: ('IIff', 'scale_in_bis'), + 37: ('II', 'set_int'), + 38: ('ff', 'set_float'), + 42: ('ff', 'decrement_float'), + 50: ('fff', 'add_float'), + 52: ('fff', 'substract_float'), + 55: ('III', 'divide_int'), + 59: ('II', 'set_random_int'), + 60: ('ff', 'set_random_float'), + 69: ('IIII', 'branch_if_not_equal'), + 79: ('I', 'wait_duration'), + 80: ('I', None)}} + + + @classmethod + def read(cls, file): + anm_list = [] + start_offset = 0 + while True: + file.seek(start_offset) + nb_sprites, nb_scripts, zero1 = unpack('<III', file.read(12)) + width, height, fmt, unknown1 = unpack('<IIII', file.read(16)) + first_name_offset, unused, secondary_name_offset = unpack('<III', file.read(12)) + version, unknown2, texture_offset, has_data, next_offset, unknown3 = unpack('<IIIIII', file.read(24)) + + if version == 0: + assert zero1 == 0 + assert unknown3 == 0 + assert has_data == 0 + elif version == 2: + assert zero1 == 0 + assert secondary_name_offset == 0 + assert has_data == 1 # Can be false but we don’t support that yet. + else: + raise WrongFormatError(version) + + instructions = cls._instructions[version] + + sprite_offsets = [unpack('<I', file.read(4))[0] for i in range(nb_sprites)] + script_offsets = [unpack('<II', file.read(8)) for i in range(nb_scripts)] + + self = cls() + + self.size = (width, height) + self.version = version + + # Names + if first_name_offset: + file.seek(start_offset + first_name_offset) + self.first_name = read_string(file, 32, 'ascii') #TODO: 32, really? + if secondary_name_offset: + file.seek(start_offset + secondary_name_offset) + self.secondary_name = read_string(file, 32, 'ascii') #TODO: 32, really? + + + # Sprites + for offset in sprite_offsets: + file.seek(start_offset + offset) + idx, x, y, width, height = unpack('<Iffff', file.read(20)) + self.sprites[idx] = x, y, width, height + + + # Scripts + for i, offset in script_offsets: + self.scripts[i] = Script() + instruction_offsets = [] + file.seek(start_offset + offset) + while True: + instruction_offsets.append(file.tell() - (start_offset + offset)) + if version == 0: + time, opcode, size = unpack('<HBB', file.read(4)) + elif version == 2: + opcode, size, time, mask = unpack('<HHHH', file.read(8)) + if opcode == 0xffff: + break + size -= 8 + data = file.read(size) + if opcode in instructions: + args = unpack('<%s' % instructions[opcode][0], data) + else: + args = (data,) + logger.warn('unknown opcode %d', opcode) + + self.scripts[i].append((time, opcode, args)) + if version == 0 and opcode == 0: + break + + # Translate offsets to instruction pointers and register interrupts + for instr_offset, (j, instr) in zip(instruction_offsets, enumerate(self.scripts[i])): + time, opcode, args = instr + if version == 0: + if opcode == 5: + args = (instruction_offsets.index(args[0]),) + elif opcode == 22: + interrupt = args[0] + self.scripts[i].interrupts[interrupt] = j + 1 + elif version == 2: + if opcode == 4: + args = (instruction_offsets.index(args[0]), args[1]) + elif opcode == 5: + args = (args[0], instruction_offsets.index(args[1]), args[2]) + elif opcode == 21: + interrupt = args[0] + self.scripts[i].interrupts[interrupt] = j + 1 + elif opcode == 69: + args = (args[0], args[1], instruction_offsets.index(args[2]), args[3]) + self.scripts[i][j] = time, opcode, args + + # Texture + if has_data: + file.seek(start_offset + texture_offset) + magic = file.read(4) + assert magic == b'THTX' + zero, fmt, width, height, size = unpack('<HHHHI', file.read(12)) + assert zero == 0 + data = file.read(size) + self.texture = Texture(width, height, fmt, data) + + anm_list.append(self) + + if next_offset: + start_offset += next_offset + else: + break + + return anm_list
--- a/pytouhou/formats/anm0.pyx Wed Nov 26 13:37:07 2014 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,250 +0,0 @@ -# -*- encoding: utf-8 -*- -## -## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com> -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 3 only. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## - -"""ANM0 files handling. - -This module provides classes for handling the ANM0 file format. -The ANM0 format is a format used in Touhou 6: EoSD to describe sprites -and animations. -Almost everything rendered in the game is described by an ANM0 file. -""" - -from struct import pack, unpack -from pytouhou.utils.helpers import read_string, get_logger - -from pytouhou.formats import WrongFormatError -from pytouhou.formats.thtx import Texture - - -logger = get_logger(__name__) - -#TODO: refactor/clean up - - -class Script(list): - def __init__(self): - list.__init__(self) - self.interrupts = {} - - - -cdef class ANM: - def __init__(self): - self.version = 0 - self.size_inv[:] = [0, 0] - self.first_name = None - self.secondary_name = None - self.sprites = {} - self.scripts = {} - - property size: - def __set__(self, tuple value): - width, height = value - self.size_inv[:] = [1. / <double>width, 1. / <double>height] - - - -class ANM0(object): - _instructions = {0: {0: ('', 'delete'), - 1: ('I', 'set_sprite'), - 2: ('ff', 'set_scale'), - 3: ('I', 'set_alpha'), - 4: ('BBBx', 'set_color'), - 5: ('I', 'jump'), - 7: ('', 'toggle_mirrored'), - 9: ('fff', 'set_3d_rotations'), - 10: ('fff', 'set_3d_rotations_speed'), - 11: ('ff', 'set_scale_speed'), - 12: ('ii', 'fade'), - 13: ('', 'set_blendmode_add'), - 14: ('', 'set_blendmode_alphablend'), - 15: ('', 'keep_still'), - 16: ('ii', 'set_random_sprite'), - 17: ('fff', 'set_3d_translation'), - 18: ('fffi', 'move_to_linear'), - 19: ('fffi', 'move_to_decel'), - 20: ('fffi', 'move_to_accel'), - 21: ('', 'wait'), - 22: ('i', 'interrupt_label'), - 23: ('', 'set_corner_relative_placement'), - 24: ('', 'wait_ex'), - 25: ('i', 'set_allow_offset'), #TODO: better name - 26: ('i', 'set_automatic_orientation'), - 27: ('f', 'shift_texture_x'), - 28: ('f', 'shift_texture_y'), - 29: ('i', 'set_visible'), - 30: ('ffi', 'scale_in'), - 31: ('i', None)}, - - 2: {0: ('', 'noop'), - 1: ('', 'delete'), - 2: ('', 'keep_still'), - 3: ('I', 'set_sprite'), - 4: ('II', 'jump_bis'), - 5: ('III', 'jump_ex'), - 6: ('fff', 'set_3d_translation'), - 7: ('ff', 'set_scale'), - 8: ('I', 'set_alpha'), - 9: ('BBBx', 'set_color'), - 10: ('', 'toggle_mirrored'), - 12: ('fff', 'set_3d_rotations'), - 13: ('fff', 'set_3d_rotations_speed'), - 14: ('ff', 'set_scale_speed'), - 15: ('ii', 'fade'), - 16: ('I', 'set_blendmode'), - 17: ('fffi', 'move_to_linear'), - 18: ('fffi', 'move_to_decel'), - 19: ('fffi', 'move_to_accel'), - 20: ('', 'wait'), - 21: ('i', 'interrupt_label'), - 22: ('', 'set_corner_relative_placement'), - 23: ('', 'wait_ex'), - 24: ('i', 'set_allow_offset'), #TODO: better name - 25: ('i', 'set_automatic_orientation'), - 26: ('f', 'shift_texture_x'), - 27: ('f', 'shift_texture_y'), - 28: ('i', 'set_visible'), - 29: ('ffi', 'scale_in'), - 30: ('i', None), - 31: ('I', None), - 32: ('IIfff', 'move_in_linear_bis'), - 33: ('IIBBBx', 'change_color_in'), - 34: ('III', 'fade_bis'), - 35: ('IIfff', 'rotate_in_bis'), - 36: ('IIff', 'scale_in_bis'), - 37: ('II', 'set_int'), - 38: ('ff', 'set_float'), - 42: ('ff', 'decrement_float'), - 50: ('fff', 'add_float'), - 52: ('fff', 'substract_float'), - 55: ('III', 'divide_int'), - 59: ('II', 'set_random_int'), - 60: ('ff', 'set_random_float'), - 69: ('IIII', 'branch_if_not_equal'), - 79: ('I', 'wait_duration'), - 80: ('I', None)}} - - - @classmethod - def read(cls, file): - anm_list = [] - start_offset = 0 - while True: - file.seek(start_offset) - nb_sprites, nb_scripts, zero1 = unpack('<III', file.read(12)) - width, height, fmt, unknown1 = unpack('<IIII', file.read(16)) - first_name_offset, unused, secondary_name_offset = unpack('<III', file.read(12)) - version, unknown2, texture_offset, has_data, next_offset, unknown3 = unpack('<IIIIII', file.read(24)) - - if version == 0: - assert zero1 == 0 - assert unknown3 == 0 - assert has_data == 0 - elif version == 2: - assert zero1 == 0 - assert secondary_name_offset == 0 - assert has_data == 1 # Can be false but we don’t support that yet. - else: - raise WrongFormatError(version) - - instructions = cls._instructions[version] - - sprite_offsets = [unpack('<I', file.read(4))[0] for i in range(nb_sprites)] - script_offsets = [unpack('<II', file.read(8)) for i in range(nb_scripts)] - - self = ANM() - - self.size = (width, height) - self.version = version - - # Names - if first_name_offset: - file.seek(start_offset + first_name_offset) - self.first_name = read_string(file, 32, 'ascii') #TODO: 32, really? - if secondary_name_offset: - file.seek(start_offset + secondary_name_offset) - self.secondary_name = read_string(file, 32, 'ascii') #TODO: 32, really? - - - # Sprites - for offset in sprite_offsets: - file.seek(start_offset + offset) - idx, x, y, width, height = unpack('<Iffff', file.read(20)) - self.sprites[idx] = x, y, width, height - - - # Scripts - for i, offset in script_offsets: - self.scripts[i] = Script() - instruction_offsets = [] - file.seek(start_offset + offset) - while True: - instruction_offsets.append(file.tell() - (start_offset + offset)) - if version == 0: - time, opcode, size = unpack('<HBB', file.read(4)) - elif version == 2: - opcode, size, time, mask = unpack('<HHHH', file.read(8)) - if opcode == 0xffff: - break - size -= 8 - data = file.read(size) - if opcode in instructions: - args = unpack('<%s' % instructions[opcode][0], data) - else: - args = (data,) - logger.warn('unknown opcode %d', opcode) - - self.scripts[i].append((time, opcode, args)) - if version == 0 and opcode == 0: - break - - # Translate offsets to instruction pointers and register interrupts - for instr_offset, (j, instr) in zip(instruction_offsets, enumerate(self.scripts[i])): - time, opcode, args = instr - if version == 0: - if opcode == 5: - args = (instruction_offsets.index(args[0]),) - elif opcode == 22: - interrupt = args[0] - self.scripts[i].interrupts[interrupt] = j + 1 - elif version == 2: - if opcode == 4: - args = (instruction_offsets.index(args[0]), args[1]) - elif opcode == 5: - args = (args[0], instruction_offsets.index(args[1]), args[2]) - elif opcode == 21: - interrupt = args[0] - self.scripts[i].interrupts[interrupt] = j + 1 - elif opcode == 69: - args = (args[0], args[1], instruction_offsets.index(args[2]), args[3]) - self.scripts[i][j] = time, opcode, args - - # Texture - if has_data: - file.seek(start_offset + texture_offset) - magic = file.read(4) - assert magic == b'THTX' - zero, fmt, width, height, size = unpack('<HHHHI', file.read(12)) - assert zero == 0 - data = file.read(size) - self.texture = Texture(width, height, fmt, data) - - anm_list.append(self) - - if next_offset: - start_offset += next_offset - else: - break - - return anm_list
--- a/pytouhou/game/sprite.pxd Wed Nov 26 13:37:07 2014 +0100 +++ b/pytouhou/game/sprite.pxd Wed Nov 26 14:00:17 2014 +0100 @@ -1,5 +1,5 @@ from pytouhou.utils.interpolator cimport Interpolator -from pytouhou.formats.anm0 cimport ANM +from pytouhou.formats.animation cimport Animation cdef class Sprite: cdef public long blendfunc, frame @@ -10,7 +10,7 @@ cdef public Interpolator scale_interpolator, fade_interpolator cdef public Interpolator offset_interpolator, rotation_interpolator cdef public Interpolator color_interpolator - cdef public ANM anm + cdef public Animation anm cdef void *_rendering_data
--- a/setup.py Wed Nov 26 13:37:07 2014 +0100 +++ b/setup.py Wed Nov 26 14:00:17 2014 +0100 @@ -122,7 +122,7 @@ elif extension_name == 'pytouhou.ui.anmrenderer' and not anmviewer: extension_names.pop() continue - elif package == 'pytouhou.formats' and extension_name != 'pytouhou.formats.anm0': + elif package == 'pytouhou.formats' and extension_name != 'pytouhou.formats.animation': continue else: compile_args = package_args
