1#!perl -w -I \temp\1\bin
2#
3#this module handles ogf models with OGF_format_version 4, from build 1893 to 3456+. Last Edit: 26.06.2011
4###################################################################################################################
5package stkutils::ogf;
6use strict;
7#use diagnostics;
8use stkutils::debug;
9use stkutils::data_packet;
10use stkutils::chunked_file;
11use POSIX qw(ctime);
12use constant esmFX		=> 0x1;
13use constant esmStopAtEnd	=> 0x2;
14use constant esmNoMix		=> 0x4;
15use constant esmSyncPart	=> 0x8;
16use constant esmHasMotionMarks	=> 0x10;	# guessed name
17# vertex constants
18use constant OGF_VERTEXFORMAT_FVF_OLD	=> 0x112;
19use constant OGF_VERTEXFORMAT_FVF_1L	=> 0x12071980;
20use constant OGF_VERTEXFORMAT_FVF_2L	=> 0x240e3300; # skeletonX_pm
21use constant OGF_VERTEXFORMAT_FVF_NL	=> 0x36154c80;
22use constant OGF_VERTEXFORMAT_FVF_1_CS	=> 1;
23use constant OGF_VERTEXFORMAT_FVF_2_CS	=> 2;
24use constant OGF_VERTEXFORMAT_FVF_3_CS	=> 3;
25use constant OGF_VERTEXFORMAT_FVF_4_CS	=> 4;
26# loddef constants
27use constant OGF3_HOPPE_HEADER	=> 1;
28use constant OGF3_HOPPE_VERT_SPLITS	=> 2;
29use constant OGF3_HOPPE_FIX_FACES	=> 3;
30# motion constants
31use constant KPF_T_PRESENT	=> 0x01;
32use constant KPF_R_ABSENT	=> 0x02;
33use constant KPF_T_HQ	=> 0x04;
34use constant S_POINTS	=> 0x01;
35use constant S_NORMALS	=> 0x02;
36use constant S_TEXCOORDS	=> 0x04;
37use constant S_LIGHTMAPS	=> 0x08;
38use constant S_INFLUENCES	=> 0x10;
39use constant S_COLORS	=> 0x20;
40use constant UINT32_MAX => 0xffffffff;
41my %chunks_loaded = (
423 => {'OGF_HEADER'		=>	0x1,
43	'OGF_TEXTURE'		=>	0x2,
44	'OGF_TEXTURE_L'		=>	0x4,
45	'OGF_CHILD_REFS'	=>	0x8,
46	'OGF_BBOX'			=>	0x10,
47	'OGF_VERTICES'		=>	0x20,
48	'OGF_INDICES'		=>	0x40,
49	'OGF_LODDATA'		=>	0x80,
50	'OGF_VCONTAINER'	=>	0x100,
51	'OGF_BSPHERE'		=>	0x200,
52	'OGF_CHILDREN_L'	=>	0x400,
53	'OGF_S_BONE_NAMES'	=>	0x800,
54	'OGF_S_MOTIONS_0'	=>	0x1000,
55	'OGF_DPATCH'		=>	0x2000,
56	'OGF_S_LODS'		=>	0x4000,
57	'OGF_CHILDREN'		=>	0x8000,
58	'OGF_S_SMPARAMS_0'	=>	0x10000,
59	'OGF_ICONTAINER'	=>	0x20000,
60	'OGF_S_SMPARAMS_1'	=>	0x40000,
61	'OGF_LODDEF2'		=>	0x80000,
62	'OGF_TREEDEF2'		=>	0x100000,
63	'OGF_S_IKDATA_0'	=>	0x200000,
64	'OGF_S_USERDATA'	=>	0x400000,
65	'OGF_S_IKDATA_1'	=>	0x800000,
66	'OGF_S_MOTIONS_1'	=>	0x1000000,
67	'OGF_S_DESC'		=>	0x2000000,
68	'OGF_S_IKDATA_2'	=>	0x4000000,
69	'OGF_S_MOTION_REFS_0'	=>	0x8000000},
704 => {'OGF_HEADER'		=>	0x1,
71	'OGF_TEXTURE'		=>	0x2,
72	'OGF_VERTICES'		=>	0x4,
73	'OGF_INDICES'		=>	0x8,
74	'OGF_P_MAP'			=>	0x10,
75	'OGF_SWIDATA'		=>	0x20,
76	'OGF_VCONTAINER'	=>	0x40,
77	'OGF_ICONTAINER'	=>	0x80,
78	'OGF_CHILDREN'		=>	0x100,
79	'OGF_CHILDREN_L'	=>	0x200,
80	'OGF_LODDEF2'		=>	0x400,
81	'OGF_TREEDEF2'		=>	0x800,
82	'OGF_S_BONE_NAMES'	=>	0x1000,
83	'OGF_S_MOTIONS_1'		=>	0x2000,
84	'OGF_S_SMPARAMS_1'	=>	0x4000,
85	'OGF_S_IKDATA_2'	=>	0x8000,
86	'OGF_S_USERDATA'	=>	0x10000,
87	'OGF_S_DESC'		=>	0x20000,
88	'OGF_S_MOTION_REFS_0'	=>	0x40000,
89	'OGF_SWICONTAINER'	=>	0x80000,
90	'OGF_GCONTAINER'	=>	0x100000,
91	'OGF_FASTPATH'		=>	0x200000,
92	'OGF_S_LODS'		=>	0x400000,
93	'OGF_S_MOTION_REFS_1'	=>	0x800000,
94	'OGF_TEXTURE_L'		=>	0x1000000,
95	'OGF_CHILD_REFS'	=>	0x2000000,
96	'OGF_BBOX'			=>	0x4000000,
97	'OGF_LODDATA'		=>	0x8000000,
98	'OGF_BSPHERE'		=>	0x10000000,
99	'OGF_DPATCH'		=>	0x20000000,
100	'OGF_S_LODS_CSKY'	=>	0x40000000,},
101);
102my %mt_names = (		#names of appropriate engine classes
1033 => {
104	0x0	=> 'MT_NORMAL',						#FVisual
105	0x1	=> 'MT_HIERRARHY',					#FHierrarhyVisual
106	0x2	=> 'MT_PROGRESSIVE',				#FProgressiveFixedVisual
107	0x3	=> 'MT_SKELETON_GEOMDEF_PM',		#CSkeletonX_PM
108	0x4	=> 'MT_SKELETON_ANIM',				#CKinematics						//CKinematicsAnimated since build 1510
109	0x6	=> 'MT_DETAIL_PATCH',				#FDetailPatch
110	0x7	=> 'MT_SKELETON_GEOMDEF_ST',		#CSkeletonX_ST
111	0x8	=> 'MT_CACHED',						#FCached
112	0x9	=> 'MT_PARTICLE',					#CPSVisual
113	0xa	=> 'MT_PROGRESSIVE2',				#FProgressive
114	0xb	=> 'MT_LOD',						#FLod
115	0xc	=> 'MT_TREE',						#FTreeVisual
116	0xd	=> 'MT_PARTICLE_EFFECT',			#PS::CParticleEffect, not used		//introduced in build 1510
117	0xe	=> 'MT_PARTICLE_GROUP',				#PS::CParticleGroup, not used		//introduced in build 1510
118	0xf	=> 'MT_SKELETON_RIGID',				#CKinematics						//introduced in build 1510
119	},
1204 => {
121	0x0	=> 'MT_NORMAL',						#FVisual
122	0x1	=> 'MT_HIERRARHY',					#FHierrarhy_Visual
123	0x2	=> 'MT_PROGRESSIVE',				#FProgressive
124	0x3	=> 'MT_SKELETON_ANIM',				#CKinematics_Animated		#CSkeletonAnimated before 2205
125	0x4	=> 'MT_SKELETON_GEOMDEF_PM',		#CSkeletonX_PM
126	0x5	=> 'MT_SKELETON_GEOMDEF_ST',		#CSkeletonX_ST
127	0x6	=> 'MT_LOD',						#FLod
128	0x7	=> 'MT_TREE_ST',					#FTreeVisual_ST
129	0x8	=> 'MT_PARTICLE_EFFECT',			#CParticleEffect, not used
130	0x9	=> 'MT_PARTICLE_GROUP',				#CParticleGroup, not used
131	0xa	=> 'MT_SKELETON_RIGID',				#CKinematics
132	0xb	=> 'MT_TREE_PM',					#FTreeVisual_PM				#introduced in build 1957
133	},
134);
135my %chunk_names = (
1363 => {
137	'OGF_HEADER'		=>	0x1,
138	'OGF_TEXTURE'		=>	0x2,
139	'OGF_TEXTURE_L'		=>	0x3,
140	'OGF_CHILD_REFS'	=>	0x5,
141	'OGF_BBOX'			=>	0x6,
142	'OGF_VERTICES'		=>	0x7,
143	'OGF_INDICES'		=>	0x8,
144	'OGF_LODDATA'		=>	0x9,
145	'OGF_VCONTAINER'	=>	0xa,
146	'OGF_BSPHERE'		=>	0xb,
147	'OGF_CHILDREN_L'	=>	0xc,
148	'OGF_S_BONE_NAMES'	=>	0xd,
149	'OGF_S_MOTIONS_0'	=>	0xe,
150	'OGF_DPATCH'		=>	0xf,
151	'OGF_S_LODS'		=>	0x10,
152	'OGF_CHILDREN'		=>	0x11,
153	'OGF_S_SMPARAMS_0'	=>	0x12,
154	'OGF_ICONTAINER'	=>	0x13,
155	'OGF_S_SMPARAMS_1'	=>	0x14,
156	'OGF_LODDEF2'		=>	0x15,
157	'OGF_TREEDEF2'		=>	0x16,
158	'OGF_S_IKDATA_0'	=>	0x17,
159	'OGF_S_USERDATA'	=>	0x18,
160	'OGF_S_IKDATA_1'	=>	0x19,
161	'OGF_S_MOTIONS_1'	=>	0x1A,
162	'OGF_S_DESC'		=>	0x1B,
163	'OGF_S_IKDATA_2'	=>	0x1C,
164	'OGF_S_MOTION_REFS_0'	=>	0x1D,
165	},
1664 => {
167	'OGF_HEADER'		=>	0x1,
168	'OGF_TEXTURE'		=>	0x2,
169	'OGF_VERTICES'		=>	0x3,
170	'OGF_INDICES'		=>	0x4,
171	'OGF_P_MAP'			=>	0x5,			#used before build 1925
172	'OGF_SWIDATA'		=>	0x6,
173	'OGF_VCONTAINER'	=>	0x7,			#used before build 2205
174	'OGF_ICONTAINER'	=>	0x8,			#used before build 2205
175	'OGF_CHILDREN'		=>	0x9,
176	'OGF_CHILDREN_L'	=>	0xa,
177	'OGF_LODDEF2'		=>	0xb,
178	'OGF_TREEDEF2'		=>	0xc,
179	'OGF_S_BONE_NAMES'	=>	0xd,
180	'OGF_S_MOTIONS_1'	=>	0xe,			#used before build 2205
181	'OGF_S_SMPARAMS_1'	=>	0xf,			#used before build 2205
182	'OGF_S_IKDATA_2'	=>	0x10,
183	'OGF_S_USERDATA'	=>	0x11,
184	'OGF_S_DESC'		=>	0x12,
185	'OGF_S_MOTION_REFS_0'	=>	0x13,
186	'OGF_SWICONTAINER'	=>	0x14,			#introduced in build 1957
187	'OGF_GCONTAINER'	=>	0x15,			#introduced in build 2205
188	'OGF_FASTPATH'		=>	0x16,			#introduced in build 2205
189	'OGF_S_LODS'		=>	0x17,			#introduced in build ????
190	'OGF_S_MOTION_REFS_1'	=>	0x18,		#introduced in Clear Sky
191	},
192);
193sub new {
194	my $class = shift;
195	my $self = {};
196	$self->{bbox} = {};
197	$self->{bsphere} = {};
198	$self->{c_scale} = {};
199	$self->{c_bias} = {};
200	$self->{fast_swidata} = {};
201	$self->{loaded_chunks} = 0;
202	bless $self, $class;
203	return $self;
204}
205sub read {
206	my $self = shift;
207	my ($cf) = @_;
208	if (!($cf->find_chunk(0x1))) {
209		stkutils::debug::fail(__PACKAGE__.'::read', __LINE__, '$cf->find_chunk(OGF_HEADER)', 'cannot find OGF_HEADER');
210	}
211	$self->read_header($cf);
212	$cf->close_found_chunk();
213	SWITCH: {
214		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_NORMAL' && do { $self->read_visual($cf); last SWITCH; };
215		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_HIERRARHY' && do { $self->read_hierrarhy_visual($cf); last SWITCH; };
216		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_PROGRESSIVE' && do { $self->read_progressive($cf); last SWITCH; };
217		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_ANIM' && do { $self->read_kinematics_animated($cf); last SWITCH; };
218		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_GEOMDEF_PM' && do { $self->read_skeletonx_pm($cf); last SWITCH; };
219		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_GEOMDEF_ST' && do { $self->read_skeletonx_st($cf); last SWITCH; };
220		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_PROGRESSIVE2' && do { $self->read_progressive2($cf); last SWITCH; };
221		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_LOD' && do { $self->read_lod($cf); last SWITCH; };
222		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_TREE' && do { $self->read_tree_visual_st($cf); last SWITCH; };
223		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_TREE_ST' && do { $self->read_tree_visual_st($cf); last SWITCH; };
224		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_RIGID' && do { $self->read_kinematics($cf); last SWITCH; };
225		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_TREE_PM' && do { $self->read_tree_visual_pm($cf); last SWITCH; };
226		stkutils::debug::fail(__PACKAGE__.'::read', __LINE__, '', 'unexpected model type '.$self->{type});
227	}
228}
229sub read_header {
230	my $self = shift;
231	my ($cf) = @_;
232	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
233	($self->{format_version}, $self->{type}, $self->{shader_id}) = $packet->unpack('CCv');
234	if ($self->{format_version} == 4) {
235		$self->read_bbox($packet);
236		$self->read_bsphere($packet);
237	}
238	$self->set_loaded('OGF_HEADER');
239}
240sub read_render_visual {
241	my $self = shift;
242	my ($cf) = @_;
243	if ($self->{format_version} == 3) {
244		if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_BBOX'})) {
245			my $packet = stkutils::data_packet->new($cf->r_chunk_data());
246			$self->read_bbox($packet);
247			$cf->close_found_chunk();
248		} else {
249			stkutils::debug::fail(__PACKAGE__.'::read_render_visual', __LINE__, '$cf->find_chunk(OGF_BBOX)', 'cannot find OGF_BBOX');
250		}
251	}
252	if ($self->{format_version} == 3 && $cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_BSPHERE'})) {
253		my $packet = stkutils::data_packet->new($cf->r_chunk_data());
254		$self->read_bsphere($packet);
255		$cf->close_found_chunk();
256	}
257	if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_S_DESC'})) {
258		$self->read_s_desc($cf);
259		$cf->close_found_chunk();
260	}
261	if ($self->{format_version} == 3 && $cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_TEXTURE_L'})) {
262		$self->read_texture_l($cf);
263		$cf->close_found_chunk();
264	} elsif ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_TEXTURE'})) {
265		$self->read_texture($cf);
266		$cf->close_found_chunk();
267	}
268};
269sub read_visual {
270	my $self = shift;
271	my ($cf) = @_;
272	$self->read_render_visual($cf);
273	if ($self->{format_version} == 4 && $cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_GCONTAINER'})) {
274		$self->read_gcontainer($cf);
275		$cf->close_found_chunk();
276		if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_FASTPATH'})) {
277			$self->read_fastpath($cf) ;
278			$cf->close_found_chunk();
279		}
280	}
281	if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_VCONTAINER'})) {
282		$self->read_vcontainer($cf);
283		$cf->close_found_chunk();
284	} elsif ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_VERTICES'})) {
285		$self->read_vertices($cf);
286		$cf->close_found_chunk();
287	}
288	if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_ICONTAINER'})) {
289		$self->read_icontainer($cf);
290		$cf->close_found_chunk();
291	} elsif ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_INDICES'})) {
292		$self->read_indices($cf);
293		$cf->close_found_chunk();
294	}
295}
296sub read_hierrarhy_visual {
297	my $self = shift;
298	my ($cf) = @_;
299	$self->read_render_visual($cf);
300	if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_CHILDREN_L'})) {
301		$self->read_children_l($cf);
302	} elsif ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_CHILDREN'})) {
303		$self->read_children($cf);
304	} elsif ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_CHILD_REFS'})) {
305		$self->read_child_refs($cf);
306	} else {
307		stkutils::debug::fail(__PACKAGE__.'::read_hierrarhy_visual', __LINE__, '', 'Invalid visual, no children');
308	}
309	$cf->close_found_chunk();
310};
311sub read_progressive {
312	my $self = shift;
313	my ($cf) = @_;
314	$self->read_visual($cf);
315	if ($self->{format_version} == 4 && $cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_SWIDATA'})) {
316		$self->read_swidata($cf);
317		$cf->close_found_chunk();
318	} else {
319		if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_LODDATA'})) {
320			$self->read_loddata($cf);
321			$cf->close_found_chunk();
322		} else {
323			stkutils::debug::fail(__PACKAGE__.'::read_progressive', __LINE__, '', 'Invalid visual, no loddata');
324		}
325	}
326}
327sub read_kinematics {
328	my $self = shift;
329	my ($cf) = @_;
330	$self->read_hierrarhy_visual($cf);
331	if ($self->{format_version} == 4) {
332		my $size = $cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_S_LODS'});
333		if ($size) {
334			if ($size < 0x100) {
335				$self->read_s_lods_csky($cf);
336			} else {
337				$self->read_s_lods($cf);
338			}
339			$cf->close_found_chunk();
340		}
341	}
342	if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_S_USERDATA'})) {
343		$self->read_s_userdata($cf);
344		$cf->close_found_chunk();
345	}
346	if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_S_BONE_NAMES'})) {
347		$self->read_s_bone_names($cf);
348		$cf->close_found_chunk();
349	} else {
350		stkutils::debug::fail(__PACKAGE__.'::read_kinematics', __LINE__, '$cf->find_chunk('.$chunk_names{$self->{format_version}}{'OGF_S_BONE_NAMES'}.')', 'cannot find OGF_S_BONE_NAMES');
351	}
352	if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_S_IKDATA_2'})) {
353		$self->read_s_ikdata($cf, 2);
354		$cf->close_found_chunk();
355	} elsif ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_S_IKDATA_1'})) {
356		$self->read_s_ikdata($cf, 1);
357		$cf->close_found_chunk();
358	} elsif ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_S_IKDATA_0'})) {
359		stkutils::debug::fail(__PACKAGE__.'::read_kinematics', __LINE__, '', 'sorry, ik data of builds 15xx not implemented yet');
360		$self->read_s_ikdata($cf, 0);
361		$cf->close_found_chunk();
362	}
363}
364sub read_kinematics_animated {
365	my $self = shift;
366	my ($cf) = @_;
367	$self->read_kinematics($cf);
368	if ($self->{format_version} == 4 && $cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_S_MOTION_REFS_1'})) {
369		$self->read_smotion_refs_1($cf);
370		$cf->close_found_chunk();
371		return;
372	} elsif ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_S_MOTION_REFS_0'})) {
373		$self->read_smotion_refs_0($cf);
374		$cf->close_found_chunk();
375		return;
376	} elsif ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_S_SMPARAMS_1'})) {
377		$self->read_s_smparams($cf, 1);
378		$cf->close_found_chunk();
379	} elsif ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_S_SMPARAMS_0'})) {
380		$self->read_s_smparams($cf, 0);
381		$cf->close_found_chunk();
382	}
383	if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_S_MOTIONS_1'})) {
384		$self->read_smotions($cf, 1);
385		$cf->close_found_chunk();
386	} elsif ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_S_MOTIONS_0'})) {
387		$self->read_smotions($cf, 0);
388		$cf->close_found_chunk();
389	} else {
390		stkutils::debug::fail(__PACKAGE__.'::read_kinematics_animated', __LINE__, '', 'Invalid visual, no motions');
391	}
392}
393sub read_skeletonx {
394	my $self = shift;
395	my ($cf) = @_;
396	if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_VERTICES'})) {
397		$cf->close_found_chunk();
398		return;
399	} else {
400		stkutils::debug::fail(__PACKAGE__.'::read_skeletonx', __LINE__, '$cf->find_chunk('.$chunk_names{$self->{format_version}}{'OGF_VERTICES'}.')', 'cannot find OGF_VERTICES');
401	}
402}
403sub read_skeletonx_pm {
404	my $self = shift;
405	my ($cf) = @_;
406	$self->read_skeletonx($cf);
407	$self->read_progressive($cf);
408}
409sub read_skeletonx_st {
410	my $self = shift;
411	my ($cf) = @_;
412	$self->read_skeletonx($cf);
413	$self->read_visual($cf);
414}
415sub read_progressive2 {
416	my $self = shift;
417	my ($cf) = @_;
418	$self->read_render_visual($cf);
419	if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_LODS'})) {
420		$self->read_s_lods($cf);
421		$cf->close_found_chunk();
422	} else {
423		stkutils::debug::fail('ogf::read_progressive', 417, 'Invalid visual, no lods');
424	}
425}
426sub read_lod {
427	my $self = shift;
428	my ($cf) = @_;
429	$self->read_hierrarhy_visual($cf);
430	if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_LODDEF2'})) {
431		$self->read_loddef2($cf);
432		$cf->close_found_chunk();
433	} else {
434		stkutils::debug::fail('ogf::read_lod', 428, 'cannot find chunk OGF_LODDEF2');
435	}
436}
437sub read_tree_visual {
438	my $self = shift;
439	my ($cf) = @_;
440	$self->read_visual($cf);
441	if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_TREEDEF2'})) {
442		$self->read_treedef2($cf);
443		$cf->close_found_chunk();
444	} else {
445		stkutils::debug::fail(__PACKAGE__.'::read_tree_visual', __LINE__, '!$cf->find_chunk(OGF_TREEDEF2)', 'cannot find OGF_TREEDEF2');
446	}
447}
448sub read_tree_visual_st {
449	my $self = shift;
450	my ($cf) = @_;
451	$self->read_tree_visual($cf);
452}
453sub read_tree_visual_pm {
454	my $self = shift;
455	my ($cf) = @_;
456	$self->read_tree_visual($cf);
457	if ($cf->find_chunk($chunk_names{$self->{format_version}}{'OGF_SWICONTAINER'})) {
458		$self->read_swicontainer($cf);
459		$cf->close_found_chunk();
460	} else {
461		stkutils::debug::fail('ogf::read_tree_visual_pm', 457, 'cannot find OGF_SWICONTAINER');
462	}
463}
464sub read_bbox {
465	my $self = shift;
466	my ($packet) = @_;
467	@{$self->{bbox}->{min}} = $packet->unpack('f3');
468	@{$self->{bbox}->{max}} = $packet->unpack('f3');
469	$self->set_loaded('OGF_BBOX');
470}
471sub read_bsphere {
472	my $self = shift;
473	my ($packet) = @_;
474	@{$self->{bsphere}->{c}} = $packet->unpack('f3');
475	($self->{bsphere}->{r}) = $packet->unpack('f');
476	$self->set_loaded('OGF_BSPHERE');
477}
478sub read_s_desc {
479	my $self = shift;
480	my ($cf) = @_;
481	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
482	($self->{ogf_object}, $self->{ogf_creator},
483	 $self->{unk},
484	 $self->{creator}, $self->{create_time},
485	 $self->{editor}, $self->{edit_time}) = $packet->unpack('Z*Z*VZ*VZ*V');
486	stkutils::debug::fail('ogf::read_s_desc', 486, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
487	$self->set_loaded('OGF_S_DESC');
488}
489sub read_texture_l {
490	my $self = shift;
491	my ($cf) = @_;
492	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
493	($self->{texture_id}, $self->{shader_id}) = $packet->unpack('VV');
494	$self->set_loaded('OGF_TEXTURE_L');
495	stkutils::debug::fail('ogf::read_texture_l', 495, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
496}
497sub read_texture {
498	my $self = shift;
499	my ($cf) = @_;
500	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
501	($self->{texture_name}, $self->{shader_name}) = $packet->unpack('Z*Z*');
502	$self->set_loaded('OGF_TEXTURE');
503	stkutils::debug::fail('ogf::read_texture', __LINE__, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
504}
505sub read_gcontainer {
506	my $self = shift;
507	my ($cf) = @_;
508	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
509	($self->{ext_vb_index}, $self->{ext_vb_offset}, $self->{ext_vb_size},
510	$self->{ext_ib_index}, $self->{ext_ib_offset}, $self->{ext_ib_size}) = $packet->unpack('VVVVVV');
511	$self->set_loaded('OGF_GCONTAINER');
512}
513sub read_fastpath {
514	my $self = shift;
515	my ($cf) = @_;
516	$self->{m_fast} = $cf->r_chunk_data();
517#	$cf->find_chunk(0x15);
518#	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
519#	@{$self->{fast_gcontainer}} = $packet->unpack('V6');
520#	$cf->close_found_chunk();
521#	if ($cf->find_chunk(0x6)) {
522#		read_swidata($self->{fast_swidata}, $cf);
523#		$cf->close_found_chunk();
524#	}
525	$self->set_loaded('OGF_FASTPATH');
526}
527sub read_vcontainer {
528	my $self = shift;
529	my ($cf) = @_;
530	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
531	($self->{ext_vb_index}, $self->{ext_vb_offset}, $self->{ext_vb_size}) = $packet->unpack('VVV');
532	$self->set_loaded('OGF_VCONTAINER');
533}
534sub read_vertices {
535	my $self = shift;
536	my ($cf) = @_;
537	my $packet = stkutils::data_packet->new($cf->r_chunk_data(8));
538	($self->{vertex_format}, $self->{vertex_count}) = $packet->unpack('VV');
539	if ($self->{vertex_format} == OGF_VERTEXFORMAT_FVF_OLD) {
540		for (my $i = 0; $i < $self->{vertex_count}; $i++) {
541			my $packet = stkutils::data_packet->new($cf->r_chunk_data(32));
542			my $vertice = {};
543			@{$vertice->{point}} = $packet->unpack('f3');
544			@{$vertice->{normal}} = $packet->unpack('f3');
545			@{$vertice->{textcoords}} = $packet->unpack('f2');
546			push @{$self->{vertices}}, $vertice;
547		}
548	} elsif ($self->{format_version} == 3 && $self->{vertex_format} == OGF_VERTEXFORMAT_FVF_1L) {
549		for (my $i = 0; $i < $self->{vertex_count}; $i++) {
550			my $packet = stkutils::data_packet->new($cf->r_chunk_data(36));
551			my $vertice = {};
552			@{$vertice->{point}} = $packet->unpack('f3');
553			@{$vertice->{normal}} = $packet->unpack('f3');
554			@{$vertice->{textcoords}} = $packet->unpack('f2');
555			($vertice->{matrix}) = $packet->unpack('V');
556			push @{$self->{vertices}}, $vertice;
557		}
558	} elsif ($self->{vertex_format} == OGF_VERTEXFORMAT_FVF_1L or $self->{vertex_format} == OGF_VERTEXFORMAT_FVF_1_CS) {
559		for (my $i = 0; $i < $self->{vertex_count}; $i++) {
560			my $packet = stkutils::data_packet->new($cf->r_chunk_data(60));
561			my $vertice = {};
562			@{$vertice->{point}} = $packet->unpack('f3');
563			@{$vertice->{normal}} = $packet->unpack('f3');
564			@{$vertice->{t}} = $packet->unpack('f3');
565			@{$vertice->{b}} = $packet->unpack('f3');
566			@{$vertice->{textcoords}} = $packet->unpack('f2');
567			($vertice->{matrix}) = $packet->unpack('V');
568			push @{$self->{vertices}}, $vertice;
569		}
570	} elsif ($self->{vertex_format} == OGF_VERTEXFORMAT_FVF_2L or $self->{vertex_format} == OGF_VERTEXFORMAT_FVF_2_CS) {
571		for (my $i = 0; $i < $self->{vertex_count}; $i++) {
572			my $packet = stkutils::data_packet->new($cf->r_chunk_data(64));
573			my $vertice = {};
574			($vertice->{matrix0}, $vertice->{matrix1}) = $packet->unpack('vv');
575			@{$vertice->{point}} = $packet->unpack('f3');
576			@{$vertice->{normal}} = $packet->unpack('f3');
577			@{$vertice->{t}} = $packet->unpack('f3');
578			@{$vertice->{b}} = $packet->unpack('f3');
579			($vertice->{w}) = $packet->unpack('f');
580			@{$vertice->{textcoords}} = $packet->unpack('f2');
581			push @{$self->{vertices}}, $vertice;
582		}
583	} elsif ($self->{vertex_format} == OGF_VERTEXFORMAT_FVF_3_CS) {
584		for (my $i = 0; $i < $self->{vertex_count}; $i++) {
585			my $packet = stkutils::data_packet->new($cf->r_chunk_data(70));
586			my $vertice = {};
587			($vertice->{matrix0}, $vertice->{matrix1}, $vertice->{matrix2}) = $packet->unpack('vvv');
588			@{$vertice->{point}} = $packet->unpack('f3');
589			@{$vertice->{normal}} = $packet->unpack('f3');
590			@{$vertice->{t}} = $packet->unpack('f3');
591			@{$vertice->{b}} = $packet->unpack('f3');
592			($vertice->{w0}, $vertice->{w1}) = $packet->unpack('ff');
593			@{$vertice->{textcoords}} = $packet->unpack('f2');
594			push @{$self->{vertices}}, $vertice;
595		}
596	} elsif ($self->{vertex_format} == OGF_VERTEXFORMAT_FVF_4_CS) {
597		for (my $i = 0; $i < $self->{vertex_count}; $i++) {
598			my $packet = stkutils::data_packet->new($cf->r_chunk_data(76));
599			my $vertice = {};
600			($vertice->{matrix0}, $vertice->{matrix1}, $vertice->{matrix2}, $vertice->{matrix3}) = $packet->unpack('vvvv');
601			@{$vertice->{point}} = $packet->unpack('f3');
602			@{$vertice->{normal}} = $packet->unpack('f3');
603			@{$vertice->{t}} = $packet->unpack('f3');
604			@{$vertice->{b}} = $packet->unpack('f3');
605			($vertice->{w0}, $vertice->{w1}, $vertice->{w2}) = $packet->unpack('fff');
606			@{$vertice->{textcoords}} = $packet->unpack('f2');
607			push @{$self->{vertices}}, $vertice;
608		}
609	} else {
610		stkutils::debug::fail('ogf::read_vertices', 605, 'unsupported FVF');
611	}
612	stkutils::debug::fail('ogf::read_vertices', 607, 'there some data in packet left: '.($cf->{end_offsets}[$#{$cf->{end_offsets}}] - $cf->{offset})) unless $cf->{offset} == $cf->{end_offsets}[$#{$cf->{end_offsets}}];
613	$self->set_loaded('OGF_VERTICES');
614}
615sub read_icontainer {
616	my $self = shift;
617	my ($cf) = @_;
618	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
619	($self->{ext_ib_index}, $self->{ext_ib_offset}, $self->{ext_ib_size}) = $packet->unpack('VVV');
620	$self->set_loaded('OGF_ICONTAINER');
621}
622sub read_indices {
623	my $self = shift;
624	my ($cf) = @_;
625	my $packet = stkutils::data_packet->new($cf->r_chunk_data(4));
626	($self->{indices_count}) = $packet->unpack('V');
627	for (my $i = 0; $i < $self->{indices_count}; $i++) {
628		my $packet = stkutils::data_packet->new($cf->r_chunk_data(2));
629		my ($index) = $packet->unpack('v');
630		push @{$self->{indices}}, $index;
631	}
632	stkutils::debug::fail('ogf::read_indices', 627, 'there some data in packet left: '.($cf->{end_offsets}[$#{$cf->{end_offsets}}] - $cf->{offset})) unless $cf->{offset} == $cf->{end_offsets}[$#{$cf->{end_offsets}}];
633	$self->set_loaded('OGF_INDICES');
634}
635sub read_children_l {
636	my $self = shift;
637	my ($cf) = @_;
638	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
639	@{$self->{children_l}} = $packet->unpack('V/V');
640	stkutils::debug::fail('ogf::read_children_l', 635, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
641	$self->set_loaded('OGF_CHILDREN_L');
642}
643sub read_children {
644	my $self = shift;
645	my ($cf) = @_;
646	for (my $expected_id = 0;; $expected_id++) {
647		my ($nnnn, $size) = $cf->r_chunk_open();
648		defined $nnnn or last;
649		if ($nnnn != $expected_id) {
650			stkutils::debug::fail('ogf::read_children', 645, 'unexpected chunk $nnnn');
651		}
652		my $child = stkutils::ogf->new();
653		$child->read($cf);
654		$cf->r_chunk_close();
655		push @{$self->{children}}, $child;
656	}
657	$self->set_loaded('OGF_CHILDREN');
658}
659sub read_child_refs {
660	my $self = shift;
661	my ($cf) = @_;
662	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
663	my ($count) = $packet->unpack('V');
664	for (my $i = 0; $i < $count; $i++) {
665		my ($ref) = $packet->unpack('Z*');
666		push @{$self->{child_refs}}, $ref;
667	}
668	stkutils::debug::fail('ogf::read_child_refs', 664, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
669	$self->set_loaded('OGF_CHILD_REFS');
670}
671sub read_swidata {
672	my $self = shift;
673	my ($cf) = @_;
674	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
675	@{$self->{swi_reserved}} = $packet->unpack('V4');
676	($self->{swi_count}) = $packet->unpack('V');
677	for (my $i = 0; $i < $self->{swi_count}; $i++) {
678		my $swi = {};
679		($swi->{offset},
680		$swi->{num_tris},
681		$swi->{num_verts}) = $packet->unpack('lvv');
682		push @{$self->{swi_data}}, $swi;
683	}
684	stkutils::debug::fail('ogf::read_swidata', 675, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
685	set_loaded($self, 'OGF_SWIDATA');
686}
687sub read_loddata {
688	my $self = shift;
689	my ($cf) = @_;
690	my $loddata = {};
691	while (1) {
692		my ($id, $size) = $cf->r_chunk_open();
693		defined $id or last;
694			SWITCH: {
695				$id == OGF3_HOPPE_HEADER && do { read_hoppe_header($loddata, $cf); last SWITCH; };
696				$id == OGF3_HOPPE_VERT_SPLITS && do {read_hoppe_vertsplits($loddata, $self, $cf); last SWITCH; };
697				$id == OGF3_HOPPE_FIX_FACES && do {read_hoppe_fix_faces($loddata, $cf); last SWITCH; };
698				stkutils::debug::fail('ogf::read_loddata', 694, "unexpected chunk $id");
699			}
700		$cf->r_chunk_close();
701	}
702	$cf->r_chunk_close();
703	push @{$self->{loddata}}, $loddata;
704	$self->set_loaded('OGF_LODDATA');
705}
706sub read_hoppe_header {
707	my $self = shift;
708	my ($cf) = @_;
709	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
710	($self->{min_vertices}, $self->{max_vertices}) = $packet->unpack('VV');
711}
712sub read_hoppe_vertsplits {
713	my $self = shift;
714	my ($global, $cf) = @_;
715	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
716	$self->{num_vertsplits} = $global->{vertex_count} - $self->{min_vertices};
717	for (my $i = 0; $i < $self->{num_vertsplits}; $i++) {
718		my $split = {};
719		($split->{vert}, $split->{num_tris}, $split->{num_verts}) = $packet->unpack('vCC');
720		push @{$self->{vertsplits}}, $split;
721	}
722}
723sub read_hoppe_fix_faces {
724	my $self = shift;
725	my ($cf) = @_;
726	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
727	($self->{num_fix_faces}) = $packet->unpack('V');
728	@{$self->{fix_faces}} = $packet->unpack("(v)$self->{num_fix_faces}");
729}
730sub read_s_lods_csky {
731	my $self = shift;
732	my ($cf) = @_;
733	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
734	($self->{s_lods_ref}) = $packet->unpack('Z*');
735	$self->set_loaded('OGF_S_LODS_CSKY');
736}
737sub read_s_lods {
738	my $self = shift;
739	my ($cf) = @_;
740	for (my $expected_id = 0;; $expected_id++) {
741		my ($nnnn, $size) = $cf->r_chunk_open();
742		defined $nnnn or last;
743		if ($nnnn != $expected_id) {
744			stkutils::debug::fail('ogf::read_s_lods', 741, 'unexpected chunk $nnnn');
745		}
746		my $lod = stkutils::ogf->new();
747		$lod->read($cf);
748		$cf->r_chunk_close();
749		push @{$self->{s_lods}}, $lod;
750	}
751	$self->set_loaded('OGF_S_LODS');
752}
753sub read_s_userdata {
754	my $self = shift;
755	my ($cf) = @_;
756	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
757	my $n = $packet->length();
758	($self->{userdata}) = $packet->unpack("a$n");
759	stkutils::debug::fail('ogf::read_s_userdata', 757, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
760	$self->set_loaded('OGF_S_USERDATA');
761}
762sub read_s_bone_names {
763	my $self = shift;
764	my ($cf) = @_;
765	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
766	my ($count) = $packet->unpack('V');
767	for (my $i = 0; $i < $count; $i++) {
768		my $bone_name_obj = {};
769		($bone_name_obj->{name}, $bone_name_obj->{parent_name}) = $packet->unpack('Z*Z*');
770		read_obb($bone_name_obj, $packet);
771		push @{$self->{bone_names}}, $bone_name_obj;
772	}
773	$self->set_loaded('OGF_S_BONE_NAMES');
774	stkutils::debug::fail('ogf::read_s_bone_names', 772, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
775}
776sub read_obb {
777	my $self = $_[0];
778	@{$self->{rotate}} = $_[1]->unpack('f9');
779	@{$self->{translate}} = $_[1]->unpack('f3');
780	@{$self->{halfsize}} = $_[1]->unpack('f3');
781}
782sub read_s_ikdata {
783#loc_44C45A:
784#mov     ecx, [esi+4]
785#mov     edx, [esp+1B8h+var_1AC]
786#mov     esi, [ecx+edx*4]
787#lea     eax, [esi+0E8h]
788#push    eax
789#mov     ecx, edi
790#call    ds:?r_stringZ@IReader@@QAEXPAD@Z ; IReader::r_stringZ(char *)
791#push    70h
792#lea     ecx, [esi+78h]
793#push    ecx
794#mov     ecx, edi
795#call    ds:?r@IReader@@QAEXPAXH@Z ; IReader::r(void *,int)
796#push    3Ch
797#lea     edx, [esi+128h]
798#push    edx
799#mov     ecx, edi
800#call    ds:?r@IReader@@QAEXPAXH@Z ; IReader::r(void *,int)
801#push    0Ch
802#lea     eax, [esi+60h]
803#push    eax
804#mov     ecx, edi
805#call    ds:?r@IReader@@QAEXPAXH@Z ; IReader::r(void *,int)
806#push    0Ch
807#lea     ecx, [esi+6Ch]
808#push    ecx
809#mov     ecx, edi
810#call    ds:?r@IReader@@QAEXPAXH@Z ; IReader::r(void *,int)
811#push    4
812#lea     edx, [esp+1BCh+var_190]
813#push    edx
814#mov     ecx, edi
815#call    ds:?r@IReader@@QAEXPAXH@Z ; IReader::r(void *,int)
816#mov     eax, [esp+1B8h+var_190]
817#mov     [esi+164h], eax
818#push    0Ch
819#add     esi, 168h
820#push    esi
821#mov     ecx, edi
822#call    ds:?r@IReader@@QAEXPAXH@Z ; IReader::r(void *,int)
823#add     [esp+1B8h+var_1AC], 1
824#mov     edx, [esp+1B8h+var_1AC]
825#jmp     loc_44C410
826	my $self = shift;
827	my ($cf, $mode) = @_;
828	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
829	while ($packet->length()) {
830		my $ik = {};
831		$ik->{bone_shape} = {};
832		$ik->{joint_data} = {};
833		if ($mode == 2) {
834			($ik->{version}, $ik->{game_mtl_name}) = $packet->unpack('VZ*');
835		} else {
836			($ik->{game_mtl_name}) = $packet->unpack('Z*');
837		}
838		read_s_bone_shape($ik->{bone_shape}, $packet);
839		read_s_joint_ik_data($ik->{joint_data}, $packet, $ik->{version});
840		@{$ik->{bind_rotation}} = $packet->unpack('f3');
841		@{$ik->{bind_position}} = $packet->unpack('f3');
842		($ik->{mass}, @{$ik->{center_of_mass}}) = $packet->unpack('ff3');
843		push @{$self->{ik_data}}, $ik;
844	}
845	$self->set_loaded('OGF_S_IKDATA_'.$mode);
846	stkutils::debug::fail('ogf::read_s_ikdata', 801, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
847}
848sub read_s_bone_shape {
849	my $self = $_[0];
850	($self->{type}, $self->{flags}) = $_[1]->unpack('vv');
851	$self->{box} = {};
852	$self->{sphere} = {};
853	$self->{cylinder} = {};
854	read_obb($self->{box}, $_[1]);
855	read_sphere($self->{sphere}, $_[1]);
856	read_cylinder($self->{cylinder}, $_[1]);
857}
858sub read_sphere {
859	my $self = $_[0];
860	@{$self->{p}} = $_[1]->unpack('f3');
861	($self->{r}) = $_[1]->unpack('f');
862}
863sub read_cylinder {
864	my $self = $_[0];
865	@{$self->{center}} = $_[1]->unpack('f3');
866	@{$self->{direction}} = $_[1]->unpack('f3');
867	($self->{height}) = $_[1]->unpack('f');
868	($self->{radius}) = $_[1]->unpack('f');
869}
870sub read_s_joint_ik_data {
871	my $self = shift;
872	my ($packet, $version) = @_;
873	($self->{type}) = $packet->unpack('V');
874	@{$self->{limits}} = [];
875	${$self->{limits}}[0] = {};
876	read_s_joint_limit(${$self->{limits}}[0], $packet);
877	${$self->{limits}}[1] = {};
878	read_s_joint_limit(${$self->{limits}}[1], $packet);
879	${$self->{limits}}[2] = {};
880	read_s_joint_limit(${$self->{limits}}[2], $packet);
881	($self->{spring_factor},
882	 $self->{damping_factor},
883	 $self->{ik_flags},
884	 $self->{break_force},
885	 $self->{break_torque}) = $packet->unpack('ffVff');
886	($self->{friction}) = $packet->unpack('f') if defined $version;
887}
888sub read_s_joint_limit {
889	my $self = $_[0];
890	@{$self->{limit}} = $_[1]->unpack('f2');
891	($self->{spring_factor}, $self->{damping_factor}) = $_[1]->unpack('ff');
892}
893sub read_smotion_refs_1 {
894	my $self = shift;
895	my ($cf) = @_;
896	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
897	@{$self->{motion_refs_1}} = $packet->unpack('V/(Z*)');
898	stkutils::debug::fail('ogf::read_smotion_refs_1', 853, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
899	$self->set_loaded('OGF_S_MOTION_REFS_1');
900}
901sub read_smotion_refs_0 {
902	my $self = shift;
903	my ($cf) = @_;
904	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
905	($self->{motion_refs_0}) = $packet->unpack('Z*');
906	stkutils::debug::fail('ogf::read_smotion_refs_0', 854, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
907	$self->set_loaded('OGF_S_MOTION_REFS_0');
908}
909sub read_s_smparams {
910	my $self = shift;
911	my ($cf, $mode) = @_;
912	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
913	($self->{sm_params_version}) = $packet->unpack('v') if $mode == 1;  ### build 1865 - exists!!!
914	my ($partition_count) = $packet->unpack('v');
915	for (my $i = 0; $i < $partition_count; $i++) {
916		my $part = {};
917		($part->{name}, $part->{bone_count}) = $packet->unpack('Z*v');
918		for (my $i = 0; $i < $part->{bone_count}; $i++) {
919			my $bone = {};
920			if ($mode == 0 || $self->{sm_params_version} == 1) {
921				($bone->{bone_id}) = $packet->unpack('V');
922			} elsif ($self->{sm_params_version} == 2) {
923				($bone->{bone_name}) = $packet->unpack('Z*');
924			} elsif ($self->{sm_params_version} == 3 || $self->{sm_params_version} == 4) {
925				($bone->{bone_name}, $bone->{bone_id}) = $packet->unpack('Z*V');
926			}
927			push @{$part->{bones}}, $bone;
928		}
929		push @{$self->{s_smparams_partitions}}, $part;
930	}
931	my ($motion_count) = $packet->unpack('v');
932	for (my $i = 0; $i < $motion_count; $i++) {
933		my $mot = {};
934		if ($mode == 1) {
935			($mot->{name}, $mot->{flags}) = $packet->unpack('Z*V');
936			read_motion_def($mot, $packet);
937			if ($self->{sm_params_version} == 4) {
938				my ($num_marks) = $packet->unpack('V');
939				for (my $j = 0; $j < $num_marks; ++$j) {
940					my $mmark = {};
941					read_motion_mark($mmark, $packet);
942					push @{$mot->{mmarks}}, $mmark;
943				}
944			}
945		} else {
946			($mot->{name}, $mot->{flags}) = $packet->unpack('Z*C');
947			read_motion_def($mot, $packet);
948			my $flag = $packet->unpack('C');
949			$mot->{flags} += 0x2 if $flag != 1;
950		}
951		push @{$self->{s_smparams_motions}}, $mot;
952	}
953	stkutils::debug::fail('ogf::read_s_smparams', 908, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
954	$self->set_loaded('OGF_S_SMPARAMS_'.$mode);
955}
956sub read_motion_def {
957	my $self = $_[0];
958	($self->{bone_or_part},
959	 $self->{motion},
960	 $self->{speed},
961	 $self->{power},
962	 $self->{accrue},
963	 $self->{falloff}) = $_[1]->unpack('vvffff');
964}
965sub read_motion_mark {
966	my $self = $_[0];
967	$self->{name} = '';
968	my $c;
969	while (1) {
970		($c) = $_[1]->unpack('a');
971		last if ($c eq "\n" || $c eq "\r");
972		$self->{name} .= $c;
973	}
974	($c) = $_[1]->unpack('a');
975	die unless $c eq "\n";
976	my ($count) = $_[1]->unpack('V');
977	for (my $i = 0; $i < $count; $i++) {
978		my $int = {};
979		($int->{min}, $int->{max}) = $_[1]->unpack('ff');
980		push @{$self->{intervals}}, $int;
981	}
982}
983sub read_smotions {
984	my $self = shift;
985	my ($cf, $mode) = @_;
986	for (my $expected_id = 0;; $expected_id++) {
987		my ($nnnn, $size) = $cf->r_chunk_open();
988		defined $nnnn or last;
989		if ($nnnn != $expected_id) {
990			stkutils::debug::fail('ogf::read_smotions', 945, 'unexpected chunk $nnnn');
991		}
992		if ($nnnn == 0) {
993			$self->{motions_count} = unpack('V', $cf->r_chunk_data());
994		} else {
995			my $motion = {};
996			read_motion($motion, $cf, $mode);
997			push @{$self->{motions}}, $motion;
998		}
999		$cf->r_chunk_close();
1000	}
1001	$self->set_loaded('OGF_S_MOTIONS_'.$mode);
1002}
1003sub read_motion {
1004	my $self = shift;
1005	my ($cf, $mode) = @_;
1006	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
1007	($self->{name}, $self->{keys_count}) = $packet->unpack('Z*V');
1008	if ($self->{keys_count} == 0) {
1009		stkutils::debug::fail('ogf::read_motion', 965, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
1010		return;
1011	}
1012	if ($mode == 1) {
1013		for (my $i = 0; $packet->length() > 0; $i++) {
1014			my @keyst;
1015			my $bone = {};
1016			($bone->{flags}) = $packet->unpack('C');
1017			die unless ($bone->{flags} & ~0x07) == 0;
1018			if ($bone->{flags} & KPF_R_ABSENT) {
1019				@{$bone->{keysr}} = $packet->unpack('s4');
1020			} else {
1021				($bone->{crc_keysr}, @{$bone->{keysr}}) = $packet->unpack("V(s4)$self->{keys_count}");
1022			}
1023	#		dequantize_qr(\@{$bone->{keysr}});
1024			if ($bone->{flags} & KPF_T_PRESENT) {
1025				($bone->{crc_keyst}) = $packet->unpack('V');
1026				if ($bone->{flags} & KPF_T_HQ) {
1027					@{$bone->{keyst}} = $packet->unpack("(s3)$self->{keys_count}");
1028				} else {
1029					@{$bone->{keyst}} = $packet->unpack("(c3)$self->{keys_count}");
1030				}
1031				@{$bone->{sizet}} = $packet->unpack('f3');
1032			} else {
1033				die unless ($bone->{flags} & KPF_T_HQ) == 0;
1034			}
1035			@{$bone->{initt}} = $packet->unpack('f3');
1036			push @{$self->{bones}}, $bone;
1037		}
1038	} else {
1039		for (my $i = 0; $packet->length() > 0; $i++) {
1040			my $bone = {};
1041			@{$bone->{keys}} = $packet->unpack("(s4f3)$self->{keys_count}");
1042	#		dequantize_qr(\@{$bone->{keysr}});
1043			push @{$self->{bones}}, $bone;
1044		}
1045	}
1046	stkutils::debug::fail('ogf::read_motion', 1002, 'there some data in packet left: '.$packet->length()) unless $packet->length() == 0;
1047}
1048sub read_loddef {
1049# if ( !IReader__find_chunk(a3, 21, 0) )
1050#    xrDebug__fail(Debug, "data->find_chunk(OGF_LODDEF)", ".\\FLOD.cpp", 10);
1051#  v6 = (int)((char *)v5 + 144);
1052# v69 = 8;
1053# do
1054#  {
1055 #   v7 = v6 - 56;
1056#    IReader__r(v4, v6 - 56, 96);
1057#    v8 = *(float *)(v6 - 32) - *(float *)(v6 - 56);
1058#    v9 = *(float *)(v6 - 28) - *(float *)(v6 - 52);
1059#    v10 = *(float *)(v6 - 24) - *(float *)(v6 - 48);
1060#    v11 = *(float *)(v6 - 8) - *(float *)(v6 - 32);
1061#    v12 = *(float *)(v6 - 4) - *(float *)(v6 - 28);
1062#    v13 = *(float *)v6 - *(float *)(v6 - 24);
1063#    v48 = v13 * v9 - v12 * v10;
1064#    v52 = v10 * v11 - v13 * v8;
1065#    v56 = v12 * v8 - v9 * v11;
1066#    v70 = v56 * v56 + v52 * v52 + v48 * v48;
1067#    if ( std__numeric_limits_float___min() >= v70 )
1068#    {
1069#      v14 = v56;
1070#    }
1071#    else
1072#    {
1073#      v15 = sqrt(1.0 / v70);
1074#      v48 = v48 * v15;
1075#      v52 = v52 * v15;
1076#      v14 = v15 * v56;
1077#    }
1078#    v66 = v14;
1079#    v16 = *(float *)(v6 - 8) - *(float *)(v6 - 32);
1080#    v60 = v48;
1081#    v63 = v52;
1082#    v17 = *(float *)(v6 - 4) - *(float *)(v6 - 28);
1083#    v18 = *(float *)v6 - *(float *)(v6 - 24);
1084#    v19 = *(float *)(v6 + 16) - *(float *)(v6 - 8);
1085#    v20 = *(float *)(v6 + 20) - *(float *)(v6 - 4);
1086#    v21 = *(float *)(v6 + 24) - *(float *)v6;
1087#    v49 = v21 * v17 - v20 * v18;
1088#    v53 = v18 * v19 - v21 * v16;
1089#    v57 = v20 * v16 - v17 * v19;
1090#    v71 = v57 * v57 + v53 * v53 + v49 * v49;
1091#    if ( std__numeric_limits_float___min() >= v71 )
1092#    {
1093#      v22 = v57;
1094#    }
1095#    else
1096#    {
1097#      v23 = sqrt(1.0 / v71);
1098#      v49 = v49 * v23;
1099#      v53 = v53 * v23;
1100#      v22 = v23 * v57;
1101#    }
1102#    v61 = v49 + v60;
1103#    v64 = v53 + v63;
1104#    v67 = v22 + v66;
1105#    v24 = *(float *)(v6 + 16) - *(float *)(v6 - 8);
1106#    v25 = *(float *)(v6 + 20) - *(float *)(v6 - 4);
1107#    v26 = *(float *)(v6 + 24) - *(float *)v6;
1108#    v27 = *(float *)v7 - *(float *)(v6 + 16);
1109#    v28 = *(float *)(v6 - 52) - *(float *)(v6 + 20);
1110#    v29 = *(float *)(v6 - 48) - *(float *)(v6 + 24);
1111#    v50 = v29 * v25 - v28 * v26;
1112#    v54 = v26 * v27 - v29 * v24;
1113#    v58 = v28 * v24 - v25 * v27;
1114#    v72 = v58 * v58 + v54 * v54 + v50 * v50;
1115#    if ( std__numeric_limits_float___min() >= v72 )
1116#    {
1117#      v30 = v58;
1118#    }
1119#    else
1120#    {
1121#      v31 = sqrt(1.0 / v72);
1122#      v50 = v50 * v31;
1123#      v54 = v54 * v31;
1124#      v30 = v31 * v58;
1125#    }
1126#    v62 = v50 + v61;
1127#    v65 = v54 + v64;
1128#    v68 = v30 + v67;
1129#    v32 = *(float *)v7 - *(float *)(v6 + 16);
1130#    v33 = *(float *)(v6 - 52) - *(float *)(v6 + 20);
1131#    v34 = *(float *)(v6 - 48) - *(float *)(v6 + 24);
1132#    v35 = *(float *)(v6 - 32) - *(float *)v7;
1133#    v36 = *(float *)(v6 - 28) - *(float *)(v6 - 52);
1134#    v37 = *(float *)(v6 - 24) - *(float *)(v6 - 48);
1135#    v51 = v37 * v33 - v36 * v34;
1136#    v55 = v34 * v35 - v37 * v32;
1137#    v59 = v36 * v32 - v33 * v35;
1138#    v73 = v59 * v59 + v55 * v55 + v51 * v51;
1139#   if ( std__numeric_limits_float___min() >= v73 )
1140#    {
1141#      v38 = v59;
1142#    }
1143#    else
1144#    {
1145#      v39 = sqrt(1.0 / v73);
1146#      v51 = v51 * v39;
1147#      v55 = v55 * v39;
1148#      v38 = v39 * v59;
1149#    }
1150#    v6 += 108;
1151#    v40 = v69-- == 1;
1152#    v41 = v38 + v68;
1153#    v42 = (v51 + v62) * 0.25;
1154#    v43 = (v55 + v65) * 0.25;
1155#    v44 = v41 * 0.25;
1156#    v45 = sqrt(1.0 / (v44 * v44 + v43 * v43 + v42 * v42));
1157#    *(float *)(v6 - 68) = v42 * v45;
1158#    *(float *)(v6 - 64) = v43 * v45;
1159#    *(float *)(v6 - 60) = v44 * v45;
1160#    *(float *)(v6 - 68) = -*(float *)(v6 - 68);
1161#    *(float *)(v6 - 64) = -*(float *)(v6 - 64);
1162#    *(float *)(v6 - 60) = -*(float *)(v6 - 60);
1163#  }
1164#  while ( !v40 );
1165}
1166sub read_loddef2 {
1167	my $self = shift;
1168	my ($cf) = @_;
1169	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
1170	for (my $i = 0; $i < 8; $i++) {
1171		my $lod_face = {};
1172		for (my $j = 0; $j < 4; $j++) {
1173			my $vertex = {};
1174			@{$vertex->{v}} = $packet->unpack('f3');
1175			@{$vertex->{t}} = $packet->unpack('f2');
1176			($vertex->{c_rgb_hemi}, $vertex->{c_sun}) = $packet->unpack('VC');
1177			@{$vertex->{pad}} = $packet->unpack('C3');
1178			push @{$lod_face->{vertices}}, $vertex;
1179		}
1180		push @{$self->{lod_faces}}, $lod_face;
1181	}
1182	$self->set_loaded('OGF_LODDEF2');
1183}
1184sub read_treedef {
1185#  IReader__r(v5, (char *)v4 + 132, 64);
1186#  IReader__r(v5, (char *)v4 + 196, 16);
1187#  IReader__r(v5, (char *)v4 + 212, 16);
1188#  *((_DWORD *)v4 + 16) = CShaderManager__CreateGeom(v25, *((_DWORD *)v4 + 17), *((_DWORD *)v4 + 20));
1189#  *((_DWORD *)v4 + 24) = sub_599BF0("consts");
1190#  *((_DWORD *)v4 + 25) = sub_599BF0("wave");
1191#  *((_DWORD *)v4 + 26) = sub_599BF0("wind");
1192#  *((_DWORD *)v4 + 27) = sub_599BF0("c_bias");
1193#  *((_DWORD *)v4 + 28) = sub_599BF0("c_scale");
1194#  *((_DWORD *)v4 + 29) = sub_599BF0("m_m2w");
1195#  *((_DWORD *)v4 + 30) = sub_599BF0("m_w2v2p");
1196#  *((_DWORD *)v4 + 31) = sub_599BF0("v_eye");
1197
1198}
1199sub read_treedef2 {
1200	my $self = shift;
1201	my ($cf) = @_;
1202	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
1203	@{$self->{tree_xform}} = $packet->unpack('f16');
1204	read_ogf_color($self->{c_scale}, $packet);
1205	read_ogf_color($self->{c_bias}, $packet);
1206	$self->set_loaded('OGF_TREEDEF2');
1207}
1208sub read_ogf_color {
1209	my $self = shift;
1210	my ($packet) = @_;
1211	@{$self->{rgb}} = $packet->unpack('f3');
1212	($self->{hemi}, $self->{sun}) = $packet->unpack('ff');
1213}
1214sub read_swicontainer {
1215	my $self = shift;
1216	my ($cf) = @_;
1217	my $packet = stkutils::data_packet->new($cf->r_chunk_data());
1218	($self->{ext_swib_index}) = $packet->unpack('V');
1219
1220}
1221sub write {
1222	my $self = shift;
1223	my ($cf, $subversion) = @_;
1224	$self->{subversion} = $subversion;
1225	$self->write_header($cf);
1226	SWITCH: {
1227		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_NORMAL' && do { $self->write_visual($cf); last SWITCH; };
1228		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_HIERRARHY' && do { $self->write_hierrarhy_visual($cf); last SWITCH; };
1229		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_PROGRESSIVE' && do { $self->write_progressive($cf); last SWITCH; };
1230		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_ANIM' && do { $self->write_kinematics_animated($cf); last SWITCH; };
1231		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_GEOMDEF_PM' && do { $self->write_skeletonx_pm($cf); last SWITCH; };
1232		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_GEOMDEF_ST' && do { $self->write_skeletonx_st($cf); last SWITCH; };
1233		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_PROGRESSIVE2' && do { $self->write_progressive2($cf); last SWITCH; };
1234		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_LOD' && do { $self->write_lod($cf); last SWITCH; };
1235		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_TREE' && do { $self->write_tree_visual_st($cf); last SWITCH; };
1236		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_TREE_ST' && do { $self->write_tree_visual_st($cf); last SWITCH; };
1237		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_RIGID' && do { $self->write_kinematics($cf); last SWITCH; };
1238		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_TREE_PM' && do { $self->write_tree_visual_pm($cf); last SWITCH; };
1239		stkutils::debug::fail(__PACKAGE__.'::write', __LINE__, '', "unexpected model type $self->{type}");
1240	}
1241}
1242sub write_header {
1243	my $self = shift;
1244	my ($cf) = @_;
1245	$cf->w_chunk_open(0x1);
1246	$cf->w_chunk_data(pack('CCv', $self->{format_version}, $self->{type}, $self->{shader_id}));
1247	if ($self->{format_version} == 4) {
1248		$self->write_bbox($cf);
1249		$self->write_bsphere($cf);
1250	}
1251	$cf->w_chunk_close();
1252}
1253sub write_render_visual {
1254	my $self = shift;
1255	my ($cf) = @_;
1256	if ($self->{format_version} == 3 && $self->check_loaded('OGF_BBOX')) {
1257		$cf->w_chunk_open(0x6);
1258		$self->write_bbox($cf);
1259		$cf->w_chunk_close();
1260	}
1261	if ($self->{format_version} == 3 && $self->check_loaded('OGF_BSPHERE')) {
1262		$cf->w_chunk_open(0xb);
1263		$self->write_bsphere($cf);
1264		$cf->w_chunk_close();
1265	}
1266	if ($self->check_loaded('OGF_S_DESC')) {
1267		$self->write_s_desc($cf);
1268	}
1269	if ($self->{format_version} == 3 && $self->check_loaded('OGF_TEXTURE_L')) {
1270		$self->write_texture_l($cf);
1271	}
1272	if ($self->check_loaded('OGF_TEXTURE')) {
1273		$self->write_texture($cf);
1274	}
1275};
1276sub write_visual {
1277	my $self = shift;
1278	my ($cf) = @_;
1279	$self->write_render_visual($cf);
1280	if ($self->{format_version} == 4 && $self->check_loaded('OGF_GCONTAINER')) {
1281		$self->write_gcontainer($cf);
1282		if ($self->check_loaded('OGF_FASTPATH')) {
1283			$self->write_fastpath($cf) ;
1284		}
1285	}
1286	if ($self->check_loaded('OGF_VCONTAINER')) {
1287		$self->write_vcontainer($cf);
1288	} elsif ($self->check_loaded('OGF_VERTICES')) {
1289		$self->write_vertices($cf);
1290	}
1291	if ($self->check_loaded('OGF_ICONTAINER')) {
1292		$self->write_icontainer($cf);
1293	} elsif ($self->check_loaded('OGF_INDICES')) {
1294		$self->write_indices($cf);
1295	}
1296}
1297sub write_hierrarhy_visual {
1298	my $self = shift;
1299	my ($cf) = @_;
1300	$self->write_render_visual($cf);
1301	if ($self->check_loaded('OGF_CHILDREN_L')) {
1302		$self->write_children_l($cf);
1303	} elsif ($self->check_loaded('OGF_CHILDREN')) {
1304		$self->write_children($cf);
1305	} elsif ($self->check_loaded('OGF_CHILD_REFS')) {
1306		$self->write_child_refs($cf);
1307	}
1308};
1309sub write_progressive {
1310	my $self = shift;
1311	my ($cf) = @_;
1312	$self->write_visual($cf);
1313	if ($self->{format_version} == 4) {
1314		$self->write_swidata($cf);
1315	} else {
1316		$self->write_loddata($cf);
1317	}
1318}
1319sub write_kinematics {
1320	my $self = shift;
1321	my ($cf) = @_;
1322	$self->write_hierrarhy_visual($cf);
1323	if ($self->{format_version} == 4) {
1324		if ($self->check_loaded('OGF_S_LODS_CSKY')) {
1325			$self->write_s_lods_csky($cf);
1326		} elsif ($self->check_loaded('OGF_S_LODS')) {
1327			$self->write_s_lods($cf);
1328		}
1329	}
1330	if ($self->check_loaded('OGF_S_USERDATA')) {
1331		$self->write_s_userdata($cf);
1332	}
1333	if ($self->check_loaded('OGF_S_BONE_NAMES')) {
1334		$self->write_s_bone_names($cf);
1335	}
1336	if ($self->check_loaded('OGF_S_IKDATA_2')) {
1337		$self->write_s_ikdata($cf, 2);
1338	} elsif ($self->check_loaded('OGF_S_IKDATA_1')) {
1339		$self->write_s_ikdata($cf, 1);
1340	}
1341}
1342sub write_kinematics_animated {
1343	my $self = shift;
1344	my ($cf) = @_;
1345	$self->write_kinematics($cf);
1346	if ($self->{format_version} == 4 && $self->check_loaded('OGF_S_MOTION_REFS_1')) {
1347		$self->write_smotion_refs_1($cf);
1348		return;
1349	} elsif ($self->check_loaded('OGF_S_MOTION_REFS_0')) {
1350		$self->write_smotion_refs_0($cf);
1351		return;
1352	} elsif ($self->check_loaded('OGF_S_SMPARAMS_1')) {
1353		$self->write_s_smparams($cf, 1);
1354	} elsif ($self->{format_version} == 3 && $self->check_loaded('OGF_S_SMPARAMS_0')) {
1355		$self->write_s_smparams($cf, 2);
1356	}
1357	if ($self->check_loaded('OGF_S_MOTIONS_1')) {
1358		$self->write_smotions($cf, 1);
1359	} elsif ($self->{format_version} == 3 && $self->check_loaded('OGF_S_MOTIONS_0')) {
1360		$self->write_smotions($cf, 0);
1361	} else {
1362		stkutils::debug::fail('ogf::write_kinematics_animated', 1184, 'no motions to write');
1363	}
1364}
1365sub write_skeletonx_pm {
1366	my $self = shift;
1367	my ($cf) = @_;
1368	$self->write_progressive($cf);
1369}
1370sub write_skeletonx_st {
1371	my $self = shift;
1372	my ($cf) = @_;
1373	$self->write_visual($cf);
1374}
1375sub write_progressive2 {
1376	my $self = shift;
1377	my ($cf) = @_;
1378	$self->write_render_visual($cf);
1379	$self->write_s_lods($cf);
1380}
1381sub write_lod {
1382	my $self = shift;
1383	my ($cf) = @_;
1384	$self->write_hierrarhy_visual($cf);
1385	if ($self->check_loaded('OGF_LODDEF2')) {
1386		$self->write_loddef2($cf);
1387	}
1388}
1389sub write_tree_visual {
1390	my $self = shift;
1391	my ($cf) = @_;
1392	$self->write_visual($cf);
1393	$self->write_treedef2($cf);
1394}
1395sub write_tree_visual_st {
1396	my $self = shift;
1397	my ($cf) = @_;
1398	$self->write_tree_visual($cf);
1399}
1400sub write_tree_visual_pm {
1401	my $self = shift;
1402	my ($cf) = @_;
1403	$self->write_tree_visual($cf);
1404	$self->write_swicontainer($cf);
1405}
1406sub write_bbox {
1407	my $self = shift;
1408	my ($cf) = @_;
1409	$cf->w_chunk_data(pack('f3f3', @{$self->{bbox}->{min}}, @{$self->{bbox}->{max}}));
1410}
1411sub write_bsphere {
1412	my $self = shift;
1413	my ($cf) = @_;
1414	$cf->w_chunk_data(pack('f3f', @{$self->{bsphere}->{c}}, $self->{bsphere}->{r}));
1415}
1416sub write_s_desc {
1417	my $self = shift;
1418	my ($cf) = @_;
1419	$cf->w_chunk($chunk_names{$self->{format_version}}{'OGF_S_DESC'}, pack('Z*Z*VZ*VZ*V', $self->{ogf_object}, $self->{ogf_creator}, $self->{unk}, $self->{creator}, $self->{create_time}, $self->{editor}, $self->{edit_time}));
1420}
1421sub write_texture_l {
1422	my $self = shift;
1423	my ($cf) = @_;
1424	$cf->w_chunk($chunk_names{$self->{format_version}}{'OGF_TEXTURE_L'}, pack('VV', $self->{texture_id}, $self->{shader_id}));
1425}
1426sub write_texture {
1427	my $self = shift;
1428	my ($cf) = @_;
1429	$cf->w_chunk($chunk_names{$self->{format_version}}{'OGF_TEXTURE'}, pack('Z*Z*', $self->{texture_name}, $self->{shader_name}));
1430}
1431sub write_vcontainer {
1432	my $self = shift;
1433	my ($cf) = @_;
1434	$cf->w_chunk($chunk_names{$self->{format_version}}{'OGF_VCONTAINER'}, pack('VVV', $self->{ext_vb_index}, $self->{ext_vb_offset}, $self->{ext_vb_size}));
1435}
1436sub write_vertices {
1437	my $self = shift;
1438	my ($cf) = @_;
1439	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_VERTICES'});
1440	$cf->w_chunk_data(pack('VV', $self->{vertex_format}, $self->{vertex_count}));
1441	if ($self->{vertex_format} == OGF_VERTEXFORMAT_FVF_OLD) {
1442		foreach my $vertice (@{$self->{vertices}}) {
1443			$cf->w_chunk_data(pack('f3f3f2', @{$vertice->{point}}, @{$vertice->{normal}}, @{$vertice->{textcoords}}));
1444		}
1445	} elsif ($self->{format_version} == 3 && $self->{vertex_format} == OGF_VERTEXFORMAT_FVF_1L) {
1446		foreach my $vertice (@{$self->{vertices}}) {
1447			$cf->w_chunk_data(pack('f3f3f2l', @{$vertice->{point}}, @{$vertice->{normal}}, @{$vertice->{textcoords}}, $vertice->{matrix}));
1448		}
1449	} elsif ($self->{vertex_format} == OGF_VERTEXFORMAT_FVF_1L or $self->{vertex_format} == OGF_VERTEXFORMAT_FVF_1_CS) {
1450		foreach my $vertice (@{$self->{vertices}}) {
1451			$cf->w_chunk_data(pack('f3f3f3f3f2l', @{$vertice->{point}}, @{$vertice->{normal}}, @{$vertice->{t}}, @{$vertice->{b}}, @{$vertice->{textcoords}}, $vertice->{matrix}));
1452		}
1453	} elsif ($self->{vertex_format} == OGF_VERTEXFORMAT_FVF_2L or $self->{vertex_format} == OGF_VERTEXFORMAT_FVF_2_CS) {
1454		foreach my $vertice (@{$self->{vertices}}) {
1455			$cf->w_chunk_data(pack('vvf3f3f3f3ff2', $vertice->{matrix0}, $vertice->{matrix1}, @{$vertice->{point}}, @{$vertice->{normal}}, @{$vertice->{t}}, @{$vertice->{b}}, $vertice->{w}, @{$vertice->{textcoords}}));
1456		}
1457	} elsif ($self->{vertex_format} == OGF_VERTEXFORMAT_FVF_3_CS) {
1458		foreach my $vertice (@{$self->{vertices}}) {
1459			$cf->w_chunk_data(pack('vvvf3f3f3f3fff2', $vertice->{matrix0}, $vertice->{matrix1}, $vertice->{matrix2}, @{$vertice->{point}}, @{$vertice->{normal}}, @{$vertice->{t}}, @{$vertice->{b}}, $vertice->{w0}, $vertice->{w1}, @{$vertice->{textcoords}}));
1460		}
1461	} elsif ($self->{vertex_format} == OGF_VERTEXFORMAT_FVF_4_CS) {
1462		foreach my $vertice (@{$self->{vertices}}) {
1463			$cf->w_chunk_data(pack('vvvvf3f3f3f3ffff2', $vertice->{matrix0}, $vertice->{matrix1}, $vertice->{matrix2}, $vertice->{matrix3}, @{$vertice->{point}}, @{$vertice->{normal}}, @{$vertice->{t}}, @{$vertice->{b}}, $vertice->{w0}, $vertice->{w1}, $vertice->{w2}, @{$vertice->{textcoords}}));
1464		}
1465	}
1466	$cf->w_chunk_close();
1467}
1468sub write_icontainer {
1469	my $self = shift;
1470	my ($cf) = @_;
1471	$cf->w_chunk($chunk_names{$self->{format_version}}{'OGF_ICONTAINER'}, pack('VVV', $self->{ext_ib_index}, $self->{ext_ib_offset}, $self->{ext_ib_size}));
1472}
1473sub write_indices {
1474	my $self = shift;
1475	my ($cf) = @_;
1476	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_INDICES'});
1477	$cf->w_chunk_data(pack('V', $self->{indices_count}));
1478	foreach my $index (@{$self->{indices}}) {
1479		$cf->w_chunk_data(pack('v', $index));
1480	}
1481	$cf->w_chunk_close();
1482}
1483sub write_children_l {
1484	my $self = shift;
1485	my ($cf) = @_;
1486	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_CHILDREN_L'});
1487	$cf->w_chunk_data(pack('V', $#{$self->{children_l}} + 1));
1488	foreach my $child (@{$self->{children_l}}) {
1489		$cf->w_chunk_data(pack('V', $child));
1490	}
1491	$cf->w_chunk_close();
1492}
1493sub write_children {
1494	my $self = shift;
1495	my ($cf) = @_;
1496	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_CHILDREN'});
1497	my $id = 0;
1498	foreach my $child (@{$self->{children}}) {
1499		$cf->w_chunk_open($id);
1500		$child->write($cf);
1501		$cf->w_chunk_close();
1502		$id++;
1503	}
1504	$cf->w_chunk_close();
1505}
1506sub write_child_refs {
1507	my $self = shift;
1508	my ($cf) = @_;
1509	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_CHILD_REFS'});
1510	$cf->w_chunk_data(pack('V', $#{$self->{children_l}} + 1));
1511	foreach my $child (@{$self->{child_refs}}) {
1512		$cf->w_chunk_data(pack('Z*', $child));
1513	}
1514	$cf->w_chunk_close();
1515}
1516sub write_swidata {
1517	my $self = shift;
1518	my ($cf) = @_;
1519	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_SWIDATA'});
1520	$cf->w_chunk_data(pack('V4V', @{$self->{swi_reserved}}, $self->{swi_count}));
1521	foreach my $swi (@{$self->{swi_data}}) {
1522		$cf->w_chunk_data(pack('lvv', $swi->{offset}, $swi->{num_tris}, $swi->{num_verts}));
1523	}
1524	$cf->w_chunk_close();
1525}
1526sub write_loddata {
1527	my $self = shift;
1528	my ($cf) = @_;
1529	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_LODDATA'});
1530	foreach my $lod (@{$self->{loddata}}) {
1531		write_hoppe_header($lod, $cf);
1532		write_hoppe_vertsplits($lod, $cf);
1533		write_hoppe_fix_faces($lod, $cf);
1534	}
1535	$cf->w_chunk_close();
1536}
1537sub write_hoppe_header {
1538	my $self = shift;
1539	my ($cf) = @_;
1540	$cf->w_chunk(OGF3_HOPPE_HEADER, pack('VV', $self->{min_vertices}, $self->{max_vertices}));
1541}
1542sub write_hoppe_vertsplits {
1543	my $self = shift;
1544	my ($cf) = @_;
1545	$cf->w_chunk_open(OGF3_HOPPE_VERT_SPLITS);
1546	foreach my $vertsplit (@{$self->{vertsplits}}) {
1547		$cf->w_chunk_data(pack('vCC', $vertsplit->{vert}, $vertsplit->{num_tris}, $vertsplit->{num_verts}));
1548	}
1549	$cf->w_chunk_close();
1550}
1551sub write_hoppe_fix_faces {
1552	my $self = shift;
1553	my ($cf) = @_;
1554	$cf->w_chunk_open(OGF3_HOPPE_FIX_FACES);
1555	$cf->w_chunk_data(pack('V', $self->{num_fix_faces}));
1556	$cf->w_chunk_data(pack("(v)$self->{num_fix_faces}", @{$self->{fix_faces}}));
1557	$cf->w_chunk_close();
1558}
1559sub write_s_lods_csky {
1560	my $self = shift;
1561	my ($cf) = @_;
1562	$cf->w_chunk($chunk_names{$self->{format_version}}{'OGF_S_LODS'}, substr(pack('Z*', $self->{s_lods_ref}), 0, -1));
1563}
1564sub write_s_lods {
1565	my $self = shift;
1566	my ($cf) = @_;
1567	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_S_LODS'});
1568	my $id = 0;
1569	foreach my $lod (@{$self->{s_lods}}) {
1570		$cf->w_chunk_open($id);
1571		$lod->write($cf);
1572		$cf->w_chunk_close();
1573		$id++;
1574	}
1575	$cf->w_chunk_close();
1576}
1577sub write_s_userdata {
1578	my $self = shift;
1579	my ($cf) = @_;
1580	my $len = length($self->{userdata});
1581	$cf->w_chunk($chunk_names{$self->{format_version}}{'OGF_S_USERDATA'}, pack("a$len", $self->{userdata}));
1582}
1583sub write_s_bone_names {
1584	my $self = shift;
1585	my ($cf) = @_;
1586	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_S_BONE_NAMES'});
1587	$cf->w_chunk_data(pack('V', $#{$self->{bone_names}} + 1));
1588	foreach my $bone_name_obj (@{$self->{bone_names}}) {
1589		$cf->w_chunk_data(pack('Z*Z*', $bone_name_obj->{name}, $bone_name_obj->{parent_name}));
1590		write_obb($bone_name_obj, $cf);
1591	}
1592	$cf->w_chunk_close();
1593}
1594sub write_obb {
1595	my $self = shift;
1596	my ($cf) = @_;
1597	$cf->w_chunk_data(pack('f9f3f3', @{$self->{rotate}}, @{$self->{translate}}, @{$self->{halfsize}}));
1598}
1599sub write_sphere {
1600	my $self = $_[0];
1601	$_[1]->w_chunk_data(pack('f3f', @{$self->{p}}, $self->{r}));
1602}
1603sub write_cylinder {
1604	my $self = $_[0];
1605	$_[1]->w_chunk_data(pack('f3f3ff', @{$self->{center}}, @{$self->{direction}}, $self->{height}, $self->{radius}));
1606}
1607sub write_s_ikdata {
1608	my $self = shift;
1609	my ($cf, $mode) = @_;
1610	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_S_IKDATA_'.$mode});
1611	foreach my $ik (@{$self->{ik_data}}) {
1612		if ($mode == 2) {
1613			$cf->w_chunk_data(pack('VZ*', $ik->{version}, $ik->{game_mtl_name}));
1614		} else {
1615			$cf->w_chunk_data(pack('Z*', $ik->{game_mtl_name}));
1616		}
1617		write_s_bone_shape($ik->{bone_shape}, $cf);
1618		write_s_joint_ik_data($ik->{joint_data}, $cf);
1619		if ($mode > 0) {
1620			$cf->w_chunk_data(pack('f3f3', @{$ik->{bind_rotation}}, @{$ik->{bind_position}}));
1621		}
1622		$cf->w_chunk_data(pack('ff3', $ik->{mass}, @{$ik->{center_of_mass}}));
1623	}
1624	$cf->w_chunk_close();
1625}
1626sub write_s_bone_shape {
1627	my $self = $_[0];
1628	$_[1]->w_chunk_data(pack('vv', $self->{type}, $self->{flags}));
1629	write_obb($self->{box}, $_[1]);
1630	write_sphere($self->{sphere}, $_[1]);
1631	write_cylinder($self->{cylinder}, $_[1]);
1632}
1633sub write_s_joint_ik_data {
1634	my $self = $_[0];
1635	$_[1]->w_chunk_data(pack('V', $self->{type}));
1636	write_s_joint_limit(${$self->{limits}}[0], $_[1]);
1637	write_s_joint_limit(${$self->{limits}}[1], $_[1]);
1638	write_s_joint_limit(${$self->{limits}}[2], $_[1]);
1639	$_[1]->w_chunk_data(pack('ffVff', $self->{spring_factor}, $self->{damping_factor}, $self->{ik_flags}, $self->{break_force}, $self->{break_torque}));
1640	$_[1]->w_chunk_data(pack('f', $self->{friction})) if defined $self->{friction};
1641}
1642sub write_s_joint_limit {
1643	my $self = $_[0];
1644	$_[1]->w_chunk_data(pack('f2ff', @{$self->{limit}}, $self->{spring_factor}, $self->{damping_factor}));
1645}
1646sub write_smotion_refs_1 {
1647	my $self = shift;
1648	my ($cf) = @_;
1649	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_S_MOTION_REFS_1'});
1650	$cf->w_chunk_data(pack('V', $#{$self->{motion_refs_1}} + 1));
1651	foreach my $ref (@{$self->{motion_refs_1}}) {
1652		$cf->w_chunk_data(pack('Z*', $ref));
1653	}
1654	$cf->w_chunk_close();
1655}
1656sub write_smotion_refs_0 {
1657	my $self = shift;
1658	my ($cf) = @_;
1659	$cf->w_chunk($chunk_names{$self->{format_version}}{'OGF_S_MOTION_REFS_0'}, pack('Z*', $self->{motion_refs_0}));
1660}
1661sub write_s_smparams {
1662	my $self = shift;
1663	my ($cf, $mode) = @_;
1664	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_S_SMPARAMS_'.$mode});
1665	$cf->w_chunk_data(pack('vv', $self->{sm_params_version}, $#{$self->{s_smparams_partitions}} + 1));
1666	foreach my $part (@{$self->{s_smparams_partitions}}) {
1667		$cf->w_chunk_data(pack('Z*v', $part->{name}, $part->{bone_count}));
1668		foreach my $bone (@{$part->{bones}}) {
1669			if ($mode == 0 || $self->{sm_params_version} == 1) {
1670				$cf->w_chunk_data(pack('V', $bone->{bone_id}));
1671			} elsif ($self->{sm_params_version} == 2) {
1672				$cf->w_chunk_data(pack('Z*', $bone->{bone_name}));
1673			} elsif ($self->{sm_params_version} == 3 || $self->{sm_params_version} == 4) {
1674				$cf->w_chunk_data(pack('Z*V', $bone->{bone_name}, $bone->{bone_id}));
1675			}
1676		}
1677	}
1678	$cf->w_chunk_data(pack('v', $#{$self->{s_smparams_motions}} + 1));
1679	foreach my $mot (@{$self->{s_smparams_motions}}) {
1680		if ($mode == 1) {
1681			$cf->w_chunk_data(pack('Z*V', $mot->{name}, $mot->{flags}));
1682			write_motion_def($mot, $cf);
1683			if ($self->{sm_params_version} == 4) {
1684				$cf->w_chunk_data(pack('V', $#{$mot->{mmarks}} + 1));
1685				foreach my $nmark (@{$mot->{mmarks}}) {
1686					write_motion_mark($nmark, $cf);
1687				}
1688			}
1689		} else {
1690			my $flag = 0;
1691			if ($mot->{flags} & 0x2) {
1692				$mot->{flags} -= 0x2;
1693				$flag = 1;
1694			}
1695			$cf->w_chunk_data(pack('Z*C', $mot->{name}, $mot->{flags}));
1696			write_motion_def($mot, $cf);
1697			$cf->w_chunk_data(pack('C', $flag));
1698		}
1699	}
1700	$cf->w_chunk_close();
1701}
1702sub write_motion_def {
1703	my $self = shift;
1704	my ($cf) = @_;
1705	$cf->w_chunk_data(pack('vvffff', $self->{bone_or_part}, $self->{motion}, $self->{speed}, $self->{power}, $self->{accrue}, $self->{falloff}));
1706}
1707sub write_motion_mark {
1708	my $self = shift;
1709	my ($cf) = @_;
1710	my $len = length($self->{name});
1711	$cf->w_chunk_data(pack("(a)$len", $self->{name}));
1712	$cf->w_chunk_data(pack('V', $#{$self->{intervals}} + 1));
1713	foreach my $int (@{$self->{intervals}}) {
1714		$cf->w_chunk_data(pack('ff', $int->{min}, $int->{max}));
1715	}
1716}
1717sub write_smotions {
1718	my $self = shift;
1719	my ($cf, $mode) = @_;
1720	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_S_MOTIONS_'.$mode});
1721	$cf->w_chunk_open(0);
1722	$cf->w_chunk_data(pack('V', $self->{motions_count}));
1723	$cf->w_chunk_close();
1724	my $id = 1;
1725	foreach my $motion (@{$self->{motions}}) {
1726		$cf->w_chunk_open($id++);
1727		write_motion($motion, $cf, $mode);
1728		$cf->w_chunk_close();
1729	}
1730	$cf->w_chunk_close();
1731}
1732sub write_motion {
1733	my $self = shift;
1734	my ($cf, $mode) = @_;
1735	$cf->w_chunk_data(pack('Z*V', $self->{name}, $self->{keys_count}));
1736	if ($self->{keys_count} == 0) {
1737		return;
1738	}
1739	if ($mode == 1) {
1740		foreach my $bone (@{$self->{bones}}) {
1741			$cf->w_chunk_data(pack('C', $bone->{flags}));
1742			if ($bone->{flags} & KPF_R_ABSENT) {
1743				$cf->w_chunk_data(pack('s4', @{$bone->{keysr}}));
1744			} else {
1745				$cf->w_chunk_data(pack("V(s4)$self->{keys_count}", $bone->{crc_keysr}, @{$bone->{keysr}}));
1746			}
1747			if ($bone->{flags} & KPF_T_PRESENT) {
1748				$cf->w_chunk_data(pack('V', $bone->{crc_keyst}));
1749				if ($bone->{flags} & KPF_T_HQ) {
1750					for (my $j = 0; $j < $self->{keys_count}; $j++) {
1751						$cf->w_chunk_data(pack('s3', @{$bone->{keyst}}[$j*3..$j*3+2]));
1752					}
1753				} else {
1754					for (my $j = 0; $j < $self->{keys_count}; $j++) {
1755						$cf->w_chunk_data(pack('c3', @{$bone->{keyst}}[$j*3..$j*3+2]));
1756					}
1757				}
1758				$cf->w_chunk_data(pack('f3', @{$bone->{sizet}}));
1759			}
1760			$cf->w_chunk_data(pack('f3', @{$bone->{initt}}));
1761		}
1762	} else {
1763		foreach my $bone (@{$self->{bones}}) {
1764			for (my $n = 0; $n < $self->{keys_count}; $n++) {
1765				$cf->w_chunk_data(pack('s4f3', @{$bone->{keys}}[$n*7..$n*7+6]));
1766			}
1767		}
1768	}
1769}
1770sub write_loddef2 {
1771	my $self = shift;
1772	my ($cf) = @_;
1773	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_LODDEF2'});
1774	foreach my $lod_face (@{$self->{lod_faces}}) {
1775		foreach my $vertex (@{$lod_face->{vertices}}) {
1776			$cf->w_chunk_data(pack('f3f2VCC3', @{$vertex->{v}}, @{$vertex->{t}}, $vertex->{c_rgb_hemi}, $vertex->{c_sun}, @{$vertex->{pad}}));
1777		}
1778	}
1779	$cf->w_chunk_close();
1780}
1781sub write_treedef2 {
1782	my $self = shift;
1783	my ($cf) = @_;
1784	$cf->w_chunk_open($chunk_names{$self->{format_version}}{'OGF_TREEDEF2'});
1785	$cf->w_chunk_data(pack('f16', @{$self->{tree_xform}}));
1786	write_ogf_color($self->{c_scale}, $cf);
1787	write_ogf_color($self->{c_bias}, $cf);
1788	$cf->w_chunk_close();
1789}
1790sub write_ogf_color {
1791	my $self = shift;
1792	my ($cf) = @_;
1793	$cf->w_chunk_data(pack('f3ff', @{$self->{rgb}}, $self->{hemi}, $self->{sun}));
1794}
1795sub write_swicontainer {
1796	my $self = shift;
1797	my ($cf) = @_;
1798	$cf->w_chunk($chunk_names{$self->{format_version}}{'OGF_SWICONTAINER'}, pack('V', $self->{ext_swib_index}));
1799}
1800sub convert {
1801	my $self = shift;
1802	my ($nVer, $subversion) = @_;
1803	if ($nVer == $self->{format_version}) {
1804		if ($nVer == 3) {
1805			$self->subver_3($subversion);
1806		} else {
1807			$self->subver_4($subversion);
1808		}
1809	} elsif ($nVer == 3) {
1810		$self->conv_to_3();
1811	} else {
1812		$self->conv_to_4();
1813	}
1814}
1815sub subver_4 {
1816	my $self = shift;
1817	my ($subversion) = @_;
1818	### Attention! Now it support only FSL_VISUALS converting! ###
1819	if ($subversion == 1 || $subversion == 2 || $subversion == 3) {
1820		if ($self->check_loaded('OGF_GCONTAINER')) {
1821			print "yes, type = $self->{type}\n";
1822			$self->set_loaded('OGF_ICONTAINER');
1823			$self->set_loaded('OGF_VCONTAINER');
1824			$self->{loaded_chunks} -= $chunks_loaded{$self->{format_version}}{'OGF_GCONTAINER'};
1825			die if $self->check_loaded('OGF_GCONTAINER');
1826			die unless $self->check_loaded('OGF_VCONTAINER');
1827		}
1828		if ($self->{type} == 11) {
1829			delete $self->{ext_swib_index};
1830			$self->{type} = 7;
1831			$self->{loaded_chunks} -= $chunks_loaded{$self->{format_version}}{'OGF_SWICONTAINER'} if $self->check_loaded('OGF_SWICONTAINER');
1832			die if $self->check_loaded('OGF_GCONTAINER');
1833			die unless $self->check_loaded('OGF_VCONTAINER');
1834		}
1835		if ($self->{type} == 2) {
1836			delete $self->{swi_data};
1837			$self->{type} = 0;
1838			$self->{loaded_chunks} -= $chunks_loaded{$self->{format_version}}{'OGF_SWIDATA'} if $self->check_loaded('OGF_SWIDATA');
1839			die if $self->check_loaded('OGF_GCONTAINER');
1840			die unless $self->check_loaded('OGF_VCONTAINER');
1841		}
1842	} elsif ($subversion == 4) {
1843		if ($self->check_loaded('OGF_GCONTAINER')) {
1844			$self->set_loaded('OGF_ICONTAINER');
1845			$self->set_loaded('OGF_VCONTAINER');
1846			$self->{loaded_chunks} -= $chunks_loaded{$self->{format_version}}{'OGF_GCONTAINER'};
1847		}
1848	} else {
1849		if ($self->check_loaded('OGF_VCONTAINER')) {
1850			$self->set_loaded('OGF_GCONTAINER');
1851			$self->{loaded_chunks} -= $chunks_loaded{$self->{format_version}}{'OGF_VCONTAINER'};
1852			$self->{loaded_chunks} -= $chunks_loaded{$self->{format_version}}{'OGF_ICONTAINER'};
1853		}
1854	}
1855}
1856sub conv_to_3 {
1857	my $self = shift;
1858	SWITCH: {
1859		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_NORMAL' && do {last SWITCH; };
1860		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_HIERRARHY' && do {last SWITCH; }; #FIXME: childeren may vary in different builds
1861	#MT_PROGRESSIVE ???
1862		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_PROGRESSIVE' && do { $self->swi_to_loddata(); last SWITCH; };
1863	#MT_SKELETON_ANIM ???
1864		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_ANIM' && do { die; last SWITCH; };
1865	#MT_SKELETON_GEOMDEF_PM ???
1866		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_GEOMDEF_PM' && do {  $self->swi_to_loddata(); $self->{type} = 0x3; last SWITCH; };
1867		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_GEOMDEF_ST' && do { $self->{type} = 0x7; last SWITCH; };
1868		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_LOD' && do { $self->{type} = 0xb; last SWITCH; };
1869		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_TREE_ST' && do { $self->{type} = 0xc; last SWITCH; };
1870	#MT_SKELETON_RIGID ???
1871		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_RIGID' && do { die; last SWITCH; };
1872		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_TREE_PM' && do { $self->{type} = 0xc; last SWITCH; };
1873		stkutils::debug::fail('ogf::conv_to_3', __LINE__, 'unexpected model type');
1874	}
1875}
1876sub conv_to_4 {
1877	my $self = shift;
1878	SWITCH: {
1879		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_NORMAL' && do {last SWITCH; };
1880		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_HIERRARHY' && do {last SWITCH; }; #FIXME: children may vary in different builds
1881	#MT_PROGRESSIVE ???
1882		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_PROGRESSIVE' && do { $self->loddata_to_swi(); last SWITCH; };
1883	#MT_SKELETON_ANIM ???
1884		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_ANIM' && do { die; last SWITCH; };
1885	#MT_SKELETON_GEOMDEF_PM ???
1886		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_GEOMDEF_PM' && do { $self->loddata_to_swi(); $self->{type} = 0x4; last SWITCH; };
1887		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_GEOMDEF_ST' && do { $self->{type} = 0x5; last SWITCH; };
1888	#MT_PROGRESSIVE2 ???
1889		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_PROGRESSIVE2' && do {  die; last SWITCH; };
1890		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_LOD' && do {$self->{type} = 0x6; last SWITCH; };
1891		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_TREE' && do { $self->{type} = 0x7; last SWITCH; };
1892	#MT_SKELETON_RIGID ???
1893		$mt_names{$self->{format_version}}{$self->{type}} eq 'MT_SKELETON_RIGID' && do { die; last SWITCH; };
1894		stkutils::debug::fail('ogf::conv_to_4', __LINE__, 'unexpected model type');
1895	}
1896}
1897sub swi_to_loddata {
1898	my $self = shift;
1899	stkutils::debug::fail('ogf::swi_to_loddata', __LINE__, "not implemented yet");
1900}
1901sub loddata_to_swi {
1902	my $self = shift;
1903	stkutils::debug::fail('ogf::loddata_to_swi', __LINE__, "not implemented yet");
1904}
1905sub make_signature {
1906	my $self = shift;
1907	$self->{signature} |= S_POINTS;
1908	$self->{signature} |= S_NORMALS;
1909	$self->{signature} |= S_TEXCOORDS;
1910	if ($self->{vertex_format} != OGF_VERTEXFORMAT_FVF_OLD) {
1911		$self->{signature} |= S_INFLUENCES;
1912	}
1913}
1914sub to_object {
1915	my $self = shift;
1916	my $mesh = {};
1917	if ($self->hierarhical()) {
1918		my $shader_id = 0;
1919		foreach my $child (@{$self->{children}}) {
1920			$child->make_signature();
1921			$mesh->{signature} |= $child->{signature};
1922			$child->push_to_mesh($mesh, $shader_id, $shader_id);
1923			$shader_id++;
1924		}
1925	} else {
1926		$self->push_to_mesh($mesh, 0, 0);
1927	}
1928}
1929sub push_to_mesh {
1930	my $self = shift;
1931	my ($mesh, $texture, $eshader) = @_;
1932	my $vb_offset =  $self->{vertex_count} & UINT32_MAX;
1933	for (my $i = 0; $i < $self->{indices_count}; $i += 3) {
1934		my $v0 = @{$self->{indices}}[$i + 0];
1935		my $v1 = @{$self->{indices}}[$i + 1];
1936		my $v2 = @{$self->{indices}}[$i + 2];
1937		my @p0 = @{$self->{vertices}[$v0]->{point}};
1938		my @p1 = @{$self->{vertices}[$v1]->{point}};
1939		my @p2 = @{$self->{vertices}[$v2]->{point}};
1940		if ((join ('', @p0) cmp join ('', @p1)) ||
1941			(join ('', @p1) cmp join ('', @p2)) ||
1942			(join ('', @p2) cmp join ('', @p0))) {
1943			next;
1944		}
1945		my $area = calc_area(@p0, @p1, @p2);
1946		if ($area <= 0.000001) {
1947			next;
1948		}
1949		my $perim = calc_perimeter(@{$self->{vertices}[$v0]->{textcoords}}, @{$self->{vertices}[$v1]->{textcoords}}, @{$self->{vertices}[$v2]->{textcoords}});
1950		if ($perim <= 1/8192.) {
1951			next;
1952		}
1953		my $face = set_face($self->{signature}, $vb_offset + $v0, $vb_offset + $v1, $vb_offset + $v2);
1954		push @{$mesh->{faces}}, $face;
1955	}
1956	if ($#{$mesh->{faces}} != -1) {
1957		for (my $i = $self->{vertex_count}; $i != 0; $i--) {
1958#			my $ref = b_proxy($vb_offset);
1959#			push @{$mesh->{refs}}, $ref;
1960			$vb_offset++;
1961		}
1962#		m_vb.push(vb, &ib, xform);
1963	}
1964}
1965sub set_face {
1966	my $face = {};
1967	my $sign = shift;
1968	if ($sign & S_POINTS) {
1969		@{$face->{v}} = @_;
1970	}
1971	if ($sign & S_NORMALS) {
1972		@{$face->{n}} = @_;
1973	}
1974	if ($sign & S_TEXCOORDS) {
1975		@{$face->{tc}} = @_;
1976	}
1977	return $face;
1978}
1979sub calc_area {
1980	my $tr1 = ($_[4] - $_[3])*($_[8] - $_[6]) - ($_[5] - $_[3])*($_[7] - $_[6]);
1981	my $tr2 = ($_[1] - $_[0])*($_[8] - $_[6]) - ($_[2] - $_[0])*($_[7] - $_[6]);
1982	my $tr3 = ($_[1] - $_[0])*($_[5] - $_[3]) - ($_[2] - $_[0])*($_[4] - $_[3]);
1983	return vector_length($tr1, $tr2, $tr3);
1984}
1985sub calc_perimeter {
1986	return (vector_length(@_[0..2]) + vector_length(@_[3..5]) + vector_length(@_[6..8]));
1987}
1988sub vector_length {
1989	return sqrt($_[0]*$_[0] + $_[1]*$_[1] + $_[2]*$_[2]);
1990}
1991sub hierrarhical {
1992	my $self = shift;
1993	my $type = $mt_names{$self->{format_version}}{$self->{type}};
1994	if ($type eq 'MT_NORMAL' ||
1995		$type eq 'MT_HIERRARHY' ||
1996		$type eq 'MT_SKELETON_ANIM' ||
1997		$type eq 'MT_SKELETON_RIGID') {
1998		return 1;
1999	} else {return 0;}
2000}
2001sub set_loaded {
2002	my $self = shift;
2003	my ($chunk) = @_;
2004	$self->{loaded_chunks} += $chunks_loaded{$self->{format_version}}{$chunk};
2005}
2006sub check_loaded {
2007	my $self = shift;
2008	my ($chunk) = @_;
2009	return ($self->{loaded_chunks} & $chunks_loaded{$self->{format_version}}{$chunk});
2010}
2011sub check_unhandled_chunks {
2012	my $self = shift;
2013	my ($cf, $fn) = @_;
2014	my %rev_names = reverse %{$chunk_names{$self->{format_version}}};
2015	$cf->{fh}->seek(0, 0);
2016	my $fsize = -s $fn;
2017	for (my $offset = 0; $offset < $fsize;) {
2018		my $data = '';
2019		$cf->{fh}->read($data, 8);
2020		my ($index, $size) = unpack('VV', $data);
2021		if ($index == 0 && $size ==0) {
2022			last;
2023		}
2024		if (!($self->check_loaded($rev_names{$index}))) {
2025			my $name = $rev_names{$index};
2026			if ($name) {
2027				stkutils::debug::fail(__PACKAGE__.'::check_unhandled_chunks', __LINE__, '$self->check_loaded('.$rev_names{$index}.')', "chunk $name is unhandled");
2028			} else {
2029				stkutils::debug::fail(__PACKAGE__.'::check_unhandled_chunks', __LINE__, '$self->check_loaded('.$index.')', "chunk $index is unhandled");
2030			}
2031		}
2032		$cf->{fh}->seek($size, 1);
2033		$offset = tell $cf->{fh};
2034	}
2035}
20361;