Package pyffi :: Package formats :: Package cgf
[hide private]
[frames] | no frames]

Source Code for Package pyffi.formats.cgf

   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 
220 221 -class _MetaCgfFormat(pyffi.object_models.xml.MetaFileFormat):
222 """Metaclass which constructs the chunk map during class creation."""
223 - def __init__(cls, name, bases, dct):
224 super(_MetaCgfFormat, cls).__init__(name, bases, dct) 225 226 # map chunk type integers to chunk type classes 227 cls.CHUNK_MAP = dict( 228 (getattr(cls.ChunkType, chunk_name), 229 getattr(cls, '%sChunk' % chunk_name)) 230 for chunk_name in cls.ChunkType._enumkeys 231 if chunk_name != "ANY")
232
233 -class CgfFormat(pyffi.object_models.xml.FileFormat):
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
267 - class String16(pyffi.object_models.common.FixedString):
268 """String of fixed length 16.""" 269 _len = 16
270
271 - class String32(pyffi.object_models.common.FixedString):
272 """String of fixed length 32.""" 273 _len = 32
274
275 - class String64(pyffi.object_models.common.FixedString):
276 """String of fixed length 64.""" 277 _len = 64
278
279 - class String128(pyffi.object_models.common.FixedString):
280 """String of fixed length 128.""" 281 _len = 128
282
283 - class String256(pyffi.object_models.common.FixedString):
284 """String of fixed length 256.""" 285 _len = 256
286
287 - class FileSignature(BasicBase):
288 """The CryTek file signature with which every 289 cgf file starts."""
290 - def __init__(self, **kwargs):
291 super(CgfFormat.FileSignature, self).__init__(**kwargs)
292
293 - def __str__(self):
294 return 'XXXXXX'
295
296 - def _str(self, data):
297 if data.game == "Aion": 298 return 'NCAion'.encode("ascii") 299 else: 300 return 'CryTek'.encode("ascii")
301
302 - def read(self, stream, data):
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
315 - def write(self, stream, data):
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
323 - def get_value(self):
324 """Get signature. 325 326 :return: The signature. 327 """ 328 return self.__str__()
329
330 - def set_value(self, value):
331 """Not implemented.""" 332 raise NotImplementedError("Cannot set signature value.")
333
334 - def get_size(self, data=None):
335 """Return number of bytes that the signature occupies in a file. 336 337 :return: Number of bytes. 338 """ 339 return 8
340
341 - def get_hash(self, data=None):
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
348 - class Ref(BasicBase):
349 """Reference to a chunk, up the hierarchy.""" 350 _is_template = True 351 _has_links = True 352 _has_refs = True
353 - def __init__(self, **kwargs):
354 super(CgfFormat.Ref, self).__init__(**kwargs) 355 self._template = kwargs.get('template', type(None)) 356 self._value = None
357
358 - def get_value(self):
359 """Get chunk being referred to. 360 361 :return: The chunk being referred to. 362 """ 363 return self._value
364
365 - def set_value(self, value):
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
380 - def read(self, stream, data):
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
390 - def write(self, stream, data):
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 435 446
447 - def get_refs(self, data=None):
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
458 - def __str__(self):
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
466 - def get_size(self, data=None):
467 """Return number of bytes this type occupies in a file. 468 469 :return: Number of bytes. 470 """ 471 return 4
472
473 - def get_hash(self, data=None):
474 """Return a hash value for the chunk referred to. 475 476 :return: An immutable object that can be used as a hash. 477 """ 478 return self._value.get_hash() if not self._value is None else None
479
480 - class Ptr(Ref):
481 """Reference to a chunk, down the hierarchy.""" 482 _is_template = True 483 _has_links = True 484 _has_refs = False 485
486 - def __str__(self):
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
494 - def get_refs(self, data=None):
495 """Ptr does not point down, so get_refs returns empty list. 496 497 :return: C{[]} 498 """ 499 return []
500 501 @staticmethod
502 - def version_number(version_str):
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
515 - class CgfError(Exception):
516 """Exception for CGF specific errors.""" 517 pass
518
519 - class Data(pyffi.object_models.FileFormat.Data):
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
539 - def __init__(self, filetype=0xffff0000, game="Far Cry"):
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
577 - def inspect_version_only(self, stream):
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
643 - def get_global_child_nodes(self, edge_filter=EdgeFilter()):
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
655 - def replace_global_node(self, oldbranch, newbranch, 656 edge_filter=EdgeFilter()):
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
664 - def get_detail_child_nodes(self, edge_filter=EdgeFilter()):
665 yield self.header 666 yield self.chunk_table
667
668 - def get_detail_child_names(self, edge_filter=EdgeFilter()):
669 yield "header" 670 yield "chunk table"
671 672 # overriding pyffi.object_models.FileFormat.Data methods 673
674 - def inspect(self, stream):
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
697 - def read(self, stream):
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
864 - def write(self, stream):
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
964 - def update_versions(self):
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
974 - class Chunk:
975 - def tree(self, block_type = None, follow_all = True):
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
994 - def apply_scale(self, scale):
995 """Apply scale factor on data.""" 996 pass
997
998 - class ChunkTable:
999 - def get_chunk_types(self):
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
1006 - class BoneInitialPosChunk:
1007 - def apply_scale(self, scale):
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
1016 - def get_global_node_parent(self):
1017 """Get the block parent (used for instance in the QSkope global 1018 view).""" 1019 return self.mesh
1020
1021 - class DataStreamChunk:
1022 - def apply_scale(self, scale):
1023 """Apply scale factor on data.""" 1024 if abs(scale - 1.0) < CgfFormat.EPSILON: 1025 return 1026 for vert in self.vertices: 1027 vert.x *= scale 1028 vert.y *= scale 1029 vert.z *= scale
1030
1031 - class Matrix33:
1032 - def as_list(self):
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
1040 - def as_tuple(self):
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
1048 - def __str__(self):
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
1055 - def set_identity(self):
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
1067 - def is_identity(self):
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
1082 - def get_copy(self):
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
1096 - def get_transpose(self):
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
1110 - def is_scale_rotation(self):
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
1130 - def is_rotation(self):
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
1144 - def get_determinant(self):
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
1153 - def get_scale(self):
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
1172 - def get_scale_rotation(self):
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
1190 - def set_scale_rotation(self, scale, rotation):
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
1210 - def get_scale_quat(self):
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
1244 - def get_inverse(self):
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
1260 - def __mul__(self, rhs):
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
1292 - def __div__(self, rhs):
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
1309 - def __rmul__(self, lhs):
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
1316 - def __eq__(self, mat):
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
1332 - def __ne__(self, mat):
1333 return not self.__eq__(mat)
1334
1335 - class Matrix44:
1336 - def as_list(self):
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
1345 - def as_tuple(self):
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
1354 - def set_rows(self, row0, row1, row2, row3):
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
1361 - def __str__(self):
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
1372 - def set_identity(self):
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
1391 - def is_identity(self):
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
1413 - def get_copy(self):
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
1434 - def get_matrix_33(self):
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
1448 - def set_matrix_33(self, m):
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
1462 - def get_translation(self):
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
1470 - def set_translation(self, translation):
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
1492 - def get_scale_quat_translation(self):
1493 rotscl = self.get_matrix_33() 1494 scale, quat = rotscl.get_scale_quat() 1495 trans = self.get_translation() 1496 return (scale, quat, trans)
1497
1498 - def set_scale_rotation_translation(self, scale, rotation, translation):
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
1529 - def get_inverse(self, fast=True):
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
1580 - def __mul__(self, x):
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
1626 - def __div__(self, x):
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
1649 - def __rmul__(self, x):
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
1655 - def __eq__(self, m):
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
1678 - def __ne__(self, m):
1679 return not self.__eq__(m)
1680
1681 - def __add__(self, x):
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
1723 - def __sub__(self, x):
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
1766 - def sup_norm(self):
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
1772 - class MeshChunk:
1773 - def apply_scale(self, scale):
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
1789 - def get_vertices(self):
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
1798 - def get_normals(self):
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
1807 - def get_colors(self):
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
1821 - def get_num_triangles(self):
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
1830 - def get_triangles(self):
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
1840 - def get_material_indices(self):
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
1850 - def get_uvs(self):
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
1859 - def get_uv_triangles(self):
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 ###
1871 - def set_vertices_normals(self, vertices, normals):
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
2626 - def update_tangent_space(self):
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
2662 - class MeshMorphTargetChunk:
2663 - def apply_scale(self, scale):
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
2672 - def get_global_node_parent(self):
2673 """Get the block parent (used for instance in the QSkope global view).""" 2674 return self.mesh
2675
2676 - def get_global_display(self):
2677 """Return a name for the block.""" 2678 return self.target_name
2679
2680 - class MeshSubsetsChunk:
2681 - def apply_scale(self, scale):
2682 """Apply scale factor on data.""" 2683 if abs(scale - 1.0) < CgfFormat.EPSILON: 2684 return 2685 for meshsubset in self.mesh_subsets: 2686 meshsubset.radius *= scale 2687 meshsubset.center.x *= scale 2688 meshsubset.center.y *= scale 2689 meshsubset.center.z *= scale
2690
2691 - class MtlChunk:
2692 - def get_name_shader_script(self):
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
2735 - class NodeChunk:
2736 - def get_global_node_parent(self):
2737 """Get the block parent (used for instance in the QSkope global view).""" 2738 return self.parent
2739
2740 - def apply_scale(self, scale):
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
2751 - def update_pos_rot_scl(self):
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
2765 - class SourceInfoChunk:
2766 - def get_global_display(self):
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
2771 - class TimingChunk:
2772 - def get_global_display(self):
2773 """Return a name for the block.""" 2774 return self.global_range.name
2775
2776 - class Vector3:
2777 - def as_list(self):
2778 return [self.x, self.y, self.z]
2779
2780 - def as_tuple(self):
2781 return (self.x, self.y, self.z)
2782
2783 - def norm(self):
2784 return (self.x*self.x + self.y*self.y + self.z*self.z) ** 0.5
2785
2786 - def normalize(self):
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
2794 - def get_copy(self):
2795 v = CgfFormat.Vector3() 2796 v.x = self.x 2797 v.y = self.y 2798 v.z = self.z 2799 return v
2800
2801 - def __str__(self):
2802 return "[ %6.3f %6.3f %6.3f ]"%(self.x, self.y, self.z)
2803
2804 - def __mul__(self, x):
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
2824 - def __rmul__(self, x):
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
2834 - def __div__(self, x):
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
2844 - def __add__(self, x):
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
2860 - def __radd__(self, x):
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
2870 - def __sub__(self, x):
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
2886 - def __rsub__(self, x):
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
2896 - def __neg__(self):
2897 v = CgfFormat.Vector3() 2898 v.x = -self.x 2899 v.y = -self.y 2900 v.z = -self.z 2901 return v
2902 2903 # cross product
2904 - def crossproduct(self, x):
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
2914 - def __eq__(self, x):
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
2924 - def __ne__(self, x):
2925 return not self.__eq__(x)
2926