comparison pytouhou/formats/anm0.pyx @ 525:43ecf0f98f4d

Precalculate the inverse of the texture size at ANM load, to not recalculate at every sprite change.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Wed, 18 Dec 2013 18:15:45 +0100
parents pytouhou/formats/anm0.py@40d5f3083ebc
children
comparison
equal deleted inserted replaced
524:7f016dfbdfb1 525:43ecf0f98f4d
1 # -*- encoding: utf-8 -*-
2 ##
3 ## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com>
4 ##
5 ## This program is free software; you can redistribute it and/or modify
6 ## it under the terms of the GNU General Public License as published
7 ## by the Free Software Foundation; version 3 only.
8 ##
9 ## This program is distributed in the hope that it will be useful,
10 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 ## GNU General Public License for more details.
13 ##
14
15 """ANM0 files handling.
16
17 This module provides classes for handling the ANM0 file format.
18 The ANM0 format is a format used in Touhou 6: EoSD to describe sprites
19 and animations.
20 Almost everything rendered in the game is described by an ANM0 file.
21 """
22
23 from struct import pack, unpack
24 from pytouhou.utils.helpers import read_string, get_logger
25
26 from pytouhou.formats import WrongFormatError
27 from pytouhou.formats.thtx import Texture
28
29
30 logger = get_logger(__name__)
31
32 #TODO: refactor/clean up
33
34
35 class Script(list):
36 def __init__(self):
37 list.__init__(self)
38 self.interrupts = {}
39
40
41
42 cdef class ANM:
43 def __init__(self):
44 self.version = 0
45 self.size_inv[:] = [0, 0]
46 self.first_name = None
47 self.secondary_name = None
48 self.sprites = {}
49 self.scripts = {}
50
51 property size:
52 def __set__(self, tuple value):
53 width, height = value
54 self.size_inv[:] = [1. / <double>width, 1. / <double>height]
55
56
57
58 class ANM0(object):
59 _instructions = {0: {0: ('', 'delete'),
60 1: ('I', 'set_sprite'),
61 2: ('ff', 'set_scale'),
62 3: ('I', 'set_alpha'),
63 4: ('BBBx', 'set_color'),
64 5: ('I', 'jump'),
65 7: ('', 'toggle_mirrored'),
66 9: ('fff', 'set_3d_rotations'),
67 10: ('fff', 'set_3d_rotations_speed'),
68 11: ('ff', 'set_scale_speed'),
69 12: ('ii', 'fade'),
70 13: ('', 'set_blendmode_add'),
71 14: ('', 'set_blendmode_alphablend'),
72 15: ('', 'keep_still'),
73 16: ('ii', 'set_random_sprite'),
74 17: ('fff', 'set_3d_translation'),
75 18: ('fffi', 'move_to_linear'),
76 19: ('fffi', 'move_to_decel'),
77 20: ('fffi', 'move_to_accel'),
78 21: ('', 'wait'),
79 22: ('i', 'interrupt_label'),
80 23: ('', 'set_corner_relative_placement'),
81 24: ('', 'wait_ex'),
82 25: ('i', 'set_allow_offset'), #TODO: better name
83 26: ('i', 'set_automatic_orientation'),
84 27: ('f', 'shift_texture_x'),
85 28: ('f', 'shift_texture_y'),
86 29: ('i', 'set_visible'),
87 30: ('ffi', 'scale_in'),
88 31: ('i', None)},
89
90 2: {0: ('', 'noop'),
91 1: ('', 'delete'),
92 2: ('', 'keep_still'),
93 3: ('I', 'set_sprite'),
94 4: ('II', 'jump_bis'),
95 5: ('III', 'jump_ex'),
96 6: ('fff', 'set_3d_translation'),
97 7: ('ff', 'set_scale'),
98 8: ('I', 'set_alpha'),
99 9: ('BBBx', 'set_color'),
100 10: ('', 'toggle_mirrored'),
101 12: ('fff', 'set_3d_rotations'),
102 13: ('fff', 'set_3d_rotations_speed'),
103 14: ('ff', 'set_scale_speed'),
104 15: ('ii', 'fade'),
105 16: ('I', 'set_blendmode'),
106 17: ('fffi', 'move_to_linear'),
107 18: ('fffi', 'move_to_decel'),
108 19: ('fffi', 'move_to_accel'),
109 20: ('', 'wait'),
110 21: ('i', 'interrupt_label'),
111 22: ('', 'set_corner_relative_placement'),
112 23: ('', 'wait_ex'),
113 24: ('i', 'set_allow_offset'), #TODO: better name
114 25: ('i', 'set_automatic_orientation'),
115 26: ('f', 'shift_texture_x'),
116 27: ('f', 'shift_texture_y'),
117 28: ('i', 'set_visible'),
118 29: ('ffi', 'scale_in'),
119 30: ('i', None),
120 31: ('I', None),
121 32: ('IIfff', 'move_in_linear_bis'),
122 33: ('IIBBBx', 'change_color_in'),
123 34: ('III', 'fade_bis'),
124 35: ('IIfff', 'rotate_in_bis'),
125 36: ('IIff', 'scale_in_bis'),
126 37: ('II', 'set_int'),
127 38: ('ff', 'set_float'),
128 42: ('ff', 'decrement_float'),
129 50: ('fff', 'add_float'),
130 52: ('fff', 'substract_float'),
131 55: ('III', 'divide_int'),
132 59: ('II', 'set_random_int'),
133 60: ('ff', 'set_random_float'),
134 69: ('IIII', 'branch_if_not_equal'),
135 79: ('I', 'wait_duration'),
136 80: ('I', None)}}
137
138
139 @classmethod
140 def read(cls, file):
141 anm_list = []
142 start_offset = 0
143 while True:
144 file.seek(start_offset)
145 nb_sprites, nb_scripts, zero1 = unpack('<III', file.read(12))
146 width, height, fmt, unknown1 = unpack('<IIII', file.read(16))
147 first_name_offset, unused, secondary_name_offset = unpack('<III', file.read(12))
148 version, unknown2, texture_offset, has_data, next_offset, unknown3 = unpack('<IIIIII', file.read(24))
149
150 if version == 0:
151 assert zero1 == 0
152 assert unknown3 == 0
153 assert has_data == 0
154 elif version == 2:
155 assert zero1 == 0
156 assert secondary_name_offset == 0
157 assert has_data == 1 # Can be false but we don’t support that yet.
158 else:
159 raise WrongFormatError(version)
160
161 instructions = cls._instructions[version]
162
163 sprite_offsets = [unpack('<I', file.read(4))[0] for i in range(nb_sprites)]
164 script_offsets = [unpack('<II', file.read(8)) for i in range(nb_scripts)]
165
166 self = ANM()
167
168 self.size = (width, height)
169 self.version = version
170
171 # Names
172 if first_name_offset:
173 file.seek(start_offset + first_name_offset)
174 self.first_name = read_string(file, 32, 'ascii') #TODO: 32, really?
175 if secondary_name_offset:
176 file.seek(start_offset + secondary_name_offset)
177 self.secondary_name = read_string(file, 32, 'ascii') #TODO: 32, really?
178
179
180 # Sprites
181 for offset in sprite_offsets:
182 file.seek(start_offset + offset)
183 idx, x, y, width, height = unpack('<Iffff', file.read(20))
184 self.sprites[idx] = x, y, width, height
185
186
187 # Scripts
188 for i, offset in script_offsets:
189 self.scripts[i] = Script()
190 instruction_offsets = []
191 file.seek(start_offset + offset)
192 while True:
193 instruction_offsets.append(file.tell() - (start_offset + offset))
194 if version == 0:
195 time, opcode, size = unpack('<HBB', file.read(4))
196 elif version == 2:
197 opcode, size, time, mask = unpack('<HHHH', file.read(8))
198 if opcode == 0xffff:
199 break
200 size -= 8
201 data = file.read(size)
202 if opcode in instructions:
203 args = unpack('<%s' % instructions[opcode][0], data)
204 else:
205 args = (data,)
206 logger.warn('unknown opcode %d', opcode)
207
208 self.scripts[i].append((time, opcode, args))
209 if version == 0 and opcode == 0:
210 break
211
212 # Translate offsets to instruction pointers and register interrupts
213 for instr_offset, (j, instr) in zip(instruction_offsets, enumerate(self.scripts[i])):
214 time, opcode, args = instr
215 if version == 0:
216 if opcode == 5:
217 args = (instruction_offsets.index(args[0]),)
218 elif opcode == 22:
219 interrupt = args[0]
220 self.scripts[i].interrupts[interrupt] = j + 1
221 elif version == 2:
222 if opcode == 4:
223 args = (instruction_offsets.index(args[0]), args[1])
224 elif opcode == 5:
225 args = (args[0], instruction_offsets.index(args[1]), args[2])
226 elif opcode == 21:
227 interrupt = args[0]
228 self.scripts[i].interrupts[interrupt] = j + 1
229 elif opcode == 69:
230 args = (args[0], args[1], instruction_offsets.index(args[2]), args[3])
231 self.scripts[i][j] = time, opcode, args
232
233 # Texture
234 if has_data:
235 file.seek(start_offset + texture_offset)
236 magic = file.read(4)
237 assert magic == b'THTX'
238 zero, fmt, width, height, size = unpack('<HHHHI', file.read(12))
239 assert zero == 0
240 data = file.read(size)
241 self.texture = Texture(width, height, fmt, data)
242
243 anm_list.append(self)
244
245 if next_offset:
246 start_offset += next_offset
247 else:
248 break
249
250 return anm_list