| Home | Trees | Indices | Help |
|
|---|
|
|
1 """
2 :mod:`pyffi.formats.cgf` --- Crytek (.cgf and .cga)
3 ===================================================
4
5 Implementation
6 --------------
7
8 .. autoclass:: CgfFormat
9 :show-inheritance:
10 :members:
11
12 Regression tests
13 ----------------
14
15 Read a CGF file
16 ^^^^^^^^^^^^^^^
17
18 >>> # get file version and file type, and read cgf file
19 >>> stream = open('tests/cgf/test.cgf', 'rb')
20 >>> data = CgfFormat.Data()
21 >>> # read chunk table only
22 >>> data.inspect(stream)
23 >>> # check chunk types
24 >>> list(chunktype.__name__ for chunktype in data.chunk_table.get_chunk_types())
25 ['SourceInfoChunk', 'TimingChunk']
26 >>> data.chunks # no chunks yet
27 []
28 >>> # read full file
29 >>> data.read(stream)
30 >>> # get all chunks
31 >>> for chunk in data.chunks:
32 ... print(chunk) # doctest: +ELLIPSIS
33 <class 'pyffi.formats.cgf.SourceInfoChunk'> instance at ...
34 * source_file : <None>
35 * date : Fri Sep 28 22:40:44 2007
36 * author : blender@BLENDER
37 <BLANKLINE>
38 <class 'pyffi.formats.cgf.TimingChunk'> instance at ...
39 * secs_per_tick : 0.000208333338378
40 * ticks_per_frame : 160
41 * global_range :
42 <class 'pyffi.formats.cgf.RangeEntity'> instance at ...
43 * name : GlobalRange
44 * start : 0
45 * end : 100
46 * num_sub_ranges : 0
47 <BLANKLINE>
48
49 Parse all CGF files in a directory tree
50 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
51
52 >>> for stream, data in CgfFormat.walkData('tests/cgf'):
53 ... print(stream.name)
54 ... try:
55 ... data.read(stream)
56 ... except Exception:
57 ... print("Warning: read failed due corrupt file, corrupt format description, or bug.")
58 ... print(len(data.chunks))
59 ... # do something with the chunks
60 ... for chunk in data.chunks:
61 ... chunk.apply_scale(2.0)
62 tests/cgf/invalid.cgf
63 Warning: read failed due corrupt file, corrupt format description, or bug.
64 0
65 tests/cgf/monkey.cgf
66 14
67 tests/cgf/test.cgf
68 2
69 tests/cgf/vcols.cgf
70 6
71
72 Create a CGF file from scratch
73 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
74
75 >>> from pyffi.formats.cgf import CgfFormat
76 >>> node1 = CgfFormat.NodeChunk()
77 >>> node1.name = "hello"
78 >>> node2 = CgfFormat.NodeChunk()
79 >>> node1.num_children = 1
80 >>> node1.children.update_size()
81 >>> node1.children[0] = node2
82 >>> node2.name = "world"
83 >>> from tempfile import TemporaryFile
84 >>> stream = TemporaryFile()
85 >>> data = CgfFormat.Data() # default is far cry
86 >>> data.chunks = [node1, node2]
87 >>> # note: write returns number of padding bytes
88 >>> data.write(stream)
89 0
90 >>> # py3k returns 0 on seek; this hack removes return code from doctest
91 >>> if stream.seek(0): pass
92 >>> data.inspect_version_only(stream)
93 >>> hex(data.header.version)
94 '0x744'
95 >>> data.read(stream)
96 >>> # get all chunks
97 >>> for chunk in data.chunks:
98 ... print(chunk) # doctest: +ELLIPSIS +REPORT_NDIFF
99 <class 'pyffi.formats.cgf.NodeChunk'> instance at 0x...
100 * name : hello
101 * object : None
102 * parent : None
103 * num_children : 1
104 * material : None
105 * is_group_head : False
106 * is_group_member : False
107 * reserved_1 :
108 <class 'pyffi.object_models.xml.array.Array'> instance at 0x...
109 0: 0
110 1: 0
111 * transform :
112 [ 0.000 0.000 0.000 0.000 ]
113 [ 0.000 0.000 0.000 0.000 ]
114 [ 0.000 0.000 0.000 0.000 ]
115 [ 0.000 0.000 0.000 0.000 ]
116 * pos : [ 0.000 0.000 0.000 ]
117 * rot :
118 <class 'pyffi.formats.cgf.Quat'> instance at 0x...
119 * x : 0.0
120 * y : 0.0
121 * z : 0.0
122 * w : 0.0
123 * scl : [ 0.000 0.000 0.000 ]
124 * pos_ctrl : None
125 * rot_ctrl : None
126 * scl_ctrl : None
127 * property_string : <None>
128 * children :
129 <class 'pyffi.object_models.xml.array.Array'> instance at 0x...
130 0: <class 'pyffi.formats.cgf.NodeChunk'> instance at 0x...
131 <BLANKLINE>
132 <class 'pyffi.formats.cgf.NodeChunk'> instance at 0x...
133 * name : world
134 * object : None
135 * parent : None
136 * num_children : 0
137 * material : None
138 * is_group_head : False
139 * is_group_member : False
140 * reserved_1 :
141 <class 'pyffi.object_models.xml.array.Array'> instance at 0x...
142 0: 0
143 1: 0
144 * transform :
145 [ 0.000 0.000 0.000 0.000 ]
146 [ 0.000 0.000 0.000 0.000 ]
147 [ 0.000 0.000 0.000 0.000 ]
148 [ 0.000 0.000 0.000 0.000 ]
149 * pos : [ 0.000 0.000 0.000 ]
150 * rot :
151 <class 'pyffi.formats.cgf.Quat'> instance at 0x...
152 * x : 0.0
153 * y : 0.0
154 * z : 0.0
155 * w : 0.0
156 * scl : [ 0.000 0.000 0.000 ]
157 * pos_ctrl : None
158 * rot_ctrl : None
159 * scl_ctrl : None
160 * property_string : <None>
161 * children : <class 'pyffi.object_models.xml.array.Array'> instance at 0x...
162 <BLANKLINE>
163 """
164
165 # --------------------------------------------------------------------------
166 # ***** BEGIN LICENSE BLOCK *****
167 #
168 # Copyright (c) 2007-2011, Python File Format Interface
169 # All rights reserved.
170 #
171 # Redistribution and use in source and binary forms, with or without
172 # modification, are permitted provided that the following conditions
173 # are met:
174 #
175 # * Redistributions of source code must retain the above copyright
176 # notice, this list of conditions and the following disclaimer.
177 #
178 # * Redistributions in binary form must reproduce the above
179 # copyright notice, this list of conditions and the following
180 # disclaimer in the documentation and/or other materials provided
181 # with the distribution.
182 #
183 # * Neither the name of the Python File Format Interface
184 # project nor the names of its contributors may be used to endorse
185 # or promote products derived from this software without specific
186 # prior written permission.
187 #
188 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
189 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
190 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
191 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
192 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
193 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
194 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
195 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
196 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
197 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
198 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
199 # POSSIBILITY OF SUCH DAMAGE.
200 #
201 # ***** END LICENSE BLOCK *****
202 # --------------------------------------------------------------------------
203
204 import itertools
205 import logging
206 import struct
207 import os
208 import re
209 import warnings
210 from itertools import izip
211
212
213 import pyffi.object_models.common
214 import pyffi.object_models
215 import pyffi.object_models.xml
216 import pyffi.utils.mathutils
217 import pyffi.utils.tangentspace
218 from pyffi.object_models.xml.basic import BasicBase
219 from pyffi.utils.graph import EdgeFilter
232
234 """Stores all information about the cgf file format."""
235 __metaclass__ = _MetaCgfFormat
236 xml_file_name = 'cgf.xml'
237 # where to look for cgf.xml and in what order: CGFXMLPATH env var,
238 # or module directory
239 xml_file_path = [os.getenv('CGFXMLPATH'), os.path.dirname(__file__)]
240 EPSILON = 0.0001 # used for comparing floats
241 # regular expression for file name extension matching on cgf files
242 RE_FILENAME = re.compile(r'^.*\.(cgf|cga|chr|caf)$', re.IGNORECASE)
243
244 # version and user version for far cry
245 VER_FARCRY = 0x744
246 UVER_FARCRY = 1
247
248 # version and user version for crysis
249 VER_CRYSIS = 0x744
250 UVER_CRYSIS = 2
251
252 # basic types
253 int = pyffi.object_models.common.Int
254 uint = pyffi.object_models.common.UInt
255 byte = pyffi.object_models.common.Byte
256 ubyte = pyffi.object_models.common.UByte
257 short = pyffi.object_models.common.Short
258 ushort = pyffi.object_models.common.UShort
259 char = pyffi.object_models.common.Char
260 float = pyffi.object_models.common.Float
261 bool = pyffi.object_models.common.Bool
262 String = pyffi.object_models.common.ZString
263 SizedString = pyffi.object_models.common.SizedString
264
265 # implementation of cgf-specific basic types
266
270
274
278
282
286
288 """The CryTek file signature with which every
289 cgf file starts."""
292
295
297 if data.game == "Aion":
298 return 'NCAion'.encode("ascii")
299 else:
300 return 'CryTek'.encode("ascii")
301
303 """Read signature from stream.
304
305 :param stream: The stream to read from.
306 :type stream: file
307 """
308 gamesig = self._str(data)
309 signat = stream.read(8)
310 if not signat.startswith(gamesig):
311 raise ValueError(
312 "invalid CGF signature: expected %s but got %s"
313 % (gamesig, signat))
314
316 """Write signature to stream.
317
318 :param stream: The stream to read from.
319 :type stream: file
320 """
321 stream.write(self._str(data).ljust(8, '\x00'.encode("ascii")))
322
329
333
335 """Return number of bytes that the signature occupies in a file.
336
337 :return: Number of bytes.
338 """
339 return 8
340
342 """Return a hash value for the signature.
343
344 :return: An immutable object that can be used as a hash.
345 """
346 return self.__str__()
347
349 """Reference to a chunk, up the hierarchy."""
350 _is_template = True
351 _has_links = True
352 _has_refs = True
354 super(CgfFormat.Ref, self).__init__(**kwargs)
355 self._template = kwargs.get('template', type(None))
356 self._value = None
357
359 """Get chunk being referred to.
360
361 :return: The chunk being referred to.
362 """
363 return self._value
364
366 """Set chunk reference.
367
368 :param value: The value to assign.
369 :type value: L{CgfFormat.Chunk}
370 """
371 if value == None:
372 self._value = None
373 else:
374 if not isinstance(value, self._template):
375 raise TypeError(
376 'expected an instance of %s but got instance of %s'
377 %(self._template, value.__class__))
378 self._value = value
379
381 """Read chunk index.
382
383 :param stream: The stream to read from.
384 :type stream: file
385 """
386 self._value = None # fix_links will set this field
387 block_index, = struct.unpack('<i', stream.read(4))
388 data._link_stack.append(block_index)
389
391 """Write chunk index.
392
393 :param stream: The stream to write to.
394 :type stream: file
395 """
396 if self._value is None:
397 stream.write(struct.pack('<i', -1))
398 else:
399 stream.write(struct.pack(
400 '<i', data._block_index_dct[self._value]))
401
403 """Resolve chunk index into a chunk.
404
405 :keyword block_dct: Dictionary mapping block index to block.
406 :type block_dct: dict
407 """
408 logger = logging.getLogger("pyffi.cgf.data")
409 block_index = data._link_stack.pop(0)
410 # case when there's no link
411 if block_index == -1:
412 self._value = None
413 return
414 # other case: look up the link and check the link type
415 try:
416 block = data._block_dct[block_index]
417 except KeyError:
418 # make this raise an exception when all reference errors
419 # are sorted out
420 logger.warn("invalid chunk reference (%i)" % block_index)
421 self._value = None
422 return
423 if not isinstance(block, self._template):
424 if block_index == 0:
425 # crysis often uses index 0 to refer to an invalid index
426 # so don't complain on this one
427 block = None
428 else:
429 # make this raise an exception when all reference errors
430 # are sorted out
431 logger.warn("""\
432 expected instance of %s
433 but got instance of %s""" % (self._template, block.__class__))
434 self._value = block
435
437 """Return the chunk reference.
438
439 :return: Empty list if no reference, or single item list containing
440 the reference.
441 """
442 if self._value != None:
443 return [self._value]
444 else:
445 return []
446
448 """Return the chunk reference.
449
450 :return: Empty list if no reference, or single item list containing
451 the reference.
452 """
453 if self._value != None:
454 return [self._value]
455 else:
456 return []
457
459 # don't recurse
460 if self._value != None:
461 return '%s instance at 0x%08X'\
462 % (self._value.__class__, id(self._value))
463 else:
464 return 'None'
465
467 """Return number of bytes this type occupies in a file.
468
469 :return: Number of bytes.
470 """
471 return 4
472
479
481 """Reference to a chunk, down the hierarchy."""
482 _is_template = True
483 _has_links = True
484 _has_refs = False
485
487 # avoid infinite recursion
488 if self._value != None:
489 return '%s instance at 0x%08X'\
490 % (self._value.__class__, id(self._value))
491 else:
492 return 'None'
493
500
501 @staticmethod
503 """Converts version string into an integer.
504
505 :param version_str: The version string.
506 :type version_str: str
507 :return: A version integer.
508
509 >>> hex(CgfFormat.version_number('744'))
510 '0x744'
511 """
512 return int(version_str, 16)
513
514 # exceptions
518
520 """A class to contain the actual cgf data.
521
522 Note that L{versions} and L{chunk_table} are not automatically kept
523 in sync with the L{chunks}, but they are
524 resynchronized when calling L{write}.
525
526 :ivar game: The cgf game.
527 :type game: ``int``
528 :ivar header: The cgf header.
529 :type header: L{CgfFormat.Header}
530 :ivar chunks: List of chunks (the actual data).
531 :type chunks: ``list`` of L{CgfFormat.Chunk}
532 :ivar versions: List of chunk versions.
533 :type versions: ``list`` of L{int}
534 """
535 _link_stack = None
536 _block_index_dct = None
537 _block_dct = None
538
540 # 0xffff0000 = CgfFormat.FileType.GEOM
541
542 """Initialize cgf data. By default, this creates an empty
543 cgf document of the given filetype and game.
544
545 :param filetype: The file type (animation, or geometry).
546 :type filetype: ``int``
547 :param game: The game.
548 :type game: ``str``
549 """
550 # create new header
551 self.header = CgfFormat.Header()
552 self.header.type = filetype
553 self.header.version = 0x744 # no other chunk table versions
554 # empty list of chunks
555 self.chunks = []
556 # empty list of versions (one per chunk)
557 self.versions = []
558 # chunk table
559 self.chunk_table = CgfFormat.ChunkTable()
560 # game
561 # TODO store this in a way that can be displayed by qskope
562 self.game = game
563 # set version and user version
564 self.version = self.header.version
565 if self.game == "Far Cry":
566 self.user_version = CgfFormat.UVER_FARCRY
567 elif self.game == "Crysis":
568 self.user_version = CgfFormat.UVER_CRYSIS
569 elif self.game == "Aion":
570 # XXX guessing for Aion!
571 self.user_version = CgfFormat.UVER_FARCRY
572 else:
573 raise ValueError("unknown game %s" % game)
574
575 # new functions
576
578 """This function checks the version only, and is faster
579 than the usual inspect function (which reads the full
580 chunk table). Sets the L{header} and L{game} instance
581 variables if the stream contains a valid cgf file.
582
583 Call this function if you simply wish to check that a file is
584 a cgf file without having to parse even the header.
585
586 :raise ``ValueError``: If the stream does not contain a cgf file.
587 :param stream: The stream from which to read.
588 :type stream: ``file``
589 """
590 pos = stream.tell()
591 try:
592 signat = stream.read(8)
593 filetype, version, offset = struct.unpack('<III',
594 stream.read(12))
595 except IOError:
596 raise
597 except Exception:
598 # something went wrong with unpack
599 # this means that the file is less than 20 bytes
600 # cannot be a cgf file
601 raise ValueError("File too small to be a cgf file.")
602 finally:
603 stream.seek(pos)
604
605 # test the data
606 if (signat[:6] != "CryTek".encode("ascii")
607 and signat[:6] != "NCAion".encode("ascii")):
608 raise ValueError(
609 "Invalid signature (got '%s' instead of 'CryTek' or 'NCAion')"
610 % signat[:6])
611 if filetype not in (CgfFormat.FileType.GEOM,
612 CgfFormat.FileType.ANIM):
613 raise ValueError("Invalid file type.")
614 if version not in CgfFormat.versions.values():
615 raise ValueError("Invalid file version.")
616 # quick and lame game check:
617 # far cry has chunk table at the end, crysis at the start
618 if signat[:6] == "NCAion".encode("ascii"):
619 self.game = "Aion"
620 elif offset == 0x14:
621 self.game = "Crysis"
622 else:
623 self.game = "Far Cry"
624 # load the actual header
625 try:
626 self.header.read(stream, self)
627 finally:
628 stream.seek(pos)
629 # set version and user version
630 self.version = self.header.version
631 if self.game == "Far Cry":
632 self.user_version = CgfFormat.UVER_FARCRY
633 elif self.game == "Crysis":
634 self.user_version = CgfFormat.UVER_CRYSIS
635 elif self.game == "Aion":
636 # XXX guessing for Aion!
637 self.user_version = CgfFormat.UVER_FARCRY
638 else:
639 raise ValueError("unknown game %s" % game)
640
641 # GlobalNode
642
644 """Returns chunks without parent."""
645 # calculate all children of all chunks
646 children = set()
647 for chunk in self.chunks:
648 children |= set(chunk.get_global_child_nodes())
649 # iterate over all chunks that are NOT in the list of children
650 return (chunk for chunk in self.chunks
651 if not chunk in children)
652
653 # DetailNode
654
657 for i, chunk in enumerate(self.chunks):
658 if chunk is oldbranch:
659 self.chunks[i] = newbranch
660 else:
661 chunk.replace_global_node(oldbranch, newbranch,
662 edge_filter=edge_filter)
663
667
671
672 # overriding pyffi.object_models.FileFormat.Data methods
673
675 """Quickly checks whether the stream appears to contain
676 cgf data, and read the cgf header and chunk table. Resets stream to
677 original position.
678
679 Call this function if you only need to inspect the header and
680 chunk table.
681
682 :param stream: The file to inspect.
683 :type stream: ``file``
684 """
685 logger = logging.getLogger("pyffi.cgf.data")
686 pos = stream.tell()
687 try:
688 logger.debug("Reading header at 0x%08X." % stream.tell())
689 self.inspect_version_only(stream)
690 self.header.read(stream, data=self)
691 stream.seek(self.header.offset)
692 logger.debug("Reading chunk table version 0x%08X at 0x%08X." % (self.header.version, stream.tell()))
693 self.chunk_table.read(stream, self)
694 finally:
695 stream.seek(pos)
696
698 """Read a cgf file. Does not reset stream position.
699
700 :param stream: The stream from which to read.
701 :type stream: ``file``
702 """
703 validate = True # whether we validate on reading
704
705 logger = logging.getLogger("pyffi.cgf.data")
706 self.inspect(stream)
707
708 # is it a caf file? these are missing chunk headers on controllers
709 # (note: stream.name may not be a python string for some file
710 # implementations, notably PyQt4, so convert it explicitely)
711 is_caf = (str(stream.name)[-4:].lower() == ".caf")
712
713 chunk_types = [
714 chunk_type for chunk_type in dir(CgfFormat.ChunkType) \
715 if chunk_type[:2] != '__']
716
717 # get the chunk sizes (for double checking that we have all data)
718 if validate:
719 chunk_offsets = [chunkhdr.offset
720 for chunkhdr in self.chunk_table.chunk_headers]
721 chunk_offsets.append(self.header.offset)
722 chunk_sizes = []
723 for chunkhdr in self.chunk_table.chunk_headers:
724 next_chunk_offsets = [offset for offset in chunk_offsets
725 if offset > chunkhdr.offset]
726 if next_chunk_offsets:
727 chunk_sizes.append(min(next_chunk_offsets) - chunkhdr.offset)
728 else:
729 stream.seek(0, 2)
730 chunk_sizes.append(stream.tell() - chunkhdr.offset)
731
732 # read the chunks
733 self._link_stack = [] # list of chunk identifiers, as added to the stack
734 self._block_dct = {} # maps chunk index to actual chunk
735 self.chunks = [] # records all chunks as read from cgf file in proper order
736 self.versions = [] # records all chunk versions as read from cgf file
737 for chunknum, chunkhdr in enumerate(self.chunk_table.chunk_headers):
738 # check that id is unique
739 if chunkhdr.id in self._block_dct:
740 raise ValueError('chunk id %i not unique'%chunkhdr.id)
741
742 # get chunk type
743 for chunk_type in chunk_types:
744 if getattr(CgfFormat.ChunkType, chunk_type) == chunkhdr.type:
745 break
746 else:
747 raise ValueError('unknown chunk type 0x%08X'%chunkhdr.type)
748 try:
749 chunk = getattr(CgfFormat, '%sChunk' % chunk_type)()
750 except AttributeError:
751 raise ValueError(
752 'undecoded chunk type 0x%08X (%sChunk)'
753 %(chunkhdr.type, chunk_type))
754 # check the chunk version
755 if not self.game in chunk.get_games():
756 logger.error(
757 'game %s does not support %sChunk; '
758 'trying anyway'
759 % (self.game, chunk_type))
760 if not chunkhdr.version in chunk.get_versions(self.game):
761 logger.error(
762 'chunk version 0x%08X not supported for '
763 'game %s and %sChunk; '
764 'trying anyway'
765 % (chunkhdr.version, self.game, chunk_type))
766
767 # now read the chunk
768 stream.seek(chunkhdr.offset)
769 logger.debug("Reading %s chunk version 0x%08X at 0x%08X"
770 % (chunk_type, chunkhdr.version, stream.tell()))
771
772 # in far cry, most chunks start with a copy of chunkhdr
773 # in crysis, more chunks start with chunkhdr
774 # caf files are special: they don't have headers on controllers
775 if not(self.user_version == CgfFormat.UVER_FARCRY
776 and chunkhdr.type in [
777 CgfFormat.ChunkType.SourceInfo,
778 CgfFormat.ChunkType.BoneNameList,
779 CgfFormat.ChunkType.BoneLightBinding,
780 CgfFormat.ChunkType.BoneInitialPos,
781 CgfFormat.ChunkType.MeshMorphTarget]) \
782 and not(self.user_version == CgfFormat.UVER_CRYSIS
783 and chunkhdr.type in [
784 CgfFormat.ChunkType.BoneNameList,
785 CgfFormat.ChunkType.BoneInitialPos]) \
786 and not(is_caf
787 and chunkhdr.type in [
788 CgfFormat.ChunkType.Controller]) \
789 and not((self.game == "Aion") and chunkhdr.type in [
790 CgfFormat.ChunkType.MeshPhysicsData,
791 CgfFormat.ChunkType.MtlName]):
792 chunkhdr_copy = CgfFormat.ChunkHeader()
793 chunkhdr_copy.read(stream, self)
794 # check that the copy is valid
795 # note: chunkhdr_copy.offset != chunkhdr.offset check removed
796 # as many crysis cgf files have this wrong
797 if chunkhdr_copy.type != chunkhdr.type \
798 or chunkhdr_copy.version != chunkhdr.version \
799 or chunkhdr_copy.id != chunkhdr.id:
800 raise ValueError(
801 'chunk starts with invalid header:\n\
802 expected\n%sbut got\n%s'%(chunkhdr, chunkhdr_copy))
803 else:
804 chunkhdr_copy = None
805
806 # quick hackish trick with version... not beautiful but it works
807 self.version = chunkhdr.version
808 try:
809 chunk.read(stream, self)
810 finally:
811 self.version = self.header.version
812 self.chunks.append(chunk)
813 self.versions.append(chunkhdr.version)
814 self._block_dct[chunkhdr.id] = chunk
815
816 if validate:
817 # calculate size
818 # (quick hackish trick with version)
819 self.version = chunkhdr.version
820 try:
821 size = chunk.get_size(self)
822 finally:
823 self.version = self.header.version
824 # take into account header copy
825 if chunkhdr_copy:
826 size += chunkhdr_copy.get_size(self)
827 # check with number of bytes read
828 if size != stream.tell() - chunkhdr.offset:
829 logger.error("""\
830 get_size returns wrong size when reading %s at 0x%08X
831 actual bytes read is %i, get_size yields %i (expected %i bytes)"""
832 % (chunk.__class__.__name__,
833 chunkhdr.offset,
834 size,
835 stream.tell() - chunkhdr.offset,
836 chunk_sizes[chunknum]))
837 # check for padding bytes
838 if chunk_sizes[chunknum] & 3 == 0:
839 padlen = ((4 - size & 3) & 3)
840 #assert(stream.read(padlen) == '\x00' * padlen)
841 size += padlen
842 # check size
843 if size != chunk_sizes[chunknum]:
844 logger.warn("""\
845 chunk size mismatch when reading %s at 0x%08X
846 %i bytes available, but actual bytes read is %i"""
847 % (chunk.__class__.__name__,
848 chunkhdr.offset,
849 chunk_sizes[chunknum], size))
850
851 # fix links
852 for chunk, chunkversion in zip(self.chunks, self.versions):
853 # (quick hackish trick with version)
854 self.version = chunkversion
855 try:
856 #print(chunk.__class__)
857 chunk.fix_links(self)
858 finally:
859 self.version = self.header.version
860 if self._link_stack != []:
861 raise CgfFormat.CgfError(
862 'not all links have been popped from the stack (bug?)')
863
865 """Write a cgf file. The L{header} and L{chunk_table} are
866 recalculated from L{chunks}. Returns number of padding bytes
867 written (this is for debugging purposes only).
868
869 :param stream: The stream to which to write.
870 :type stream: file
871 :return: Number of padding bytes written.
872 """
873 logger = logging.getLogger("pyffi.cgf.data")
874 # is it a caf file? these are missing chunk headers on controllers
875 is_caf = (str(stream.name)[-4:].lower() == ".caf")
876
877 # variable to track number of padding bytes
878 total_padding = 0
879
880 # chunk versions
881 self.update_versions()
882
883 # write header
884 hdr_pos = stream.tell()
885 self.header.offset = -1 # is set at the end
886 self.header.write(stream, self)
887
888 # chunk id is simply its index in the chunks list
889 self._block_index_dct = dict(
890 (chunk, i) for i, chunk in enumerate(self.chunks))
891
892 # write chunks and add headers to chunk table
893 self.chunk_table = CgfFormat.ChunkTable()
894 self.chunk_table.num_chunks = len(self.chunks)
895 self.chunk_table.chunk_headers.update_size()
896 #print(self.chunk_table) # DEBUG
897
898 # crysis: write chunk table now
899 if self.user_version == CgfFormat.UVER_CRYSIS:
900 self.header.offset = stream.tell()
901 self.chunk_table.write(stream, self)
902
903 for chunkhdr, chunk, chunkversion in zip(self.chunk_table.chunk_headers,
904 self.chunks, self.versions):
905 logger.debug("Writing %s chunk version 0x%08X at 0x%08X" % (chunk.__class__.__name__, chunkhdr.version, stream.tell()))
906
907 # set up chunk header
908 chunkhdr.type = getattr(
909 CgfFormat.ChunkType, chunk.__class__.__name__[:-5])
910 chunkhdr.version = chunkversion
911 chunkhdr.offset = stream.tell()
912 chunkhdr.id = self._block_index_dct[chunk]
913 # write chunk header
914 if not(self.user_version == CgfFormat.UVER_FARCRY
915 and chunkhdr.type in [
916 CgfFormat.ChunkType.SourceInfo,
917 CgfFormat.ChunkType.BoneNameList,
918 CgfFormat.ChunkType.BoneLightBinding,
919 CgfFormat.ChunkType.BoneInitialPos,
920 CgfFormat.ChunkType.MeshMorphTarget]) \
921 and not(self.user_version == CgfFormat.UVER_CRYSIS
922 and chunkhdr.type in [
923 CgfFormat.ChunkType.BoneNameList,
924 CgfFormat.ChunkType.BoneInitialPos]) \
925 and not(is_caf
926 and chunkhdr.type in [
927 CgfFormat.ChunkType.Controller]):
928 #print(chunkhdr) # DEBUG
929 chunkhdr.write(stream, self)
930 # write chunk (with version hack)
931 self.version = chunkversion
932 try:
933 chunk.write(stream, self)
934 finally:
935 self.version = self.header.version
936 # write padding bytes to align blocks
937 padlen = (4 - stream.tell() & 3) & 3
938 if padlen:
939 stream.write("\x00".encode("ascii") * padlen)
940 total_padding += padlen
941
942 # write/update chunk table
943 logger.debug("Writing chunk table version 0x%08X at 0x%08X"
944 % (self.header.version, stream.tell()))
945 if self.user_version == CgfFormat.UVER_CRYSIS:
946 end_pos = stream.tell()
947 stream.seek(self.header.offset)
948 self.chunk_table.write(stream, self)
949 else:
950 self.header.offset = stream.tell()
951 self.chunk_table.write(stream, self)
952 end_pos = stream.tell()
953
954 # update header
955 stream.seek(hdr_pos)
956 self.header.write(stream, self)
957
958 # seek end of written data
959 stream.seek(end_pos)
960
961 # return number of padding bytes written
962 return total_padding
963
965 """Update L{versions} for the given chunks and game."""
966 try:
967 self.versions = [max(chunk.get_versions(self.game))
968 for chunk in self.chunks]
969 except KeyError:
970 raise CgfFormat.CgfError("game %s not supported" % self.game)
971
972 # extensions of generated structures
973
976 """A generator for parsing all blocks in the tree (starting from and
977 including C{self}).
978
979 :param block_type: If not ``None``, yield only blocks of the type C{block_type}.
980 :param follow_all: If C{block_type} is not ``None``, then if this is ``True`` the function will parse the whole tree. Otherwise, the function will not follow branches that start by a non-C{block_type} block."""
981 # yield self
982 if not block_type:
983 yield self
984 elif isinstance(self, block_type):
985 yield self
986 elif not follow_all:
987 return # don't recurse further
988
989 # yield tree attached to each child
990 for child in self.get_refs():
991 for block in child.tree(block_type = block_type, follow_all = follow_all):
992 yield block
993
997
1000 """Iterate all chunk types (in the form of Python classes) referenced
1001 in this table.
1002 """
1003 return (CgfFormat.CHUNK_MAP[chunk_header.type]
1004 for chunk_header in self.chunk_headers)
1005
1008 """Apply scale factor on data."""
1009 if abs(scale - 1.0) < CgfFormat.EPSILON:
1010 return
1011 for mat in self.initial_pos_matrices:
1012 mat.pos.x *= scale
1013 mat.pos.y *= scale
1014 mat.pos.z *= scale
1015
1017 """Get the block parent (used for instance in the QSkope global
1018 view)."""
1019 return self.mesh
1020
1030
1033 """Return matrix as 3x3 list."""
1034 return [
1035 [self.m_11, self.m_12, self.m_13],
1036 [self.m_21, self.m_22, self.m_23],
1037 [self.m_31, self.m_32, self.m_33]
1038 ]
1039
1041 """Return matrix as 3x3 tuple."""
1042 return (
1043 (self.m_11, self.m_12, self.m_13),
1044 (self.m_21, self.m_22, self.m_23),
1045 (self.m_31, self.m_32, self.m_33)
1046 )
1047
1049 return(
1050 "[ %6.3f %6.3f %6.3f ]\n[ %6.3f %6.3f %6.3f ]\n[ %6.3f %6.3f %6.3f ]\n"
1051 % (self.m_11, self.m_12, self.m_13,
1052 self.m_21, self.m_22, self.m_23,
1053 self.m_31, self.m_32, self.m_33))
1054
1056 """Set to identity matrix."""
1057 self.m_11 = 1.0
1058 self.m_12 = 0.0
1059 self.m_13 = 0.0
1060 self.m_21 = 0.0
1061 self.m_22 = 1.0
1062 self.m_23 = 0.0
1063 self.m_31 = 0.0
1064 self.m_32 = 0.0
1065 self.m_33 = 1.0
1066
1068 """Return ``True`` if the matrix is close to identity."""
1069 if (abs(self.m_11 - 1.0) > CgfFormat.EPSILON
1070 or abs(self.m_12) > CgfFormat.EPSILON
1071 or abs(self.m_13) > CgfFormat.EPSILON
1072 or abs(self.m_21) > CgfFormat.EPSILON
1073 or abs(self.m_22 - 1.0) > CgfFormat.EPSILON
1074 or abs(self.m_23) > CgfFormat.EPSILON
1075 or abs(self.m_31) > CgfFormat.EPSILON
1076 or abs(self.m_32) > CgfFormat.EPSILON
1077 or abs(self.m_33 - 1.0) > CgfFormat.EPSILON):
1078 return False
1079 else:
1080 return True
1081
1083 """Return a copy of the matrix."""
1084 mat = CgfFormat.Matrix33()
1085 mat.m_11 = self.m_11
1086 mat.m_12 = self.m_12
1087 mat.m_13 = self.m_13
1088 mat.m_21 = self.m_21
1089 mat.m_22 = self.m_22
1090 mat.m_23 = self.m_23
1091 mat.m_31 = self.m_31
1092 mat.m_32 = self.m_32
1093 mat.m_33 = self.m_33
1094 return mat
1095
1097 """Get transposed of the matrix."""
1098 mat = CgfFormat.Matrix33()
1099 mat.m_11 = self.m_11
1100 mat.m_12 = self.m_21
1101 mat.m_13 = self.m_31
1102 mat.m_21 = self.m_12
1103 mat.m_22 = self.m_22
1104 mat.m_23 = self.m_32
1105 mat.m_31 = self.m_13
1106 mat.m_32 = self.m_23
1107 mat.m_33 = self.m_33
1108 return mat
1109
1111 """Returns true if the matrix decomposes nicely into scale * rotation."""
1112 # NOTE: 0.01 instead of CgfFormat.EPSILON to work around bad files
1113
1114 # calculate self * self^T
1115 # this should correspond to
1116 # (scale * rotation) * (scale * rotation)^T
1117 # = scale * rotation * rotation^T * scale^T
1118 # = scale * scale^T
1119 self_transpose = self.get_transpose()
1120 mat = self * self_transpose
1121
1122 # off diagonal elements should be zero
1123 if (abs(mat.m_12) + abs(mat.m_13)
1124 + abs(mat.m_21) + abs(mat.m_23)
1125 + abs(mat.m_31) + abs(mat.m_32)) > 0.01:
1126 return False
1127
1128 return True
1129
1131 """Returns ``True`` if the matrix is a rotation matrix
1132 (a member of SO(3))."""
1133 # NOTE: 0.01 instead of CgfFormat.EPSILON to work around bad files
1134
1135 if not self.is_scale_rotation():
1136 return False
1137 scale = self.get_scale()
1138 if abs(scale.x - 1.0) > 0.01 \
1139 or abs(scale.y - 1.0) > 0.01 \
1140 or abs(scale.z - 1.0) > 0.01:
1141 return False
1142 return True
1143
1145 """Return determinant."""
1146 return (self.m_11*self.m_22*self.m_33
1147 +self.m_12*self.m_23*self.m_31
1148 +self.m_13*self.m_21*self.m_32
1149 -self.m_31*self.m_22*self.m_13
1150 -self.m_21*self.m_12*self.m_33
1151 -self.m_11*self.m_32*self.m_23)
1152
1154 """Gets the scale (assuming is_scale_rotation is true!)."""
1155 # calculate self * self^T
1156 # this should correspond to
1157 # (rotation * scale)* (rotation * scale)^T
1158 # = scale * scale^T
1159 # = diagonal matrix with scales squared on the diagonal
1160 mat = self * self.get_transpose()
1161
1162 scale = CgfFormat.Vector3()
1163 scale.x = mat.m_11 ** 0.5
1164 scale.y = mat.m_22 ** 0.5
1165 scale.z = mat.m_33 ** 0.5
1166
1167 if self.get_determinant() < 0:
1168 return -scale
1169 else:
1170 return scale
1171
1173 """Decompose the matrix into scale and rotation, where scale is a float
1174 and rotation is a C{Matrix33}. Returns a pair (scale, rotation)."""
1175 rot = self.get_copy()
1176 scale = self.get_scale()
1177 if min(abs(x) for x in scale.as_tuple()) < CgfFormat.EPSILON:
1178 raise ZeroDivisionError('scale is zero, unable to obtain rotation')
1179 rot.m_11 /= scale.x
1180 rot.m_12 /= scale.x
1181 rot.m_13 /= scale.x
1182 rot.m_21 /= scale.y
1183 rot.m_22 /= scale.y
1184 rot.m_23 /= scale.y
1185 rot.m_31 /= scale.z
1186 rot.m_32 /= scale.z
1187 rot.m_33 /= scale.z
1188 return (scale, rot)
1189
1191 """Compose the matrix as the product of scale * rotation."""
1192 if not isinstance(scale, CgfFormat.Vector3):
1193 raise TypeError('scale must be Vector3')
1194 if not isinstance(rotation, CgfFormat.Matrix33):
1195 raise TypeError('rotation must be Matrix33')
1196
1197 if not rotation.is_rotation():
1198 raise ValueError('rotation must be rotation matrix')
1199
1200 self.m_11 = rotation.m_11 * scale.x
1201 self.m_12 = rotation.m_12 * scale.x
1202 self.m_13 = rotation.m_13 * scale.x
1203 self.m_21 = rotation.m_21 * scale.y
1204 self.m_22 = rotation.m_22 * scale.y
1205 self.m_23 = rotation.m_23 * scale.y
1206 self.m_31 = rotation.m_31 * scale.z
1207 self.m_32 = rotation.m_32 * scale.z
1208 self.m_33 = rotation.m_33 * scale.z
1209
1211 """Decompose matrix into scale and quaternion."""
1212 scale, rot = self.get_scale_rotation()
1213 quat = CgfFormat.Quat()
1214 trace = 1.0 + rot.m_11 + rot.m_22 + rot.m_33
1215
1216 if trace > CgfFormat.EPSILON:
1217 s = (trace ** 0.5) * 2
1218 quat.x = -( rot.m_32 - rot.m_23 ) / s
1219 quat.y = -( rot.m_13 - rot.m_31 ) / s
1220 quat.z = -( rot.m_21 - rot.m_12 ) / s
1221 quat.w = 0.25 * s
1222 elif rot.m_11 > max((rot.m_22, rot.m_33)):
1223 s = (( 1.0 + rot.m_11 - rot.m_22 - rot.m_33 ) ** 0.5) * 2
1224 quat.x = 0.25 * s
1225 quat.y = (rot.m_21 + rot.m_12 ) / s
1226 quat.z = (rot.m_13 + rot.m_31 ) / s
1227 quat.w = -(rot.m_32 - rot.m_23 ) / s
1228 elif rot.m_22 > rot.m_33:
1229 s = (( 1.0 + rot.m_22 - rot.m_11 - rot.m_33 ) ** 0.5) * 2
1230 quat.x = (rot.m_21 + rot.m_12 ) / s
1231 quat.y = 0.25 * s
1232 quat.z = (rot.m_32 + rot.m_23 ) / s
1233 quat.w = -(rot.m_13 - rot.m_31 ) / s
1234 else:
1235 s = (( 1.0 + rot.m_33 - rot.m_11 - rot.m_22 ) ** 0.5) * 2
1236 quat.x = (rot.m_13 + rot.m_31 ) / s
1237 quat.y = (rot.m_32 + rot.m_23 ) / s
1238 quat.z = 0.25 * s
1239 quat.w = -(rot.m_21 - rot.m_12 ) / s
1240
1241 return scale, quat
1242
1243
1245 """Get inverse (assuming is_scale_rotation is true!)."""
1246 # transpose inverts rotation but keeps the scale
1247 # dividing by scale^2 inverts the scale as well
1248 scale = self.get_scale()
1249 mat = self.get_transpose()
1250 mat.m_11 /= scale.x ** 2
1251 mat.m_12 /= scale.x ** 2
1252 mat.m_13 /= scale.x ** 2
1253 mat.m_21 /= scale.y ** 2
1254 mat.m_22 /= scale.y ** 2
1255 mat.m_23 /= scale.y ** 2
1256 mat.m_31 /= scale.z ** 2
1257 mat.m_32 /= scale.z ** 2
1258 mat.m_33 /= scale.z ** 2
1259
1261 if isinstance(rhs, (float, int, long)):
1262 mat = CgfFormat.Matrix33()
1263 mat.m_11 = self.m_11 * rhs
1264 mat.m_12 = self.m_12 * rhs
1265 mat.m_13 = self.m_13 * rhs
1266 mat.m_21 = self.m_21 * rhs
1267 mat.m_22 = self.m_22 * rhs
1268 mat.m_23 = self.m_23 * rhs
1269 mat.m_31 = self.m_31 * rhs
1270 mat.m_32 = self.m_32 * rhs
1271 mat.m_33 = self.m_33 * rhs
1272 return mat
1273 elif isinstance(rhs, CgfFormat.Vector3):
1274 raise TypeError("matrix*vector not supported;\
1275 please use left multiplication (vector*matrix)")
1276 elif isinstance(rhs, CgfFormat.Matrix33):
1277 mat = CgfFormat.Matrix33()
1278 mat.m_11 = self.m_11 * rhs.m_11 + self.m_12 * rhs.m_21 + self.m_13 * rhs.m_31
1279 mat.m_12 = self.m_11 * rhs.m_12 + self.m_12 * rhs.m_22 + self.m_13 * rhs.m_32
1280 mat.m_13 = self.m_11 * rhs.m_13 + self.m_12 * rhs.m_23 + self.m_13 * rhs.m_33
1281 mat.m_21 = self.m_21 * rhs.m_11 + self.m_22 * rhs.m_21 + self.m_23 * rhs.m_31
1282 mat.m_22 = self.m_21 * rhs.m_12 + self.m_22 * rhs.m_22 + self.m_23 * rhs.m_32
1283 mat.m_23 = self.m_21 * rhs.m_13 + self.m_22 * rhs.m_23 + self.m_23 * rhs.m_33
1284 mat.m_31 = self.m_31 * rhs.m_11 + self.m_32 * rhs.m_21 + self.m_33 * rhs.m_31
1285 mat.m_32 = self.m_31 * rhs.m_12 + self.m_32 * rhs.m_22 + self.m_33 * rhs.m_32
1286 mat.m_33 = self.m_31 * rhs.m_13 + self.m_32 * rhs.m_23 + self.m_33 * rhs.m_33
1287 return mat
1288 else:
1289 raise TypeError(
1290 "do not know how to multiply Matrix33 with %s"%rhs.__class__)
1291
1293 if isinstance(rhs, (float, int, long)):
1294 mat = CgfFormat.Matrix33()
1295 mat.m_11 = self.m_11 / rhs
1296 mat.m_12 = self.m_12 / rhs
1297 mat.m_13 = self.m_13 / rhs
1298 mat.m_21 = self.m_21 / rhs
1299 mat.m_22 = self.m_22 / rhs
1300 mat.m_23 = self.m_23 / rhs
1301 mat.m_31 = self.m_31 / rhs
1302 mat.m_32 = self.m_32 / rhs
1303 mat.m_33 = self.m_33 / rhs
1304 return mat
1305 else:
1306 raise TypeError(
1307 "do not know how to divide Matrix33 by %s"%rhs.__class__)
1308
1310 if isinstance(lhs, (float, int, long)):
1311 return self * lhs # commutes
1312 else:
1313 raise TypeError(
1314 "do not know how to multiply %s with Matrix33"%lhs.__class__)
1315
1317 if not isinstance(mat, CgfFormat.Matrix33):
1318 raise TypeError(
1319 "do not know how to compare Matrix33 and %s"%mat.__class__)
1320 if (abs(self.m_11 - mat.m_11) > CgfFormat.EPSILON
1321 or abs(self.m_12 - mat.m_12) > CgfFormat.EPSILON
1322 or abs(self.m_13 - mat.m_13) > CgfFormat.EPSILON
1323 or abs(self.m_21 - mat.m_21) > CgfFormat.EPSILON
1324 or abs(self.m_22 - mat.m_22) > CgfFormat.EPSILON
1325 or abs(self.m_23 - mat.m_23) > CgfFormat.EPSILON
1326 or abs(self.m_31 - mat.m_31) > CgfFormat.EPSILON
1327 or abs(self.m_32 - mat.m_32) > CgfFormat.EPSILON
1328 or abs(self.m_33 - mat.m_33) > CgfFormat.EPSILON):
1329 return False
1330 return True
1331
1333 return not self.__eq__(mat)
1334
1337 """Return matrix as 4x4 list."""
1338 return [
1339 [self.m_11, self.m_12, self.m_13, self.m_14],
1340 [self.m_21, self.m_22, self.m_23, self.m_24],
1341 [self.m_31, self.m_32, self.m_33, self.m_34],
1342 [self.m_41, self.m_42, self.m_43, self.m_44]
1343 ]
1344
1346 """Return matrix as 4x4 tuple."""
1347 return (
1348 (self.m_11, self.m_12, self.m_13, self.m_14),
1349 (self.m_21, self.m_22, self.m_23, self.m_24),
1350 (self.m_31, self.m_32, self.m_33, self.m_34),
1351 (self.m_41, self.m_42, self.m_43, self.m_44)
1352 )
1353
1355 """Set matrix from rows."""
1356 self.m_11, self.m_12, self.m_13, self.m_14 = row0
1357 self.m_21, self.m_22, self.m_23, self.m_24 = row1
1358 self.m_31, self.m_32, self.m_33, self.m_34 = row2
1359 self.m_41, self.m_42, self.m_43, self.m_44 = row3
1360
1362 return(
1363 '[ %6.3f %6.3f %6.3f %6.3f ]\n'
1364 '[ %6.3f %6.3f %6.3f %6.3f ]\n'
1365 '[ %6.3f %6.3f %6.3f %6.3f ]\n'
1366 '[ %6.3f %6.3f %6.3f %6.3f ]\n'
1367 % (self.m_11, self.m_12, self.m_13, self.m_14,
1368 self.m_21, self.m_22, self.m_23, self.m_24,
1369 self.m_31, self.m_32, self.m_33, self.m_34,
1370 self.m_41, self.m_42, self.m_43, self.m_44))
1371
1373 """Set to identity matrix."""
1374 self.m_11 = 1.0
1375 self.m_12 = 0.0
1376 self.m_13 = 0.0
1377 self.m_14 = 0.0
1378 self.m_21 = 0.0
1379 self.m_22 = 1.0
1380 self.m_23 = 0.0
1381 self.m_24 = 0.0
1382 self.m_31 = 0.0
1383 self.m_32 = 0.0
1384 self.m_33 = 1.0
1385 self.m_34 = 0.0
1386 self.m_41 = 0.0
1387 self.m_42 = 0.0
1388 self.m_43 = 0.0
1389 self.m_44 = 1.0
1390
1392 """Return ``True`` if the matrix is close to identity."""
1393 if (abs(self.m_11 - 1.0) > CgfFormat.EPSILON
1394 or abs(self.m_12) > CgfFormat.EPSILON
1395 or abs(self.m_13) > CgfFormat.EPSILON
1396 or abs(self.m_14) > CgfFormat.EPSILON
1397 or abs(self.m_21) > CgfFormat.EPSILON
1398 or abs(self.m_22 - 1.0) > CgfFormat.EPSILON
1399 or abs(self.m_23) > CgfFormat.EPSILON
1400 or abs(self.m_24) > CgfFormat.EPSILON
1401 or abs(self.m_31) > CgfFormat.EPSILON
1402 or abs(self.m_32) > CgfFormat.EPSILON
1403 or abs(self.m_33 - 1.0) > CgfFormat.EPSILON
1404 or abs(self.m_34) > CgfFormat.EPSILON
1405 or abs(self.m_41) > CgfFormat.EPSILON
1406 or abs(self.m_42) > CgfFormat.EPSILON
1407 or abs(self.m_43) > CgfFormat.EPSILON
1408 or abs(self.m_44 - 1.0) > CgfFormat.EPSILON):
1409 return False
1410 else:
1411 return True
1412
1414 """Create a copy of the matrix."""
1415 mat = CgfFormat.Matrix44()
1416 mat.m_11 = self.m_11
1417 mat.m_12 = self.m_12
1418 mat.m_13 = self.m_13
1419 mat.m_14 = self.m_14
1420 mat.m_21 = self.m_21
1421 mat.m_22 = self.m_22
1422 mat.m_23 = self.m_23
1423 mat.m_24 = self.m_24
1424 mat.m_31 = self.m_31
1425 mat.m_32 = self.m_32
1426 mat.m_33 = self.m_33
1427 mat.m_34 = self.m_34
1428 mat.m_41 = self.m_41
1429 mat.m_42 = self.m_42
1430 mat.m_43 = self.m_43
1431 mat.m_44 = self.m_44
1432 return mat
1433
1435 """Returns upper left 3x3 part."""
1436 m = CgfFormat.Matrix33()
1437 m.m_11 = self.m_11
1438 m.m_12 = self.m_12
1439 m.m_13 = self.m_13
1440 m.m_21 = self.m_21
1441 m.m_22 = self.m_22
1442 m.m_23 = self.m_23
1443 m.m_31 = self.m_31
1444 m.m_32 = self.m_32
1445 m.m_33 = self.m_33
1446 return m
1447
1449 """Sets upper left 3x3 part."""
1450 if not isinstance(m, CgfFormat.Matrix33):
1451 raise TypeError('argument must be Matrix33')
1452 self.m_11 = m.m_11
1453 self.m_12 = m.m_12
1454 self.m_13 = m.m_13
1455 self.m_21 = m.m_21
1456 self.m_22 = m.m_22
1457 self.m_23 = m.m_23
1458 self.m_31 = m.m_31
1459 self.m_32 = m.m_32
1460 self.m_33 = m.m_33
1461
1463 """Returns lower left 1x3 part."""
1464 t = CgfFormat.Vector3()
1465 t.x = self.m_41
1466 t.y = self.m_42
1467 t.z = self.m_43
1468 return t
1469
1471 """Returns lower left 1x3 part."""
1472 if not isinstance(translation, CgfFormat.Vector3):
1473 raise TypeError('argument must be Vector3')
1474 self.m_41 = translation.x
1475 self.m_42 = translation.y
1476 self.m_43 = translation.z
1477
1479 if not self.get_matrix_33().is_scale_rotation(): return False
1480 if abs(self.m_14) > CgfFormat.EPSILON: return False
1481 if abs(self.m_24) > CgfFormat.EPSILON: return False
1482 if abs(self.m_34) > CgfFormat.EPSILON: return False
1483 if abs(self.m_44 - 1.0) > CgfFormat.EPSILON: return False
1484 return True
1485
1487 rotscl = self.get_matrix_33()
1488 scale, rot = rotscl.get_scale_rotation()
1489 trans = self.get_translation()
1490 return (scale, rot, trans)
1491
1493 rotscl = self.get_matrix_33()
1494 scale, quat = rotscl.get_scale_quat()
1495 trans = self.get_translation()
1496 return (scale, quat, trans)
1497
1499 if not isinstance(scale, CgfFormat.Vector3):
1500 raise TypeError('scale must be Vector3')
1501 if not isinstance(rotation, CgfFormat.Matrix33):
1502 raise TypeError('rotation must be Matrix33')
1503 if not isinstance(translation, CgfFormat.Vector3):
1504 raise TypeError('translation must be Vector3')
1505
1506 if not rotation.is_rotation():
1507 logger = logging.getLogger("pyffi.cgf.matrix")
1508 mat = rotation * rotation.get_transpose()
1509 idmat = CgfFormat.Matrix33()
1510 idmat.set_identity()
1511 error = (mat - idmat).sup_norm()
1512 logger.warning("improper rotation matrix (error is %f)" % error)
1513 logger.debug(" matrix =")
1514 for line in str(rotation).split("\n"):
1515 logger.debug(" %s" % line)
1516 logger.debug(" its determinant = %f" % rotation.get_determinant())
1517 logger.debug(" matrix * matrix^T =")
1518 for line in str(mat).split("\n"):
1519 logger.debug(" %s" % line)
1520
1521 self.m_14 = 0.0
1522 self.m_24 = 0.0
1523 self.m_34 = 0.0
1524 self.m_44 = 1.0
1525
1526 self.set_matrix_33(rotation * scale)
1527 self.set_translation(translation)
1528
1530 """Calculates inverse (fast assumes is_scale_rotation_translation is True)."""
1531 def adjoint(m, ii, jj):
1532 result = []
1533 for i, row in enumerate(m):
1534 if i == ii: continue
1535 result.append([])
1536 for j, x in enumerate(row):
1537 if j == jj: continue
1538 result[-1].append(x)
1539 return result
1540 def determinant(m):
1541 if len(m) == 2:
1542 return m[0][0]*m[1][1] - m[1][0]*m[0][1]
1543 result = 0.0
1544 for i in xrange(len(m)):
1545 det = determinant(adjoint(m, i, 0))
1546 if i & 1:
1547 result -= m[i][0] * det
1548 else:
1549 result += m[i][0] * det
1550 return result
1551
1552 if fast:
1553 m = self.get_matrix_33().get_inverse()
1554 t = -(self.get_translation() * m)
1555
1556 n = CgfFormat.Matrix44()
1557 n.m_14 = 0.0
1558 n.m_24 = 0.0
1559 n.m_34 = 0.0
1560 n.m_44 = 1.0
1561 n.set_matrix_33(m)
1562 n.set_translation(t)
1563 return n
1564 else:
1565 m = self.as_list()
1566 nn = [[0.0 for i in xrange(4)] for j in xrange(4)]
1567 det = determinant(m)
1568 if abs(det) < CgfFormat.EPSILON:
1569 raise ZeroDivisionError('cannot invert matrix:\n%s'%self)
1570 for i in xrange(4):
1571 for j in xrange(4):
1572 if (i+j) & 1:
1573 nn[j][i] = -determinant(adjoint(m, i, j)) / det
1574 else:
1575 nn[j][i] = determinant(adjoint(m, i, j)) / det
1576 n = CgfFormat.Matrix44()
1577 n.set_rows(*nn)
1578 return n
1579
1581 if isinstance(x, (float, int, long)):
1582 m = CgfFormat.Matrix44()
1583 m.m_11 = self.m_11 * x
1584 m.m_12 = self.m_12 * x
1585 m.m_13 = self.m_13 * x
1586 m.m_14 = self.m_14 * x
1587 m.m_21 = self.m_21 * x
1588 m.m_22 = self.m_22 * x
1589 m.m_23 = self.m_23 * x
1590 m.m_24 = self.m_24 * x
1591 m.m_31 = self.m_31 * x
1592 m.m_32 = self.m_32 * x
1593 m.m_33 = self.m_33 * x
1594 m.m_34 = self.m_34 * x
1595 m.m_41 = self.m_41 * x
1596 m.m_42 = self.m_42 * x
1597 m.m_43 = self.m_43 * x
1598 m.m_44 = self.m_44 * x
1599 return m
1600 elif isinstance(x, CgfFormat.Vector3):
1601 raise TypeError("matrix*vector not supported; please use left multiplication (vector*matrix)")
1602 elif isinstance(x, CgfFormat.Vector4):
1603 raise TypeError("matrix*vector not supported; please use left multiplication (vector*matrix)")
1604 elif isinstance(x, CgfFormat.Matrix44):
1605 m = CgfFormat.Matrix44()
1606 m.m_11 = self.m_11 * x.m_11 + self.m_12 * x.m_21 + self.m_13 * x.m_31 + self.m_14 * x.m_41
1607 m.m_12 = self.m_11 * x.m_12 + self.m_12 * x.m_22 + self.m_13 * x.m_32 + self.m_14 * x.m_42
1608 m.m_13 = self.m_11 * x.m_13 + self.m_12 * x.m_23 + self.m_13 * x.m_33 + self.m_14 * x.m_43
1609 m.m_14 = self.m_11 * x.m_14 + self.m_12 * x.m_24 + self.m_13 * x.m_34 + self.m_14 * x.m_44
1610 m.m_21 = self.m_21 * x.m_11 + self.m_22 * x.m_21 + self.m_23 * x.m_31 + self.m_24 * x.m_41
1611 m.m_22 = self.m_21 * x.m_12 + self.m_22 * x.m_22 + self.m_23 * x.m_32 + self.m_24 * x.m_42
1612 m.m_23 = self.m_21 * x.m_13 + self.m_22 * x.m_23 + self.m_23 * x.m_33 + self.m_24 * x.m_43
1613 m.m_24 = self.m_21 * x.m_14 + self.m_22 * x.m_24 + self.m_23 * x.m_34 + self.m_24 * x.m_44
1614 m.m_31 = self.m_31 * x.m_11 + self.m_32 * x.m_21 + self.m_33 * x.m_31 + self.m_34 * x.m_41
1615 m.m_32 = self.m_31 * x.m_12 + self.m_32 * x.m_22 + self.m_33 * x.m_32 + self.m_34 * x.m_42
1616 m.m_33 = self.m_31 * x.m_13 + self.m_32 * x.m_23 + self.m_33 * x.m_33 + self.m_34 * x.m_43
1617 m.m_34 = self.m_31 * x.m_14 + self.m_32 * x.m_24 + self.m_33 * x.m_34 + self.m_34 * x.m_44
1618 m.m_41 = self.m_41 * x.m_11 + self.m_42 * x.m_21 + self.m_43 * x.m_31 + self.m_44 * x.m_41
1619 m.m_42 = self.m_41 * x.m_12 + self.m_42 * x.m_22 + self.m_43 * x.m_32 + self.m_44 * x.m_42
1620 m.m_43 = self.m_41 * x.m_13 + self.m_42 * x.m_23 + self.m_43 * x.m_33 + self.m_44 * x.m_43
1621 m.m_44 = self.m_41 * x.m_14 + self.m_42 * x.m_24 + self.m_43 * x.m_34 + self.m_44 * x.m_44
1622 return m
1623 else:
1624 raise TypeError("do not know how to multiply Matrix44 with %s"%x.__class__)
1625
1627 if isinstance(x, (float, int, long)):
1628 m = CgfFormat.Matrix44()
1629 m.m_11 = self.m_11 / x
1630 m.m_12 = self.m_12 / x
1631 m.m_13 = self.m_13 / x
1632 m.m_14 = self.m_14 / x
1633 m.m_21 = self.m_21 / x
1634 m.m_22 = self.m_22 / x
1635 m.m_23 = self.m_23 / x
1636 m.m_24 = self.m_24 / x
1637 m.m_31 = self.m_31 / x
1638 m.m_32 = self.m_32 / x
1639 m.m_33 = self.m_33 / x
1640 m.m_34 = self.m_34 / x
1641 m.m_41 = self.m_41 / x
1642 m.m_42 = self.m_42 / x
1643 m.m_43 = self.m_43 / x
1644 m.m_44 = self.m_44 / x
1645 return m
1646 else:
1647 raise TypeError("do not know how to divide Matrix44 by %s"%x.__class__)
1648
1650 if isinstance(x, (float, int, long)):
1651 return self * x
1652 else:
1653 raise TypeError("do not know how to multiply %s with Matrix44"%x.__class__)
1654
1656 if isinstance(m, type(None)):
1657 return False
1658 if not isinstance(m, CgfFormat.Matrix44):
1659 raise TypeError("do not know how to compare Matrix44 and %s"%m.__class__)
1660 if abs(self.m_11 - m.m_11) > CgfFormat.EPSILON: return False
1661 if abs(self.m_12 - m.m_12) > CgfFormat.EPSILON: return False
1662 if abs(self.m_13 - m.m_13) > CgfFormat.EPSILON: return False
1663 if abs(self.m_14 - m.m_14) > CgfFormat.EPSILON: return False
1664 if abs(self.m_21 - m.m_21) > CgfFormat.EPSILON: return False
1665 if abs(self.m_22 - m.m_22) > CgfFormat.EPSILON: return False
1666 if abs(self.m_23 - m.m_23) > CgfFormat.EPSILON: return False
1667 if abs(self.m_24 - m.m_24) > CgfFormat.EPSILON: return False
1668 if abs(self.m_31 - m.m_31) > CgfFormat.EPSILON: return False
1669 if abs(self.m_32 - m.m_32) > CgfFormat.EPSILON: return False
1670 if abs(self.m_33 - m.m_33) > CgfFormat.EPSILON: return False
1671 if abs(self.m_34 - m.m_34) > CgfFormat.EPSILON: return False
1672 if abs(self.m_41 - m.m_41) > CgfFormat.EPSILON: return False
1673 if abs(self.m_42 - m.m_42) > CgfFormat.EPSILON: return False
1674 if abs(self.m_43 - m.m_43) > CgfFormat.EPSILON: return False
1675 if abs(self.m_44 - m.m_44) > CgfFormat.EPSILON: return False
1676 return True
1677
1679 return not self.__eq__(m)
1680
1682 if isinstance(x, (CgfFormat.Matrix44)):
1683 m = CgfFormat.Matrix44()
1684 m.m_11 = self.m_11 + x.m_11
1685 m.m_12 = self.m_12 + x.m_12
1686 m.m_13 = self.m_13 + x.m_13
1687 m.m_14 = self.m_14 + x.m_14
1688 m.m_21 = self.m_21 + x.m_21
1689 m.m_22 = self.m_22 + x.m_22
1690 m.m_23 = self.m_23 + x.m_23
1691 m.m_24 = self.m_24 + x.m_24
1692 m.m_31 = self.m_31 + x.m_31
1693 m.m_32 = self.m_32 + x.m_32
1694 m.m_33 = self.m_33 + x.m_33
1695 m.m_34 = self.m_34 + x.m_34
1696 m.m_41 = self.m_41 + x.m_41
1697 m.m_42 = self.m_42 + x.m_42
1698 m.m_43 = self.m_43 + x.m_43
1699 m.m_44 = self.m_44 + x.m_44
1700 return m
1701 elif isinstance(x, (int, long, float)):
1702 m = CgfFormat.Matrix44()
1703 m.m_11 = self.m_11 + x
1704 m.m_12 = self.m_12 + x
1705 m.m_13 = self.m_13 + x
1706 m.m_14 = self.m_14 + x
1707 m.m_21 = self.m_21 + x
1708 m.m_22 = self.m_22 + x
1709 m.m_23 = self.m_23 + x
1710 m.m_24 = self.m_24 + x
1711 m.m_31 = self.m_31 + x
1712 m.m_32 = self.m_32 + x
1713 m.m_33 = self.m_33 + x
1714 m.m_34 = self.m_34 + x
1715 m.m_41 = self.m_41 + x
1716 m.m_42 = self.m_42 + x
1717 m.m_43 = self.m_43 + x
1718 m.m_44 = self.m_44 + x
1719 return m
1720 else:
1721 raise TypeError("do not know how to add Matrix44 and %s"%x.__class__)
1722
1724 if isinstance(x, (CgfFormat.Matrix44)):
1725 m = CgfFormat.Matrix44()
1726 m.m_11 = self.m_11 - x.m_11
1727 m.m_12 = self.m_12 - x.m_12
1728 m.m_13 = self.m_13 - x.m_13
1729 m.m_14 = self.m_14 - x.m_14
1730 m.m_21 = self.m_21 - x.m_21
1731 m.m_22 = self.m_22 - x.m_22
1732 m.m_23 = self.m_23 - x.m_23
1733 m.m_24 = self.m_24 - x.m_24
1734 m.m_31 = self.m_31 - x.m_31
1735 m.m_32 = self.m_32 - x.m_32
1736 m.m_33 = self.m_33 - x.m_33
1737 m.m_34 = self.m_34 - x.m_34
1738 m.m_41 = self.m_41 - x.m_41
1739 m.m_42 = self.m_42 - x.m_42
1740 m.m_43 = self.m_43 - x.m_43
1741 m.m_44 = self.m_44 - x.m_44
1742 return m
1743 elif isinstance(x, (int, long, float)):
1744 m = CgfFormat.Matrix44()
1745 m.m_11 = self.m_11 - x
1746 m.m_12 = self.m_12 - x
1747 m.m_13 = self.m_13 - x
1748 m.m_14 = self.m_14 - x
1749 m.m_21 = self.m_21 - x
1750 m.m_22 = self.m_22 - x
1751 m.m_23 = self.m_23 - x
1752 m.m_24 = self.m_24 - x
1753 m.m_31 = self.m_31 - x
1754 m.m_32 = self.m_32 - x
1755 m.m_33 = self.m_33 - x
1756 m.m_34 = self.m_34 - x
1757 m.m_41 = self.m_41 - x
1758 m.m_42 = self.m_42 - x
1759 m.m_43 = self.m_43 - x
1760 m.m_44 = self.m_44 - x
1761 return m
1762 else:
1763 raise TypeError("do not know how to substract Matrix44 and %s"
1764 % x.__class__)
1765
1767 """Calculate supremum norm of matrix (maximum absolute value of all
1768 entries)."""
1769 return max(max(abs(elem) for elem in row)
1770 for row in self.as_list())
1771
1774 """Apply scale factor on data."""
1775 if abs(scale - 1.0) < CgfFormat.EPSILON:
1776 return
1777 for vert in self.vertices:
1778 vert.p.x *= scale
1779 vert.p.y *= scale
1780 vert.p.z *= scale
1781
1782 self.min_bound.x *= scale
1783 self.min_bound.y *= scale
1784 self.min_bound.z *= scale
1785 self.max_bound.x *= scale
1786 self.max_bound.y *= scale
1787 self.max_bound.z *= scale
1788
1790 """Generator for all vertices."""
1791 if self.vertices:
1792 for vert in self.vertices:
1793 yield vert.p
1794 elif self.vertices_data:
1795 for vert in self.vertices_data.vertices:
1796 yield vert
1797
1799 """Generator for all normals."""
1800 if self.vertices:
1801 for vert in self.vertices:
1802 yield vert.n
1803 elif self.normals_data:
1804 for norm in self.normals_data.normals:
1805 yield norm
1806
1808 """Generator for all vertex colors."""
1809 if self.vertex_colors:
1810 for color in self.vertex_colors:
1811 # Far Cry has no alpha channel
1812 yield (color.r, color.g, color.b, 255)
1813 elif self.colors_data:
1814 if self.colors_data.rgb_colors:
1815 for color in self.colors_data.rgb_colors:
1816 yield (color.r, color.g, color.b, 255)
1817 elif self.colors_data.rgba_colors:
1818 for color in self.colors_data.rgba_colors:
1819 yield (color.r, color.g, color.b, color.a)
1820
1822 """Get number of triangles."""
1823 if self.faces:
1824 return self.num_faces
1825 elif self.indices_data:
1826 return self.indices_data.num_elements // 3
1827 else:
1828 return 0
1829
1831 """Generator for all triangles."""
1832 if self.faces:
1833 for face in self.faces:
1834 yield face.v_0, face.v_1, face.v_2
1835 elif self.indices_data:
1836 it = iter(self.indices_data.indices)
1837 while True:
1838 yield it.next(), it.next(), it.next()
1839
1841 """Generator for all materials (per triangle)."""
1842 if self.faces:
1843 for face in self.faces:
1844 yield face.material
1845 elif self.mesh_subsets:
1846 for meshsubset in self.mesh_subsets.mesh_subsets:
1847 for i in xrange(meshsubset.num_indices // 3):
1848 yield meshsubset.mat_id
1849
1851 """Generator for all uv coordinates."""
1852 if self.uvs:
1853 for uv in self.uvs:
1854 yield uv.u, uv.v
1855 elif self.uvs_data:
1856 for uv in self.uvs_data.uvs:
1857 yield uv.u, 1.0 - uv.v # OpenGL fix!
1858
1860 """Generator for all uv triangles."""
1861 if self.uv_faces:
1862 for uvface in self.uv_faces:
1863 yield uvface.t_0, uvface.t_1, uvface.t_2
1864 elif self.indices_data:
1865 # Crysis: UV triangles coincide with triangles
1866 it = iter(self.indices_data.indices)
1867 while True:
1868 yield it.next(), it.next(), it.next()
1869
1870 ### DEPRECATED: USE set_geometry INSTEAD ###
1872 """B{Deprecated. Use L{set_geometry} instead.} Set vertices and normals. This used to be the first function to call
1873 when setting mesh geometry data.
1874
1875 Returns list of chunks that have been added."""
1876 # Far Cry
1877 self.num_vertices = len(vertices)
1878 self.vertices.update_size()
1879
1880 # Crysis
1881 self.vertices_data = CgfFormat.DataStreamChunk()
1882 self.vertices_data.data_stream_type = CgfFormat.DataStreamType.VERTICES
1883 self.vertices_data.bytes_per_element = 12
1884 self.vertices_data.num_elements = len(vertices)
1885 self.vertices_data.vertices.update_size()
1886
1887 self.normals_data = CgfFormat.DataStreamChunk()
1888 self.normals_data.data_stream_type = CgfFormat.DataStreamType.NORMALS
1889 self.normals_data.bytes_per_element = 12
1890 self.normals_data.num_elements = len(vertices)
1891 self.normals_data.normals.update_size()
1892
1893 # set vertex coordinates and normals for Far Cry
1894 for cryvert, vert, norm in izip(self.vertices, vertices, normals):
1895 cryvert.p.x = vert[0]
1896 cryvert.p.y = vert[1]
1897 cryvert.p.z = vert[2]
1898 cryvert.n.x = norm[0]
1899 cryvert.n.y = norm[1]
1900 cryvert.n.z = norm[2]
1901
1902 # set vertex coordinates and normals for Crysis
1903 for cryvert, crynorm, vert, norm in izip(self.vertices_data.vertices,
1904 self.normals_data.normals,
1905 vertices, normals):
1906 cryvert.x = vert[0]
1907 cryvert.y = vert[1]
1908 cryvert.z = vert[2]
1909 crynorm.x = norm[0]
1910 crynorm.y = norm[1]
1911 crynorm.z = norm[2]
1912
1913 ### STILL WIP!!! ###
1914 - def set_geometry(self,
1915 verticeslist = None, normalslist = None,
1916 triangleslist = None, matlist = None,
1917 uvslist = None, colorslist = None):
1918 """Set geometry data.
1919
1920 >>> from pyffi.formats.cgf import CgfFormat
1921 >>> chunk = CgfFormat.MeshChunk()
1922 >>> vertices1 = [(0,0,0),(0,1,0),(1,0,0),(1,1,0)]
1923 >>> vertices2 = [(0,0,1),(0,1,1),(1,0,1),(1,1,1)]
1924 >>> normals1 = [(0,0,-1),(0,0,-1),(0,0,-1),(0,0,-1)]
1925 >>> normals2 = [(0,0,1),(0,0,1),(0,0,1),(0,0,1)]
1926 >>> triangles1 = [(0,1,2),(2,1,3)]
1927 >>> triangles2 = [(0,1,2),(2,1,3)]
1928 >>> uvs1 = [(0,0),(0,1),(1,0),(1,1)]
1929 >>> uvs2 = [(0,0),(0,1),(1,0),(1,1)]
1930 >>> colors1 = [(0,1,2,3),(4,5,6,7),(8,9,10,11),(12,13,14,15)]
1931 >>> colors_2 = [(50,51,52,53),(54,55,56,57),(58,59,60,61),(62,63,64,65)]
1932 >>> chunk.set_geometry(verticeslist = [vertices1, vertices2],
1933 ... normalslist = [normals1, normals2],
1934 ... triangleslist = [triangles1, triangles2],
1935 ... uvslist = [uvs1, uvs2],
1936 ... matlist = [2,5],
1937 ... colorslist = [colors1, colors_2])
1938 >>> print(chunk) # doctest: +ELLIPSIS +REPORT_UDIFF
1939 <class 'pyffi.formats.cgf.MeshChunk'> instance at ...
1940 * has_vertex_weights : False
1941 * has_vertex_colors : True
1942 * in_world_space : False
1943 * reserved_1 : 0
1944 * reserved_2 : 0
1945 * flags_1 : 0
1946 * flags_2 : 0
1947 * num_vertices : 8
1948 * num_indices : 12
1949 * num_uvs : 8
1950 * num_faces : 4
1951 * material : None
1952 * num_mesh_subsets : 2
1953 * mesh_subsets : <class 'pyffi.formats.cgf.MeshSubsetsChunk'> instance at ...
1954 * vert_anim : None
1955 * vertices :
1956 <class 'pyffi.object_models.xml.array.Array'> instance at ...
1957 0: <class 'pyffi.formats.cgf.Vertex'> instance at ...
1958 * p : [ 0.000 0.000 0.000 ]
1959 * n : [ 0.000 0.000 -1.000 ]
1960 1: <class 'pyffi.formats.cgf.Vertex'> instance at ...
1961 * p : [ 0.000 1.000 0.000 ]
1962 * n : [ 0.000 0.000 -1.000 ]
1963 2: <class 'pyffi.formats.cgf.Vertex'> instance at ...
1964 * p : [ 1.000 0.000 0.000 ]
1965 * n : [ 0.000 0.000 -1.000 ]
1966 3: <class 'pyffi.formats.cgf.Vertex'> instance at ...
1967 * p : [ 1.000 1.000 0.000 ]
1968 * n : [ 0.000 0.000 -1.000 ]
1969 4: <class 'pyffi.formats.cgf.Vertex'> instance at ...
1970 * p : [ 0.000 0.000 1.000 ]
1971 * n : [ 0.000 0.000 1.000 ]
1972 5: <class 'pyffi.formats.cgf.Vertex'> instance at ...
1973 * p : [ 0.000 1.000 1.000 ]
1974 * n : [ 0.000 0.000 1.000 ]
1975 6: <class 'pyffi.formats.cgf.Vertex'> instance at ...
1976 * p : [ 1.000 0.000 1.000 ]
1977 * n : [ 0.000 0.000 1.000 ]
1978 7: <class 'pyffi.formats.cgf.Vertex'> instance at ...
1979 * p : [ 1.000 1.000 1.000 ]
1980 * n : [ 0.000 0.000 1.000 ]
1981 * faces :
1982 <class 'pyffi.object_models.xml.array.Array'> instance at ...
1983 0: <class 'pyffi.formats.cgf.Face'> instance at ...
1984 * v_0 : 0
1985 * v_1 : 1
1986 * v_2 : 2
1987 * material : 2
1988 * sm_group : 1
1989 1: <class 'pyffi.formats.cgf.Face'> instance at ...
1990 * v_0 : 2
1991 * v_1 : 1
1992 * v_2 : 3
1993 * material : 2
1994 * sm_group : 1
1995 2: <class 'pyffi.formats.cgf.Face'> instance at ...
1996 * v_0 : 4
1997 * v_1 : 5
1998 * v_2 : 6
1999 * material : 5
2000 * sm_group : 1
2001 3: <class 'pyffi.formats.cgf.Face'> instance at ...
2002 * v_0 : 6
2003 * v_1 : 5
2004 * v_2 : 7
2005 * material : 5
2006 * sm_group : 1
2007 * uvs :
2008 <class 'pyffi.object_models.xml.array.Array'> instance at ...
2009 0: <class 'pyffi.formats.cgf.UV'> instance at ...
2010 * u : 0.0
2011 * v : 0.0
2012 1: <class 'pyffi.formats.cgf.UV'> instance at ...
2013 * u : 0.0
2014 * v : 1.0
2015 2: <class 'pyffi.formats.cgf.UV'> instance at ...
2016 * u : 1.0
2017 * v : 0.0
2018 3: <class 'pyffi.formats.cgf.UV'> instance at ...
2019 * u : 1.0
2020 * v : 1.0
2021 4: <class 'pyffi.formats.cgf.UV'> instance at ...
2022 * u : 0.0
2023 * v : 0.0
2024 5: <class 'pyffi.formats.cgf.UV'> instance at ...
2025 * u : 0.0
2026 * v : 1.0
2027 6: <class 'pyffi.formats.cgf.UV'> instance at ...
2028 * u : 1.0
2029 * v : 0.0
2030 7: <class 'pyffi.formats.cgf.UV'> instance at ...
2031 * u : 1.0
2032 * v : 1.0
2033 * uv_faces :
2034 <class 'pyffi.object_models.xml.array.Array'> instance at ...
2035 0: <class 'pyffi.formats.cgf.UVFace'> instance at ...
2036 * t_0 : 0
2037 * t_1 : 1
2038 * t_2 : 2
2039 1: <class 'pyffi.formats.cgf.UVFace'> instance at ...
2040 * t_0 : 2
2041 * t_1 : 1
2042 * t_2 : 3
2043 2: <class 'pyffi.formats.cgf.UVFace'> instance at ...
2044 * t_0 : 4
2045 * t_1 : 5
2046 * t_2 : 6
2047 3: <class 'pyffi.formats.cgf.UVFace'> instance at ...
2048 * t_0 : 6
2049 * t_1 : 5
2050 * t_2 : 7
2051 * vertex_colors :
2052 <class 'pyffi.object_models.xml.array.Array'> instance at ...
2053 0: <class 'pyffi.formats.cgf.IRGB'> instance at ...
2054 * r : 0
2055 * g : 1
2056 * b : 2
2057 1: <class 'pyffi.formats.cgf.IRGB'> instance at ...
2058 * r : 4
2059 * g : 5
2060 * b : 6
2061 2: <class 'pyffi.formats.cgf.IRGB'> instance at ...
2062 * r : 8
2063 * g : 9
2064 * b : 10
2065 3: <class 'pyffi.formats.cgf.IRGB'> instance at ...
2066 * r : 12
2067 * g : 13
2068 * b : 14
2069 4: <class 'pyffi.formats.cgf.IRGB'> instance at ...
2070 * r : 50
2071 * g : 51
2072 * b : 52
2073 5: <class 'pyffi.formats.cgf.IRGB'> instance at ...
2074 * r : 54
2075 * g : 55
2076 * b : 56
2077 6: <class 'pyffi.formats.cgf.IRGB'> instance at ...
2078 * r : 58
2079 * g : 59
2080 * b : 60
2081 7: <class 'pyffi.formats.cgf.IRGB'> instance at ...
2082 * r : 62
2083 * g : 63
2084 * b : 64
2085 * vertices_data : <class 'pyffi.formats.cgf.DataStreamChunk'> instance at ...
2086 * normals_data : <class 'pyffi.formats.cgf.DataStreamChunk'> instance at ...
2087 * uvs_data : <class 'pyffi.formats.cgf.DataStreamChunk'> instance at ...
2088 * colors_data : <class 'pyffi.formats.cgf.DataStreamChunk'> instance at ...
2089 * colors_2_data : None
2090 * indices_data : <class 'pyffi.formats.cgf.DataStreamChunk'> instance at ...
2091 * tangents_data : <class 'pyffi.formats.cgf.DataStreamChunk'> instance at ...
2092 * sh_coeffs_data : None
2093 * shape_deformation_data : None
2094 * bone_map_data : None
2095 * face_map_data : None
2096 * vert_mats_data : None
2097 * reserved_data :
2098 <class 'pyffi.object_models.xml.array.Array'> instance at ...
2099 0: None
2100 1: None
2101 2: None
2102 3: None
2103 * physics_data :
2104 <class 'pyffi.object_models.xml.array.Array'> instance at ...
2105 0: None
2106 1: None
2107 2: None
2108 3: None
2109 * min_bound : [ 0.000 0.000 0.000 ]
2110 * max_bound : [ 1.000 1.000 1.000 ]
2111 * reserved_3 :
2112 <class 'pyffi.object_models.xml.array.Array'> instance at ...
2113 0: 0
2114 1: 0
2115 2: 0
2116 3: 0
2117 4: 0
2118 5: 0
2119 6: 0
2120 7: 0
2121 8: 0
2122 9: 0
2123 10: 0
2124 11: 0
2125 12: 0
2126 13: 0
2127 14: 0
2128 15: 0
2129 16: 0
2130 etc...
2131 <BLANKLINE>
2132 >>> print(chunk.mesh_subsets) # doctest: +ELLIPSIS
2133 <class 'pyffi.formats.cgf.MeshSubsetsChunk'> instance at ...
2134 * flags :
2135 <class 'pyffi.formats.cgf.MeshSubsetsFlags'> instance at ...
2136 * sh_has_decompr_mat : 0
2137 * bone_indices : 0
2138 * num_mesh_subsets : 2
2139 * reserved_1 : 0
2140 * reserved_2 : 0
2141 * reserved_3 : 0
2142 * mesh_subsets :
2143 <class 'pyffi.object_models.xml.array.Array'> instance at ...
2144 0: <class 'pyffi.formats.cgf.MeshSubset'> instance at ...
2145 * first_index : 0
2146 * num_indices : 6
2147 * first_vertex : 0
2148 * num_vertices : 4
2149 * mat_id : 2
2150 * radius : 0.7071067...
2151 * center : [ 0.500 0.500 0.000 ]
2152 1: <class 'pyffi.formats.cgf.MeshSubset'> instance at ...
2153 * first_index : 6
2154 * num_indices : 6
2155 * first_vertex : 4
2156 * num_vertices : 4
2157 * mat_id : 5
2158 * radius : 0.7071067...
2159 * center : [ 0.500 0.500 1.000 ]
2160 <BLANKLINE>
2161 >>> print(chunk.vertices_data) # doctest: +ELLIPSIS
2162 <class 'pyffi.formats.cgf.DataStreamChunk'> instance at ...
2163 * flags : 0
2164 * data_stream_type : VERTICES
2165 * num_elements : 8
2166 * bytes_per_element : 12
2167 * reserved_1 : 0
2168 * reserved_2 : 0
2169 * vertices :
2170 <class 'pyffi.object_models.xml.array.Array'> instance at ...
2171 0: [ 0.000 0.000 0.000 ]
2172 1: [ 0.000 1.000 0.000 ]
2173 2: [ 1.000 0.000 0.000 ]
2174 3: [ 1.000 1.000 0.000 ]
2175 4: [ 0.000 0.000 1.000 ]
2176 5: [ 0.000 1.000 1.000 ]
2177 6: [ 1.000 0.000 1.000 ]
2178 7: [ 1.000 1.000 1.000 ]
2179 <BLANKLINE>
2180 >>> print(chunk.normals_data) # doctest: +ELLIPSIS
2181 <class 'pyffi.formats.cgf.DataStreamChunk'> instance at ...
2182 * flags : 0
2183 * data_stream_type : NORMALS
2184 * num_elements : 8
2185 * bytes_per_element : 12
2186 * reserved_1 : 0
2187 * reserved_2 : 0
2188 * normals :
2189 <class 'pyffi.object_models.xml.array.Array'> instance at ...
2190 0: [ 0.000 0.000 -1.000 ]
2191 1: [ 0.000 0.000 -1.000 ]
2192 2: [ 0.000 0.000 -1.000 ]
2193 3: [ 0.000 0.000 -1.000 ]
2194 4: [ 0.000 0.000 1.000 ]
2195 5: [ 0.000 0.000 1.000 ]
2196 6: [ 0.000 0.000 1.000 ]
2197 7: [ 0.000 0.000 1.000 ]
2198 <BLANKLINE>
2199 >>> print(chunk.indices_data) # doctest: +ELLIPSIS
2200 <class 'pyffi.formats.cgf.DataStreamChunk'> instance at ...
2201 * flags : 0
2202 * data_stream_type : INDICES
2203 * num_elements : 12
2204 * bytes_per_element : 2
2205 * reserved_1 : 0
2206 * reserved_2 : 0
2207 * indices :
2208 <class 'pyffi.object_models.xml.array.Array'> instance at ...
2209 0: 0
2210 1: 1
2211 2: 2
2212 3: 2
2213 4: 1
2214 5: 3
2215 6: 4
2216 7: 5
2217 8: 6
2218 9: 6
2219 10: 5
2220 11: 7
2221 <BLANKLINE>
2222 >>> print(chunk.uvs_data) # doctest: +ELLIPSIS
2223 <class 'pyffi.formats.cgf.DataStreamChunk'> instance at ...
2224 * flags : 0
2225 * data_stream_type : UVS
2226 * num_elements : 8
2227 * bytes_per_element : 8
2228 * reserved_1 : 0
2229 * reserved_2 : 0
2230 * uvs :
2231 <class 'pyffi.object_models.xml.array.Array'> instance at ...
2232 0: <class 'pyffi.formats.cgf.UV'> instance at ...
2233 * u : 0.0
2234 * v : 1.0
2235 1: <class 'pyffi.formats.cgf.UV'> instance at ...
2236 * u : 0.0
2237 * v : 0.0
2238 2: <class 'pyffi.formats.cgf.UV'> instance at ...
2239 * u : 1.0
2240 * v : 1.0
2241 3: <class 'pyffi.formats.cgf.UV'> instance at ...
2242 * u : 1.0
2243 * v : 0.0
2244 4: <class 'pyffi.formats.cgf.UV'> instance at ...
2245 * u : 0.0
2246 * v : 1.0
2247 5: <class 'pyffi.formats.cgf.UV'> instance at ...
2248 * u : 0.0
2249 * v : 0.0
2250 6: <class 'pyffi.formats.cgf.UV'> instance at ...
2251 * u : 1.0
2252 * v : 1.0
2253 7: <class 'pyffi.formats.cgf.UV'> instance at ...
2254 * u : 1.0
2255 * v : 0.0
2256 <BLANKLINE>
2257 >>> print(chunk.tangents_data) # doctest: +ELLIPSIS
2258 <class 'pyffi.formats.cgf.DataStreamChunk'> instance at ...
2259 * flags : 0
2260 * data_stream_type : TANGENTS
2261 * num_elements : 8
2262 * bytes_per_element : 16
2263 * reserved_1 : 0
2264 * reserved_2 : 0
2265 * tangents :
2266 <class 'pyffi.object_models.xml.array.Array'> instance at ...
2267 0, 0: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2268 * x : 32767
2269 * y : 0
2270 * z : 0
2271 * w : 32767
2272 0, 1: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2273 * x : 0
2274 * y : -32767
2275 * z : 0
2276 * w : 32767
2277 1, 0: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2278 * x : 32767
2279 * y : 0
2280 * z : 0
2281 * w : 32767
2282 1, 1: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2283 * x : 0
2284 * y : -32767
2285 * z : 0
2286 * w : 32767
2287 2, 0: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2288 * x : 32767
2289 * y : 0
2290 * z : 0
2291 * w : 32767
2292 2, 1: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2293 * x : 0
2294 * y : -32767
2295 * z : 0
2296 * w : 32767
2297 3, 0: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2298 * x : 32767
2299 * y : 0
2300 * z : 0
2301 * w : 32767
2302 3, 1: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2303 * x : 0
2304 * y : -32767
2305 * z : 0
2306 * w : 32767
2307 4, 0: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2308 * x : 32767
2309 * y : 0
2310 * z : 0
2311 * w : 32767
2312 4, 1: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2313 * x : 0
2314 * y : -32767
2315 * z : 0
2316 * w : 32767
2317 5, 0: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2318 * x : 32767
2319 * y : 0
2320 * z : 0
2321 * w : 32767
2322 5, 1: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2323 * x : 0
2324 * y : -32767
2325 * z : 0
2326 * w : 32767
2327 6, 0: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2328 * x : 32767
2329 * y : 0
2330 * z : 0
2331 * w : 32767
2332 6, 1: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2333 * x : 0
2334 * y : -32767
2335 * z : 0
2336 * w : 32767
2337 7, 0: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2338 * x : 32767
2339 * y : 0
2340 * z : 0
2341 * w : 32767
2342 7, 1: <class 'pyffi.formats.cgf.Tangent'> instance at ...
2343 * x : 0
2344 * y : -32767
2345 * z : 0
2346 * w : 32767
2347 <BLANKLINE>
2348 >>> print(chunk.colors_data) # doctest: +ELLIPSIS
2349 <class 'pyffi.formats.cgf.DataStreamChunk'> instance at ...
2350 * flags : 0
2351 * data_stream_type : COLORS
2352 * num_elements : 8
2353 * bytes_per_element : 4
2354 * reserved_1 : 0
2355 * reserved_2 : 0
2356 * rgba_colors :
2357 <class 'pyffi.object_models.xml.array.Array'> instance at ...
2358 0: <class 'pyffi.formats.cgf.IRGBA'> instance at ...
2359 * r : 0
2360 * g : 1
2361 * b : 2
2362 * a : 3
2363 1: <class 'pyffi.formats.cgf.IRGBA'> instance at ...
2364 * r : 4
2365 * g : 5
2366 * b : 6
2367 * a : 7
2368 2: <class 'pyffi.formats.cgf.IRGBA'> instance at ...
2369 * r : 8
2370 * g : 9
2371 * b : 10
2372 * a : 11
2373 3: <class 'pyffi.formats.cgf.IRGBA'> instance at ...
2374 * r : 12
2375 * g : 13
2376 * b : 14
2377 * a : 15
2378 4: <class 'pyffi.formats.cgf.IRGBA'> instance at ...
2379 * r : 50
2380 * g : 51
2381 * b : 52
2382 * a : 53
2383 5: <class 'pyffi.formats.cgf.IRGBA'> instance at ...
2384 * r : 54
2385 * g : 55
2386 * b : 56
2387 * a : 57
2388 6: <class 'pyffi.formats.cgf.IRGBA'> instance at ...
2389 * r : 58
2390 * g : 59
2391 * b : 60
2392 * a : 61
2393 7: <class 'pyffi.formats.cgf.IRGBA'> instance at ...
2394 * r : 62
2395 * g : 63
2396 * b : 64
2397 * a : 65
2398 <BLANKLINE>
2399
2400 :param verticeslist: A list of lists of vertices (one list per material).
2401 :param normalslist: A list of lists of normals (one list per material).
2402 :param triangleslist: A list of lists of triangles (one list per material).
2403 :param matlist: A list of material indices. Optional.
2404 :param uvslist: A list of lists of uvs (one list per material). Optional.
2405 :param colorslist: A list of lists of RGBA colors (one list per material).
2406 Optional. Each color is a tuple (r, g, b, a) with each component an
2407 integer between 0 and 255.
2408 """
2409 # argument sanity checking
2410 # check length of lists
2411 if len(verticeslist) != len(normalslist):
2412 raise ValueError("normalslist must have same length as verticeslist")
2413 if len(triangleslist) != len(normalslist):
2414 raise ValueError("triangleslist must have same length as verticeslist")
2415 if not matlist is None and len(verticeslist) != len(matlist):
2416 raise ValueError("matlist must have same length as verticeslist")
2417 if not uvslist is None and len(verticeslist) != len(uvslist):
2418 raise ValueError("uvslist must have same length as verticeslist")
2419 if not colorslist is None and len(verticeslist) != len(colorslist):
2420 raise ValueError("colorslist must have same length as verticeslist")
2421
2422 # check length of lists in lists
2423 for vertices, normals in izip(verticeslist, normalslist):
2424 if len(vertices) != len(normals):
2425 raise ValueError("vertex and normal lists must have same length")
2426 if not uvslist is None:
2427 for vertices, uvs in izip(verticeslist, uvslist):
2428 if len(vertices) != len(uvs):
2429 raise ValueError("vertex and uv lists must have same length")
2430 if not colorslist is None:
2431 for vertices, colors in izip(verticeslist, colorslist):
2432 if len(vertices) != len(colors):
2433 raise ValueError("vertex and color lists must have same length")
2434
2435 # get total number of vertices
2436 numvertices = sum(len(vertices) for vertices in verticeslist)
2437 if numvertices > 65535:
2438 raise ValueError("cannot store geometry: too many vertices (%i and maximum is 65535)" % numvertices)
2439 numtriangles = sum(len(triangles) for triangles in triangleslist)
2440
2441 # Far Cry data preparation
2442 self.num_vertices = numvertices
2443 self.vertices.update_size()
2444 selfvertices_iter = iter(self.vertices)
2445 self.num_faces = numtriangles
2446 self.faces.update_size()
2447 selffaces_iter = iter(self.faces)
2448 if not uvslist is None:
2449 self.num_uvs = numvertices
2450 self.uvs.update_size()
2451 self.uv_faces.update_size()
2452 selfuvs_iter = iter(self.uvs)
2453 selfuv_faces_iter = iter(self.uv_faces)
2454 if not colorslist is None:
2455 self.has_vertex_colors = True
2456 self.vertex_colors.update_size()
2457 selfvertex_colors_iter = iter(self.vertex_colors)
2458
2459 # Crysis data preparation
2460 self.num_indices = numtriangles * 3
2461
2462 self.vertices_data = CgfFormat.DataStreamChunk()
2463 self.vertices_data.data_stream_type = CgfFormat.DataStreamType.VERTICES
2464 self.vertices_data.bytes_per_element = 12
2465 self.vertices_data.num_elements = numvertices
2466 self.vertices_data.vertices.update_size()
2467 selfvertices_data_iter = iter(self.vertices_data.vertices)
2468
2469 self.normals_data = CgfFormat.DataStreamChunk()
2470 self.normals_data.data_stream_type = CgfFormat.DataStreamType.NORMALS
2471 self.normals_data.bytes_per_element = 12
2472 self.normals_data.num_elements = numvertices
2473 self.normals_data.normals.update_size()
2474 selfnormals_data_iter = iter(self.normals_data.normals)
2475
2476 self.indices_data = CgfFormat.DataStreamChunk()
2477 self.indices_data.data_stream_type = CgfFormat.DataStreamType.INDICES
2478 self.indices_data.bytes_per_element = 2
2479 self.indices_data.num_elements = numtriangles * 3
2480 self.indices_data.indices.update_size()
2481
2482 if not uvslist is None:
2483 # uvs
2484 self.uvs_data = CgfFormat.DataStreamChunk()
2485 self.uvs_data.data_stream_type = CgfFormat.DataStreamType.UVS
2486 self.uvs_data.bytes_per_element = 8
2487 self.uvs_data.num_elements = numvertices
2488 self.uvs_data.uvs.update_size()
2489 selfuvs_data_iter = iter(self.uvs_data.uvs)
2490 # have tangent space
2491 has_tangentspace = True
2492 else:
2493 # no tangent space
2494 has_tangentspace = False
2495
2496 if not colorslist is None:
2497 # vertex colors
2498 self.colors_data = CgfFormat.DataStreamChunk()
2499 self.colors_data.data_stream_type = CgfFormat.DataStreamType.COLORS
2500 self.colors_data.bytes_per_element = 4
2501 self.colors_data.num_elements = numvertices
2502 self.colors_data.rgba_colors.update_size()
2503 selfcolors_data_iter = iter(self.colors_data.rgba_colors)
2504
2505 self.num_mesh_subsets = len(verticeslist)
2506 self.mesh_subsets = CgfFormat.MeshSubsetsChunk()
2507 self.mesh_subsets.num_mesh_subsets = self.num_mesh_subsets
2508 self.mesh_subsets.mesh_subsets.update_size()
2509
2510 # set up default iterators
2511 if matlist is None:
2512 matlist = itertools.repeat(0)
2513 if uvslist is None:
2514 uvslist = itertools.repeat(None)
2515 if colorslist is None:
2516 colorslist = itertools.repeat(None)
2517
2518 # now iterate over all materials
2519 firstvertexindex = 0
2520 firstindicesindex = 0
2521 for vertices, normals, triangles, mat, uvs, colors, meshsubset in izip(
2522 verticeslist, normalslist,
2523 triangleslist, matlist,
2524 uvslist, colorslist,
2525 self.mesh_subsets.mesh_subsets):
2526
2527 # set Crysis mesh subset info
2528 meshsubset.first_index = firstindicesindex
2529 meshsubset.num_indices = len(triangles) * 3
2530 meshsubset.first_vertex = firstvertexindex
2531 meshsubset.num_vertices = len(vertices)
2532 meshsubset.mat_id = mat
2533 center, radius = pyffi.utils.mathutils.getCenterRadius(vertices)
2534 meshsubset.radius = radius
2535 meshsubset.center.x = center[0]
2536 meshsubset.center.y = center[1]
2537 meshsubset.center.z = center[2]
2538
2539 # set vertex coordinates and normals for Far Cry
2540 for vert, norm in izip(vertices, normals):
2541 cryvert = selfvertices_iter.next()
2542 cryvert.p.x = vert[0]
2543 cryvert.p.y = vert[1]
2544 cryvert.p.z = vert[2]
2545 cryvert.n.x = norm[0]
2546 cryvert.n.y = norm[1]
2547 cryvert.n.z = norm[2]
2548
2549 # set vertex coordinates and normals for Crysis
2550 for vert, norm in izip(vertices, normals):
2551 cryvert = selfvertices_data_iter.next()
2552 crynorm = selfnormals_data_iter.next()
2553 cryvert.x = vert[0]
2554 cryvert.y = vert[1]
2555 cryvert.z = vert[2]
2556 crynorm.x = norm[0]
2557 crynorm.y = norm[1]
2558 crynorm.z = norm[2]
2559
2560 # set Far Cry face info
2561 for triangle in triangles:
2562 cryface = selffaces_iter.next()
2563 cryface.v_0 = triangle[0] + firstvertexindex
2564 cryface.v_1 = triangle[1] + firstvertexindex
2565 cryface.v_2 = triangle[2] + firstvertexindex
2566 cryface.material = mat
2567
2568 # set Crysis face info
2569 for i, vertexindex in enumerate(itertools.chain(*triangles)):
2570 self.indices_data.indices[i + firstindicesindex] \
2571 = vertexindex + firstvertexindex
2572
2573 if not uvs is None:
2574 # set Far Cry uv info
2575 for triangle in triangles:
2576 cryuvface = selfuv_faces_iter.next()
2577 cryuvface.t_0 = triangle[0] + firstvertexindex
2578 cryuvface.t_1 = triangle[1] + firstvertexindex
2579 cryuvface.t_2 = triangle[2] + firstvertexindex
2580 for uv in uvs:
2581 cryuv = selfuvs_iter.next()
2582 cryuv.u = uv[0]
2583 cryuv.v = uv[1]
2584
2585 # set Crysis uv info
2586 for uv in uvs:
2587 cryuv = selfuvs_data_iter.next()
2588 cryuv.u = uv[0]
2589 cryuv.v = 1.0 - uv[1] # OpenGL fix
2590
2591 if not colors is None:
2592 # set Far Cry color info
2593 for color in colors:
2594 crycolor = selfvertex_colors_iter.next()
2595 crycolor.r = color[0]
2596 crycolor.g = color[1]
2597 crycolor.b = color[2]
2598 # note: Far Cry does not support alpha color channel
2599
2600 # set Crysis color info
2601 for color in colors:
2602 crycolor = selfcolors_data_iter.next()
2603 crycolor.r = color[0]
2604 crycolor.g = color[1]
2605 crycolor.b = color[2]
2606 crycolor.a = color[3]
2607
2608 # update index offsets
2609 firstvertexindex += len(vertices)
2610 firstindicesindex += 3 * len(triangles)
2611
2612 # update tangent space
2613 if has_tangentspace:
2614 self.update_tangent_space()
2615
2616 # set global bounding box
2617 minbound, maxbound = pyffi.utils.mathutils.getBoundingBox(
2618 list(itertools.chain(*verticeslist)))
2619 self.min_bound.x = minbound[0]
2620 self.min_bound.y = minbound[1]
2621 self.min_bound.z = minbound[2]
2622 self.max_bound.x = maxbound[0]
2623 self.max_bound.y = maxbound[1]
2624 self.max_bound.z = maxbound[2]
2625
2627 """Recalculate tangent space data."""
2628 # set up tangent space
2629 self.tangents_data = CgfFormat.DataStreamChunk()
2630 self.tangents_data.data_stream_type = CgfFormat.DataStreamType.TANGENTS
2631 self.tangents_data.bytes_per_element = 16
2632 self.tangents_data.num_elements = self.num_vertices
2633 self.tangents_data.tangents.update_size()
2634 selftangents_data_iter = iter(self.tangents_data.tangents)
2635
2636 # set Crysis tangents info
2637 tangents, binormals, orientations = pyffi.utils.tangentspace.getTangentSpace(
2638 vertices = list((vert.x, vert.y, vert.z)
2639 for vert in self.vertices_data.vertices),
2640 normals = list((norm.x, norm.y, norm.z)
2641 for norm in self.normals_data.normals),
2642 uvs = list((uv.u, uv.v)
2643 for uv in self.uvs_data.uvs),
2644 triangles = list(self.get_triangles()),
2645 orientation = True)
2646
2647 for crytangent, tan, bin, orient in izip(self.tangents_data.tangents,
2648 tangents, binormals, orientations):
2649 if orient > 0:
2650 tangent_w = 32767
2651 else:
2652 tangent_w = -32767
2653 crytangent[1].x = int(32767 * tan[0])
2654 crytangent[1].y = int(32767 * tan[1])
2655 crytangent[1].z = int(32767 * tan[2])
2656 crytangent[1].w = tangent_w
2657 crytangent[0].x = int(32767 * bin[0])
2658 crytangent[0].y = int(32767 * bin[1])
2659 crytangent[0].z = int(32767 * bin[2])
2660 crytangent[0].w = tangent_w
2661
2664 """Apply scale factor on data."""
2665 if abs(scale - 1.0) < CgfFormat.EPSILON:
2666 return
2667 for morphvert in self.morph_vertices:
2668 morphvert.vertex_target.x *= scale
2669 morphvert.vertex_target.y *= scale
2670 morphvert.vertex_target.z *= scale
2671
2673 """Get the block parent (used for instance in the QSkope global view)."""
2674 return self.mesh
2675
2679
2690
2693 """Extract name, shader, and script."""
2694 name = self.name
2695 shader_begin = name.find("(")
2696 shader_end = name.find(")")
2697 script_begin = name.find("/")
2698 if (script_begin != -1):
2699 if (name.count("/") != 1):
2700 # must have exactly one script
2701 raise ValueError("%s malformed, has multiple ""/"""%name)
2702 mtlscript = name[script_begin+1:]
2703 else:
2704 mtlscript = ""
2705 if (shader_begin != -1): # if a shader was specified
2706 mtl_end = shader_begin
2707 # must have exactly one shader
2708 if (name.count("(") != 1):
2709 # some names are buggy and have "((" instead of "("
2710 # like in jungle_camp_sleeping_barack
2711 # here we handle that case
2712 if name[shader_begin + 1] == "(" \
2713 and name[shader_begin + 1:].count("(") == 1:
2714 shader_begin += 1
2715 else:
2716 raise ValueError("%s malformed, has multiple ""("""%name)
2717 if (name.count(")") != 1):
2718 raise ValueError("%s malformed, has multiple "")"""%name)
2719 # shader name should non-empty
2720 if shader_begin > shader_end:
2721 raise ValueError("%s malformed, ""("" comes after "")"""%name)
2722 # script must be immediately followed by the material
2723 if (script_begin != -1) and (shader_end + 1 != script_begin):
2724 raise ValueError("%s malformed, shader not followed by script"%name)
2725 mtlname = name[:mtl_end]
2726 mtlshader = name[shader_begin+1:shader_end]
2727 else:
2728 if script_begin != -1:
2729 mtlname = name[:script_begin]
2730 else:
2731 mtlname = name[:]
2732 mtlshader = ""
2733 return mtlname, mtlshader, mtlscript
2734
2737 """Get the block parent (used for instance in the QSkope global view)."""
2738 return self.parent
2739
2741 """Apply scale factor on data."""
2742 if abs(scale - 1.0) < CgfFormat.EPSILON:
2743 return
2744 self.transform.m_41 *= scale
2745 self.transform.m_42 *= scale
2746 self.transform.m_43 *= scale
2747 self.pos.x *= scale
2748 self.pos.y *= scale
2749 self.pos.z *= scale
2750
2752 """Update position, rotation, and scale, from the transform."""
2753 scale, quat, trans = self.transform.get_scale_quat_translation()
2754 self.pos.x = trans.x
2755 self.pos.y = trans.y
2756 self.pos.z = trans.z
2757 self.rot.x = quat.x
2758 self.rot.y = quat.y
2759 self.rot.z = quat.z
2760 self.rot.w = quat.w
2761 self.scl.x = scale.x
2762 self.scl.y = scale.y
2763 self.scl.z = scale.z
2764
2767 """Return a name for the block."""
2768 idx = max(self.source_file.rfind("\\"), self.source_file.rfind("/"))
2769 return self.source_file[idx+1:]
2770
2775
2779
2782
2785
2787 norm = self.norm()
2788 if norm < CgfFormat.EPSILON:
2789 raise ZeroDivisionError('cannot normalize vector %s'%self)
2790 self.x /= norm
2791 self.y /= norm
2792 self.z /= norm
2793
2800
2803
2805 if isinstance(x, (float, int, long)):
2806 v = CgfFormat.Vector3()
2807 v.x = self.x * x
2808 v.y = self.y * x
2809 v.z = self.z * x
2810 return v
2811 elif isinstance(x, CgfFormat.Vector3):
2812 return self.x * x.x + self.y * x.y + self.z * x.z
2813 elif isinstance(x, CgfFormat.Matrix33):
2814 v = CgfFormat.Vector3()
2815 v.x = self.x * x.m_11 + self.y * x.m_21 + self.z * x.m_31
2816 v.y = self.x * x.m_12 + self.y * x.m_22 + self.z * x.m_32
2817 v.z = self.x * x.m_13 + self.y * x.m_23 + self.z * x.m_33
2818 return v
2819 elif isinstance(x, CgfFormat.Matrix44):
2820 return self * x.get_matrix_33() + x.get_translation()
2821 else:
2822 raise TypeError("do not know how to multiply Vector3 with %s"%x.__class__)
2823
2825 if isinstance(x, (float, int, long)):
2826 v = CgfFormat.Vector3()
2827 v.x = x * self.x
2828 v.y = x * self.y
2829 v.z = x * self.z
2830 return v
2831 else:
2832 raise TypeError("do not know how to multiply %s and Vector3"%x.__class__)
2833
2835 if isinstance(x, (float, int, long)):
2836 v = CgfFormat.Vector3()
2837 v.x = self.x / x
2838 v.y = self.y / x
2839 v.z = self.z / x
2840 return v
2841 else:
2842 raise TypeError("do not know how to divide Vector3 and %s"%x.__class__)
2843
2845 if isinstance(x, (float, int, long)):
2846 v = CgfFormat.Vector3()
2847 v.x = self.x + x
2848 v.y = self.y + x
2849 v.z = self.z + x
2850 return v
2851 elif isinstance(x, CgfFormat.Vector3):
2852 v = CgfFormat.Vector3()
2853 v.x = self.x + x.x
2854 v.y = self.y + x.y
2855 v.z = self.z + x.z
2856 return v
2857 else:
2858 raise TypeError("do not know how to add Vector3 and %s"%x.__class__)
2859
2861 if isinstance(x, (float, int, long)):
2862 v = CgfFormat.Vector3()
2863 v.x = x + self.x
2864 v.y = x + self.y
2865 v.z = x + self.z
2866 return v
2867 else:
2868 raise TypeError("do not know how to add %s and Vector3"%x.__class__)
2869
2871 if isinstance(x, (float, int, long)):
2872 v = CgfFormat.Vector3()
2873 v.x = self.x - x
2874 v.y = self.y - x
2875 v.z = self.z - x
2876 return v
2877 elif isinstance(x, CgfFormat.Vector3):
2878 v = CgfFormat.Vector3()
2879 v.x = self.x - x.x
2880 v.y = self.y - x.y
2881 v.z = self.z - x.z
2882 return v
2883 else:
2884 raise TypeError("do not know how to substract Vector3 and %s"%x.__class__)
2885
2887 if isinstance(x, (float, int, long)):
2888 v = CgfFormat.Vector3()
2889 v.x = x - self.x
2890 v.y = x - self.y
2891 v.z = x - self.z
2892 return v
2893 else:
2894 raise TypeError("do not know how to substract %s and Vector3"%x.__class__)
2895
2902
2903 # cross product
2905 if isinstance(x, CgfFormat.Vector3):
2906 v = CgfFormat.Vector3()
2907 v.x = self.y*x.z - self.z*x.y
2908 v.y = self.z*x.x - self.x*x.z
2909 v.z = self.x*x.y - self.y*x.x
2910 return v
2911 else:
2912 raise TypeError("do not know how to calculate crossproduct of Vector3 and %s"%x.__class__)
2913
2915 if isinstance(x, type(None)):
2916 return False
2917 if not isinstance(x, CgfFormat.Vector3):
2918 raise TypeError("do not know how to compare Vector3 and %s"%x.__class__)
2919 if abs(self.x - x.x) > CgfFormat.EPSILON: return False
2920 if abs(self.y - x.y) > CgfFormat.EPSILON: return False
2921 if abs(self.z - x.z) > CgfFormat.EPSILON: return False
2922 return True
2923
2926
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Oct 10 19:04:09 2011 | http://epydoc.sourceforge.net |