From 155b572acae5c8f83be0b501cc286297885dc478 Mon Sep 17 00:00:00 2001 From: Dane Johnson Date: Mon, 18 Jan 2021 18:25:47 -0600 Subject: [PATCH] Model loading and texturing --- CMakeLists.txt | 4 +- core/Mesh.cpp | 26 +- core/Mesh.h | 10 +- core/Util.cpp | 18 + core/Util.h | 15 + demo/cube.glb | Bin 0 -> 424872 bytes demo/main.lua | 6 + shaders/CMakeLists.txt | 22 +- shaders/makeheaders.pl | 44 +- thirdparty/CMakeLists.txt | 10 +- thirdparty/assimp/.editorconfig | 22 + .../assimp/AssimpBuildTreeSettings.cmake.in | 3 + .../assimp/AssimpConfigVersion.cmake.in | 11 + thirdparty/assimp/BUILDBINARIES_EXAMPLE.bat | 26 + thirdparty/assimp/Build.md | 73 + thirdparty/assimp/CHANGES | 607 + thirdparty/assimp/CMakeLists.txt | 680 + thirdparty/assimp/CONTRIBUTING.md | 10 + thirdparty/assimp/CREDITS | 183 + thirdparty/assimp/CodeConventions.md | 12 + thirdparty/assimp/INSTALL | 50 + thirdparty/assimp/LICENSE | 78 + thirdparty/assimp/README | 1 + thirdparty/assimp/Readme.md | 183 + .../assimp/assimp-config-version.cmake.in | 48 + thirdparty/assimp/assimp-config.cmake.in | 1 + thirdparty/assimp/assimp.pc.in | 11 + .../assimp/assimpTargets-debug.cmake.in | 114 + .../assimp/assimpTargets-release.cmake.in | 112 + thirdparty/assimp/assimpTargets.cmake.in | 105 + .../assimp/cmake-modules/Coveralls.cmake | 126 + .../assimp/cmake-modules/CoverallsClear.cmake | 31 + .../cmake-modules/CoverallsGenerateGcov.cmake | 482 + .../assimp/cmake-modules/DebSourcePPA.cmake | 347 + .../assimp/cmake-modules/FindDevIL.cmake | 72 + .../assimp/cmake-modules/FindDirectX.cmake | 101 + .../assimp/cmake-modules/FindIrrXML.cmake | 17 + .../assimp/cmake-modules/FindPkgMacros.cmake | 146 + thirdparty/assimp/cmake-modules/FindRT.cmake | 20 + .../assimp/cmake-modules/FindZLIB.cmake | 48 + .../assimp/cmake-modules/Findassimp.cmake | 81 + .../assimp/cmake-modules/MinGW_x86_64.cmake | 16 + .../cmake-modules/PrecompiledHeader.cmake | 25 + .../cmake-modules/cmake_uninstall.cmake.in | 17 + thirdparty/assimp/cmake/HunterGate.cmake | 540 + .../cmake/assimp-hunter-config.cmake.in | 14 + thirdparty/assimp/code/.#CMakeLists.txt | 1 + thirdparty/assimp/code/.editorconfig | 8 + thirdparty/assimp/code/3DS/3DSConverter.cpp | 873 + thirdparty/assimp/code/3DS/3DSExporter.cpp | 582 + thirdparty/assimp/code/3DS/3DSExporter.h | 98 + thirdparty/assimp/code/3DS/3DSHelper.h | 652 + thirdparty/assimp/code/3DS/3DSLoader.cpp | 1432 ++ thirdparty/assimp/code/3DS/3DSLoader.h | 284 + thirdparty/assimp/code/3MF/3MFXmlTags.h | 104 + thirdparty/assimp/code/3MF/D3MFExporter.cpp | 402 + thirdparty/assimp/code/3MF/D3MFExporter.h | 106 + thirdparty/assimp/code/3MF/D3MFImporter.cpp | 483 + thirdparty/assimp/code/3MF/D3MFImporter.h | 65 + thirdparty/assimp/code/3MF/D3MFOpcPackage.cpp | 207 + thirdparty/assimp/code/3MF/D3MFOpcPackage.h | 83 + thirdparty/assimp/code/AC/ACLoader.cpp | 910 + thirdparty/assimp/code/AC/ACLoader.h | 276 + thirdparty/assimp/code/AMF/AMFImporter.cpp | 705 + thirdparty/assimp/code/AMF/AMFImporter.hpp | 432 + .../assimp/code/AMF/AMFImporter_Geometry.cpp | 357 + .../assimp/code/AMF/AMFImporter_Macro.hpp | 166 + .../assimp/code/AMF/AMFImporter_Material.cpp | 329 + .../assimp/code/AMF/AMFImporter_Node.hpp | 340 + .../code/AMF/AMFImporter_Postprocess.cpp | 978 + thirdparty/assimp/code/ASE/ASELoader.cpp | 1328 ++ thirdparty/assimp/code/ASE/ASELoader.h | 207 + thirdparty/assimp/code/ASE/ASEParser.cpp | 2159 ++ thirdparty/assimp/code/ASE/ASEParser.h | 693 + .../assimp/code/Assbin/AssbinExporter.cpp | 846 + .../assimp/code/Assbin/AssbinExporter.h | 58 + .../assimp/code/Assbin/AssbinLoader.cpp | 733 + thirdparty/assimp/code/Assbin/AssbinLoader.h | 106 + thirdparty/assimp/code/Assjson/cencode.c | 109 + thirdparty/assimp/code/Assjson/cencode.h | 31 + .../assimp/code/Assjson/json_exporter.cpp | 809 + .../assimp/code/Assjson/mesh_splitter.cpp | 320 + .../assimp/code/Assjson/mesh_splitter.h | 61 + .../assimp/code/Assxml/AssxmlExporter.cpp | 655 + .../assimp/code/Assxml/AssxmlExporter.h | 51 + thirdparty/assimp/code/B3D/B3DImporter.cpp | 740 + thirdparty/assimp/code/B3D/B3DImporter.h | 134 + thirdparty/assimp/code/BVH/BVHLoader.cpp | 578 + thirdparty/assimp/code/BVH/BVHLoader.h | 176 + .../assimp/code/Blender/BlenderBMesh.cpp | 206 + thirdparty/assimp/code/Blender/BlenderBMesh.h | 94 + .../assimp/code/Blender/BlenderCustomData.cpp | 189 + .../assimp/code/Blender/BlenderCustomData.h | 89 + thirdparty/assimp/code/Blender/BlenderDNA.cpp | 375 + thirdparty/assimp/code/Blender/BlenderDNA.h | 844 + thirdparty/assimp/code/Blender/BlenderDNA.inl | 856 + .../assimp/code/Blender/BlenderIntermediate.h | 206 + .../assimp/code/Blender/BlenderLoader.cpp | 1397 ++ .../assimp/code/Blender/BlenderLoader.h | 240 + .../assimp/code/Blender/BlenderModifier.cpp | 299 + .../assimp/code/Blender/BlenderModifier.h | 155 + .../assimp/code/Blender/BlenderScene.cpp | 875 + thirdparty/assimp/code/Blender/BlenderScene.h | 983 + .../assimp/code/Blender/BlenderSceneGen.h | 266 + .../code/Blender/BlenderTessellator.cpp | 526 + .../assimp/code/Blender/BlenderTessellator.h | 214 + thirdparty/assimp/code/C4D/C4DImporter.cpp | 621 + thirdparty/assimp/code/C4D/C4DImporter.h | 117 + thirdparty/assimp/code/CApi/AssimpCExport.cpp | 156 + .../assimp/code/CApi/CInterfaceIOWrapper.cpp | 136 + .../assimp/code/CApi/CInterfaceIOWrapper.h | 99 + thirdparty/assimp/code/CMakeLists.txt | 1293 ++ thirdparty/assimp/code/COB/COBLoader.cpp | 1253 ++ thirdparty/assimp/code/COB/COBLoader.h | 156 + thirdparty/assimp/code/COB/COBScene.h | 277 + thirdparty/assimp/code/CSM/CSMLoader.cpp | 308 + thirdparty/assimp/code/CSM/CSMLoader.h | 93 + .../assimp/code/Collada/ColladaExporter.cpp | 1675 ++ .../assimp/code/Collada/ColladaExporter.h | 222 + .../assimp/code/Collada/ColladaHelper.h | 701 + .../assimp/code/Collada/ColladaLoader.cpp | 1961 ++ .../assimp/code/Collada/ColladaLoader.h | 259 + .../assimp/code/Collada/ColladaParser.cpp | 3409 ++++ .../assimp/code/Collada/ColladaParser.h | 393 + thirdparty/assimp/code/Common/Assimp.cpp | 695 + .../assimp/code/Common/BaseImporter.cpp | 643 + thirdparty/assimp/code/Common/BaseProcess.cpp | 107 + thirdparty/assimp/code/Common/BaseProcess.h | 290 + thirdparty/assimp/code/Common/Bitmap.cpp | 155 + .../assimp/code/Common/CreateAnimMesh.cpp | 88 + .../assimp/code/Common/DefaultIOStream.cpp | 154 + .../assimp/code/Common/DefaultIOSystem.cpp | 216 + .../assimp/code/Common/DefaultLogger.cpp | 418 + .../code/Common/DefaultProgressHandler.h | 65 + thirdparty/assimp/code/Common/Exporter.cpp | 629 + thirdparty/assimp/code/Common/FileLogStream.h | 107 + .../assimp/code/Common/FileSystemFilter.h | 345 + thirdparty/assimp/code/Common/IFF.h | 102 + thirdparty/assimp/code/Common/Importer.cpp | 1174 ++ thirdparty/assimp/code/Common/Importer.h | 247 + .../assimp/code/Common/ImporterRegistry.cpp | 371 + thirdparty/assimp/code/Common/PolyTools.h | 229 + .../assimp/code/Common/PostStepRegistry.cpp | 258 + .../assimp/code/Common/RemoveComments.cpp | 113 + .../assimp/code/Common/SGSpatialSort.cpp | 168 + .../assimp/code/Common/SceneCombiner.cpp | 1329 ++ .../assimp/code/Common/ScenePreprocessor.cpp | 261 + .../assimp/code/Common/ScenePreprocessor.h | 125 + thirdparty/assimp/code/Common/ScenePrivate.h | 105 + .../code/Common/SkeletonMeshBuilder.cpp | 270 + thirdparty/assimp/code/Common/SpatialSort.cpp | 342 + .../code/Common/SplitByBoneCountProcess.cpp | 407 + .../code/Common/SplitByBoneCountProcess.h | 111 + .../assimp/code/Common/StandardShapes.cpp | 507 + .../assimp/code/Common/StdOStreamLogStream.h | 101 + thirdparty/assimp/code/Common/Subdivision.cpp | 589 + .../assimp/code/Common/TargetAnimation.cpp | 248 + .../assimp/code/Common/TargetAnimation.h | 183 + thirdparty/assimp/code/Common/Version.cpp | 185 + .../code/Common/VertexTriangleAdjacency.cpp | 134 + .../code/Common/VertexTriangleAdjacency.h | 117 + .../assimp/code/Common/Win32DebugLogStream.h | 95 + .../assimp/code/Common/ZipArchiveIOSystem.cpp | 539 + thirdparty/assimp/code/Common/assbin_chunks.h | 196 + thirdparty/assimp/code/Common/scene.cpp | 140 + thirdparty/assimp/code/Common/simd.cpp | 79 + thirdparty/assimp/code/Common/simd.h | 53 + thirdparty/assimp/code/DXF/DXFHelper.h | 222 + thirdparty/assimp/code/DXF/DXFLoader.cpp | 918 + thirdparty/assimp/code/DXF/DXFLoader.h | 149 + thirdparty/assimp/code/FBX/FBXAnimation.cpp | 305 + .../assimp/code/FBX/FBXBinaryTokenizer.cpp | 466 + thirdparty/assimp/code/FBX/FBXCommon.h | 86 + thirdparty/assimp/code/FBX/FBXCompileConfig.h | 70 + thirdparty/assimp/code/FBX/FBXConverter.cpp | 3615 ++++ thirdparty/assimp/code/FBX/FBXConverter.h | 464 + thirdparty/assimp/code/FBX/FBXDeformer.cpp | 213 + thirdparty/assimp/code/FBX/FBXDocument.cpp | 718 + thirdparty/assimp/code/FBX/FBXDocument.h | 1180 ++ .../assimp/code/FBX/FBXDocumentUtil.cpp | 135 + thirdparty/assimp/code/FBX/FBXDocumentUtil.h | 120 + thirdparty/assimp/code/FBX/FBXExportNode.cpp | 571 + thirdparty/assimp/code/FBX/FBXExportNode.h | 271 + .../assimp/code/FBX/FBXExportProperty.cpp | 385 + .../assimp/code/FBX/FBXExportProperty.h | 129 + thirdparty/assimp/code/FBX/FBXExporter.cpp | 2556 +++ thirdparty/assimp/code/FBX/FBXExporter.h | 178 + .../assimp/code/FBX/FBXImportSettings.h | 164 + thirdparty/assimp/code/FBX/FBXImporter.cpp | 206 + thirdparty/assimp/code/FBX/FBXImporter.h | 100 + thirdparty/assimp/code/FBX/FBXMaterial.cpp | 405 + .../assimp/code/FBX/FBXMeshGeometry.cpp | 709 + thirdparty/assimp/code/FBX/FBXMeshGeometry.h | 235 + thirdparty/assimp/code/FBX/FBXModel.cpp | 153 + .../assimp/code/FBX/FBXNodeAttribute.cpp | 170 + thirdparty/assimp/code/FBX/FBXParser.cpp | 1309 ++ thirdparty/assimp/code/FBX/FBXParser.h | 235 + thirdparty/assimp/code/FBX/FBXProperties.cpp | 235 + thirdparty/assimp/code/FBX/FBXProperties.h | 185 + thirdparty/assimp/code/FBX/FBXTokenizer.cpp | 248 + thirdparty/assimp/code/FBX/FBXTokenizer.h | 187 + thirdparty/assimp/code/FBX/FBXUtil.cpp | 243 + thirdparty/assimp/code/FBX/FBXUtil.h | 137 + thirdparty/assimp/code/HMP/HMPFileData.h | 137 + thirdparty/assimp/code/HMP/HMPLoader.cpp | 522 + thirdparty/assimp/code/HMP/HMPLoader.h | 153 + thirdparty/assimp/code/HMP/HalfLifeFileData.h | 149 + .../assimp/code/Importer/IFC/IFCBoolean.cpp | 829 + .../assimp/code/Importer/IFC/IFCCurve.cpp | 616 + .../assimp/code/Importer/IFC/IFCGeometry.cpp | 888 + .../assimp/code/Importer/IFC/IFCLoader.cpp | 988 + .../assimp/code/Importer/IFC/IFCLoader.h | 134 + .../assimp/code/Importer/IFC/IFCMaterial.cpp | 202 + .../assimp/code/Importer/IFC/IFCOpenings.cpp | 1704 ++ .../assimp/code/Importer/IFC/IFCProfile.cpp | 190 + .../code/Importer/IFC/IFCReaderGen1_2x3.cpp | 3168 +++ .../code/Importer/IFC/IFCReaderGen2_2x3.cpp | 1918 ++ .../code/Importer/IFC/IFCReaderGen_2x3.h | 4371 +++++ .../code/Importer/IFC/IFCReaderGen_4.cpp | 6207 ++++++ .../assimp/code/Importer/IFC/IFCReaderGen_4.h | 5452 +++++ .../assimp/code/Importer/IFC/IFCUtil.cpp | 703 + thirdparty/assimp/code/Importer/IFC/IFCUtil.h | 413 + .../Importer/STEPParser/STEPFileEncoding.cpp | 432 + .../Importer/STEPParser/STEPFileEncoding.h | 65 + .../Importer/STEPParser/STEPFileReader.cpp | 559 + .../code/Importer/STEPParser/STEPFileReader.h | 71 + .../code/Importer/StepFile/StepFileGen1.cpp | 2195 +++ .../code/Importer/StepFile/StepFileGen2.cpp | 3067 +++ .../code/Importer/StepFile/StepFileGen3.cpp | 5746 ++++++ .../Importer/StepFile/StepFileImporter.cpp | 114 + .../code/Importer/StepFile/StepFileImporter.h | 69 + .../code/Importer/StepFile/StepReaderGen.h | 7288 +++++++ thirdparty/assimp/code/Irr/IRRLoader.cpp | 1488 ++ thirdparty/assimp/code/Irr/IRRLoader.h | 296 + thirdparty/assimp/code/Irr/IRRMeshLoader.cpp | 536 + thirdparty/assimp/code/Irr/IRRMeshLoader.h | 101 + thirdparty/assimp/code/Irr/IRRShared.cpp | 501 + thirdparty/assimp/code/Irr/IRRShared.h | 118 + thirdparty/assimp/code/LWO/LWOAnimation.cpp | 604 + thirdparty/assimp/code/LWO/LWOAnimation.h | 346 + thirdparty/assimp/code/LWO/LWOBLoader.cpp | 428 + thirdparty/assimp/code/LWO/LWOFileData.h | 703 + thirdparty/assimp/code/LWO/LWOLoader.cpp | 1490 ++ thirdparty/assimp/code/LWO/LWOLoader.h | 490 + thirdparty/assimp/code/LWO/LWOMaterial.cpp | 898 + thirdparty/assimp/code/LWS/LWSLoader.cpp | 937 + thirdparty/assimp/code/LWS/LWSLoader.h | 255 + thirdparty/assimp/code/MD2/MD2FileData.h | 164 + thirdparty/assimp/code/MD2/MD2Loader.cpp | 459 + thirdparty/assimp/code/MD2/MD2Loader.h | 124 + thirdparty/assimp/code/MD2/MD2NormalTable.h | 219 + thirdparty/assimp/code/MD3/MD3FileData.h | 314 + thirdparty/assimp/code/MD3/MD3Loader.cpp | 1096 ++ thirdparty/assimp/code/MD3/MD3Loader.h | 333 + thirdparty/assimp/code/MD4/MD4FileData.h | 218 + thirdparty/assimp/code/MD5/MD5Loader.cpp | 762 + thirdparty/assimp/code/MD5/MD5Loader.h | 195 + thirdparty/assimp/code/MD5/MD5Parser.cpp | 487 + thirdparty/assimp/code/MD5/MD5Parser.h | 467 + thirdparty/assimp/code/MDC/MDCFileData.h | 208 + thirdparty/assimp/code/MDC/MDCLoader.cpp | 495 + thirdparty/assimp/code/MDC/MDCLoader.h | 130 + thirdparty/assimp/code/MDC/MDCNormalTable.h | 299 + .../assimp/code/MDL/MDLDefaultColorMap.h | 120 + thirdparty/assimp/code/MDL/MDLFileData.h | 943 + thirdparty/assimp/code/MDL/MDLLoader.cpp | 1967 ++ thirdparty/assimp/code/MDL/MDLLoader.h | 443 + .../assimp/code/MDL/MDLMaterialLoader.cpp | 840 + thirdparty/assimp/code/MMD/MMDCpp14.h | 83 + thirdparty/assimp/code/MMD/MMDImporter.cpp | 372 + thirdparty/assimp/code/MMD/MMDImporter.h | 96 + thirdparty/assimp/code/MMD/MMDPmdParser.h | 597 + thirdparty/assimp/code/MMD/MMDPmxParser.cpp | 608 + thirdparty/assimp/code/MMD/MMDPmxParser.h | 782 + thirdparty/assimp/code/MMD/MMDVmdParser.h | 376 + thirdparty/assimp/code/MS3D/MS3DLoader.cpp | 665 + thirdparty/assimp/code/MS3D/MS3DLoader.h | 162 + .../assimp/code/Material/MaterialSystem.cpp | 633 + .../assimp/code/Material/MaterialSystem.h | 72 + thirdparty/assimp/code/NDO/NDOLoader.cpp | 311 + thirdparty/assimp/code/NDO/NDOLoader.h | 126 + thirdparty/assimp/code/NFF/NFFLoader.cpp | 1278 ++ thirdparty/assimp/code/NFF/NFFLoader.h | 215 + thirdparty/assimp/code/OFF/OFFLoader.cpp | 345 + thirdparty/assimp/code/OFF/OFFLoader.h | 94 + thirdparty/assimp/code/Obj/ObjExporter.cpp | 414 + thirdparty/assimp/code/Obj/ObjExporter.h | 190 + thirdparty/assimp/code/Obj/ObjFileData.h | 339 + .../assimp/code/Obj/ObjFileImporter.cpp | 784 + thirdparty/assimp/code/Obj/ObjFileImporter.h | 123 + .../assimp/code/Obj/ObjFileMtlImporter.cpp | 494 + .../assimp/code/Obj/ObjFileMtlImporter.h | 117 + thirdparty/assimp/code/Obj/ObjFileParser.cpp | 880 + thirdparty/assimp/code/Obj/ObjFileParser.h | 165 + thirdparty/assimp/code/Obj/ObjTools.h | 308 + .../assimp/code/Ogre/OgreBinarySerializer.cpp | 1113 ++ .../assimp/code/Ogre/OgreBinarySerializer.h | 423 + thirdparty/assimp/code/Ogre/OgreImporter.cpp | 150 + thirdparty/assimp/code/Ogre/OgreImporter.h | 102 + thirdparty/assimp/code/Ogre/OgreMaterial.cpp | 596 + .../assimp/code/Ogre/OgreParsingUtils.h | 136 + thirdparty/assimp/code/Ogre/OgreStructs.cpp | 1206 ++ thirdparty/assimp/code/Ogre/OgreStructs.h | 696 + .../assimp/code/Ogre/OgreXmlSerializer.cpp | 1002 + .../assimp/code/Ogre/OgreXmlSerializer.h | 118 + .../assimp/code/OpenGEX/OpenGEXExporter.cpp | 62 + .../assimp/code/OpenGEX/OpenGEXExporter.h | 68 + .../assimp/code/OpenGEX/OpenGEXImporter.cpp | 1326 ++ .../assimp/code/OpenGEX/OpenGEXImporter.h | 212 + .../assimp/code/OpenGEX/OpenGEXStructs.h | 264 + thirdparty/assimp/code/Ply/PlyExporter.cpp | 405 + thirdparty/assimp/code/Ply/PlyExporter.h | 88 + thirdparty/assimp/code/Ply/PlyLoader.cpp | 966 + thirdparty/assimp/code/Ply/PlyLoader.h | 140 + thirdparty/assimp/code/Ply/PlyParser.cpp | 1142 ++ thirdparty/assimp/code/Ply/PlyParser.h | 489 + .../PostProcessing/CalcTangentsProcess.cpp | 319 + .../code/PostProcessing/CalcTangentsProcess.h | 117 + .../ComputeUVMappingProcess.cpp | 506 + .../PostProcessing/ComputeUVMappingProcess.h | 149 + .../PostProcessing/ConvertToLHProcess.cpp | 414 + .../code/PostProcessing/ConvertToLHProcess.h | 171 + .../code/PostProcessing/DeboneProcess.cpp | 465 + .../code/PostProcessing/DeboneProcess.h | 131 + .../PostProcessing/DropFaceNormalsProcess.cpp | 109 + .../PostProcessing/DropFaceNormalsProcess.h | 83 + .../PostProcessing/EmbedTexturesProcess.cpp | 152 + .../PostProcessing/EmbedTexturesProcess.h | 85 + .../code/PostProcessing/FindDegenerates.cpp | 301 + .../code/PostProcessing/FindDegenerates.h | 130 + .../PostProcessing/FindInstancesProcess.cpp | 277 + .../PostProcessing/FindInstancesProcess.h | 137 + .../PostProcessing/FindInvalidDataProcess.cpp | 424 + .../PostProcessing/FindInvalidDataProcess.h | 106 + .../code/PostProcessing/FixNormalsStep.cpp | 184 + .../code/PostProcessing/FixNormalsStep.h | 91 + .../GenBoundingBoxesProcess.cpp | 115 + .../PostProcessing/GenBoundingBoxesProcess.h | 76 + .../PostProcessing/GenFaceNormalsProcess.cpp | 146 + .../PostProcessing/GenFaceNormalsProcess.h | 87 + .../GenVertexNormalsProcess.cpp | 239 + .../PostProcessing/GenVertexNormalsProcess.h | 111 + .../PostProcessing/ImproveCacheLocality.cpp | 379 + .../PostProcessing/ImproveCacheLocality.h | 101 + .../PostProcessing/JoinVerticesProcess.cpp | 438 + .../code/PostProcessing/JoinVerticesProcess.h | 95 + .../LimitBoneWeightsProcess.cpp | 201 + .../PostProcessing/LimitBoneWeightsProcess.h | 138 + .../code/PostProcessing/MakeVerboseFormat.cpp | 255 + .../code/PostProcessing/MakeVerboseFormat.h | 113 + .../code/PostProcessing/OptimizeGraph.cpp | 351 + .../code/PostProcessing/OptimizeGraph.h | 140 + .../code/PostProcessing/OptimizeMeshes.cpp | 256 + .../code/PostProcessing/OptimizeMeshes.h | 186 + .../PostProcessing/PretransformVertices.cpp | 728 + .../PostProcessing/PretransformVertices.h | 166 + .../code/PostProcessing/ProcessHelper.cpp | 443 + .../code/PostProcessing/ProcessHelper.h | 386 + .../RemoveRedundantMaterials.cpp | 221 + .../PostProcessing/RemoveRedundantMaterials.h | 103 + .../code/PostProcessing/RemoveVCProcess.cpp | 337 + .../code/PostProcessing/RemoveVCProcess.h | 124 + .../code/PostProcessing/ScaleProcess.cpp | 208 + .../assimp/code/PostProcessing/ScaleProcess.h | 97 + .../PostProcessing/SortByPTypeProcess.cpp | 403 + .../code/PostProcessing/SortByPTypeProcess.h | 82 + .../code/PostProcessing/SplitLargeMeshes.cpp | 623 + .../code/PostProcessing/SplitLargeMeshes.h | 209 + .../code/PostProcessing/TextureTransform.cpp | 566 + .../code/PostProcessing/TextureTransform.h | 232 + .../PostProcessing/TriangulateProcess.cpp | 530 + .../code/PostProcessing/TriangulateProcess.h | 91 + .../PostProcessing/ValidateDataStructure.cpp | 979 + .../PostProcessing/ValidateDataStructure.h | 189 + thirdparty/assimp/code/Q3BSP/Q3BSPFileData.h | 214 + .../assimp/code/Q3BSP/Q3BSPFileImporter.cpp | 706 + .../assimp/code/Q3BSP/Q3BSPFileImporter.h | 119 + .../assimp/code/Q3BSP/Q3BSPFileParser.cpp | 274 + .../assimp/code/Q3BSP/Q3BSPFileParser.h | 90 + thirdparty/assimp/code/Q3D/Q3DLoader.cpp | 619 + thirdparty/assimp/code/Q3D/Q3DLoader.h | 134 + thirdparty/assimp/code/Raw/RawLoader.cpp | 331 + thirdparty/assimp/code/Raw/RawLoader.h | 121 + thirdparty/assimp/code/SIB/SIBImporter.cpp | 946 + thirdparty/assimp/code/SIB/SIBImporter.h | 117 + thirdparty/assimp/code/SMD/SMDLoader.cpp | 1099 ++ thirdparty/assimp/code/SMD/SMDLoader.h | 418 + thirdparty/assimp/code/STL/STLExporter.cpp | 233 + thirdparty/assimp/code/STL/STLExporter.h | 84 + thirdparty/assimp/code/STL/STLLoader.cpp | 582 + thirdparty/assimp/code/STL/STLLoader.h | 125 + thirdparty/assimp/code/Step/STEPFile.h | 1016 + thirdparty/assimp/code/Step/StepExporter.cpp | 375 + thirdparty/assimp/code/Step/StepExporter.h | 111 + .../assimp/code/Terragen/TerragenLoader.cpp | 274 + .../assimp/code/Terragen/TerragenLoader.h | 105 + .../assimp/code/Unreal/UnrealLoader.cpp | 445 + thirdparty/assimp/code/Unreal/UnrealLoader.h | 207 + thirdparty/assimp/code/X/XFileExporter.cpp | 541 + thirdparty/assimp/code/X/XFileExporter.h | 140 + thirdparty/assimp/code/X/XFileHelper.h | 245 + thirdparty/assimp/code/X/XFileImporter.cpp | 696 + thirdparty/assimp/code/X/XFileImporter.h | 149 + thirdparty/assimp/code/X/XFileParser.cpp | 1512 ++ thirdparty/assimp/code/X/XFileParser.h | 157 + thirdparty/assimp/code/X3D/FIReader.cpp | 1838 ++ thirdparty/assimp/code/X3D/FIReader.hpp | 192 + thirdparty/assimp/code/X3D/X3DExporter.cpp | 735 + thirdparty/assimp/code/X3D/X3DExporter.hpp | 235 + thirdparty/assimp/code/X3D/X3DImporter.cpp | 1735 ++ thirdparty/assimp/code/X3D/X3DImporter.hpp | 834 + .../code/X3D/X3DImporter_Geometry2D.cpp | 522 + .../code/X3D/X3DImporter_Geometry3D.cpp | 999 + .../assimp/code/X3D/X3DImporter_Group.cpp | 318 + .../assimp/code/X3D/X3DImporter_Light.cpp | 290 + .../assimp/code/X3D/X3DImporter_Macro.hpp | 195 + .../assimp/code/X3D/X3DImporter_Metadata.cpp | 277 + .../code/X3D/X3DImporter_Networking.cpp | 134 + .../assimp/code/X3D/X3DImporter_Node.hpp | 780 + .../code/X3D/X3DImporter_Postprocess.cpp | 827 + .../assimp/code/X3D/X3DImporter_Rendering.cpp | 1071 + .../assimp/code/X3D/X3DImporter_Shape.cpp | 250 + .../assimp/code/X3D/X3DImporter_Texturing.cpp | 197 + thirdparty/assimp/code/X3D/X3DVocabulary.cpp | 1676 ++ thirdparty/assimp/code/XGL/XGLLoader.cpp | 956 + thirdparty/assimp/code/XGL/XGLLoader.h | 216 + thirdparty/assimp/code/glTF/glTFAsset.h | 1146 ++ thirdparty/assimp/code/glTF/glTFAsset.inl | 1481 ++ thirdparty/assimp/code/glTF/glTFAssetWriter.h | 96 + .../assimp/code/glTF/glTFAssetWriter.inl | 683 + thirdparty/assimp/code/glTF/glTFCommon.cpp | 193 + thirdparty/assimp/code/glTF/glTFCommon.h | 248 + thirdparty/assimp/code/glTF/glTFExporter.cpp | 1008 + thirdparty/assimp/code/glTF/glTFExporter.h | 119 + thirdparty/assimp/code/glTF/glTFImporter.cpp | 716 + thirdparty/assimp/code/glTF/glTFImporter.h | 92 + thirdparty/assimp/code/glTF2/glTF2Asset.h | 1101 ++ thirdparty/assimp/code/glTF2/glTF2Asset.inl | 1519 ++ .../assimp/code/glTF2/glTF2AssetWriter.h | 96 + .../assimp/code/glTF2/glTF2AssetWriter.inl | 781 + .../assimp/code/glTF2/glTF2Exporter.cpp | 1100 ++ thirdparty/assimp/code/glTF2/glTF2Exporter.h | 131 + .../assimp/code/glTF2/glTF2Importer.cpp | 1265 ++ thirdparty/assimp/code/glTF2/glTF2Importer.h | 92 + thirdparty/assimp/code/res/assimp.rc | 80 + thirdparty/assimp/code/res/resource.h | 14 + thirdparty/assimp/contrib/CMakeLists.txt | 4 + .../contrib/Open3DGC/o3dgcAdjacencyInfo.h | 155 + .../contrib/Open3DGC/o3dgcArithmeticCodec.cpp | 865 + .../contrib/Open3DGC/o3dgcArithmeticCodec.h | 339 + .../contrib/Open3DGC/o3dgcBinaryStream.h | 433 + .../assimp/contrib/Open3DGC/o3dgcCommon.h | 412 + .../contrib/Open3DGC/o3dgcDVEncodeParams.h | 62 + .../contrib/Open3DGC/o3dgcDynamicVector.h | 84 + .../Open3DGC/o3dgcDynamicVectorDecoder.cpp | 278 + .../Open3DGC/o3dgcDynamicVectorDecoder.h | 76 + .../Open3DGC/o3dgcDynamicVectorEncoder.cpp | 295 + .../Open3DGC/o3dgcDynamicVectorEncoder.h | 79 + .../assimp/contrib/Open3DGC/o3dgcFIFO.h | 97 + .../contrib/Open3DGC/o3dgcIndexedFaceSet.h | 263 + .../contrib/Open3DGC/o3dgcIndexedFaceSet.inl | 47 + .../contrib/Open3DGC/o3dgcSC3DMCDecoder.h | 111 + .../contrib/Open3DGC/o3dgcSC3DMCDecoder.inl | 850 + .../Open3DGC/o3dgcSC3DMCEncodeParams.h | 140 + .../contrib/Open3DGC/o3dgcSC3DMCEncoder.h | 116 + .../contrib/Open3DGC/o3dgcSC3DMCEncoder.inl | 927 + .../assimp/contrib/Open3DGC/o3dgcTimer.h | 134 + .../assimp/contrib/Open3DGC/o3dgcTools.cpp | 22 + .../contrib/Open3DGC/o3dgcTriangleFans.cpp | 475 + .../contrib/Open3DGC/o3dgcTriangleFans.h | 291 + .../Open3DGC/o3dgcTriangleListDecoder.h | 133 + .../Open3DGC/o3dgcTriangleListDecoder.inl | 364 + .../Open3DGC/o3dgcTriangleListEncoder.h | 101 + .../Open3DGC/o3dgcTriangleListEncoder.inl | 719 + .../assimp/contrib/Open3DGC/o3dgcVector.h | 184 + .../assimp/contrib/Open3DGC/o3dgcVector.inl | 317 + .../contrib/android-cmake/AndroidNdkGdb.cmake | 96 + .../android-cmake/AndroidNdkModules.cmake | 58 + .../assimp/contrib/android-cmake/README.md | 240 + .../android-cmake/android.toolchain.cmake | 1693 ++ .../assimp/contrib/android-cmake/ndk_links.md | 211 + thirdparty/assimp/contrib/clipper/License.txt | 29 + thirdparty/assimp/contrib/clipper/clipper.cpp | 3457 ++++ thirdparty/assimp/contrib/clipper/clipper.hpp | 306 + thirdparty/assimp/contrib/gtest/CHANGES | 157 + .../assimp/contrib/gtest/CMakeLists.txt | 286 + thirdparty/assimp/contrib/gtest/CONTRIBUTORS | 37 + thirdparty/assimp/contrib/gtest/LICENSE | 28 + thirdparty/assimp/contrib/gtest/Makefile.am | 310 + thirdparty/assimp/contrib/gtest/README.md | 280 + .../assimp/contrib/gtest/build-aux/.keep | 0 .../contrib/gtest/cmake/internal_utils.cmake | 254 + .../contrib/gtest/codegear/gtest.cbproj | 138 + .../contrib/gtest/codegear/gtest.groupproj | 54 + .../contrib/gtest/codegear/gtest_all.cc | 38 + .../contrib/gtest/codegear/gtest_link.cc | 40 + .../contrib/gtest/codegear/gtest_main.cbproj | 82 + .../gtest/codegear/gtest_unittest.cbproj | 88 + thirdparty/assimp/contrib/gtest/configure.ac | 68 + .../contrib/gtest/docs/AdvancedGuide.md | 2182 ++ .../assimp/contrib/gtest/docs/DevGuide.md | 126 + .../contrib/gtest/docs/Documentation.md | 14 + thirdparty/assimp/contrib/gtest/docs/FAQ.md | 1087 + .../assimp/contrib/gtest/docs/Primer.md | 502 + .../assimp/contrib/gtest/docs/PumpManual.md | 177 + .../assimp/contrib/gtest/docs/Samples.md | 14 + .../contrib/gtest/docs/V1_5_AdvancedGuide.md | 2096 ++ .../contrib/gtest/docs/V1_5_Documentation.md | 12 + .../assimp/contrib/gtest/docs/V1_5_FAQ.md | 886 + .../assimp/contrib/gtest/docs/V1_5_Primer.md | 497 + .../contrib/gtest/docs/V1_5_PumpManual.md | 177 + .../contrib/gtest/docs/V1_5_XcodeGuide.md | 93 + .../contrib/gtest/docs/V1_6_AdvancedGuide.md | 2178 ++ .../contrib/gtest/docs/V1_6_Documentation.md | 14 + .../assimp/contrib/gtest/docs/V1_6_FAQ.md | 1038 + .../assimp/contrib/gtest/docs/V1_6_Primer.md | 501 + .../contrib/gtest/docs/V1_6_PumpManual.md | 177 + .../assimp/contrib/gtest/docs/V1_6_Samples.md | 14 + .../contrib/gtest/docs/V1_6_XcodeGuide.md | 93 + .../contrib/gtest/docs/V1_7_AdvancedGuide.md | 2181 ++ .../contrib/gtest/docs/V1_7_Documentation.md | 14 + .../assimp/contrib/gtest/docs/V1_7_FAQ.md | 1082 + .../assimp/contrib/gtest/docs/V1_7_Primer.md | 501 + .../contrib/gtest/docs/V1_7_PumpManual.md | 177 + .../assimp/contrib/gtest/docs/V1_7_Samples.md | 14 + .../contrib/gtest/docs/V1_7_XcodeGuide.md | 93 + .../assimp/contrib/gtest/docs/XcodeGuide.md | 93 + .../gtest/include/gtest/gtest-death-test.h | 294 + .../gtest/include/gtest/gtest-message.h | 250 + .../gtest/include/gtest/gtest-param-test.h | 1444 ++ .../include/gtest/gtest-param-test.h.pump | 510 + .../gtest/include/gtest/gtest-printers.h | 993 + .../contrib/gtest/include/gtest/gtest-spi.h | 232 + .../gtest/include/gtest/gtest-test-part.h | 179 + .../gtest/include/gtest/gtest-typed-test.h | 263 + .../contrib/gtest/include/gtest/gtest.h | 2236 +++ .../gtest/include/gtest/gtest_pred_impl.h | 358 + .../contrib/gtest/include/gtest/gtest_prod.h | 58 + .../gtest/internal/custom/gtest-port.h | 69 + .../gtest/internal/custom/gtest-printers.h | 42 + .../include/gtest/internal/custom/gtest.h | 41 + .../internal/gtest-death-test-internal.h | 319 + .../include/gtest/internal/gtest-filepath.h | 206 + .../include/gtest/internal/gtest-internal.h | 1238 ++ .../include/gtest/internal/gtest-linked_ptr.h | 243 + .../internal/gtest-param-util-generated.h | 5146 +++++ .../gtest-param-util-generated.h.pump | 286 + .../include/gtest/internal/gtest-param-util.h | 731 + .../include/gtest/internal/gtest-port-arch.h | 93 + .../gtest/include/gtest/internal/gtest-port.h | 2554 +++ .../include/gtest/internal/gtest-string.h | 167 + .../include/gtest/internal/gtest-tuple.h | 1020 + .../include/gtest/internal/gtest-tuple.h.pump | 347 + .../include/gtest/internal/gtest-type-util.h | 3331 ++++ .../gtest/internal/gtest-type-util.h.pump | 297 + .../assimp/contrib/gtest/m4/acx_pthread.m4 | 363 + thirdparty/assimp/contrib/gtest/m4/gtest.m4 | 74 + thirdparty/assimp/contrib/gtest/make/Makefile | 82 + .../contrib/gtest/samples/prime_tables.h | 123 + .../assimp/contrib/gtest/samples/sample1.cc | 68 + .../assimp/contrib/gtest/samples/sample1.h | 43 + .../gtest/samples/sample10_unittest.cc | 144 + .../contrib/gtest/samples/sample1_unittest.cc | 153 + .../assimp/contrib/gtest/samples/sample2.cc | 56 + .../assimp/contrib/gtest/samples/sample2.h | 85 + .../contrib/gtest/samples/sample2_unittest.cc | 109 + .../contrib/gtest/samples/sample3-inl.h | 172 + .../contrib/gtest/samples/sample3_unittest.cc | 151 + .../assimp/contrib/gtest/samples/sample4.cc | 46 + .../assimp/contrib/gtest/samples/sample4.h | 53 + .../contrib/gtest/samples/sample4_unittest.cc | 45 + .../contrib/gtest/samples/sample5_unittest.cc | 199 + .../contrib/gtest/samples/sample6_unittest.cc | 224 + .../contrib/gtest/samples/sample7_unittest.cc | 130 + .../contrib/gtest/samples/sample8_unittest.cc | 173 + .../contrib/gtest/samples/sample9_unittest.cc | 160 + .../assimp/contrib/gtest/scripts/common.py | 83 + .../contrib/gtest/scripts/fuse_gtest_files.py | 253 + .../gtest/scripts/gen_gtest_pred_impl.py | 730 + .../contrib/gtest/scripts/gtest-config.in | 274 + .../assimp/contrib/gtest/scripts/pump.py | 855 + .../contrib/gtest/scripts/release_docs.py | 158 + .../contrib/gtest/scripts/test/Makefile | 59 + .../assimp/contrib/gtest/scripts/upload.py | 1387 ++ .../contrib/gtest/scripts/upload_gtest.py | 78 + .../assimp/contrib/gtest/src/gtest-all.cc | 48 + .../contrib/gtest/src/gtest-death-test.cc | 1342 ++ .../contrib/gtest/src/gtest-filepath.cc | 387 + .../contrib/gtest/src/gtest-internal-inl.h | 1183 ++ .../assimp/contrib/gtest/src/gtest-port.cc | 1259 ++ .../contrib/gtest/src/gtest-printers.cc | 373 + .../contrib/gtest/src/gtest-test-part.cc | 110 + .../contrib/gtest/src/gtest-typed-test.cc | 118 + thirdparty/assimp/contrib/gtest/src/gtest.cc | 5388 +++++ .../assimp/contrib/gtest/src/gtest_main.cc | 38 + .../gtest/test/gtest-death-test_ex_test.cc | 93 + .../gtest/test/gtest-death-test_test.cc | 1427 ++ .../contrib/gtest/test/gtest-filepath_test.cc | 662 + .../gtest/test/gtest-linked_ptr_test.cc | 154 + .../contrib/gtest/test/gtest-listener_test.cc | 311 + .../contrib/gtest/test/gtest-message_test.cc | 159 + .../contrib/gtest/test/gtest-options_test.cc | 215 + .../gtest/test/gtest-param-test2_test.cc | 65 + .../gtest/test/gtest-param-test_test.cc | 1055 + .../gtest/test/gtest-param-test_test.h | 57 + .../contrib/gtest/test/gtest-port_test.cc | 1304 ++ .../contrib/gtest/test/gtest-printers_test.cc | 1635 ++ .../gtest/test/gtest-test-part_test.cc | 208 + .../contrib/gtest/test/gtest-tuple_test.cc | 320 + .../gtest/test/gtest-typed-test2_test.cc | 45 + .../gtest/test/gtest-typed-test_test.cc | 380 + .../gtest/test/gtest-typed-test_test.h | 66 + .../gtest/test/gtest-unittest-api_test.cc | 341 + .../contrib/gtest/test/gtest_all_test.cc | 47 + .../test/gtest_break_on_failure_unittest.py | 212 + .../test/gtest_break_on_failure_unittest_.cc | 88 + .../gtest/test/gtest_catch_exceptions_test.py | 237 + .../test/gtest_catch_exceptions_test_.cc | 311 + .../contrib/gtest/test/gtest_color_test.py | 130 + .../contrib/gtest/test/gtest_color_test_.cc | 71 + .../contrib/gtest/test/gtest_env_var_test.py | 117 + .../contrib/gtest/test/gtest_env_var_test_.cc | 126 + .../gtest/test/gtest_environment_test.cc | 192 + .../gtest/test/gtest_filter_unittest.py | 636 + .../gtest/test/gtest_filter_unittest_.cc | 140 + .../contrib/gtest/test/gtest_help_test.py | 172 + .../contrib/gtest/test/gtest_help_test_.cc | 46 + .../gtest/test/gtest_list_tests_unittest.py | 207 + .../gtest/test/gtest_list_tests_unittest_.cc | 157 + .../contrib/gtest/test/gtest_main_unittest.cc | 45 + .../gtest/test/gtest_no_test_unittest.cc | 56 + .../contrib/gtest/test/gtest_output_test.py | 340 + .../contrib/gtest/test/gtest_output_test_.cc | 1062 + .../test/gtest_output_test_golden_lin.txt | 743 + .../gtest/test/gtest_pred_impl_unittest.cc | 2427 +++ .../gtest/test/gtest_premature_exit_test.cc | 127 + .../contrib/gtest/test/gtest_prod_test.cc | 57 + .../contrib/gtest/test/gtest_repeat_test.cc | 253 + .../contrib/gtest/test/gtest_shuffle_test.py | 325 + .../contrib/gtest/test/gtest_shuffle_test_.cc | 103 + .../gtest/test/gtest_sole_header_test.cc | 57 + .../contrib/gtest/test/gtest_stress_test.cc | 256 + .../contrib/gtest/test/gtest_test_utils.py | 320 + .../test/gtest_throw_on_failure_ex_test.cc | 92 + .../gtest/test/gtest_throw_on_failure_test.py | 171 + .../test/gtest_throw_on_failure_test_.cc | 72 + .../gtest/test/gtest_uninitialized_test.py | 70 + .../gtest/test/gtest_uninitialized_test_.cc | 43 + .../contrib/gtest/test/gtest_unittest.cc | 7706 ++++++++ .../gtest/test/gtest_xml_outfile1_test_.cc | 49 + .../gtest/test/gtest_xml_outfile2_test_.cc | 49 + .../gtest/test/gtest_xml_outfiles_test.py | 132 + .../gtest/test/gtest_xml_output_unittest.py | 308 + .../gtest/test/gtest_xml_output_unittest_.cc | 181 + .../gtest/test/gtest_xml_test_utils.py | 194 + .../assimp/contrib/gtest/test/production.cc | 36 + .../assimp/contrib/gtest/test/production.h | 55 + .../gtest/xcode/Config/DebugProject.xcconfig | 30 + .../xcode/Config/FrameworkTarget.xcconfig | 17 + .../gtest/xcode/Config/General.xcconfig | 41 + .../xcode/Config/ReleaseProject.xcconfig | 32 + .../xcode/Config/StaticLibraryTarget.xcconfig | 18 + .../gtest/xcode/Config/TestTarget.xcconfig | 8 + .../contrib/gtest/xcode/Resources/Info.plist | 30 + .../xcode/Samples/FrameworkSample/Info.plist | 28 + .../WidgetFramework.xcodeproj/project.pbxproj | 457 + .../xcode/Samples/FrameworkSample/runtests.sh | 62 + .../xcode/Samples/FrameworkSample/widget.cc | 63 + .../xcode/Samples/FrameworkSample/widget.h | 59 + .../Samples/FrameworkSample/widget_test.cc | 68 + .../contrib/gtest/xcode/Scripts/runtests.sh | 65 + .../gtest/xcode/Scripts/versiongenerate.py | 100 + .../xcode/gtest.xcodeproj/project.pbxproj | 1135 ++ .../assimp/contrib/irrXML/CMakeLists.txt | 29 + .../assimp/contrib/irrXML/CXMLReaderImpl.h | 813 + thirdparty/assimp/contrib/irrXML/heapsort.h | 73 + thirdparty/assimp/contrib/irrXML/irrArray.h | 443 + thirdparty/assimp/contrib/irrXML/irrString.h | 664 + thirdparty/assimp/contrib/irrXML/irrTypes.h | 108 + thirdparty/assimp/contrib/irrXML/irrXML.cpp | 151 + thirdparty/assimp/contrib/irrXML/irrXML.h | 546 + thirdparty/assimp/contrib/irrXML_note.txt | 6 + .../contrib/openddlparser/CMakeLists.txt | 170 + .../assimp/contrib/openddlparser/CREDITS | 19 + .../assimp/contrib/openddlparser/LICENSE | 22 + .../assimp/contrib/openddlparser/README.md | 136 + .../contrib/openddlparser/code/DDLNode.cpp | 217 + .../openddlparser/code/OpenDDLCommon.cpp | 212 + .../openddlparser/code/OpenDDLExport.cpp | 384 + .../openddlparser/code/OpenDDLParser.cpp | 1031 + .../openddlparser/code/OpenDDLStream.cpp | 96 + .../contrib/openddlparser/code/Value.cpp | 439 + .../include/openddlparser/DDLNode.h | 173 + .../include/openddlparser/OpenDDLCommon.h | 246 + .../include/openddlparser/OpenDDLExport.h | 80 + .../include/openddlparser/OpenDDLParser.h | 201 + .../openddlparser/OpenDDLParserUtils.h | 260 + .../include/openddlparser/OpenDDLStream.h | 89 + .../include/openddlparser/Value.h | 273 + thirdparty/assimp/contrib/poly2tri/AUTHORS | 8 + thirdparty/assimp/contrib/poly2tri/LICENSE | 27 + thirdparty/assimp/contrib/poly2tri/README | 51 + .../poly2tri/poly2tri/common/shapes.cc | 365 + .../contrib/poly2tri/poly2tri/common/shapes.h | 327 + .../contrib/poly2tri/poly2tri/common/utils.h | 127 + .../contrib/poly2tri/poly2tri/poly2tri.h | 38 + .../poly2tri/sweep/advancing_front.cc | 108 + .../poly2tri/poly2tri/sweep/advancing_front.h | 118 + .../contrib/poly2tri/poly2tri/sweep/cdt.cc | 71 + .../contrib/poly2tri/poly2tri/sweep/cdt.h | 105 + .../contrib/poly2tri/poly2tri/sweep/sweep.cc | 799 + .../contrib/poly2tri/poly2tri/sweep/sweep.h | 285 + .../poly2tri/poly2tri/sweep/sweep_context.cc | 211 + .../poly2tri/poly2tri/sweep/sweep_context.h | 186 + thirdparty/assimp/contrib/poly2tri_patch.txt | 75 + .../rapidjson/include/rapidjson/allocators.h | 271 + .../rapidjson/include/rapidjson/document.h | 2613 +++ .../include/rapidjson/encodedstream.h | 299 + .../rapidjson/include/rapidjson/encodings.h | 716 + .../rapidjson/include/rapidjson/error/en.h | 74 + .../rapidjson/include/rapidjson/error/error.h | 161 + .../include/rapidjson/filereadstream.h | 99 + .../include/rapidjson/filewritestream.h | 104 + .../contrib/rapidjson/include/rapidjson/fwd.h | 151 + .../include/rapidjson/internal/biginteger.h | 290 + .../include/rapidjson/internal/diyfp.h | 258 + .../include/rapidjson/internal/dtoa.h | 245 + .../include/rapidjson/internal/ieee754.h | 78 + .../include/rapidjson/internal/itoa.h | 304 + .../include/rapidjson/internal/meta.h | 181 + .../include/rapidjson/internal/pow10.h | 55 + .../include/rapidjson/internal/regex.h | 734 + .../include/rapidjson/internal/stack.h | 231 + .../include/rapidjson/internal/strfunc.h | 69 + .../include/rapidjson/internal/strtod.h | 269 + .../include/rapidjson/internal/swap.h | 46 + .../include/rapidjson/istreamwrapper.h | 115 + .../include/rapidjson/memorybuffer.h | 70 + .../include/rapidjson/memorystream.h | 71 + .../include/rapidjson/msinttypes/inttypes.h | 316 + .../include/rapidjson/msinttypes/stdint.h | 300 + .../include/rapidjson/ostreamwrapper.h | 81 + .../rapidjson/include/rapidjson/pointer.h | 1358 ++ .../include/rapidjson/prettywriter.h | 277 + .../rapidjson/include/rapidjson/rapidjson.h | 628 + .../rapidjson/include/rapidjson/reader.h | 2221 +++ .../rapidjson/include/rapidjson/schema.h | 2016 ++ .../rapidjson/include/rapidjson/stream.h | 179 + .../include/rapidjson/stringbuffer.h | 121 + .../rapidjson/include/rapidjson/writer.h | 711 + .../assimp/contrib/rapidjson/license.txt | 57 + thirdparty/assimp/contrib/rapidjson/readme.md | 160 + .../assimp/contrib/stb_image/stb_image.h | 7462 +++++++ thirdparty/assimp/contrib/unzip/crypt.h | 132 + thirdparty/assimp/contrib/unzip/ioapi.c | 177 + thirdparty/assimp/contrib/unzip/ioapi.h | 75 + thirdparty/assimp/contrib/unzip/unzip.c | 1615 ++ thirdparty/assimp/contrib/unzip/unzip.h | 354 + .../assimp/contrib/utf8cpp/doc/ReleaseNotes | 12 + .../assimp/contrib/utf8cpp/doc/utf8cpp.html | 1789 ++ .../assimp/contrib/utf8cpp/source/utf8.h | 34 + .../contrib/utf8cpp/source/utf8/checked.h | 327 + .../assimp/contrib/utf8cpp/source/utf8/core.h | 329 + .../contrib/utf8cpp/source/utf8/unchecked.h | 228 + thirdparty/assimp/contrib/zip/.travis.sh | 18 + thirdparty/assimp/contrib/zip/.travis.yml | 22 + thirdparty/assimp/contrib/zip/CMakeLists.txt | 47 + thirdparty/assimp/contrib/zip/README.md | 309 + thirdparty/assimp/contrib/zip/UNLICENSE | 26 + .../assimp/contrib/zip/cmake/asan-wrapper | 55 + .../zip/cmake/cmake_uninstall.cmake.in | 23 + thirdparty/assimp/contrib/zip/src/miniz.h | 6511 ++++++ thirdparty/assimp/contrib/zip/src/zip.c | 926 + thirdparty/assimp/contrib/zip/src/zip.h | 316 + .../assimp/contrib/zip/test/CMakeLists.txt | 19 + thirdparty/assimp/contrib/zip/test/test.c | 459 + .../assimp/contrib/zip/test/test_miniz.c | 104 + thirdparty/assimp/contrib/zip/zip.png | Bin 0 -> 5275 bytes thirdparty/assimp/contrib/zlib/CMakeLists.txt | 207 + thirdparty/assimp/contrib/zlib/README | 115 + thirdparty/assimp/contrib/zlib/adler32.c | 186 + thirdparty/assimp/contrib/zlib/compress.c | 86 + .../contrib/zlib/contrib/README.contrib | 78 + .../contrib/zlib/contrib/ada/buffer_demo.adb | 106 + .../assimp/contrib/zlib/contrib/ada/mtest.adb | 156 + .../assimp/contrib/zlib/contrib/ada/read.adb | 156 + .../contrib/zlib/contrib/ada/readme.txt | 65 + .../assimp/contrib/zlib/contrib/ada/test.adb | 463 + .../contrib/zlib/contrib/ada/zlib-streams.adb | 225 + .../contrib/zlib/contrib/ada/zlib-streams.ads | 114 + .../contrib/zlib/contrib/ada/zlib-thin.adb | 141 + .../contrib/zlib/contrib/ada/zlib-thin.ads | 450 + .../assimp/contrib/zlib/contrib/ada/zlib.adb | 701 + .../assimp/contrib/zlib/contrib/ada/zlib.ads | 328 + .../assimp/contrib/zlib/contrib/ada/zlib.gpr | 20 + .../contrib/zlib/contrib/amd64/amd64-match.S | 452 + .../contrib/zlib/contrib/asm686/README.686 | 51 + .../contrib/zlib/contrib/asm686/match.S | 357 + .../assimp/contrib/zlib/contrib/blast/README | 4 + .../assimp/contrib/zlib/contrib/blast/blast.c | 466 + .../assimp/contrib/zlib/contrib/blast/blast.h | 83 + .../assimp/contrib/zlib/contrib/blast/test.pk | Bin 0 -> 8 bytes .../contrib/zlib/contrib/blast/test.txt | 1 + .../contrib/zlib/contrib/delphi/ZLib.pas | 557 + .../contrib/zlib/contrib/delphi/ZLibConst.pas | 11 + .../contrib/zlib/contrib/delphi/readme.txt | 76 + .../contrib/zlib/contrib/delphi/zlibd32.mak | 99 + .../zlib/contrib/dotzlib/DotZLib.build | 33 + .../contrib/zlib/contrib/dotzlib/DotZLib.chm | Bin 0 -> 72726 bytes .../contrib/dotzlib/DotZLib/AssemblyInfo.cs | 58 + .../contrib/dotzlib/DotZLib/ChecksumImpl.cs | 202 + .../contrib/dotzlib/DotZLib/CircularBuffer.cs | 83 + .../zlib/contrib/dotzlib/DotZLib/CodecBase.cs | 198 + .../zlib/contrib/dotzlib/DotZLib/Deflater.cs | 106 + .../zlib/contrib/dotzlib/DotZLib/DotZLib.cs | 288 + .../contrib/dotzlib/DotZLib/DotZLib.csproj | 141 + .../contrib/dotzlib/DotZLib/GZipStream.cs | 301 + .../zlib/contrib/dotzlib/DotZLib/Inflater.cs | 105 + .../zlib/contrib/dotzlib/DotZLib/UnitTests.cs | 274 + .../zlib/contrib/dotzlib/LICENSE_1_0.txt | 23 + .../contrib/zlib/contrib/dotzlib/readme.txt | 58 + .../zlib/contrib/gcc_gvmat64/gvmat64.S | 574 + .../contrib/zlib/contrib/infback9/README | 1 + .../contrib/zlib/contrib/infback9/infback9.c | 615 + .../contrib/zlib/contrib/infback9/infback9.h | 37 + .../contrib/zlib/contrib/infback9/inffix9.h | 107 + .../contrib/zlib/contrib/infback9/inflate9.h | 47 + .../contrib/zlib/contrib/infback9/inftree9.c | 324 + .../contrib/zlib/contrib/infback9/inftree9.h | 61 + .../contrib/zlib/contrib/inflate86/inffas86.c | 1157 ++ .../contrib/zlib/contrib/inflate86/inffast.S | 1368 ++ .../contrib/zlib/contrib/iostream/test.cpp | 24 + .../zlib/contrib/iostream/zfstream.cpp | 329 + .../contrib/zlib/contrib/iostream/zfstream.h | 128 + .../contrib/zlib/contrib/iostream2/zstream.h | 307 + .../zlib/contrib/iostream2/zstream_test.cpp | 25 + .../contrib/zlib/contrib/iostream3/README | 35 + .../contrib/zlib/contrib/iostream3/TODO | 17 + .../contrib/zlib/contrib/iostream3/test.cc | 50 + .../zlib/contrib/iostream3/zfstream.cc | 479 + .../contrib/zlib/contrib/iostream3/zfstream.h | 466 + .../contrib/zlib/contrib/masmx64/bld_ml64.bat | 2 + .../contrib/zlib/contrib/masmx64/gvmat64.asm | 553 + .../contrib/zlib/contrib/masmx64/inffas8664.c | 186 + .../zlib/contrib/masmx64/inffasx64.asm | 396 + .../contrib/zlib/contrib/masmx64/readme.txt | 31 + .../contrib/zlib/contrib/masmx86/bld_ml32.bat | 2 + .../contrib/zlib/contrib/masmx86/inffas32.asm | 1080 + .../contrib/zlib/contrib/masmx86/match686.asm | 479 + .../contrib/zlib/contrib/masmx86/readme.txt | 27 + .../contrib/zlib/contrib/minizip/Makefile.am | 45 + .../contrib/minizip/MiniZip64_Changes.txt | 6 + .../zlib/contrib/minizip/MiniZip64_info.txt | 74 + .../contrib/zlib/contrib/minizip/configure.ac | 32 + .../contrib/zlib/contrib/minizip/crypt.h | 131 + .../contrib/zlib/contrib/minizip/ioapi.c | 247 + .../contrib/zlib/contrib/minizip/ioapi.h | 208 + .../contrib/zlib/contrib/minizip/iowin32.c | 462 + .../contrib/zlib/contrib/minizip/iowin32.h | 28 + .../contrib/zlib/contrib/minizip/make_vms.com | 25 + .../contrib/zlib/contrib/minizip/miniunz.c | 660 + .../contrib/zlib/contrib/minizip/miniunzip.1 | 63 + .../contrib/zlib/contrib/minizip/minizip.1 | 46 + .../contrib/zlib/contrib/minizip/minizip.c | 520 + .../zlib/contrib/minizip/minizip.pc.in | 12 + .../contrib/zlib/contrib/minizip/mztools.c | 291 + .../contrib/zlib/contrib/minizip/mztools.h | 37 + .../contrib/zlib/contrib/minizip/unzip.c | 2125 ++ .../contrib/zlib/contrib/minizip/unzip.h | 437 + .../assimp/contrib/zlib/contrib/minizip/zip.c | 2007 ++ .../assimp/contrib/zlib/contrib/minizip/zip.h | 362 + .../contrib/zlib/contrib/pascal/example.pas | 599 + .../contrib/zlib/contrib/pascal/readme.txt | 76 + .../contrib/zlib/contrib/pascal/zlibd32.mak | 99 + .../contrib/zlib/contrib/pascal/zlibpas.pas | 276 + .../assimp/contrib/zlib/contrib/puff/README | 63 + .../assimp/contrib/zlib/contrib/puff/puff.c | 840 + .../assimp/contrib/zlib/contrib/puff/puff.h | 35 + .../contrib/zlib/contrib/puff/pufftest.c | 165 + .../contrib/zlib/contrib/puff/zeros.raw | Bin 0 -> 2517 bytes .../contrib/zlib/contrib/testzlib/testzlib.c | 275 + .../zlib/contrib/testzlib/testzlib.txt | 10 + .../contrib/zlib/contrib/untgz/Makefile.msc | 17 + .../assimp/contrib/zlib/contrib/untgz/untgz.c | 674 + .../contrib/zlib/contrib/vstudio/readme.txt | 78 + .../contrib/zlib/contrib/vstudio/vc10/zlib.rc | 32 + .../zlib/contrib/vstudio/vc10/zlibvc.def | 153 + .../contrib/zlib/contrib/vstudio/vc11/zlib.rc | 32 + .../zlib/contrib/vstudio/vc11/zlibvc.def | 153 + .../contrib/zlib/contrib/vstudio/vc12/zlib.rc | 32 + .../zlib/contrib/vstudio/vc12/zlibvc.def | 153 + .../contrib/zlib/contrib/vstudio/vc14/zlib.rc | 32 + .../zlib/contrib/vstudio/vc14/zlibvc.def | 153 + .../contrib/zlib/contrib/vstudio/vc9/zlib.rc | 32 + .../zlib/contrib/vstudio/vc9/zlibvc.def | 153 + thirdparty/assimp/contrib/zlib/crc32.c | 442 + thirdparty/assimp/contrib/zlib/crc32.h | 441 + thirdparty/assimp/contrib/zlib/deflate.c | 2174 ++ thirdparty/assimp/contrib/zlib/deflate.h | 349 + thirdparty/assimp/contrib/zlib/gzclose.c | 25 + thirdparty/assimp/contrib/zlib/gzguts.h | 218 + thirdparty/assimp/contrib/zlib/gzlib.c | 637 + thirdparty/assimp/contrib/zlib/gzread.c | 652 + thirdparty/assimp/contrib/zlib/gzwrite.c | 668 + thirdparty/assimp/contrib/zlib/infback.c | 640 + thirdparty/assimp/contrib/zlib/inffast.c | 323 + thirdparty/assimp/contrib/zlib/inffast.h | 11 + thirdparty/assimp/contrib/zlib/inffixed.h | 94 + thirdparty/assimp/contrib/zlib/inflate.c | 1563 ++ thirdparty/assimp/contrib/zlib/inflate.h | 125 + thirdparty/assimp/contrib/zlib/inftrees.c | 304 + thirdparty/assimp/contrib/zlib/inftrees.h | 62 + thirdparty/assimp/contrib/zlib/trees.c | 1203 ++ thirdparty/assimp/contrib/zlib/trees.h | 128 + thirdparty/assimp/contrib/zlib/uncompr.c | 93 + .../assimp/contrib/zlib/win32/DLL_FAQ.txt | 397 + .../assimp/contrib/zlib/win32/Makefile.bor | 110 + .../assimp/contrib/zlib/win32/Makefile.gcc | 182 + .../assimp/contrib/zlib/win32/Makefile.msc | 163 + .../contrib/zlib/win32/README-WIN32.txt | 103 + .../assimp/contrib/zlib/win32/VisualC.txt | 3 + thirdparty/assimp/contrib/zlib/win32/zlib.def | 94 + thirdparty/assimp/contrib/zlib/win32/zlib1.rc | 40 + .../assimp/contrib/zlib/zconf.h.cmakein | 536 + thirdparty/assimp/contrib/zlib/zconf.h.in | 534 + .../assimp/contrib/zlib/zconf.h.included | 536 + thirdparty/assimp/contrib/zlib/zlib.h | 1917 ++ .../assimp/contrib/zlib/zlib.pc.cmakein | 13 + thirdparty/assimp/contrib/zlib/zutil.c | 325 + thirdparty/assimp/contrib/zlib/zutil.h | 263 + thirdparty/assimp/contrib/zlib_note.txt | 11 + .../doc/AssimpCmdDoc_Html/AssimpCmdDoc.chm | Bin 0 -> 128901 bytes .../doc/AssimpCmdDoc_Html/dragonsplash.png | Bin 0 -> 49960 bytes .../doc/AssimpDoc_Html/AnimationOverview.png | Bin 0 -> 52316 bytes .../doc/AssimpDoc_Html/AnimationOverview.svg | 645 + .../assimp/doc/AssimpDoc_Html/AssimpDoc.chm | Bin 0 -> 1104707 bytes .../doc/AssimpDoc_Html/dragonsplash.png | Bin 0 -> 57427 bytes thirdparty/assimp/doc/CMakeLists.txt | 42 + thirdparty/assimp/doc/Doxyfile.in | 1876 ++ thirdparty/assimp/doc/Doxyfile_Cmd | 2368 +++ thirdparty/assimp/doc/Preamble.txt | 40 + .../Assimp_Arch_Import.class.violet.html | 354 + .../Assimp_Arch_export.class.violet.html | 648 + .../doc/architecture/assimp-process.png | Bin 0 -> 5341 bytes .../architecture/assimp.object.violet.html | 107 + .../assimp_usecase.ucase.violet.html | 875 + .../assimp/doc/architecture/exporter.png | Bin 0 -> 25291 bytes .../architecture/process.class.violet.html | 337 + .../assimp/doc/architecture/usecases.png | Bin 0 -> 24282 bytes thirdparty/assimp/doc/datastructure.xml | 127 + thirdparty/assimp/doc/dox.h | 1647 ++ thirdparty/assimp/doc/dox_cmd.h | 565 + .../assimp/include/assimp/.editorconfig | 8 + .../assimp/include/assimp/BaseImporter.h | 418 + thirdparty/assimp/include/assimp/Bitmap.h | 125 + .../assimp/include/assimp/BlobIOSystem.h | 338 + .../assimp/include/assimp/ByteSwapper.h | 287 + .../assimp/include/assimp/Compiler/poppack1.h | 22 + .../assimp/include/assimp/Compiler/pstdint.h | 912 + .../include/assimp/Compiler/pushpack1.h | 43 + .../assimp/include/assimp/CreateAnimMesh.h | 58 + .../assimp/include/assimp/DefaultIOStream.h | 140 + .../assimp/include/assimp/DefaultIOSystem.h | 93 + .../assimp/include/assimp/DefaultLogger.hpp | 188 + thirdparty/assimp/include/assimp/Defines.h | 49 + .../assimp/include/assimp/Exceptional.h | 125 + thirdparty/assimp/include/assimp/Exporter.hpp | 505 + .../assimp/include/assimp/GenericProperty.h | 133 + thirdparty/assimp/include/assimp/Hash.h | 118 + thirdparty/assimp/include/assimp/IOStream.hpp | 142 + .../assimp/include/assimp/IOStreamBuffer.h | 355 + thirdparty/assimp/include/assimp/IOSystem.hpp | 357 + thirdparty/assimp/include/assimp/Importer.hpp | 659 + .../assimp/include/assimp/LineSplitter.h | 285 + thirdparty/assimp/include/assimp/LogAux.h | 131 + .../assimp/include/assimp/LogStream.hpp | 111 + thirdparty/assimp/include/assimp/Logger.hpp | 305 + thirdparty/assimp/include/assimp/Macros.h | 49 + .../assimp/include/assimp/MathFunctions.h | 86 + .../assimp/include/assimp/MemoryIOWrapper.h | 244 + .../assimp/include/assimp/NullLogger.hpp | 99 + .../assimp/include/assimp/ParsingUtils.h | 259 + thirdparty/assimp/include/assimp/Profiler.h | 99 + .../assimp/include/assimp/ProgressHandler.hpp | 145 + .../assimp/include/assimp/RemoveComments.h | 91 + .../assimp/include/assimp/SGSpatialSort.h | 150 + .../assimp/include/assimp/SceneCombiner.h | 403 + .../include/assimp/SkeletonMeshBuilder.h | 125 + .../assimp/include/assimp/SmoothingGroups.h | 108 + .../assimp/include/assimp/SmoothingGroups.inl | 138 + .../assimp/include/assimp/SpatialSort.h | 174 + .../assimp/include/assimp/StandardShapes.h | 200 + .../assimp/include/assimp/StreamReader.h | 343 + .../assimp/include/assimp/StreamWriter.h | 303 + .../assimp/include/assimp/StringComparison.h | 233 + .../assimp/include/assimp/StringUtils.h | 143 + .../assimp/include/assimp/Subdivision.h | 131 + .../assimp/include/assimp/TinyFormatter.h | 166 + thirdparty/assimp/include/assimp/Vertex.h | 348 + thirdparty/assimp/include/assimp/XMLTools.h | 83 + .../include/assimp/ZipArchiveIOSystem.h | 87 + thirdparty/assimp/include/assimp/aabb.h | 76 + thirdparty/assimp/include/assimp/ai_assert.h | 57 + thirdparty/assimp/include/assimp/anim.h | 577 + thirdparty/assimp/include/assimp/camera.h | 221 + thirdparty/assimp/include/assimp/cexport.h | 261 + thirdparty/assimp/include/assimp/cfileio.h | 138 + thirdparty/assimp/include/assimp/cimport.h | 565 + thirdparty/assimp/include/assimp/color4.h | 104 + thirdparty/assimp/include/assimp/color4.inl | 209 + thirdparty/assimp/include/assimp/config.h.in | 1018 + thirdparty/assimp/include/assimp/defs.h | 310 + thirdparty/assimp/include/assimp/fast_atof.h | 373 + .../assimp/include/assimp/importerdesc.h | 146 + .../assimp/include/assimp/irrXMLWrapper.h | 149 + thirdparty/assimp/include/assimp/light.h | 259 + thirdparty/assimp/include/assimp/material.h | 1600 ++ thirdparty/assimp/include/assimp/material.inl | 390 + thirdparty/assimp/include/assimp/matrix3x3.h | 183 + .../assimp/include/assimp/matrix3x3.inl | 357 + thirdparty/assimp/include/assimp/matrix4x4.h | 280 + .../assimp/include/assimp/matrix4x4.inl | 685 + thirdparty/assimp/include/assimp/mesh.h | 859 + thirdparty/assimp/include/assimp/metadata.h | 380 + .../assimp/include/assimp/pbrmaterial.h | 77 + .../port/AndroidJNI/AndroidJNIIOSystem.h | 92 + .../assimp/include/assimp/postprocess.h | 684 + thirdparty/assimp/include/assimp/qnan.h | 165 + thirdparty/assimp/include/assimp/quaternion.h | 130 + .../assimp/include/assimp/quaternion.inl | 286 + thirdparty/assimp/include/assimp/scene.h | 425 + thirdparty/assimp/include/assimp/texture.h | 227 + thirdparty/assimp/include/assimp/types.h | 541 + thirdparty/assimp/include/assimp/vector2.h | 107 + thirdparty/assimp/include/assimp/vector2.inl | 244 + thirdparty/assimp/include/assimp/vector3.h | 146 + thirdparty/assimp/include/assimp/vector3.inl | 309 + thirdparty/assimp/include/assimp/version.h | 115 + .../packaging/windows-innosetup/LICENSE.rtf | Bin 0 -> 2260 bytes .../assimp/packaging/windows-innosetup/WEB | 8 + .../windows-innosetup/howto-build-setup.txt | 16 + .../windows-innosetup/readme_installer.txt | 23 + .../readme_installer_vieweronly.txt | 32 + .../windows-innosetup/script_vieweronly.iss | 60 + .../windows-innosetup/script_x64.iss | 74 + .../windows-innosetup/script_x86.iss | 75 + .../packaging/windows-mkzip/bin_readme.txt | 29 + .../packaging/windows-mkzip/mkfinal.bat | 169 + .../assimp/packaging/windows-mkzip/mkrev.bat | 27 + .../port/AndroidJNI/AndroidJNIIOSystem.cpp | 207 + .../assimp/port/AndroidJNI/CMakeLists.txt | 6 + thirdparty/assimp/port/AndroidJNI/README.md | 27 + .../assimp/port/AssimpDelphi/Readme.txt | 6 + .../assimp/port/AssimpDelphi/aiColor4D.pas | 17 + .../assimp/port/AssimpDelphi/aiMaterial.pas | 153 + .../assimp/port/AssimpDelphi/aiMatrix3x3.pas | 16 + .../assimp/port/AssimpDelphi/aiMatrix4x4.pas | 16 + .../assimp/port/AssimpDelphi/aiMesh.pas | 71 + .../assimp/port/AssimpDelphi/aiQuaternion.pas | 12 + .../assimp/port/AssimpDelphi/aiScene.pas | 46 + .../assimp/port/AssimpDelphi/aiTexture.pas | 26 + .../assimp/port/AssimpDelphi/aiTypes.pas | 53 + .../assimp/port/AssimpDelphi/aiVector2D.pas | 13 + .../assimp/port/AssimpDelphi/aiVector3D.pas | 16 + .../assimp/port/AssimpDelphi/assimp.pas | 58 + thirdparty/assimp/port/AssimpNET/Readme.md | 1 + thirdparty/assimp/port/AssimpPascal/Readme.md | 1 + .../port/PyAssimp/3d_viewer_screenshot.png | Bin 0 -> 51610 bytes thirdparty/assimp/port/PyAssimp/README.md | 91 + thirdparty/assimp/port/PyAssimp/README.rst | 96 + .../assimp/port/PyAssimp/gen/materialgen.py | 96 + .../assimp/port/PyAssimp/gen/structsgen.py | 290 + .../assimp/port/PyAssimp/pyassimp/__init__.py | 1 + .../assimp/port/PyAssimp/pyassimp/core.py | 546 + .../assimp/port/PyAssimp/pyassimp/errors.py | 11 + .../assimp/port/PyAssimp/pyassimp/formats.py | 41 + .../assimp/port/PyAssimp/pyassimp/helper.py | 281 + .../assimp/port/PyAssimp/pyassimp/material.py | 89 + .../port/PyAssimp/pyassimp/postprocess.py | 530 + .../assimp/port/PyAssimp/pyassimp/structs.py | 1132 ++ .../assimp/port/PyAssimp/scripts/3d_viewer.py | 1318 ++ .../port/PyAssimp/scripts/3d_viewer_py3.py | 1316 ++ .../assimp/port/PyAssimp/scripts/README.md | 13 + .../scripts/fixed_pipeline_3d_viewer.py | 372 + .../assimp/port/PyAssimp/scripts/quicktest.py | 53 + .../assimp/port/PyAssimp/scripts/sample.py | 89 + .../port/PyAssimp/scripts/transformations.py | 1705 ++ thirdparty/assimp/port/PyAssimp/setup.py | 26 + thirdparty/assimp/port/dAssimp/README | 13 + .../assimp/port/dAssimp/assimp/animation.d | 240 + thirdparty/assimp/port/dAssimp/assimp/api.d | 686 + .../assimp/port/dAssimp/assimp/assimp.d | 63 + .../assimp/port/dAssimp/assimp/camera.d | 182 + .../assimp/port/dAssimp/assimp/config.d | 705 + .../assimp/port/dAssimp/assimp/fileIO.d | 140 + thirdparty/assimp/port/dAssimp/assimp/light.d | 215 + .../assimp/port/dAssimp/assimp/loader.d | 193 + .../assimp/port/dAssimp/assimp/material.d | 641 + thirdparty/assimp/port/dAssimp/assimp/math.d | 155 + thirdparty/assimp/port/dAssimp/assimp/mesh.d | 465 + .../assimp/port/dAssimp/assimp/postprocess.d | 597 + thirdparty/assimp/port/dAssimp/assimp/scene.d | 306 + .../assimp/port/dAssimp/assimp/texture.d | 122 + thirdparty/assimp/port/dAssimp/assimp/types.d | 249 + .../assimp/port/dAssimp/assimp/versionInfo.d | 72 + .../port/iOS/IPHONEOS_ARM64E_TOOLCHAIN.cmake | 17 + .../port/iOS/IPHONEOS_ARM64_TOOLCHAIN.cmake | 17 + .../port/iOS/IPHONEOS_ARMV6_TOOLCHAIN.cmake | 17 + .../port/iOS/IPHONEOS_ARMV7S_TOOLCHAIN.cmake | 17 + .../port/iOS/IPHONEOS_ARMV7_TOOLCHAIN.cmake | 17 + .../port/iOS/IPHONEOS_I386_TOOLCHAIN.cmake | 17 + .../port/iOS/IPHONEOS_X86_64_TOOLCHAIN.cmake | 17 + thirdparty/assimp/port/iOS/README.md | 39 + thirdparty/assimp/port/iOS/build.sh | 205 + thirdparty/assimp/port/jassimp/README | 56 + thirdparty/assimp/port/jassimp/build.xml | 54 + .../port/jassimp/jassimp-native/Android.mk | 13 + .../jassimp/jassimp-native/src/jassimp.cpp | 1995 ++ .../port/jassimp/jassimp-native/src/jassimp.h | 47 + .../jassimp/src/jassimp/AiAnimBehavior.java | 112 + .../jassimp/src/jassimp/AiAnimation.java | 175 + .../jassimp/src/jassimp/AiBlendMode.java | 117 + .../jassimp/jassimp/src/jassimp/AiBone.java | 136 + .../jassimp/src/jassimp/AiBoneWeight.java | 88 + .../src/jassimp/AiBuiltInWrapperProvider.java | 84 + .../jassimp/jassimp/src/jassimp/AiCamera.java | 303 + .../src/jassimp/AiClassLoaderIOSystem.java | 153 + .../jassimp/jassimp/src/jassimp/AiColor.java | 160 + .../jassimp/jassimp/src/jassimp/AiConfig.java | 15 + .../jassimp/src/jassimp/AiConfigOptions.java | 663 + .../jassimp/src/jassimp/AiIOStream.java | 80 + .../jassimp/src/jassimp/AiIOSystem.java | 79 + .../src/jassimp/AiInputStreamIOStream.java | 127 + .../jassimp/jassimp/src/jassimp/AiLight.java | 387 + .../jassimp/src/jassimp/AiLightType.java | 123 + .../jassimp/src/jassimp/AiMaterial.java | 1199 ++ .../jassimp/src/jassimp/AiMatrix4f.java | 133 + .../jassimp/jassimp/src/jassimp/AiMesh.java | 1421 ++ .../jassimp/src/jassimp/AiMeshAnim.java | 49 + .../jassimp/src/jassimp/AiMetadataEntry.java | 118 + .../jassimp/jassimp/src/jassimp/AiNode.java | 246 + .../jassimp/src/jassimp/AiNodeAnim.java | 501 + .../src/jassimp/AiPostProcessSteps.java | 571 + .../jassimp/src/jassimp/AiPrimitiveType.java | 113 + .../src/jassimp/AiProgressHandler.java | 46 + .../jassimp/src/jassimp/AiQuaternion.java | 165 + .../jassimp/jassimp/src/jassimp/AiScene.java | 251 + .../jassimp/src/jassimp/AiSceneFlag.java | 151 + .../jassimp/src/jassimp/AiShadingMode.java | 168 + .../jassimp/src/jassimp/AiTextureInfo.java | 224 + .../jassimp/src/jassimp/AiTextureMapMode.java | 113 + .../jassimp/src/jassimp/AiTextureMapping.java | 78 + .../jassimp/src/jassimp/AiTextureOp.java | 137 + .../jassimp/src/jassimp/AiTextureType.java | 212 + .../jassimp/jassimp/src/jassimp/AiVector.java | 195 + .../src/jassimp/AiWrapperProvider.java | 149 + .../jassimp/jassimp/src/jassimp/JaiDebug.java | 209 + .../jassimp/jassimp/src/jassimp/Jassimp.java | 402 + .../jassimp/src/jassimp/JassimpConfig.java | 66 + .../src/jassimp/JassimpLibraryLoader.java | 65 + .../jassimp/src/jassimp/package-info.java | 45 + thirdparty/assimp/port/swig/DONOTUSEYET | 1 + thirdparty/assimp/port/swig/assimp.i | 140 + thirdparty/assimp/port/swig/d/build.sh | 2 + thirdparty/assimp/port/swig/d/generate.sh | 4 + .../port/swig/interface/DefaultLogger.i | 5 + .../assimp/port/swig/interface/IOStream.i | 5 + .../assimp/port/swig/interface/IOSystem.i | 11 + .../assimp/port/swig/interface/LogStream.i | 5 + .../assimp/port/swig/interface/Logger.i | 5 + .../assimp/port/swig/interface/NullLogger.i | 5 + .../assimp/port/swig/interface/aiAnim.i | 8 + .../assimp/port/swig/interface/aiAssert.i | 5 + .../assimp/port/swig/interface/aiCamera.i | 5 + .../assimp/port/swig/interface/aiColor4D.i | 5 + .../assimp/port/swig/interface/aiConfig.i | 5 + .../assimp/port/swig/interface/aiDefines.i | 5 + .../assimp/port/swig/interface/aiFileIO.i | 5 + .../assimp/port/swig/interface/aiLight.i | 5 + .../assimp/port/swig/interface/aiMaterial.i | 33 + .../assimp/port/swig/interface/aiMatrix3x3.i | 5 + .../assimp/port/swig/interface/aiMatrix4x4.i | 5 + .../assimp/port/swig/interface/aiMesh.i | 29 + .../port/swig/interface/aiPostProcess.i | 7 + .../assimp/port/swig/interface/aiQuaternion.i | 5 + .../assimp/port/swig/interface/aiScene.i | 17 + .../assimp/port/swig/interface/aiTexture.i | 5 + .../assimp/port/swig/interface/aiTypes.i | 8 + .../assimp/port/swig/interface/aiVector2D.i | 5 + .../assimp/port/swig/interface/aiVector3D.i | 5 + .../assimp/port/swig/interface/aiVersion.i | 5 + .../assimp/port/swig/interface/assimp.i | 45 + thirdparty/assimp/revision.h.in | 28 + .../assimp/scripts/AppVeyor/cacheglobs.txt | 4 + .../assimp/scripts/AppVeyor/mtime_cache | 177 + .../BlenderImporter/BlenderScene.cpp.template | 56 + .../BlenderSceneGen.h.template | 55 + .../scripts/BlenderImporter/genblenddna.py | 299 + .../assimp/scripts/OgreImporter/assimp.tpl | 10 + .../scripts/StepImporter/CppGenerator.py | 304 + .../scripts/StepImporter/ExpressReader.py | 123 + .../StepImporter/IFCReaderGen.cpp.template | 78 + .../StepImporter/IFCReaderGen.h.template | 91 + .../StepImporter/StepReaderGen.cpp.template | 78 + .../StepImporter/StepReaderGen.h.template | 90 + .../StepImporter/extract_step_token.py | 64 + .../scripts/StepImporter/genentitylist.sh | 3 + .../scripts/StepImporter/ifc_entitylist.txt | 111 + .../StepImporter/part403ts_wg3n2635mim_lf.exp | 16378 ++++++++++++++++ .../StepImporter/schema_ap203e2_mim_lf.exp | 16378 ++++++++++++++++ .../scripts/StepImporter/schema_ifc2x3.exp | 9571 +++++++++ .../scripts/StepImporter/step_entitylist.txt | 1015 + .../assimp/scripts/adjust_header_paths.sh | 38 + .../android_crosscompile/make_android.bat | 28 + .../assimp/tools/assimp_cmd/CMakeLists.txt | 78 + .../assimp/tools/assimp_cmd/CompareDump.cpp | 952 + thirdparty/assimp/tools/assimp_cmd/Export.cpp | 174 + .../tools/assimp_cmd/ImageExtractor.cpp | 376 + thirdparty/assimp/tools/assimp_cmd/Info.cpp | 477 + thirdparty/assimp/tools/assimp_cmd/Main.cpp | 521 + thirdparty/assimp/tools/assimp_cmd/Main.h | 205 + .../assimp/tools/assimp_cmd/WriteDumb.cpp | 1430 ++ .../assimp/tools/assimp_cmd/assimp_cmd.rc | 51 + .../tools/assimp_cmd/generic_inserter.hpp | 113 + thirdparty/assimp/tools/assimp_cmd/resource.h | 21 + .../tools/assimp_view/AnimEvaluator.cpp | 170 + .../assimp/tools/assimp_view/AnimEvaluator.h | 86 + .../assimp/tools/assimp_view/AssetHelper.h | 250 + .../assimp/tools/assimp_view/Background.cpp | 471 + .../assimp/tools/assimp_view/Background.h | 128 + .../assimp/tools/assimp_view/CMakeLists.txt | 107 + thirdparty/assimp/tools/assimp_view/Camera.h | 85 + .../assimp/tools/assimp_view/Display.cpp | 2302 +++ thirdparty/assimp/tools/assimp_view/Display.h | 542 + thirdparty/assimp/tools/assimp_view/HUD.png | Bin 0 -> 40474 bytes .../assimp/tools/assimp_view/HUDMask.png | Bin 0 -> 6460 bytes .../assimp/tools/assimp_view/HelpDialog.cpp | 103 + thirdparty/assimp/tools/assimp_view/Input.cpp | 372 + .../assimp/tools/assimp_view/LogDisplay.cpp | 221 + .../assimp/tools/assimp_view/LogDisplay.h | 99 + .../assimp/tools/assimp_view/LogWindow.cpp | 246 + .../assimp/tools/assimp_view/LogWindow.h | 133 + .../assimp/tools/assimp_view/Material.cpp | 1493 ++ .../tools/assimp_view/MaterialManager.h | 208 + .../assimp/tools/assimp_view/MeshRenderer.cpp | 164 + .../assimp/tools/assimp_view/MeshRenderer.h | 99 + .../assimp/tools/assimp_view/MessageProc.cpp | 2434 +++ .../tools/assimp_view/NOTE@help.rtf.txt | 2 + .../assimp/tools/assimp_view/Normals.cpp | 155 + .../assimp/tools/assimp_view/RenderOptions.h | 113 + .../tools/assimp_view/SceneAnimator.cpp | 233 + .../assimp/tools/assimp_view/SceneAnimator.h | 249 + .../assimp/tools/assimp_view/Shaders.cpp | 1410 ++ thirdparty/assimp/tools/assimp_view/Shaders.h | 63 + .../assimp/tools/assimp_view/assimp_view.cpp | 1178 ++ .../assimp/tools/assimp_view/assimp_view.h | 282 + .../assimp/tools/assimp_view/assimp_view.rc | 468 + .../assimp/tools/assimp_view/banner.bmp | Bin 0 -> 144054 bytes .../assimp/tools/assimp_view/banner_pure.bmp | Bin 0 -> 108054 bytes thirdparty/assimp/tools/assimp_view/base.PNG | Bin 0 -> 1600 bytes .../assimp/tools/assimp_view/base_anim.bmp | Bin 0 -> 9630 bytes .../assimp/tools/assimp_view/base_display.bmp | Bin 0 -> 9630 bytes .../assimp/tools/assimp_view/base_inter.bmp | Bin 0 -> 9630 bytes .../tools/assimp_view/base_rendering.bmp | Bin 0 -> 9630 bytes .../assimp/tools/assimp_view/base_stats.bmp | Bin 0 -> 9630 bytes thirdparty/assimp/tools/assimp_view/fx.bmp | Bin 0 -> 822 bytes thirdparty/assimp/tools/assimp_view/help.rtf | 418 + thirdparty/assimp/tools/assimp_view/n.bmp | Bin 0 -> 822 bytes .../assimp/tools/assimp_view/resource.h | 235 + thirdparty/assimp/tools/assimp_view/root.bmp | Bin 0 -> 822 bytes .../assimp/tools/assimp_view/stdafx.cpp | 8 + thirdparty/assimp/tools/assimp_view/stdafx.h | 73 + thirdparty/assimp/tools/assimp_view/test.xcf | Bin 0 -> 121875 bytes thirdparty/assimp/tools/assimp_view/text1.bin | 373 + thirdparty/assimp/tools/assimp_view/tx.bmp | Bin 0 -> 822 bytes thirdparty/assimp/tools/assimp_view/txi.bmp | Bin 0 -> 822 bytes .../assimp/tools/coverity/assimp_modeling.cpp | 40 + .../assimp/tools/make/build_env_win32.bat | 57 + .../assimp/tools/make/make_all_win32_x64.bat | 18 + .../assimp/tools/shared/assimp_tools_icon.ico | Bin 0 -> 17542 bytes .../assimp/tools/shared/assimp_tools_icon.png | Bin 0 -> 104295 bytes .../assimp/tools/shared/assimp_tools_icon.svg | 184 + .../assimp/tools/shared/default_icon.xcf | Bin 0 -> 13189 bytes 1283 files changed, 533814 insertions(+), 42 deletions(-) create mode 100644 core/Util.cpp create mode 100644 core/Util.h create mode 100644 demo/cube.glb create mode 100644 thirdparty/assimp/.editorconfig create mode 100644 thirdparty/assimp/AssimpBuildTreeSettings.cmake.in create mode 100644 thirdparty/assimp/AssimpConfigVersion.cmake.in create mode 100644 thirdparty/assimp/BUILDBINARIES_EXAMPLE.bat create mode 100644 thirdparty/assimp/Build.md create mode 100644 thirdparty/assimp/CHANGES create mode 100644 thirdparty/assimp/CMakeLists.txt create mode 100644 thirdparty/assimp/CONTRIBUTING.md create mode 100644 thirdparty/assimp/CREDITS create mode 100644 thirdparty/assimp/CodeConventions.md create mode 100644 thirdparty/assimp/INSTALL create mode 100644 thirdparty/assimp/LICENSE create mode 100644 thirdparty/assimp/README create mode 100644 thirdparty/assimp/Readme.md create mode 100644 thirdparty/assimp/assimp-config-version.cmake.in create mode 100644 thirdparty/assimp/assimp-config.cmake.in create mode 100644 thirdparty/assimp/assimp.pc.in create mode 100644 thirdparty/assimp/assimpTargets-debug.cmake.in create mode 100644 thirdparty/assimp/assimpTargets-release.cmake.in create mode 100644 thirdparty/assimp/assimpTargets.cmake.in create mode 100644 thirdparty/assimp/cmake-modules/Coveralls.cmake create mode 100644 thirdparty/assimp/cmake-modules/CoverallsClear.cmake create mode 100644 thirdparty/assimp/cmake-modules/CoverallsGenerateGcov.cmake create mode 100644 thirdparty/assimp/cmake-modules/DebSourcePPA.cmake create mode 100644 thirdparty/assimp/cmake-modules/FindDevIL.cmake create mode 100644 thirdparty/assimp/cmake-modules/FindDirectX.cmake create mode 100644 thirdparty/assimp/cmake-modules/FindIrrXML.cmake create mode 100644 thirdparty/assimp/cmake-modules/FindPkgMacros.cmake create mode 100644 thirdparty/assimp/cmake-modules/FindRT.cmake create mode 100644 thirdparty/assimp/cmake-modules/FindZLIB.cmake create mode 100644 thirdparty/assimp/cmake-modules/Findassimp.cmake create mode 100644 thirdparty/assimp/cmake-modules/MinGW_x86_64.cmake create mode 100644 thirdparty/assimp/cmake-modules/PrecompiledHeader.cmake create mode 100644 thirdparty/assimp/cmake-modules/cmake_uninstall.cmake.in create mode 100644 thirdparty/assimp/cmake/HunterGate.cmake create mode 100644 thirdparty/assimp/cmake/assimp-hunter-config.cmake.in create mode 120000 thirdparty/assimp/code/.#CMakeLists.txt create mode 100644 thirdparty/assimp/code/.editorconfig create mode 100644 thirdparty/assimp/code/3DS/3DSConverter.cpp create mode 100644 thirdparty/assimp/code/3DS/3DSExporter.cpp create mode 100644 thirdparty/assimp/code/3DS/3DSExporter.h create mode 100644 thirdparty/assimp/code/3DS/3DSHelper.h create mode 100644 thirdparty/assimp/code/3DS/3DSLoader.cpp create mode 100644 thirdparty/assimp/code/3DS/3DSLoader.h create mode 100644 thirdparty/assimp/code/3MF/3MFXmlTags.h create mode 100644 thirdparty/assimp/code/3MF/D3MFExporter.cpp create mode 100644 thirdparty/assimp/code/3MF/D3MFExporter.h create mode 100644 thirdparty/assimp/code/3MF/D3MFImporter.cpp create mode 100644 thirdparty/assimp/code/3MF/D3MFImporter.h create mode 100644 thirdparty/assimp/code/3MF/D3MFOpcPackage.cpp create mode 100644 thirdparty/assimp/code/3MF/D3MFOpcPackage.h create mode 100644 thirdparty/assimp/code/AC/ACLoader.cpp create mode 100644 thirdparty/assimp/code/AC/ACLoader.h create mode 100644 thirdparty/assimp/code/AMF/AMFImporter.cpp create mode 100644 thirdparty/assimp/code/AMF/AMFImporter.hpp create mode 100644 thirdparty/assimp/code/AMF/AMFImporter_Geometry.cpp create mode 100644 thirdparty/assimp/code/AMF/AMFImporter_Macro.hpp create mode 100644 thirdparty/assimp/code/AMF/AMFImporter_Material.cpp create mode 100644 thirdparty/assimp/code/AMF/AMFImporter_Node.hpp create mode 100644 thirdparty/assimp/code/AMF/AMFImporter_Postprocess.cpp create mode 100644 thirdparty/assimp/code/ASE/ASELoader.cpp create mode 100644 thirdparty/assimp/code/ASE/ASELoader.h create mode 100644 thirdparty/assimp/code/ASE/ASEParser.cpp create mode 100644 thirdparty/assimp/code/ASE/ASEParser.h create mode 100644 thirdparty/assimp/code/Assbin/AssbinExporter.cpp create mode 100644 thirdparty/assimp/code/Assbin/AssbinExporter.h create mode 100644 thirdparty/assimp/code/Assbin/AssbinLoader.cpp create mode 100644 thirdparty/assimp/code/Assbin/AssbinLoader.h create mode 100644 thirdparty/assimp/code/Assjson/cencode.c create mode 100644 thirdparty/assimp/code/Assjson/cencode.h create mode 100644 thirdparty/assimp/code/Assjson/json_exporter.cpp create mode 100644 thirdparty/assimp/code/Assjson/mesh_splitter.cpp create mode 100644 thirdparty/assimp/code/Assjson/mesh_splitter.h create mode 100644 thirdparty/assimp/code/Assxml/AssxmlExporter.cpp create mode 100644 thirdparty/assimp/code/Assxml/AssxmlExporter.h create mode 100644 thirdparty/assimp/code/B3D/B3DImporter.cpp create mode 100644 thirdparty/assimp/code/B3D/B3DImporter.h create mode 100644 thirdparty/assimp/code/BVH/BVHLoader.cpp create mode 100644 thirdparty/assimp/code/BVH/BVHLoader.h create mode 100644 thirdparty/assimp/code/Blender/BlenderBMesh.cpp create mode 100644 thirdparty/assimp/code/Blender/BlenderBMesh.h create mode 100644 thirdparty/assimp/code/Blender/BlenderCustomData.cpp create mode 100644 thirdparty/assimp/code/Blender/BlenderCustomData.h create mode 100644 thirdparty/assimp/code/Blender/BlenderDNA.cpp create mode 100644 thirdparty/assimp/code/Blender/BlenderDNA.h create mode 100644 thirdparty/assimp/code/Blender/BlenderDNA.inl create mode 100644 thirdparty/assimp/code/Blender/BlenderIntermediate.h create mode 100644 thirdparty/assimp/code/Blender/BlenderLoader.cpp create mode 100644 thirdparty/assimp/code/Blender/BlenderLoader.h create mode 100644 thirdparty/assimp/code/Blender/BlenderModifier.cpp create mode 100644 thirdparty/assimp/code/Blender/BlenderModifier.h create mode 100644 thirdparty/assimp/code/Blender/BlenderScene.cpp create mode 100644 thirdparty/assimp/code/Blender/BlenderScene.h create mode 100644 thirdparty/assimp/code/Blender/BlenderSceneGen.h create mode 100644 thirdparty/assimp/code/Blender/BlenderTessellator.cpp create mode 100644 thirdparty/assimp/code/Blender/BlenderTessellator.h create mode 100644 thirdparty/assimp/code/C4D/C4DImporter.cpp create mode 100644 thirdparty/assimp/code/C4D/C4DImporter.h create mode 100644 thirdparty/assimp/code/CApi/AssimpCExport.cpp create mode 100644 thirdparty/assimp/code/CApi/CInterfaceIOWrapper.cpp create mode 100644 thirdparty/assimp/code/CApi/CInterfaceIOWrapper.h create mode 100644 thirdparty/assimp/code/CMakeLists.txt create mode 100644 thirdparty/assimp/code/COB/COBLoader.cpp create mode 100644 thirdparty/assimp/code/COB/COBLoader.h create mode 100644 thirdparty/assimp/code/COB/COBScene.h create mode 100644 thirdparty/assimp/code/CSM/CSMLoader.cpp create mode 100644 thirdparty/assimp/code/CSM/CSMLoader.h create mode 100644 thirdparty/assimp/code/Collada/ColladaExporter.cpp create mode 100644 thirdparty/assimp/code/Collada/ColladaExporter.h create mode 100644 thirdparty/assimp/code/Collada/ColladaHelper.h create mode 100644 thirdparty/assimp/code/Collada/ColladaLoader.cpp create mode 100644 thirdparty/assimp/code/Collada/ColladaLoader.h create mode 100644 thirdparty/assimp/code/Collada/ColladaParser.cpp create mode 100644 thirdparty/assimp/code/Collada/ColladaParser.h create mode 100644 thirdparty/assimp/code/Common/Assimp.cpp create mode 100644 thirdparty/assimp/code/Common/BaseImporter.cpp create mode 100644 thirdparty/assimp/code/Common/BaseProcess.cpp create mode 100644 thirdparty/assimp/code/Common/BaseProcess.h create mode 100644 thirdparty/assimp/code/Common/Bitmap.cpp create mode 100644 thirdparty/assimp/code/Common/CreateAnimMesh.cpp create mode 100644 thirdparty/assimp/code/Common/DefaultIOStream.cpp create mode 100644 thirdparty/assimp/code/Common/DefaultIOSystem.cpp create mode 100644 thirdparty/assimp/code/Common/DefaultLogger.cpp create mode 100644 thirdparty/assimp/code/Common/DefaultProgressHandler.h create mode 100644 thirdparty/assimp/code/Common/Exporter.cpp create mode 100644 thirdparty/assimp/code/Common/FileLogStream.h create mode 100644 thirdparty/assimp/code/Common/FileSystemFilter.h create mode 100644 thirdparty/assimp/code/Common/IFF.h create mode 100644 thirdparty/assimp/code/Common/Importer.cpp create mode 100644 thirdparty/assimp/code/Common/Importer.h create mode 100644 thirdparty/assimp/code/Common/ImporterRegistry.cpp create mode 100644 thirdparty/assimp/code/Common/PolyTools.h create mode 100644 thirdparty/assimp/code/Common/PostStepRegistry.cpp create mode 100644 thirdparty/assimp/code/Common/RemoveComments.cpp create mode 100644 thirdparty/assimp/code/Common/SGSpatialSort.cpp create mode 100644 thirdparty/assimp/code/Common/SceneCombiner.cpp create mode 100644 thirdparty/assimp/code/Common/ScenePreprocessor.cpp create mode 100644 thirdparty/assimp/code/Common/ScenePreprocessor.h create mode 100644 thirdparty/assimp/code/Common/ScenePrivate.h create mode 100644 thirdparty/assimp/code/Common/SkeletonMeshBuilder.cpp create mode 100644 thirdparty/assimp/code/Common/SpatialSort.cpp create mode 100644 thirdparty/assimp/code/Common/SplitByBoneCountProcess.cpp create mode 100644 thirdparty/assimp/code/Common/SplitByBoneCountProcess.h create mode 100644 thirdparty/assimp/code/Common/StandardShapes.cpp create mode 100644 thirdparty/assimp/code/Common/StdOStreamLogStream.h create mode 100644 thirdparty/assimp/code/Common/Subdivision.cpp create mode 100644 thirdparty/assimp/code/Common/TargetAnimation.cpp create mode 100644 thirdparty/assimp/code/Common/TargetAnimation.h create mode 100644 thirdparty/assimp/code/Common/Version.cpp create mode 100644 thirdparty/assimp/code/Common/VertexTriangleAdjacency.cpp create mode 100644 thirdparty/assimp/code/Common/VertexTriangleAdjacency.h create mode 100644 thirdparty/assimp/code/Common/Win32DebugLogStream.h create mode 100644 thirdparty/assimp/code/Common/ZipArchiveIOSystem.cpp create mode 100644 thirdparty/assimp/code/Common/assbin_chunks.h create mode 100644 thirdparty/assimp/code/Common/scene.cpp create mode 100644 thirdparty/assimp/code/Common/simd.cpp create mode 100644 thirdparty/assimp/code/Common/simd.h create mode 100644 thirdparty/assimp/code/DXF/DXFHelper.h create mode 100644 thirdparty/assimp/code/DXF/DXFLoader.cpp create mode 100644 thirdparty/assimp/code/DXF/DXFLoader.h create mode 100644 thirdparty/assimp/code/FBX/FBXAnimation.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXBinaryTokenizer.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXCommon.h create mode 100644 thirdparty/assimp/code/FBX/FBXCompileConfig.h create mode 100644 thirdparty/assimp/code/FBX/FBXConverter.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXConverter.h create mode 100644 thirdparty/assimp/code/FBX/FBXDeformer.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXDocument.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXDocument.h create mode 100644 thirdparty/assimp/code/FBX/FBXDocumentUtil.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXDocumentUtil.h create mode 100644 thirdparty/assimp/code/FBX/FBXExportNode.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXExportNode.h create mode 100644 thirdparty/assimp/code/FBX/FBXExportProperty.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXExportProperty.h create mode 100644 thirdparty/assimp/code/FBX/FBXExporter.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXExporter.h create mode 100644 thirdparty/assimp/code/FBX/FBXImportSettings.h create mode 100644 thirdparty/assimp/code/FBX/FBXImporter.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXImporter.h create mode 100644 thirdparty/assimp/code/FBX/FBXMaterial.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXMeshGeometry.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXMeshGeometry.h create mode 100644 thirdparty/assimp/code/FBX/FBXModel.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXNodeAttribute.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXParser.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXParser.h create mode 100644 thirdparty/assimp/code/FBX/FBXProperties.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXProperties.h create mode 100644 thirdparty/assimp/code/FBX/FBXTokenizer.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXTokenizer.h create mode 100644 thirdparty/assimp/code/FBX/FBXUtil.cpp create mode 100644 thirdparty/assimp/code/FBX/FBXUtil.h create mode 100644 thirdparty/assimp/code/HMP/HMPFileData.h create mode 100644 thirdparty/assimp/code/HMP/HMPLoader.cpp create mode 100644 thirdparty/assimp/code/HMP/HMPLoader.h create mode 100644 thirdparty/assimp/code/HMP/HalfLifeFileData.h create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCBoolean.cpp create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCCurve.cpp create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCGeometry.cpp create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCLoader.cpp create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCLoader.h create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCMaterial.cpp create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCOpenings.cpp create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCProfile.cpp create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCReaderGen1_2x3.cpp create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCReaderGen2_2x3.cpp create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCReaderGen_2x3.h create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCReaderGen_4.cpp create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCReaderGen_4.h create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCUtil.cpp create mode 100644 thirdparty/assimp/code/Importer/IFC/IFCUtil.h create mode 100644 thirdparty/assimp/code/Importer/STEPParser/STEPFileEncoding.cpp create mode 100644 thirdparty/assimp/code/Importer/STEPParser/STEPFileEncoding.h create mode 100644 thirdparty/assimp/code/Importer/STEPParser/STEPFileReader.cpp create mode 100644 thirdparty/assimp/code/Importer/STEPParser/STEPFileReader.h create mode 100644 thirdparty/assimp/code/Importer/StepFile/StepFileGen1.cpp create mode 100644 thirdparty/assimp/code/Importer/StepFile/StepFileGen2.cpp create mode 100644 thirdparty/assimp/code/Importer/StepFile/StepFileGen3.cpp create mode 100644 thirdparty/assimp/code/Importer/StepFile/StepFileImporter.cpp create mode 100644 thirdparty/assimp/code/Importer/StepFile/StepFileImporter.h create mode 100644 thirdparty/assimp/code/Importer/StepFile/StepReaderGen.h create mode 100644 thirdparty/assimp/code/Irr/IRRLoader.cpp create mode 100644 thirdparty/assimp/code/Irr/IRRLoader.h create mode 100644 thirdparty/assimp/code/Irr/IRRMeshLoader.cpp create mode 100644 thirdparty/assimp/code/Irr/IRRMeshLoader.h create mode 100644 thirdparty/assimp/code/Irr/IRRShared.cpp create mode 100644 thirdparty/assimp/code/Irr/IRRShared.h create mode 100644 thirdparty/assimp/code/LWO/LWOAnimation.cpp create mode 100644 thirdparty/assimp/code/LWO/LWOAnimation.h create mode 100644 thirdparty/assimp/code/LWO/LWOBLoader.cpp create mode 100644 thirdparty/assimp/code/LWO/LWOFileData.h create mode 100644 thirdparty/assimp/code/LWO/LWOLoader.cpp create mode 100644 thirdparty/assimp/code/LWO/LWOLoader.h create mode 100644 thirdparty/assimp/code/LWO/LWOMaterial.cpp create mode 100644 thirdparty/assimp/code/LWS/LWSLoader.cpp create mode 100644 thirdparty/assimp/code/LWS/LWSLoader.h create mode 100644 thirdparty/assimp/code/MD2/MD2FileData.h create mode 100644 thirdparty/assimp/code/MD2/MD2Loader.cpp create mode 100644 thirdparty/assimp/code/MD2/MD2Loader.h create mode 100644 thirdparty/assimp/code/MD2/MD2NormalTable.h create mode 100644 thirdparty/assimp/code/MD3/MD3FileData.h create mode 100644 thirdparty/assimp/code/MD3/MD3Loader.cpp create mode 100644 thirdparty/assimp/code/MD3/MD3Loader.h create mode 100644 thirdparty/assimp/code/MD4/MD4FileData.h create mode 100644 thirdparty/assimp/code/MD5/MD5Loader.cpp create mode 100644 thirdparty/assimp/code/MD5/MD5Loader.h create mode 100644 thirdparty/assimp/code/MD5/MD5Parser.cpp create mode 100644 thirdparty/assimp/code/MD5/MD5Parser.h create mode 100644 thirdparty/assimp/code/MDC/MDCFileData.h create mode 100644 thirdparty/assimp/code/MDC/MDCLoader.cpp create mode 100644 thirdparty/assimp/code/MDC/MDCLoader.h create mode 100644 thirdparty/assimp/code/MDC/MDCNormalTable.h create mode 100644 thirdparty/assimp/code/MDL/MDLDefaultColorMap.h create mode 100644 thirdparty/assimp/code/MDL/MDLFileData.h create mode 100644 thirdparty/assimp/code/MDL/MDLLoader.cpp create mode 100644 thirdparty/assimp/code/MDL/MDLLoader.h create mode 100644 thirdparty/assimp/code/MDL/MDLMaterialLoader.cpp create mode 100644 thirdparty/assimp/code/MMD/MMDCpp14.h create mode 100644 thirdparty/assimp/code/MMD/MMDImporter.cpp create mode 100644 thirdparty/assimp/code/MMD/MMDImporter.h create mode 100644 thirdparty/assimp/code/MMD/MMDPmdParser.h create mode 100644 thirdparty/assimp/code/MMD/MMDPmxParser.cpp create mode 100644 thirdparty/assimp/code/MMD/MMDPmxParser.h create mode 100644 thirdparty/assimp/code/MMD/MMDVmdParser.h create mode 100644 thirdparty/assimp/code/MS3D/MS3DLoader.cpp create mode 100644 thirdparty/assimp/code/MS3D/MS3DLoader.h create mode 100644 thirdparty/assimp/code/Material/MaterialSystem.cpp create mode 100644 thirdparty/assimp/code/Material/MaterialSystem.h create mode 100644 thirdparty/assimp/code/NDO/NDOLoader.cpp create mode 100644 thirdparty/assimp/code/NDO/NDOLoader.h create mode 100644 thirdparty/assimp/code/NFF/NFFLoader.cpp create mode 100644 thirdparty/assimp/code/NFF/NFFLoader.h create mode 100644 thirdparty/assimp/code/OFF/OFFLoader.cpp create mode 100644 thirdparty/assimp/code/OFF/OFFLoader.h create mode 100644 thirdparty/assimp/code/Obj/ObjExporter.cpp create mode 100644 thirdparty/assimp/code/Obj/ObjExporter.h create mode 100644 thirdparty/assimp/code/Obj/ObjFileData.h create mode 100644 thirdparty/assimp/code/Obj/ObjFileImporter.cpp create mode 100644 thirdparty/assimp/code/Obj/ObjFileImporter.h create mode 100644 thirdparty/assimp/code/Obj/ObjFileMtlImporter.cpp create mode 100644 thirdparty/assimp/code/Obj/ObjFileMtlImporter.h create mode 100644 thirdparty/assimp/code/Obj/ObjFileParser.cpp create mode 100644 thirdparty/assimp/code/Obj/ObjFileParser.h create mode 100644 thirdparty/assimp/code/Obj/ObjTools.h create mode 100644 thirdparty/assimp/code/Ogre/OgreBinarySerializer.cpp create mode 100644 thirdparty/assimp/code/Ogre/OgreBinarySerializer.h create mode 100644 thirdparty/assimp/code/Ogre/OgreImporter.cpp create mode 100644 thirdparty/assimp/code/Ogre/OgreImporter.h create mode 100644 thirdparty/assimp/code/Ogre/OgreMaterial.cpp create mode 100644 thirdparty/assimp/code/Ogre/OgreParsingUtils.h create mode 100644 thirdparty/assimp/code/Ogre/OgreStructs.cpp create mode 100644 thirdparty/assimp/code/Ogre/OgreStructs.h create mode 100644 thirdparty/assimp/code/Ogre/OgreXmlSerializer.cpp create mode 100644 thirdparty/assimp/code/Ogre/OgreXmlSerializer.h create mode 100644 thirdparty/assimp/code/OpenGEX/OpenGEXExporter.cpp create mode 100644 thirdparty/assimp/code/OpenGEX/OpenGEXExporter.h create mode 100644 thirdparty/assimp/code/OpenGEX/OpenGEXImporter.cpp create mode 100644 thirdparty/assimp/code/OpenGEX/OpenGEXImporter.h create mode 100644 thirdparty/assimp/code/OpenGEX/OpenGEXStructs.h create mode 100644 thirdparty/assimp/code/Ply/PlyExporter.cpp create mode 100644 thirdparty/assimp/code/Ply/PlyExporter.h create mode 100644 thirdparty/assimp/code/Ply/PlyLoader.cpp create mode 100644 thirdparty/assimp/code/Ply/PlyLoader.h create mode 100644 thirdparty/assimp/code/Ply/PlyParser.cpp create mode 100644 thirdparty/assimp/code/Ply/PlyParser.h create mode 100644 thirdparty/assimp/code/PostProcessing/CalcTangentsProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/CalcTangentsProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/ComputeUVMappingProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/ComputeUVMappingProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/ConvertToLHProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/ConvertToLHProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/DeboneProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/DeboneProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/DropFaceNormalsProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/DropFaceNormalsProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/EmbedTexturesProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/EmbedTexturesProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/FindDegenerates.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/FindDegenerates.h create mode 100644 thirdparty/assimp/code/PostProcessing/FindInstancesProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/FindInstancesProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/FindInvalidDataProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/FindInvalidDataProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/FixNormalsStep.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/FixNormalsStep.h create mode 100644 thirdparty/assimp/code/PostProcessing/GenBoundingBoxesProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/GenBoundingBoxesProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/GenFaceNormalsProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/GenFaceNormalsProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/GenVertexNormalsProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/GenVertexNormalsProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/ImproveCacheLocality.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/ImproveCacheLocality.h create mode 100644 thirdparty/assimp/code/PostProcessing/JoinVerticesProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/JoinVerticesProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/LimitBoneWeightsProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/LimitBoneWeightsProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/MakeVerboseFormat.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/MakeVerboseFormat.h create mode 100644 thirdparty/assimp/code/PostProcessing/OptimizeGraph.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/OptimizeGraph.h create mode 100644 thirdparty/assimp/code/PostProcessing/OptimizeMeshes.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/OptimizeMeshes.h create mode 100644 thirdparty/assimp/code/PostProcessing/PretransformVertices.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/PretransformVertices.h create mode 100644 thirdparty/assimp/code/PostProcessing/ProcessHelper.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/ProcessHelper.h create mode 100644 thirdparty/assimp/code/PostProcessing/RemoveRedundantMaterials.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/RemoveRedundantMaterials.h create mode 100644 thirdparty/assimp/code/PostProcessing/RemoveVCProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/RemoveVCProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/ScaleProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/ScaleProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/SortByPTypeProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/SortByPTypeProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/SplitLargeMeshes.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/SplitLargeMeshes.h create mode 100644 thirdparty/assimp/code/PostProcessing/TextureTransform.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/TextureTransform.h create mode 100644 thirdparty/assimp/code/PostProcessing/TriangulateProcess.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/TriangulateProcess.h create mode 100644 thirdparty/assimp/code/PostProcessing/ValidateDataStructure.cpp create mode 100644 thirdparty/assimp/code/PostProcessing/ValidateDataStructure.h create mode 100644 thirdparty/assimp/code/Q3BSP/Q3BSPFileData.h create mode 100644 thirdparty/assimp/code/Q3BSP/Q3BSPFileImporter.cpp create mode 100644 thirdparty/assimp/code/Q3BSP/Q3BSPFileImporter.h create mode 100644 thirdparty/assimp/code/Q3BSP/Q3BSPFileParser.cpp create mode 100644 thirdparty/assimp/code/Q3BSP/Q3BSPFileParser.h create mode 100644 thirdparty/assimp/code/Q3D/Q3DLoader.cpp create mode 100644 thirdparty/assimp/code/Q3D/Q3DLoader.h create mode 100644 thirdparty/assimp/code/Raw/RawLoader.cpp create mode 100644 thirdparty/assimp/code/Raw/RawLoader.h create mode 100644 thirdparty/assimp/code/SIB/SIBImporter.cpp create mode 100644 thirdparty/assimp/code/SIB/SIBImporter.h create mode 100644 thirdparty/assimp/code/SMD/SMDLoader.cpp create mode 100644 thirdparty/assimp/code/SMD/SMDLoader.h create mode 100644 thirdparty/assimp/code/STL/STLExporter.cpp create mode 100644 thirdparty/assimp/code/STL/STLExporter.h create mode 100644 thirdparty/assimp/code/STL/STLLoader.cpp create mode 100644 thirdparty/assimp/code/STL/STLLoader.h create mode 100644 thirdparty/assimp/code/Step/STEPFile.h create mode 100644 thirdparty/assimp/code/Step/StepExporter.cpp create mode 100644 thirdparty/assimp/code/Step/StepExporter.h create mode 100644 thirdparty/assimp/code/Terragen/TerragenLoader.cpp create mode 100644 thirdparty/assimp/code/Terragen/TerragenLoader.h create mode 100644 thirdparty/assimp/code/Unreal/UnrealLoader.cpp create mode 100644 thirdparty/assimp/code/Unreal/UnrealLoader.h create mode 100644 thirdparty/assimp/code/X/XFileExporter.cpp create mode 100644 thirdparty/assimp/code/X/XFileExporter.h create mode 100644 thirdparty/assimp/code/X/XFileHelper.h create mode 100644 thirdparty/assimp/code/X/XFileImporter.cpp create mode 100644 thirdparty/assimp/code/X/XFileImporter.h create mode 100644 thirdparty/assimp/code/X/XFileParser.cpp create mode 100644 thirdparty/assimp/code/X/XFileParser.h create mode 100644 thirdparty/assimp/code/X3D/FIReader.cpp create mode 100644 thirdparty/assimp/code/X3D/FIReader.hpp create mode 100644 thirdparty/assimp/code/X3D/X3DExporter.cpp create mode 100644 thirdparty/assimp/code/X3D/X3DExporter.hpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter.cpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter.hpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter_Geometry2D.cpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter_Geometry3D.cpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter_Group.cpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter_Light.cpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter_Macro.hpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter_Metadata.cpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter_Networking.cpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter_Node.hpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter_Postprocess.cpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter_Rendering.cpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter_Shape.cpp create mode 100644 thirdparty/assimp/code/X3D/X3DImporter_Texturing.cpp create mode 100644 thirdparty/assimp/code/X3D/X3DVocabulary.cpp create mode 100644 thirdparty/assimp/code/XGL/XGLLoader.cpp create mode 100644 thirdparty/assimp/code/XGL/XGLLoader.h create mode 100644 thirdparty/assimp/code/glTF/glTFAsset.h create mode 100644 thirdparty/assimp/code/glTF/glTFAsset.inl create mode 100644 thirdparty/assimp/code/glTF/glTFAssetWriter.h create mode 100644 thirdparty/assimp/code/glTF/glTFAssetWriter.inl create mode 100644 thirdparty/assimp/code/glTF/glTFCommon.cpp create mode 100644 thirdparty/assimp/code/glTF/glTFCommon.h create mode 100644 thirdparty/assimp/code/glTF/glTFExporter.cpp create mode 100644 thirdparty/assimp/code/glTF/glTFExporter.h create mode 100644 thirdparty/assimp/code/glTF/glTFImporter.cpp create mode 100644 thirdparty/assimp/code/glTF/glTFImporter.h create mode 100644 thirdparty/assimp/code/glTF2/glTF2Asset.h create mode 100644 thirdparty/assimp/code/glTF2/glTF2Asset.inl create mode 100644 thirdparty/assimp/code/glTF2/glTF2AssetWriter.h create mode 100644 thirdparty/assimp/code/glTF2/glTF2AssetWriter.inl create mode 100644 thirdparty/assimp/code/glTF2/glTF2Exporter.cpp create mode 100644 thirdparty/assimp/code/glTF2/glTF2Exporter.h create mode 100644 thirdparty/assimp/code/glTF2/glTF2Importer.cpp create mode 100644 thirdparty/assimp/code/glTF2/glTF2Importer.h create mode 100644 thirdparty/assimp/code/res/assimp.rc create mode 100644 thirdparty/assimp/code/res/resource.h create mode 100644 thirdparty/assimp/contrib/CMakeLists.txt create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcAdjacencyInfo.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcArithmeticCodec.cpp create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcArithmeticCodec.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcBinaryStream.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcCommon.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcDVEncodeParams.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcDynamicVector.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcDynamicVectorDecoder.cpp create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcDynamicVectorDecoder.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcDynamicVectorEncoder.cpp create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcDynamicVectorEncoder.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcFIFO.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcIndexedFaceSet.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcIndexedFaceSet.inl create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcSC3DMCDecoder.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcSC3DMCDecoder.inl create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcSC3DMCEncodeParams.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcSC3DMCEncoder.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcSC3DMCEncoder.inl create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcTimer.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcTools.cpp create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcTriangleFans.cpp create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcTriangleFans.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcTriangleListDecoder.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcTriangleListDecoder.inl create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcTriangleListEncoder.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcTriangleListEncoder.inl create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcVector.h create mode 100644 thirdparty/assimp/contrib/Open3DGC/o3dgcVector.inl create mode 100644 thirdparty/assimp/contrib/android-cmake/AndroidNdkGdb.cmake create mode 100644 thirdparty/assimp/contrib/android-cmake/AndroidNdkModules.cmake create mode 100644 thirdparty/assimp/contrib/android-cmake/README.md create mode 100644 thirdparty/assimp/contrib/android-cmake/android.toolchain.cmake create mode 100644 thirdparty/assimp/contrib/android-cmake/ndk_links.md create mode 100644 thirdparty/assimp/contrib/clipper/License.txt create mode 100644 thirdparty/assimp/contrib/clipper/clipper.cpp create mode 100644 thirdparty/assimp/contrib/clipper/clipper.hpp create mode 100644 thirdparty/assimp/contrib/gtest/CHANGES create mode 100644 thirdparty/assimp/contrib/gtest/CMakeLists.txt create mode 100644 thirdparty/assimp/contrib/gtest/CONTRIBUTORS create mode 100644 thirdparty/assimp/contrib/gtest/LICENSE create mode 100644 thirdparty/assimp/contrib/gtest/Makefile.am create mode 100644 thirdparty/assimp/contrib/gtest/README.md create mode 100644 thirdparty/assimp/contrib/gtest/build-aux/.keep create mode 100644 thirdparty/assimp/contrib/gtest/cmake/internal_utils.cmake create mode 100644 thirdparty/assimp/contrib/gtest/codegear/gtest.cbproj create mode 100644 thirdparty/assimp/contrib/gtest/codegear/gtest.groupproj create mode 100644 thirdparty/assimp/contrib/gtest/codegear/gtest_all.cc create mode 100644 thirdparty/assimp/contrib/gtest/codegear/gtest_link.cc create mode 100644 thirdparty/assimp/contrib/gtest/codegear/gtest_main.cbproj create mode 100644 thirdparty/assimp/contrib/gtest/codegear/gtest_unittest.cbproj create mode 100644 thirdparty/assimp/contrib/gtest/configure.ac create mode 100644 thirdparty/assimp/contrib/gtest/docs/AdvancedGuide.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/DevGuide.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/Documentation.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/FAQ.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/Primer.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/PumpManual.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/Samples.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_5_AdvancedGuide.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_5_Documentation.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_5_FAQ.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_5_Primer.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_5_PumpManual.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_5_XcodeGuide.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_6_AdvancedGuide.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_6_Documentation.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_6_FAQ.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_6_Primer.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_6_PumpManual.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_6_Samples.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_6_XcodeGuide.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_7_AdvancedGuide.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_7_Documentation.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_7_FAQ.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_7_Primer.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_7_PumpManual.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_7_Samples.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/V1_7_XcodeGuide.md create mode 100644 thirdparty/assimp/contrib/gtest/docs/XcodeGuide.md create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/gtest-death-test.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/gtest-message.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/gtest-param-test.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/gtest-param-test.h.pump create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/gtest-printers.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/gtest-spi.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/gtest-test-part.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/gtest-typed-test.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/gtest.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/gtest_pred_impl.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/gtest_prod.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/custom/gtest-port.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/custom/gtest-printers.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/custom/gtest.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-death-test-internal.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-filepath.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-internal.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-linked_ptr.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-param-util-generated.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-param-util-generated.h.pump create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-param-util.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-port-arch.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-port.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-string.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-tuple.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-tuple.h.pump create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-type-util.h create mode 100644 thirdparty/assimp/contrib/gtest/include/gtest/internal/gtest-type-util.h.pump create mode 100644 thirdparty/assimp/contrib/gtest/m4/acx_pthread.m4 create mode 100644 thirdparty/assimp/contrib/gtest/m4/gtest.m4 create mode 100644 thirdparty/assimp/contrib/gtest/make/Makefile create mode 100644 thirdparty/assimp/contrib/gtest/samples/prime_tables.h create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample1.cc create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample1.h create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample10_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample1_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample2.cc create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample2.h create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample2_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample3-inl.h create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample3_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample4.cc create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample4.h create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample4_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample5_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample6_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample7_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample8_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/samples/sample9_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/scripts/common.py create mode 100644 thirdparty/assimp/contrib/gtest/scripts/fuse_gtest_files.py create mode 100644 thirdparty/assimp/contrib/gtest/scripts/gen_gtest_pred_impl.py create mode 100644 thirdparty/assimp/contrib/gtest/scripts/gtest-config.in create mode 100644 thirdparty/assimp/contrib/gtest/scripts/pump.py create mode 100644 thirdparty/assimp/contrib/gtest/scripts/release_docs.py create mode 100644 thirdparty/assimp/contrib/gtest/scripts/test/Makefile create mode 100644 thirdparty/assimp/contrib/gtest/scripts/upload.py create mode 100644 thirdparty/assimp/contrib/gtest/scripts/upload_gtest.py create mode 100644 thirdparty/assimp/contrib/gtest/src/gtest-all.cc create mode 100644 thirdparty/assimp/contrib/gtest/src/gtest-death-test.cc create mode 100644 thirdparty/assimp/contrib/gtest/src/gtest-filepath.cc create mode 100644 thirdparty/assimp/contrib/gtest/src/gtest-internal-inl.h create mode 100644 thirdparty/assimp/contrib/gtest/src/gtest-port.cc create mode 100644 thirdparty/assimp/contrib/gtest/src/gtest-printers.cc create mode 100644 thirdparty/assimp/contrib/gtest/src/gtest-test-part.cc create mode 100644 thirdparty/assimp/contrib/gtest/src/gtest-typed-test.cc create mode 100644 thirdparty/assimp/contrib/gtest/src/gtest.cc create mode 100644 thirdparty/assimp/contrib/gtest/src/gtest_main.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-death-test_ex_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-death-test_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-filepath_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-linked_ptr_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-listener_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-message_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-options_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-param-test2_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-param-test_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-param-test_test.h create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-port_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-printers_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-test-part_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-tuple_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-typed-test2_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-typed-test_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-typed-test_test.h create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest-unittest-api_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_all_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_break_on_failure_unittest.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_break_on_failure_unittest_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_catch_exceptions_test.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_catch_exceptions_test_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_color_test.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_color_test_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_env_var_test.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_env_var_test_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_environment_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_filter_unittest.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_filter_unittest_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_help_test.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_help_test_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_list_tests_unittest.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_list_tests_unittest_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_main_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_no_test_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_output_test.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_output_test_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_output_test_golden_lin.txt create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_pred_impl_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_premature_exit_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_prod_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_repeat_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_shuffle_test.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_shuffle_test_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_sole_header_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_stress_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_test_utils.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_throw_on_failure_ex_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_throw_on_failure_test.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_throw_on_failure_test_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_uninitialized_test.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_uninitialized_test_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_unittest.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_xml_outfile1_test_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_xml_outfile2_test_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_xml_outfiles_test.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_xml_output_unittest.py create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_xml_output_unittest_.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/gtest_xml_test_utils.py create mode 100644 thirdparty/assimp/contrib/gtest/test/production.cc create mode 100644 thirdparty/assimp/contrib/gtest/test/production.h create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Config/DebugProject.xcconfig create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Config/FrameworkTarget.xcconfig create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Config/General.xcconfig create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Config/ReleaseProject.xcconfig create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Config/StaticLibraryTarget.xcconfig create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Config/TestTarget.xcconfig create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Resources/Info.plist create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Samples/FrameworkSample/Info.plist create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Samples/FrameworkSample/WidgetFramework.xcodeproj/project.pbxproj create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Samples/FrameworkSample/runtests.sh create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Samples/FrameworkSample/widget.cc create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Samples/FrameworkSample/widget.h create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Samples/FrameworkSample/widget_test.cc create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Scripts/runtests.sh create mode 100644 thirdparty/assimp/contrib/gtest/xcode/Scripts/versiongenerate.py create mode 100644 thirdparty/assimp/contrib/gtest/xcode/gtest.xcodeproj/project.pbxproj create mode 100644 thirdparty/assimp/contrib/irrXML/CMakeLists.txt create mode 100644 thirdparty/assimp/contrib/irrXML/CXMLReaderImpl.h create mode 100644 thirdparty/assimp/contrib/irrXML/heapsort.h create mode 100644 thirdparty/assimp/contrib/irrXML/irrArray.h create mode 100644 thirdparty/assimp/contrib/irrXML/irrString.h create mode 100644 thirdparty/assimp/contrib/irrXML/irrTypes.h create mode 100644 thirdparty/assimp/contrib/irrXML/irrXML.cpp create mode 100644 thirdparty/assimp/contrib/irrXML/irrXML.h create mode 100644 thirdparty/assimp/contrib/irrXML_note.txt create mode 100644 thirdparty/assimp/contrib/openddlparser/CMakeLists.txt create mode 100644 thirdparty/assimp/contrib/openddlparser/CREDITS create mode 100644 thirdparty/assimp/contrib/openddlparser/LICENSE create mode 100644 thirdparty/assimp/contrib/openddlparser/README.md create mode 100644 thirdparty/assimp/contrib/openddlparser/code/DDLNode.cpp create mode 100644 thirdparty/assimp/contrib/openddlparser/code/OpenDDLCommon.cpp create mode 100644 thirdparty/assimp/contrib/openddlparser/code/OpenDDLExport.cpp create mode 100644 thirdparty/assimp/contrib/openddlparser/code/OpenDDLParser.cpp create mode 100644 thirdparty/assimp/contrib/openddlparser/code/OpenDDLStream.cpp create mode 100644 thirdparty/assimp/contrib/openddlparser/code/Value.cpp create mode 100644 thirdparty/assimp/contrib/openddlparser/include/openddlparser/DDLNode.h create mode 100644 thirdparty/assimp/contrib/openddlparser/include/openddlparser/OpenDDLCommon.h create mode 100644 thirdparty/assimp/contrib/openddlparser/include/openddlparser/OpenDDLExport.h create mode 100644 thirdparty/assimp/contrib/openddlparser/include/openddlparser/OpenDDLParser.h create mode 100644 thirdparty/assimp/contrib/openddlparser/include/openddlparser/OpenDDLParserUtils.h create mode 100644 thirdparty/assimp/contrib/openddlparser/include/openddlparser/OpenDDLStream.h create mode 100644 thirdparty/assimp/contrib/openddlparser/include/openddlparser/Value.h create mode 100644 thirdparty/assimp/contrib/poly2tri/AUTHORS create mode 100644 thirdparty/assimp/contrib/poly2tri/LICENSE create mode 100644 thirdparty/assimp/contrib/poly2tri/README create mode 100644 thirdparty/assimp/contrib/poly2tri/poly2tri/common/shapes.cc create mode 100644 thirdparty/assimp/contrib/poly2tri/poly2tri/common/shapes.h create mode 100644 thirdparty/assimp/contrib/poly2tri/poly2tri/common/utils.h create mode 100644 thirdparty/assimp/contrib/poly2tri/poly2tri/poly2tri.h create mode 100644 thirdparty/assimp/contrib/poly2tri/poly2tri/sweep/advancing_front.cc create mode 100644 thirdparty/assimp/contrib/poly2tri/poly2tri/sweep/advancing_front.h create mode 100644 thirdparty/assimp/contrib/poly2tri/poly2tri/sweep/cdt.cc create mode 100644 thirdparty/assimp/contrib/poly2tri/poly2tri/sweep/cdt.h create mode 100644 thirdparty/assimp/contrib/poly2tri/poly2tri/sweep/sweep.cc create mode 100644 thirdparty/assimp/contrib/poly2tri/poly2tri/sweep/sweep.h create mode 100644 thirdparty/assimp/contrib/poly2tri/poly2tri/sweep/sweep_context.cc create mode 100644 thirdparty/assimp/contrib/poly2tri/poly2tri/sweep/sweep_context.h create mode 100644 thirdparty/assimp/contrib/poly2tri_patch.txt create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/allocators.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/document.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/encodedstream.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/encodings.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/error/en.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/error/error.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/filereadstream.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/filewritestream.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/fwd.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/internal/biginteger.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/internal/diyfp.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/internal/dtoa.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/internal/ieee754.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/internal/itoa.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/internal/meta.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/internal/pow10.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/internal/regex.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/internal/stack.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/internal/strfunc.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/internal/strtod.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/internal/swap.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/istreamwrapper.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/memorybuffer.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/memorystream.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/msinttypes/inttypes.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/msinttypes/stdint.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/ostreamwrapper.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/pointer.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/prettywriter.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/rapidjson.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/reader.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/schema.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/stream.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/stringbuffer.h create mode 100644 thirdparty/assimp/contrib/rapidjson/include/rapidjson/writer.h create mode 100644 thirdparty/assimp/contrib/rapidjson/license.txt create mode 100644 thirdparty/assimp/contrib/rapidjson/readme.md create mode 100644 thirdparty/assimp/contrib/stb_image/stb_image.h create mode 100644 thirdparty/assimp/contrib/unzip/crypt.h create mode 100644 thirdparty/assimp/contrib/unzip/ioapi.c create mode 100644 thirdparty/assimp/contrib/unzip/ioapi.h create mode 100644 thirdparty/assimp/contrib/unzip/unzip.c create mode 100644 thirdparty/assimp/contrib/unzip/unzip.h create mode 100644 thirdparty/assimp/contrib/utf8cpp/doc/ReleaseNotes create mode 100644 thirdparty/assimp/contrib/utf8cpp/doc/utf8cpp.html create mode 100644 thirdparty/assimp/contrib/utf8cpp/source/utf8.h create mode 100644 thirdparty/assimp/contrib/utf8cpp/source/utf8/checked.h create mode 100644 thirdparty/assimp/contrib/utf8cpp/source/utf8/core.h create mode 100644 thirdparty/assimp/contrib/utf8cpp/source/utf8/unchecked.h create mode 100755 thirdparty/assimp/contrib/zip/.travis.sh create mode 100644 thirdparty/assimp/contrib/zip/.travis.yml create mode 100644 thirdparty/assimp/contrib/zip/CMakeLists.txt create mode 100644 thirdparty/assimp/contrib/zip/README.md create mode 100644 thirdparty/assimp/contrib/zip/UNLICENSE create mode 100755 thirdparty/assimp/contrib/zip/cmake/asan-wrapper create mode 100644 thirdparty/assimp/contrib/zip/cmake/cmake_uninstall.cmake.in create mode 100644 thirdparty/assimp/contrib/zip/src/miniz.h create mode 100644 thirdparty/assimp/contrib/zip/src/zip.c create mode 100644 thirdparty/assimp/contrib/zip/src/zip.h create mode 100644 thirdparty/assimp/contrib/zip/test/CMakeLists.txt create mode 100644 thirdparty/assimp/contrib/zip/test/test.c create mode 100644 thirdparty/assimp/contrib/zip/test/test_miniz.c create mode 100644 thirdparty/assimp/contrib/zip/zip.png create mode 100644 thirdparty/assimp/contrib/zlib/CMakeLists.txt create mode 100644 thirdparty/assimp/contrib/zlib/README create mode 100644 thirdparty/assimp/contrib/zlib/adler32.c create mode 100644 thirdparty/assimp/contrib/zlib/compress.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/README.contrib create mode 100644 thirdparty/assimp/contrib/zlib/contrib/ada/buffer_demo.adb create mode 100644 thirdparty/assimp/contrib/zlib/contrib/ada/mtest.adb create mode 100644 thirdparty/assimp/contrib/zlib/contrib/ada/read.adb create mode 100644 thirdparty/assimp/contrib/zlib/contrib/ada/readme.txt create mode 100644 thirdparty/assimp/contrib/zlib/contrib/ada/test.adb create mode 100644 thirdparty/assimp/contrib/zlib/contrib/ada/zlib-streams.adb create mode 100644 thirdparty/assimp/contrib/zlib/contrib/ada/zlib-streams.ads create mode 100644 thirdparty/assimp/contrib/zlib/contrib/ada/zlib-thin.adb create mode 100644 thirdparty/assimp/contrib/zlib/contrib/ada/zlib-thin.ads create mode 100644 thirdparty/assimp/contrib/zlib/contrib/ada/zlib.adb create mode 100644 thirdparty/assimp/contrib/zlib/contrib/ada/zlib.ads create mode 100644 thirdparty/assimp/contrib/zlib/contrib/ada/zlib.gpr create mode 100644 thirdparty/assimp/contrib/zlib/contrib/amd64/amd64-match.S create mode 100644 thirdparty/assimp/contrib/zlib/contrib/asm686/README.686 create mode 100644 thirdparty/assimp/contrib/zlib/contrib/asm686/match.S create mode 100644 thirdparty/assimp/contrib/zlib/contrib/blast/README create mode 100644 thirdparty/assimp/contrib/zlib/contrib/blast/blast.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/blast/blast.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/blast/test.pk create mode 100644 thirdparty/assimp/contrib/zlib/contrib/blast/test.txt create mode 100644 thirdparty/assimp/contrib/zlib/contrib/delphi/ZLib.pas create mode 100644 thirdparty/assimp/contrib/zlib/contrib/delphi/ZLibConst.pas create mode 100644 thirdparty/assimp/contrib/zlib/contrib/delphi/readme.txt create mode 100644 thirdparty/assimp/contrib/zlib/contrib/delphi/zlibd32.mak create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/DotZLib.build create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/DotZLib.chm create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/DotZLib/AssemblyInfo.cs create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/DotZLib/ChecksumImpl.cs create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/DotZLib/CircularBuffer.cs create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/DotZLib/CodecBase.cs create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/DotZLib/Deflater.cs create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/DotZLib/DotZLib.cs create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/DotZLib/DotZLib.csproj create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/DotZLib/GZipStream.cs create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/DotZLib/Inflater.cs create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/DotZLib/UnitTests.cs create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/LICENSE_1_0.txt create mode 100644 thirdparty/assimp/contrib/zlib/contrib/dotzlib/readme.txt create mode 100644 thirdparty/assimp/contrib/zlib/contrib/gcc_gvmat64/gvmat64.S create mode 100644 thirdparty/assimp/contrib/zlib/contrib/infback9/README create mode 100644 thirdparty/assimp/contrib/zlib/contrib/infback9/infback9.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/infback9/infback9.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/infback9/inffix9.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/infback9/inflate9.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/infback9/inftree9.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/infback9/inftree9.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/inflate86/inffas86.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/inflate86/inffast.S create mode 100644 thirdparty/assimp/contrib/zlib/contrib/iostream/test.cpp create mode 100644 thirdparty/assimp/contrib/zlib/contrib/iostream/zfstream.cpp create mode 100644 thirdparty/assimp/contrib/zlib/contrib/iostream/zfstream.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/iostream2/zstream.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/iostream2/zstream_test.cpp create mode 100644 thirdparty/assimp/contrib/zlib/contrib/iostream3/README create mode 100644 thirdparty/assimp/contrib/zlib/contrib/iostream3/TODO create mode 100644 thirdparty/assimp/contrib/zlib/contrib/iostream3/test.cc create mode 100644 thirdparty/assimp/contrib/zlib/contrib/iostream3/zfstream.cc create mode 100644 thirdparty/assimp/contrib/zlib/contrib/iostream3/zfstream.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/masmx64/bld_ml64.bat create mode 100644 thirdparty/assimp/contrib/zlib/contrib/masmx64/gvmat64.asm create mode 100644 thirdparty/assimp/contrib/zlib/contrib/masmx64/inffas8664.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/masmx64/inffasx64.asm create mode 100644 thirdparty/assimp/contrib/zlib/contrib/masmx64/readme.txt create mode 100644 thirdparty/assimp/contrib/zlib/contrib/masmx86/bld_ml32.bat create mode 100644 thirdparty/assimp/contrib/zlib/contrib/masmx86/inffas32.asm create mode 100644 thirdparty/assimp/contrib/zlib/contrib/masmx86/match686.asm create mode 100644 thirdparty/assimp/contrib/zlib/contrib/masmx86/readme.txt create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/Makefile.am create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/MiniZip64_Changes.txt create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/MiniZip64_info.txt create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/configure.ac create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/crypt.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/ioapi.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/ioapi.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/iowin32.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/iowin32.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/make_vms.com create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/miniunz.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/miniunzip.1 create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/minizip.1 create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/minizip.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/minizip.pc.in create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/mztools.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/mztools.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/unzip.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/unzip.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/zip.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/minizip/zip.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/pascal/example.pas create mode 100644 thirdparty/assimp/contrib/zlib/contrib/pascal/readme.txt create mode 100644 thirdparty/assimp/contrib/zlib/contrib/pascal/zlibd32.mak create mode 100644 thirdparty/assimp/contrib/zlib/contrib/pascal/zlibpas.pas create mode 100644 thirdparty/assimp/contrib/zlib/contrib/puff/README create mode 100644 thirdparty/assimp/contrib/zlib/contrib/puff/puff.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/puff/puff.h create mode 100644 thirdparty/assimp/contrib/zlib/contrib/puff/pufftest.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/puff/zeros.raw create mode 100644 thirdparty/assimp/contrib/zlib/contrib/testzlib/testzlib.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/testzlib/testzlib.txt create mode 100644 thirdparty/assimp/contrib/zlib/contrib/untgz/Makefile.msc create mode 100644 thirdparty/assimp/contrib/zlib/contrib/untgz/untgz.c create mode 100644 thirdparty/assimp/contrib/zlib/contrib/vstudio/readme.txt create mode 100644 thirdparty/assimp/contrib/zlib/contrib/vstudio/vc10/zlib.rc create mode 100644 thirdparty/assimp/contrib/zlib/contrib/vstudio/vc10/zlibvc.def create mode 100644 thirdparty/assimp/contrib/zlib/contrib/vstudio/vc11/zlib.rc create mode 100644 thirdparty/assimp/contrib/zlib/contrib/vstudio/vc11/zlibvc.def create mode 100644 thirdparty/assimp/contrib/zlib/contrib/vstudio/vc12/zlib.rc create mode 100644 thirdparty/assimp/contrib/zlib/contrib/vstudio/vc12/zlibvc.def create mode 100644 thirdparty/assimp/contrib/zlib/contrib/vstudio/vc14/zlib.rc create mode 100644 thirdparty/assimp/contrib/zlib/contrib/vstudio/vc14/zlibvc.def create mode 100644 thirdparty/assimp/contrib/zlib/contrib/vstudio/vc9/zlib.rc create mode 100644 thirdparty/assimp/contrib/zlib/contrib/vstudio/vc9/zlibvc.def create mode 100644 thirdparty/assimp/contrib/zlib/crc32.c create mode 100644 thirdparty/assimp/contrib/zlib/crc32.h create mode 100644 thirdparty/assimp/contrib/zlib/deflate.c create mode 100644 thirdparty/assimp/contrib/zlib/deflate.h create mode 100644 thirdparty/assimp/contrib/zlib/gzclose.c create mode 100644 thirdparty/assimp/contrib/zlib/gzguts.h create mode 100644 thirdparty/assimp/contrib/zlib/gzlib.c create mode 100644 thirdparty/assimp/contrib/zlib/gzread.c create mode 100644 thirdparty/assimp/contrib/zlib/gzwrite.c create mode 100644 thirdparty/assimp/contrib/zlib/infback.c create mode 100644 thirdparty/assimp/contrib/zlib/inffast.c create mode 100644 thirdparty/assimp/contrib/zlib/inffast.h create mode 100644 thirdparty/assimp/contrib/zlib/inffixed.h create mode 100644 thirdparty/assimp/contrib/zlib/inflate.c create mode 100644 thirdparty/assimp/contrib/zlib/inflate.h create mode 100644 thirdparty/assimp/contrib/zlib/inftrees.c create mode 100644 thirdparty/assimp/contrib/zlib/inftrees.h create mode 100644 thirdparty/assimp/contrib/zlib/trees.c create mode 100644 thirdparty/assimp/contrib/zlib/trees.h create mode 100644 thirdparty/assimp/contrib/zlib/uncompr.c create mode 100644 thirdparty/assimp/contrib/zlib/win32/DLL_FAQ.txt create mode 100644 thirdparty/assimp/contrib/zlib/win32/Makefile.bor create mode 100644 thirdparty/assimp/contrib/zlib/win32/Makefile.gcc create mode 100644 thirdparty/assimp/contrib/zlib/win32/Makefile.msc create mode 100644 thirdparty/assimp/contrib/zlib/win32/README-WIN32.txt create mode 100644 thirdparty/assimp/contrib/zlib/win32/VisualC.txt create mode 100644 thirdparty/assimp/contrib/zlib/win32/zlib.def create mode 100644 thirdparty/assimp/contrib/zlib/win32/zlib1.rc create mode 100644 thirdparty/assimp/contrib/zlib/zconf.h.cmakein create mode 100644 thirdparty/assimp/contrib/zlib/zconf.h.in create mode 100644 thirdparty/assimp/contrib/zlib/zconf.h.included create mode 100644 thirdparty/assimp/contrib/zlib/zlib.h create mode 100644 thirdparty/assimp/contrib/zlib/zlib.pc.cmakein create mode 100644 thirdparty/assimp/contrib/zlib/zutil.c create mode 100644 thirdparty/assimp/contrib/zlib/zutil.h create mode 100644 thirdparty/assimp/contrib/zlib_note.txt create mode 100644 thirdparty/assimp/doc/AssimpCmdDoc_Html/AssimpCmdDoc.chm create mode 100644 thirdparty/assimp/doc/AssimpCmdDoc_Html/dragonsplash.png create mode 100644 thirdparty/assimp/doc/AssimpDoc_Html/AnimationOverview.png create mode 100644 thirdparty/assimp/doc/AssimpDoc_Html/AnimationOverview.svg create mode 100644 thirdparty/assimp/doc/AssimpDoc_Html/AssimpDoc.chm create mode 100644 thirdparty/assimp/doc/AssimpDoc_Html/dragonsplash.png create mode 100644 thirdparty/assimp/doc/CMakeLists.txt create mode 100644 thirdparty/assimp/doc/Doxyfile.in create mode 100644 thirdparty/assimp/doc/Doxyfile_Cmd create mode 100644 thirdparty/assimp/doc/Preamble.txt create mode 100644 thirdparty/assimp/doc/architecture/Assimp_Arch_Import.class.violet.html create mode 100644 thirdparty/assimp/doc/architecture/Assimp_Arch_export.class.violet.html create mode 100644 thirdparty/assimp/doc/architecture/assimp-process.png create mode 100644 thirdparty/assimp/doc/architecture/assimp.object.violet.html create mode 100644 thirdparty/assimp/doc/architecture/assimp_usecase.ucase.violet.html create mode 100644 thirdparty/assimp/doc/architecture/exporter.png create mode 100644 thirdparty/assimp/doc/architecture/process.class.violet.html create mode 100644 thirdparty/assimp/doc/architecture/usecases.png create mode 100644 thirdparty/assimp/doc/datastructure.xml create mode 100644 thirdparty/assimp/doc/dox.h create mode 100644 thirdparty/assimp/doc/dox_cmd.h create mode 100644 thirdparty/assimp/include/assimp/.editorconfig create mode 100644 thirdparty/assimp/include/assimp/BaseImporter.h create mode 100644 thirdparty/assimp/include/assimp/Bitmap.h create mode 100644 thirdparty/assimp/include/assimp/BlobIOSystem.h create mode 100644 thirdparty/assimp/include/assimp/ByteSwapper.h create mode 100644 thirdparty/assimp/include/assimp/Compiler/poppack1.h create mode 100644 thirdparty/assimp/include/assimp/Compiler/pstdint.h create mode 100644 thirdparty/assimp/include/assimp/Compiler/pushpack1.h create mode 100644 thirdparty/assimp/include/assimp/CreateAnimMesh.h create mode 100644 thirdparty/assimp/include/assimp/DefaultIOStream.h create mode 100644 thirdparty/assimp/include/assimp/DefaultIOSystem.h create mode 100644 thirdparty/assimp/include/assimp/DefaultLogger.hpp create mode 100644 thirdparty/assimp/include/assimp/Defines.h create mode 100644 thirdparty/assimp/include/assimp/Exceptional.h create mode 100644 thirdparty/assimp/include/assimp/Exporter.hpp create mode 100644 thirdparty/assimp/include/assimp/GenericProperty.h create mode 100644 thirdparty/assimp/include/assimp/Hash.h create mode 100644 thirdparty/assimp/include/assimp/IOStream.hpp create mode 100644 thirdparty/assimp/include/assimp/IOStreamBuffer.h create mode 100644 thirdparty/assimp/include/assimp/IOSystem.hpp create mode 100644 thirdparty/assimp/include/assimp/Importer.hpp create mode 100644 thirdparty/assimp/include/assimp/LineSplitter.h create mode 100644 thirdparty/assimp/include/assimp/LogAux.h create mode 100644 thirdparty/assimp/include/assimp/LogStream.hpp create mode 100644 thirdparty/assimp/include/assimp/Logger.hpp create mode 100644 thirdparty/assimp/include/assimp/Macros.h create mode 100644 thirdparty/assimp/include/assimp/MathFunctions.h create mode 100644 thirdparty/assimp/include/assimp/MemoryIOWrapper.h create mode 100644 thirdparty/assimp/include/assimp/NullLogger.hpp create mode 100644 thirdparty/assimp/include/assimp/ParsingUtils.h create mode 100644 thirdparty/assimp/include/assimp/Profiler.h create mode 100644 thirdparty/assimp/include/assimp/ProgressHandler.hpp create mode 100644 thirdparty/assimp/include/assimp/RemoveComments.h create mode 100644 thirdparty/assimp/include/assimp/SGSpatialSort.h create mode 100644 thirdparty/assimp/include/assimp/SceneCombiner.h create mode 100644 thirdparty/assimp/include/assimp/SkeletonMeshBuilder.h create mode 100644 thirdparty/assimp/include/assimp/SmoothingGroups.h create mode 100644 thirdparty/assimp/include/assimp/SmoothingGroups.inl create mode 100644 thirdparty/assimp/include/assimp/SpatialSort.h create mode 100644 thirdparty/assimp/include/assimp/StandardShapes.h create mode 100644 thirdparty/assimp/include/assimp/StreamReader.h create mode 100644 thirdparty/assimp/include/assimp/StreamWriter.h create mode 100644 thirdparty/assimp/include/assimp/StringComparison.h create mode 100644 thirdparty/assimp/include/assimp/StringUtils.h create mode 100644 thirdparty/assimp/include/assimp/Subdivision.h create mode 100644 thirdparty/assimp/include/assimp/TinyFormatter.h create mode 100644 thirdparty/assimp/include/assimp/Vertex.h create mode 100644 thirdparty/assimp/include/assimp/XMLTools.h create mode 100644 thirdparty/assimp/include/assimp/ZipArchiveIOSystem.h create mode 100644 thirdparty/assimp/include/assimp/aabb.h create mode 100644 thirdparty/assimp/include/assimp/ai_assert.h create mode 100644 thirdparty/assimp/include/assimp/anim.h create mode 100644 thirdparty/assimp/include/assimp/camera.h create mode 100644 thirdparty/assimp/include/assimp/cexport.h create mode 100644 thirdparty/assimp/include/assimp/cfileio.h create mode 100644 thirdparty/assimp/include/assimp/cimport.h create mode 100644 thirdparty/assimp/include/assimp/color4.h create mode 100644 thirdparty/assimp/include/assimp/color4.inl create mode 100644 thirdparty/assimp/include/assimp/config.h.in create mode 100644 thirdparty/assimp/include/assimp/defs.h create mode 100644 thirdparty/assimp/include/assimp/fast_atof.h create mode 100644 thirdparty/assimp/include/assimp/importerdesc.h create mode 100644 thirdparty/assimp/include/assimp/irrXMLWrapper.h create mode 100644 thirdparty/assimp/include/assimp/light.h create mode 100644 thirdparty/assimp/include/assimp/material.h create mode 100644 thirdparty/assimp/include/assimp/material.inl create mode 100644 thirdparty/assimp/include/assimp/matrix3x3.h create mode 100644 thirdparty/assimp/include/assimp/matrix3x3.inl create mode 100644 thirdparty/assimp/include/assimp/matrix4x4.h create mode 100644 thirdparty/assimp/include/assimp/matrix4x4.inl create mode 100644 thirdparty/assimp/include/assimp/mesh.h create mode 100644 thirdparty/assimp/include/assimp/metadata.h create mode 100644 thirdparty/assimp/include/assimp/pbrmaterial.h create mode 100644 thirdparty/assimp/include/assimp/port/AndroidJNI/AndroidJNIIOSystem.h create mode 100644 thirdparty/assimp/include/assimp/postprocess.h create mode 100644 thirdparty/assimp/include/assimp/qnan.h create mode 100644 thirdparty/assimp/include/assimp/quaternion.h create mode 100644 thirdparty/assimp/include/assimp/quaternion.inl create mode 100644 thirdparty/assimp/include/assimp/scene.h create mode 100644 thirdparty/assimp/include/assimp/texture.h create mode 100644 thirdparty/assimp/include/assimp/types.h create mode 100644 thirdparty/assimp/include/assimp/vector2.h create mode 100644 thirdparty/assimp/include/assimp/vector2.inl create mode 100644 thirdparty/assimp/include/assimp/vector3.h create mode 100644 thirdparty/assimp/include/assimp/vector3.inl create mode 100644 thirdparty/assimp/include/assimp/version.h create mode 100644 thirdparty/assimp/packaging/windows-innosetup/LICENSE.rtf create mode 100644 thirdparty/assimp/packaging/windows-innosetup/WEB create mode 100644 thirdparty/assimp/packaging/windows-innosetup/howto-build-setup.txt create mode 100644 thirdparty/assimp/packaging/windows-innosetup/readme_installer.txt create mode 100644 thirdparty/assimp/packaging/windows-innosetup/readme_installer_vieweronly.txt create mode 100644 thirdparty/assimp/packaging/windows-innosetup/script_vieweronly.iss create mode 100644 thirdparty/assimp/packaging/windows-innosetup/script_x64.iss create mode 100644 thirdparty/assimp/packaging/windows-innosetup/script_x86.iss create mode 100644 thirdparty/assimp/packaging/windows-mkzip/bin_readme.txt create mode 100644 thirdparty/assimp/packaging/windows-mkzip/mkfinal.bat create mode 100644 thirdparty/assimp/packaging/windows-mkzip/mkrev.bat create mode 100644 thirdparty/assimp/port/AndroidJNI/AndroidJNIIOSystem.cpp create mode 100644 thirdparty/assimp/port/AndroidJNI/CMakeLists.txt create mode 100644 thirdparty/assimp/port/AndroidJNI/README.md create mode 100644 thirdparty/assimp/port/AssimpDelphi/Readme.txt create mode 100644 thirdparty/assimp/port/AssimpDelphi/aiColor4D.pas create mode 100644 thirdparty/assimp/port/AssimpDelphi/aiMaterial.pas create mode 100644 thirdparty/assimp/port/AssimpDelphi/aiMatrix3x3.pas create mode 100644 thirdparty/assimp/port/AssimpDelphi/aiMatrix4x4.pas create mode 100644 thirdparty/assimp/port/AssimpDelphi/aiMesh.pas create mode 100644 thirdparty/assimp/port/AssimpDelphi/aiQuaternion.pas create mode 100644 thirdparty/assimp/port/AssimpDelphi/aiScene.pas create mode 100644 thirdparty/assimp/port/AssimpDelphi/aiTexture.pas create mode 100644 thirdparty/assimp/port/AssimpDelphi/aiTypes.pas create mode 100644 thirdparty/assimp/port/AssimpDelphi/aiVector2D.pas create mode 100644 thirdparty/assimp/port/AssimpDelphi/aiVector3D.pas create mode 100644 thirdparty/assimp/port/AssimpDelphi/assimp.pas create mode 100644 thirdparty/assimp/port/AssimpNET/Readme.md create mode 100644 thirdparty/assimp/port/AssimpPascal/Readme.md create mode 100644 thirdparty/assimp/port/PyAssimp/3d_viewer_screenshot.png create mode 100644 thirdparty/assimp/port/PyAssimp/README.md create mode 100644 thirdparty/assimp/port/PyAssimp/README.rst create mode 100644 thirdparty/assimp/port/PyAssimp/gen/materialgen.py create mode 100644 thirdparty/assimp/port/PyAssimp/gen/structsgen.py create mode 100644 thirdparty/assimp/port/PyAssimp/pyassimp/__init__.py create mode 100644 thirdparty/assimp/port/PyAssimp/pyassimp/core.py create mode 100644 thirdparty/assimp/port/PyAssimp/pyassimp/errors.py create mode 100644 thirdparty/assimp/port/PyAssimp/pyassimp/formats.py create mode 100644 thirdparty/assimp/port/PyAssimp/pyassimp/helper.py create mode 100644 thirdparty/assimp/port/PyAssimp/pyassimp/material.py create mode 100644 thirdparty/assimp/port/PyAssimp/pyassimp/postprocess.py create mode 100644 thirdparty/assimp/port/PyAssimp/pyassimp/structs.py create mode 100755 thirdparty/assimp/port/PyAssimp/scripts/3d_viewer.py create mode 100755 thirdparty/assimp/port/PyAssimp/scripts/3d_viewer_py3.py create mode 100644 thirdparty/assimp/port/PyAssimp/scripts/README.md create mode 100755 thirdparty/assimp/port/PyAssimp/scripts/fixed_pipeline_3d_viewer.py create mode 100755 thirdparty/assimp/port/PyAssimp/scripts/quicktest.py create mode 100755 thirdparty/assimp/port/PyAssimp/scripts/sample.py create mode 100644 thirdparty/assimp/port/PyAssimp/scripts/transformations.py create mode 100644 thirdparty/assimp/port/PyAssimp/setup.py create mode 100644 thirdparty/assimp/port/dAssimp/README create mode 100644 thirdparty/assimp/port/dAssimp/assimp/animation.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/api.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/assimp.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/camera.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/config.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/fileIO.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/light.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/loader.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/material.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/math.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/mesh.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/postprocess.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/scene.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/texture.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/types.d create mode 100644 thirdparty/assimp/port/dAssimp/assimp/versionInfo.d create mode 100644 thirdparty/assimp/port/iOS/IPHONEOS_ARM64E_TOOLCHAIN.cmake create mode 100644 thirdparty/assimp/port/iOS/IPHONEOS_ARM64_TOOLCHAIN.cmake create mode 100644 thirdparty/assimp/port/iOS/IPHONEOS_ARMV6_TOOLCHAIN.cmake create mode 100644 thirdparty/assimp/port/iOS/IPHONEOS_ARMV7S_TOOLCHAIN.cmake create mode 100644 thirdparty/assimp/port/iOS/IPHONEOS_ARMV7_TOOLCHAIN.cmake create mode 100644 thirdparty/assimp/port/iOS/IPHONEOS_I386_TOOLCHAIN.cmake create mode 100644 thirdparty/assimp/port/iOS/IPHONEOS_X86_64_TOOLCHAIN.cmake create mode 100644 thirdparty/assimp/port/iOS/README.md create mode 100755 thirdparty/assimp/port/iOS/build.sh create mode 100644 thirdparty/assimp/port/jassimp/README create mode 100644 thirdparty/assimp/port/jassimp/build.xml create mode 100644 thirdparty/assimp/port/jassimp/jassimp-native/Android.mk create mode 100644 thirdparty/assimp/port/jassimp/jassimp-native/src/jassimp.cpp create mode 100644 thirdparty/assimp/port/jassimp/jassimp-native/src/jassimp.h create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiAnimBehavior.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiAnimation.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiBlendMode.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiBone.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiBoneWeight.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiBuiltInWrapperProvider.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiCamera.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiClassLoaderIOSystem.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiColor.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiConfig.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiConfigOptions.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiIOStream.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiIOSystem.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiInputStreamIOStream.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiLight.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiLightType.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiMaterial.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiMatrix4f.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiMesh.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiMeshAnim.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiMetadataEntry.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiNode.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiNodeAnim.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiPostProcessSteps.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiPrimitiveType.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiProgressHandler.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiQuaternion.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiScene.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiSceneFlag.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiShadingMode.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiTextureInfo.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiTextureMapMode.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiTextureMapping.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiTextureOp.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiTextureType.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiVector.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/AiWrapperProvider.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/JaiDebug.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/Jassimp.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/JassimpConfig.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/JassimpLibraryLoader.java create mode 100644 thirdparty/assimp/port/jassimp/jassimp/src/jassimp/package-info.java create mode 100644 thirdparty/assimp/port/swig/DONOTUSEYET create mode 100644 thirdparty/assimp/port/swig/assimp.i create mode 100755 thirdparty/assimp/port/swig/d/build.sh create mode 100755 thirdparty/assimp/port/swig/d/generate.sh create mode 100644 thirdparty/assimp/port/swig/interface/DefaultLogger.i create mode 100644 thirdparty/assimp/port/swig/interface/IOStream.i create mode 100644 thirdparty/assimp/port/swig/interface/IOSystem.i create mode 100644 thirdparty/assimp/port/swig/interface/LogStream.i create mode 100644 thirdparty/assimp/port/swig/interface/Logger.i create mode 100644 thirdparty/assimp/port/swig/interface/NullLogger.i create mode 100644 thirdparty/assimp/port/swig/interface/aiAnim.i create mode 100644 thirdparty/assimp/port/swig/interface/aiAssert.i create mode 100644 thirdparty/assimp/port/swig/interface/aiCamera.i create mode 100644 thirdparty/assimp/port/swig/interface/aiColor4D.i create mode 100644 thirdparty/assimp/port/swig/interface/aiConfig.i create mode 100644 thirdparty/assimp/port/swig/interface/aiDefines.i create mode 100644 thirdparty/assimp/port/swig/interface/aiFileIO.i create mode 100644 thirdparty/assimp/port/swig/interface/aiLight.i create mode 100644 thirdparty/assimp/port/swig/interface/aiMaterial.i create mode 100644 thirdparty/assimp/port/swig/interface/aiMatrix3x3.i create mode 100644 thirdparty/assimp/port/swig/interface/aiMatrix4x4.i create mode 100644 thirdparty/assimp/port/swig/interface/aiMesh.i create mode 100644 thirdparty/assimp/port/swig/interface/aiPostProcess.i create mode 100644 thirdparty/assimp/port/swig/interface/aiQuaternion.i create mode 100644 thirdparty/assimp/port/swig/interface/aiScene.i create mode 100644 thirdparty/assimp/port/swig/interface/aiTexture.i create mode 100644 thirdparty/assimp/port/swig/interface/aiTypes.i create mode 100644 thirdparty/assimp/port/swig/interface/aiVector2D.i create mode 100644 thirdparty/assimp/port/swig/interface/aiVector3D.i create mode 100644 thirdparty/assimp/port/swig/interface/aiVersion.i create mode 100644 thirdparty/assimp/port/swig/interface/assimp.i create mode 100644 thirdparty/assimp/revision.h.in create mode 100644 thirdparty/assimp/scripts/AppVeyor/cacheglobs.txt create mode 100644 thirdparty/assimp/scripts/AppVeyor/mtime_cache create mode 100644 thirdparty/assimp/scripts/BlenderImporter/BlenderScene.cpp.template create mode 100644 thirdparty/assimp/scripts/BlenderImporter/BlenderSceneGen.h.template create mode 100644 thirdparty/assimp/scripts/BlenderImporter/genblenddna.py create mode 100644 thirdparty/assimp/scripts/OgreImporter/assimp.tpl create mode 100644 thirdparty/assimp/scripts/StepImporter/CppGenerator.py create mode 100644 thirdparty/assimp/scripts/StepImporter/ExpressReader.py create mode 100644 thirdparty/assimp/scripts/StepImporter/IFCReaderGen.cpp.template create mode 100644 thirdparty/assimp/scripts/StepImporter/IFCReaderGen.h.template create mode 100644 thirdparty/assimp/scripts/StepImporter/StepReaderGen.cpp.template create mode 100644 thirdparty/assimp/scripts/StepImporter/StepReaderGen.h.template create mode 100644 thirdparty/assimp/scripts/StepImporter/extract_step_token.py create mode 100644 thirdparty/assimp/scripts/StepImporter/genentitylist.sh create mode 100644 thirdparty/assimp/scripts/StepImporter/ifc_entitylist.txt create mode 100644 thirdparty/assimp/scripts/StepImporter/part403ts_wg3n2635mim_lf.exp create mode 100644 thirdparty/assimp/scripts/StepImporter/schema_ap203e2_mim_lf.exp create mode 100644 thirdparty/assimp/scripts/StepImporter/schema_ifc2x3.exp create mode 100644 thirdparty/assimp/scripts/StepImporter/step_entitylist.txt create mode 100644 thirdparty/assimp/scripts/adjust_header_paths.sh create mode 100644 thirdparty/assimp/scripts/android_crosscompile/make_android.bat create mode 100644 thirdparty/assimp/tools/assimp_cmd/CMakeLists.txt create mode 100644 thirdparty/assimp/tools/assimp_cmd/CompareDump.cpp create mode 100644 thirdparty/assimp/tools/assimp_cmd/Export.cpp create mode 100644 thirdparty/assimp/tools/assimp_cmd/ImageExtractor.cpp create mode 100644 thirdparty/assimp/tools/assimp_cmd/Info.cpp create mode 100644 thirdparty/assimp/tools/assimp_cmd/Main.cpp create mode 100644 thirdparty/assimp/tools/assimp_cmd/Main.h create mode 100644 thirdparty/assimp/tools/assimp_cmd/WriteDumb.cpp create mode 100644 thirdparty/assimp/tools/assimp_cmd/assimp_cmd.rc create mode 100644 thirdparty/assimp/tools/assimp_cmd/generic_inserter.hpp create mode 100644 thirdparty/assimp/tools/assimp_cmd/resource.h create mode 100644 thirdparty/assimp/tools/assimp_view/AnimEvaluator.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/AnimEvaluator.h create mode 100644 thirdparty/assimp/tools/assimp_view/AssetHelper.h create mode 100644 thirdparty/assimp/tools/assimp_view/Background.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/Background.h create mode 100644 thirdparty/assimp/tools/assimp_view/CMakeLists.txt create mode 100644 thirdparty/assimp/tools/assimp_view/Camera.h create mode 100644 thirdparty/assimp/tools/assimp_view/Display.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/Display.h create mode 100644 thirdparty/assimp/tools/assimp_view/HUD.png create mode 100644 thirdparty/assimp/tools/assimp_view/HUDMask.png create mode 100644 thirdparty/assimp/tools/assimp_view/HelpDialog.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/Input.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/LogDisplay.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/LogDisplay.h create mode 100644 thirdparty/assimp/tools/assimp_view/LogWindow.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/LogWindow.h create mode 100644 thirdparty/assimp/tools/assimp_view/Material.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/MaterialManager.h create mode 100644 thirdparty/assimp/tools/assimp_view/MeshRenderer.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/MeshRenderer.h create mode 100644 thirdparty/assimp/tools/assimp_view/MessageProc.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/NOTE@help.rtf.txt create mode 100644 thirdparty/assimp/tools/assimp_view/Normals.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/RenderOptions.h create mode 100644 thirdparty/assimp/tools/assimp_view/SceneAnimator.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/SceneAnimator.h create mode 100644 thirdparty/assimp/tools/assimp_view/Shaders.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/Shaders.h create mode 100644 thirdparty/assimp/tools/assimp_view/assimp_view.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/assimp_view.h create mode 100644 thirdparty/assimp/tools/assimp_view/assimp_view.rc create mode 100644 thirdparty/assimp/tools/assimp_view/banner.bmp create mode 100644 thirdparty/assimp/tools/assimp_view/banner_pure.bmp create mode 100644 thirdparty/assimp/tools/assimp_view/base.PNG create mode 100644 thirdparty/assimp/tools/assimp_view/base_anim.bmp create mode 100644 thirdparty/assimp/tools/assimp_view/base_display.bmp create mode 100644 thirdparty/assimp/tools/assimp_view/base_inter.bmp create mode 100644 thirdparty/assimp/tools/assimp_view/base_rendering.bmp create mode 100644 thirdparty/assimp/tools/assimp_view/base_stats.bmp create mode 100644 thirdparty/assimp/tools/assimp_view/fx.bmp create mode 100644 thirdparty/assimp/tools/assimp_view/help.rtf create mode 100644 thirdparty/assimp/tools/assimp_view/n.bmp create mode 100644 thirdparty/assimp/tools/assimp_view/resource.h create mode 100644 thirdparty/assimp/tools/assimp_view/root.bmp create mode 100644 thirdparty/assimp/tools/assimp_view/stdafx.cpp create mode 100644 thirdparty/assimp/tools/assimp_view/stdafx.h create mode 100644 thirdparty/assimp/tools/assimp_view/test.xcf create mode 100644 thirdparty/assimp/tools/assimp_view/text1.bin create mode 100644 thirdparty/assimp/tools/assimp_view/tx.bmp create mode 100644 thirdparty/assimp/tools/assimp_view/txi.bmp create mode 100644 thirdparty/assimp/tools/coverity/assimp_modeling.cpp create mode 100644 thirdparty/assimp/tools/make/build_env_win32.bat create mode 100644 thirdparty/assimp/tools/make/make_all_win32_x64.bat create mode 100644 thirdparty/assimp/tools/shared/assimp_tools_icon.ico create mode 100644 thirdparty/assimp/tools/shared/assimp_tools_icon.png create mode 100644 thirdparty/assimp/tools/shared/assimp_tools_icon.svg create mode 100644 thirdparty/assimp/tools/shared/default_icon.xcf diff --git a/CMakeLists.txt b/CMakeLists.txt index f0640b8..1cc55ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,9 +37,9 @@ target_link_libraries(couch couchlua) add_subdirectory(thirdparty) add_subdirectory(shaders) +add_dependencies(couch shader_headers) target_include_directories(couch - PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/shaders") - + PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/shaders/") if(WIN32) target_link_libraries(couch glfw3dll) diff --git a/core/Mesh.cpp b/core/Mesh.cpp index 3b2e3ad..26d8706 100644 --- a/core/Mesh.cpp +++ b/core/Mesh.cpp @@ -5,7 +5,9 @@ Mesh::Mesh() { } Mesh::~Mesh() { - delete material; + if (material) { + delete material; + } } Mesh::Mesh(VertexList vertices, IndexList indices) { @@ -38,8 +40,26 @@ void Mesh::SetupMesh() { glBindVertexArray(0); } -Mesh Mesh::FromFile(const char *filename) { - return Mesh(); +Mesh* Mesh::FromFile(const char *filename) { + // HOCUS: https://assimp-docs.readthedocs.io/en/latest/usage/use_the_lib.html + Assimp::Importer importer; + + const aiScene* scene = importer.ReadFile( + filename, // Read the file + aiProcess_Triangulate | // We only do triangles + aiProcess_GenNormals | // We want normals precalculated + aiProcess_GenUVCoords // We want UV mappings precalculated + ); + + if (!scene) { + std::cerr << importer.GetErrorString() << std::endl; + exit(1); + } + + aiNode *root = scene->mRootNode; + aiMesh *mesh_to_import = scene->mMeshes[root->mMeshes[0]]; + + return Util::aiMesh2Mesh(mesh_to_import); } void Mesh::Draw() { diff --git a/core/Mesh.h b/core/Mesh.h index c91c581..ae57ad7 100644 --- a/core/Mesh.h +++ b/core/Mesh.h @@ -2,8 +2,16 @@ #define MESH_H #include +#include +#include + +// Thirdpart includes +#include +#include +#include #include "types.h" +#include "Util.h" #include "Spatial.h" #include "Vertex.h" #include "Index.h" @@ -17,7 +25,7 @@ public: Mesh(); ~Mesh(); Mesh(VertexList vertices, IndexList indices); - static Mesh FromFile(const char *filename); + static Mesh *FromFile(const char *filename); virtual bool IsDrawable() const {return true;} virtual void Draw(); virtual void SetupMesh(); diff --git a/core/Util.cpp b/core/Util.cpp new file mode 100644 index 0000000..3f3f741 --- /dev/null +++ b/core/Util.cpp @@ -0,0 +1,18 @@ +#include "Util.h" + +Mesh *Util::aiMesh2Mesh(aiMesh *aimesh){ + Mesh *mymesh = new Mesh(); + for (int i = 0; i < aimesh->mNumVertices; i++) { + aiVector3D aiPosition = aimesh->mVertices[i]; + aiVector3D aiUV = aimesh->mTextureCoords[0][i]; // TODO get ALL texture coords + Vertex vertex(aiPosition.x, aiPosition.y, aiPosition.z, aiUV.x, aiUV.y); + mymesh->vertices.push_back(vertex); + } + for (int i = 0; i < aimesh->mNumFaces; i++) { + // We're importing triangulated meshes, so each face is three indices + unsigned int *face = aimesh->mFaces[i].mIndices; + Index index(face[0], face[1], face[2]); + mymesh->indices.push_back(index); + } + return mymesh; +} diff --git a/core/Util.h b/core/Util.h new file mode 100644 index 0000000..20e6fe5 --- /dev/null +++ b/core/Util.h @@ -0,0 +1,15 @@ +#ifndef UTIL_H +#define UTIL_H + +// Thirdparty includes +#include + +// Gahhhhhh mutual inclusion! +#include "Mesh.h" +class Mesh; + +namespace Util { + Mesh *aiMesh2Mesh(aiMesh *mesh); +} + +#endif /* UTIL_H */ diff --git a/demo/cube.glb b/demo/cube.glb new file mode 100644 index 0000000000000000000000000000000000000000..0f1a5cd19a362ebf80b890ffae766a6d3248eb10 GIT binary patch literal 424872 zcmYe#32|d$U|?8L&Bic=g@M5<*x#>ODY3XXwM5CPS}8p>FSRJKB)>?>O36E;C_gX1 zSOKI(!6_#-FD11|!BgK~q0CUvM9;)PNk^$HwWv5VKTpX@$w<#Ysa8j+I5{;hRmsW# z$|_c}imq15OUzAGvQi2L%jqcP<)@^ABn)C}V-dV+rQFow46s(Hdgs!lR3(s-+{BX9 zqRhk`kfv&-l>E}9oYdgVl++X@tCFJ9R2`+%+|1(Q%(7Is#ALA3qYZTobqr$>5+SJ- zC8b5FARlJtrKDDXttm;ZaL&&!N>Q>hsMUcv(idu%l8#bAQju?JNn%b;W^zz|X?jLp zYH_iWRdp@cNvXvdU{An(2X?KFQbAE>Ze~el8B{W{q@*Y_sk8*dtyT)~5B3c4^!Ec> z@8=)n>*%9oWvHVR;u_)X?;qq6Z=hslRI8(unU|884ANq(qXhFj$d%v#D}ne05*Wpa zxdl0?MPLJq^Gl18L4gl)U}kP&I>?0RYNe#ow6xTsu*}qQB`XshrQFQi)R4*oP>g^T z=@;asE5ZGroS#>cn3`K>TIl3tApxHLy$rEO~PkF*tepF)%PR z*hBGtC=KFcV}ry%7$%R64U!{-VfsMou(4rcAR2^W>d@IBJ|PU!3&J3EAR2_x*)Tqc z24R>QD4PKa8umjNpzs2*U^Ivg!eIFZ5DS7qd~`J+8JHM44Uz+4kUj=vb3pE~2gyJ% zSRTTJlQ1#3nNYXF6oBQC%>?-kO$|sbTtC!4us)Ck6odK5=GgbLHP}sJYp^@2&|p{e zvmVZu;bpL&_uk%q`<9Cq6WAK;U^Ga5@y~jDkosMJ?Cty68tfPzT(pqpWw1Z>^uOKH z9TzP?<{UqJ(SqT?MT;JgJOhJ0j0Twt(jU9#q6Gs31K1ppJup7Ve2}>?8f5OyKlb(@ zd5}2BUXVR7J|hDkg9rm7g9rmV0~Z4ugD?XZgD`_QgD8U#0}q2J0}lf~g8&0F11Ez3 z11EzR11kd)0|x^u0|x^)gCGMh0|SE~0|NsKLuY`WJ1>_M7Xt$WucwDg5Ca3K6~M&6 z0ivpr91jMGB9lH=l+w(%D}*&kn9oU%fL{j#=y|f!octglo?+zFq9fFFuY1& zV6d9Oz#v{QXIG#N0|NtFlDE4H!+#K5uy^@n1_lPs0*}aI1_o{+5N5n|x9$%EgYmbj zkcg6?#Bzm#qWrYXoKyu+F|J^dl$p!GP%&rgT+bpE%gJlLTO6%tIdizsplgro{rcPU z4@ssNOpctoK6=&~k4b`d2?oKzrV@L6WdFb4f2RK5XL<3OjcQk3BzK>7+qZvT-|TBk z&*dNFouBkDzuvy`-`4o&KbP!3`+3{u;ZOb(ijV`+X_lAk{op|-Of2I59 z#yzi(>A1gdAKUZ~JD43+ik)7@9W&!KHIi&s@Tr$51!vz&tCJ}X3mFi zKeyjBS?{)w@7vSaugiaPi|r|I=dM3y_4&)q=9}UtEIS!%>gwNb`d#N!zIN-sx}KjI z{r{~L_a^<;7w3BU`g^#-kEc%gzH8&3+ldwY`*iw+{IWlvnd++_t$#lA|I1GMZ7=To z)oh->eO*2Olw-g6i$BJn_>dO$zi;As+uLbs=;U;iY|s<-+koArLp-Ww0&V``@O&lB(ex%}|`O}t#r6E1RIC0lS%Tp zU|@2qbLM`=UE#N{tc-ZPr{+(@|2?hG;|(+9BH16WU%9ledHTxRr!F5A+;L`r@p@0LFlsNa z{8uwGkE2NP%oE0haQj_tzo*RB?vVfTBbDFpR#fE7=MMi*Zk0TjE;9dn>|dLGHt+4l zKdtAy+Ir8wBi_7Y*8Kh%kK>zT-_*|cx7RbVw_7pwhDLtfKfaHT*+l&lZUtAqv0LLm z%l-Kj^^Ehac9|P5e-(c4vFGUWjaf-cyFL})nXWX~iS3!~jDG%k!diXN^Z4tk@BjZ( zSDE(`e@^5fN2?*6@6b|>N7 z^}_$R7R}e2asIR03i(6&CfRj17q(kJSbU{=!RpojOXl>;H||{XG*M zPuMztKR&-`|DS1Fla&k@C$Ikd@%4NCk1-Gb=UuCNx9I-*Z-4V#Z(LVhJpbd}+tVLD z4PVhFUXi+1-rd9E?4|qmjhlb|uKxE~_w4=qpW~0c;7>Nn4|`U$#QV&9-%IQ}Y-cXx z4`(<1oT_ZT(OY@;zrX6+|NMRZx9r*bci-P{H?H4z-@d-~aKY~-wNCwBolZCU?w9vI ziFqdYO~|vMonj)SMSQvYvaT<)N0vdY`iRB|kZa*I%;n;!P|Fd(H4_Cr5vQ%DmNm zZ+s(`{hb)QBFV;I$&ooyV(}{xh05OcmnM1(R6?wUiuY7(&et>f6Wg+}dGa!0^`}QY zlg=KI-hFKH;mb{z<+fiGkd1pRWZ-_MOzhS38N%-2GCqfX9_eO&!S-gt^=m1gE-6XA zzgRV$HEa&g&g0)JK1QluvYPd#qp9!Nu5+>(tEB(P{8`eP_o?_}@yFuN#h*VH{CT-y z>VfP-eiF*>#d1_WTb+8&c%<+8j-JEnc8}kAu_~oVl;2x@``58T3BjBzzYgxaT&Q@= zd1Lst7n02WDHFb);9&mCvB7VJT+Hf2eoX6|Gs`Ztou4r!!0aJQok*0J%;!hF2N{Jc z;^u5fy)C@#(u}t?Z=+8*9og^gXc(rWaPvgYai?yj@Sf5~owA!BIM3qJZrfMHkgQ}p zky~|{;ZDwFytAE-m&|fydGy*VOWc{|%nYIR=dv9(s_XBXHRIh8ll%F*X4FoMWPT+& z^NPUQyU&aa_Ht?%{M{+Px+Nej?GsmS0}$ciKwr;>)1pxmIcyW-d4v@Wr(Et-J79^T31y zY-~NrDRqy^nwyVL`2JyXqx_i%Dl2^MUsPrMYou6z@?6*YvvF%aWGC&M+^tueyg+GP zx1Qdy)WjC-Z}|KKV969pSy z9gdhQ_-5hl^wYZ~RnPNiJZEZ8(aI=3ypv6S%_3vjzMB8V9}g^>R$OXzU2^TsIk%^t z6K#*Sl%L$(Y!!3d;y&ln$>MWY8CT_ zyWmFK>?Xc*)+sZ*516cx+#;dZ`}D=sJ-*p~i!Ak?zbvxV<6rNZ%wAf|nxQdq-PAcT zE6+~dCwuJaDb00F$`-qtLw85*Uj6sLtsirR^&eeWI;}nX%NAara^1(fiVp1CeeS}S zpoYb7H}GDU43K)~CXsjH>_Jv`UCGtiZ|^5E^?AHblg#^;aA(eCU$%unO>2^wq%Iw} zGdpnU`8)fJXB1caeY)qI67*@`r)>LUzqfK&-DQrE+wf_jtNgO>4>@%D`wI^is_~Tl z*s*KRY4twenc|mJJr(9A{fRXYt6u5)=;HI(nF(tCF*WC-_Y5I=YQ<2_TJz6MSA zGqV+on&%&9-+K0WLxl9CAC1b+40D_<6?>$Zg2ghmbM%jFbB?*XAlS)+p=4FS@t4k4 z+8QqznmKm9pHQwGaU?**)&w4`3;N5?lZ^;>x7%rtzfdnjebG$CvAMT#un zB-tO^Mwu60$dNHHT3y&DXSni2Vxg&8^4T4VjMncvG;KBt`JE0pwprx)R-d;*2l5)K zHZ(= zS@+5ZUM?R)3=c+yttwu^c4Et&1J^WbS4|&Pk5F@Q zg``F+fgeYtIPxkod-Fnl_O80deLMXf`x}qNuO(*$U0r_3?`hYvYj+%)W2G0|mFl(K z{b>5u^KqPCrz);_e8$SS<6t%Slc+hH&aM1??(91ClwkkcZ}nW5Y{~?q6XvhG?)HOW zOSSTp(~(jUOpgw%)beYezobNJ*Xv!1m$RirRvFIfZOkw|7sk$ectt|_+PR&ASFVY> z@m}k%lM3A6%QtiRmey9m7OBGXTke`a+w|gP#`6OojlPr#h9xU*NqaCi{IqPtv5%F4 zn#z8y(;qi%U4CwFjQO2{k{&kW+jGOddMT_lE@PPHER~Y>&F_NxqA!iJd+&C<*z7;! zL05rb($8lpg3)8pk zHZf)}TeFTYsyYAheQrzVh}=0mUj24L;kW)M85bvSQ#@oWc4ZnL!()eAmT3;J3uGfy zLoVe>xat|O8`#Z#DK5yU!09bnWZrmt=A#Aa5zS(pYE8;r6J(qXA~*M0cFNybb1>jl zih){&;qeobwSBFOI3t8@lnb8hoVtrERqj^*jL5Q*%@0aT*rcS+IqB&>s1wYWIPPZ6 zuxLkkl%H#@wtm2z*M>Z+(+)j4W@cRT!(#EvsJ}dH^543-W^UELa^w+*(vKK}d8G;L z%NOTxOfUOT=JL+xhp$rCT1VyPn+@01dfrs1<#C+5y*2-8-xd2WypbDUTFG!oH`$ASrs1+!c}ZY)S?zoEHB^n&b$ zvMq5r1;%v_Sx5GCC`}5}%}TxDPhR@4p*yq3&h3!*VXfWO z#~mk$CeCU)&O342c?*TIgko+f#_7usvXriWA@i{G*wWkeAshCe);q6pr^@kHzrn7> zjS3o4g1Wld6Wv!m`t5Ka>wlfhzP5wYC5vVq3+;I3aj}Lkga2Jh;d9l8Ok2A3E`8=T;uM_D;xcoxMW;&B{le54tFlc? zW*k|iDAlAWDDZAWvCGsV*B?60uk^lXf9uRCN&OhG^Go**^SRgNC9()}F$OjjS{Gmc zHA8s@*I|DPzMH+1-5wgu+Lq{Gk*E|Pqq9!@$YtG$>T0=H6f2tcUeK znMnWY#QzuMbgr2(MHNqYW-PK#@y3TqN_(9c)IMY`Off#T?BbfWXV~8>NWQr7;?K#- z*@6n^4pyHl36NjJW5X;Nka&55?=vB#TZ>bt+%^mFG3f7i3VFb@l0E6Q&|T{lHhhim zwym7Sbyjhr%t6N4UFCCrux4G#iBxlR+fnFza$zXn^PDMS#&F_r20MW=p_`HtnMRT*8UcwF~c5L2al z&skJrZ)tk=<9cSt!v&5NrWXaIyq!C;5|t7fCw4tAPI>xyt-o&L`#Ea2lSO6-UNMwP z(9*fs&vNd7yn?bs!t^zVoFrr~Z1Zf@jsXS;W# zSBLQ@)8DRBB9H#FwF*kG^E};ook{gWAj^Swk_I<978Qv^OH|(DzmR$(dQ5MyLW`#(^t2ccNE>bTGQlk^S5F=_p_L5yy_K4|>-VkMF296xhf7 zE%R~rhxV0EtsgGX_%1NH-Oy-v#rdY*B-t6qtnQ@AYJ|Kqeez1h%D43qU;k&;yzVDK zi)46`eq7LDS9cA+Yr-46Ojc-l!!bwCRacJO|M=f7+hzULhFOAV*$Z4d3k><0U1j6e z9qZ^m>G)Og4$tIOtGo8h@b>*8^J|ZHj}!}AF~iSNW~)}cBsHe4_KdbKTz|av!osMaYD&^(zccZRh1S zFMH!qZ7=!oDQ|H5XOEApo%6X~mq~eE;c{1wOZ+_R*_&{o3Z_WzZ4nc!$=oMcCT4^p)?Py_UsW`j#Hc z?e9mlqG!3-Pkg?hUvgtx!nIRkQ@+nyaQSO%uj&)in0Tl1!z^oz4z{a$9+L5kZ8KY3 z;Z;0s!9%rOtqTGZZ%e3e=Vwfmsd*7K#l7cSkdKG2)_aYk`>lO$%IbEx9NQ-6GOw${ z>=@6>I36)Z)z^JFPDr6|27{mHAG7QHFVY%J#c*#&m(YGdZHzcKe4X-&wZM?HdB`9js!IQxPt-)tbZ^)p_;DK{kbc=dT8?)!GFDybLW)`Lb`)HB6VS z$yv8q%!&UxJ0tgmJ&T^}7OXZpmw5f|lppKf`WPe}ZOC$BSa9eO$C2w>cq4YPO-a-^ z$f(t_hV%2T-0ZhSeOwQ%Z~YIG_2Keb;dhK{)nnm(%g)BAH-BPW^!?&COSU^%hG)4M z8RlQjJB}QRSYR3v%@EwO zykLfqijTDMZL1cWJC|&FTG#Jcb8}IAhU0<;?O%NAGJ>yKRy&-V;+o#2T=%;`$>S+! zf@d1vjEJ_gmNNSM%jcw2qb8n@L6XdqH?>Qix+wS5X!A5?b~T1iR|@VP z<%}u3B|1%Pb%LGV^yYq(kO|=_4L=U9DPUe9ze2aW-G|vO+mC-jLU;Fs%fe!1Q!Zbc zA^3FZg~Y879DBOzoCMogCL2w^aMVmeq|duyLk5Gv1A~2cE;;9N2E_)>+)(_XzV+q; zqsgXfRq4;SIK4O){%C4L(=jtmwhenibE`fC{qps!ezW+OP4#!BgqW!dX6=x)J1Jn4 zR#?^S+_XHllXYVHTHc#Y>0%!po^XD0o&GM&Xh~MMwxI9VRCkVuEn9AFU3|Xf#TFaG ztv7hKe#<)OxF%((^ak~Fht3=}a5%+DbY%%T?Z^N1+tJvg}|pzVj&TjSI_ z-;1II)f94h*U!+k;+=7@ak`h#2J<(4)>pYEDFp47oM)kUVoR!qwbvgRA3Z*f9oq~) zdW-VQE$F`>8J@1u(e+_hXPU$9m28&fDlY!K{x?)F_(+}9QHZ*9A!+)jSAK0RvK`!q zbsqd{S?9;n?D=rrg_t+RA?ZRI5+Y_3Unn|MhOFD8;-%1}PrO-Bsjp z)!#yaPqkiYN^@M=gU?cVnzw&YMryoy34rsDp?}~9% zR48s{be*FwZJ#{N`iNN7$`3l*TVp;YUJz33U<>v5a9^#$Y8U@u{u{DO^ZSqX^{q9Z z-?i+(V#m~WfjilUIezfoTpl@FcCwqheu3-F{EsR}ZwU){zF3xY>f;UG8BFJ2ZW35F z&r>lkMfZ7#lTONqi`{n>c2l%+o%0({zC~tRH5q@jUn){>Z9Q?cL!L!7M|S?<%^W{U*!U`ynYT#D#i|%^ z_Aky5yymRJXxQ5uF551|dxq^AtLNKh;hpCjLc$fNU36L;w3)jiLe`ZdTJpfU2@@91 zw9a@bzxdjt#!0n5CS=)|UHsNiwsu;>uMcc;&%}xxZaD9_8#UW`mC+|{7rTg8UV;1e zITi&@^l_7%;Imz1mg?Cl>YQ#>*1OmqZj5^I^}wsec;-W?QYU2 zsx^hByMWarSS=#qQT6QZ)eD3RB`yg~?Bm|k8Blx3zjZ-6r?l=9#`4t{jID0lD0fu3 z-rS-7SGf0ylmyFXi}FTe?aAS@Uxoboc55rcedn6fKc<$m_I$tVt8|g|tML486S?>7 zN1mC62l&obdJ)5sq#eM%p=D>w$2V&uPhC>U#GJn zU5ckSJ~;YA&R1#+yCL%$ru#)%odInPRo}!-4&CCZnEU9!v=iQ5v)7(jzGLQ9rTG$W zE0;NoX{~Lndgoc7*kRVKx$3asJ-ZO)wqT7~`I?|CZ|`F}AKtvdGr#ic8HXF%UCueK zADI_*+iaQslP5RtJ_l1LYYjisCK0|*J5PV9+QKL{NoSS!O6%sI27g!CoMb6q<{G-} zRR4-C({}6YemHsT3ky$YiMH~cnHA0+w|;G1dF@I^SXOFaX2y%Bfh{M!_&XVk`FH34 z;p15?^yh|>K~=PfRKfa7V#^XPEDA_WTcIEkF0%8$+SC_fI~mWqPEQPf%%eKdO-Ht3 zd8@Rl&I0#`Wr?=EA=!7K@hUxiFgs zo7Fe8`YdmMDBUUb)nR+xz$g zjypFr->AIU$o42tV2R?qsR_kB{Dy1=U$vxC*0@^c^gTB+xuAZ~XKC6}hmc9jf64gn zc#?H>-i2I6tEjmxi%KJ=ZVs^LkG#cWlXQV??e#;s@AbMDzG!JUnCH-MytJ%aOjW_- z`KHfH|2Qsa-lUuo*?iIO=4LVfuy3atoBW-RPTXG9J>iyEQM_bC{Y0rHM?0rk{1Dk* z;`VQIYEr?XEqP|STJxVIPB3lwl5iv}?3&(H$NQym5^Re$W|+>C)-rw2@z?wy)61u^ z-1SYq3olC47?wS=xW4L5k5TKY6P!Jb=g-LG32u1Lct`N6f}jkSSz2fIn`v)A&|2}qdGEK#;I?(llCAIkRxa4OX}9-k zizvH`8SBk`F1zYo_7YrrBP?>^m5|I1)6+})@`WD7syMwid9kme_*cX`)tzT;%w}(S zYpL1Fb~Rw$ZEwAt!qWu zWOh}pEUvEJkay(x#qD9AyuxD_&FIvS=Qz20sU(jpgg-Hmwigz$$g_QRzl;<*}3`~V?zxbR(7&FKP~^j9qMvX)aBPRkwCGj z6GEQ;-KZ~dvhLD>t2z@c46DipBFv6UOnURDyF3g3hS78J3KQLUtbbjdPm}4pxHv> z!~0UiE%?ipwm1a6-y)nVTq1GuUhuU`3$KYCP)f3|Sa7t^SIwhqf8Rys$!t>>Cv%1| zhA+D~Lq02JbwzBdZB>ZaA^v$v6PjC!j746~3iWjB;NHaH+7tAMEx>GorU^`l$F*$co4uW@AB!dvec|CA=kSmED^U;{wu%XYfIY0 zBPm-HIn$bMD1Rtm+MXM@Q=z7NLz8;;%(J{i~iL3YOR;+_Iq|_s941H zJr#EoB{ZI_wSD(OF(Cf;l2;Wi&6Bzwsl{qLbBV0Fk-kgvmd~4Z{uObKl?o2Fn#$ec zi`qqdcZsEba%-VC?jzVgrEg0=U#yaJ~D z)Zsn0dTGsb!B${8F7CZ>sALnDdBi{-MS^g@@TMb1fOn)fXv-#Eth}Eu47MbTC*ywcIB}GkEX{v zy*G$4i_O{`nb4HQG*c_IW#t-$?wdz+$|9%FIC`z<<_XieH#*PDPa<&|oN#lKf zb5Whk$7QJ-Yb^_EdJa8XCZsic&SS{2Rpj;!)dHkB{lPvJ5|7 z+E@u5-phYCL1)WZ!|zvDi5N)p|GckYl*2u3>}im$e$Yb&W^G zITtQm6d3hz$~ObAn`>9Ju3x(7jKkv%vsbf*wmq0{%D`%?SagR&(j##aV@LFg1^bP3 z4`)oac#^n_;pE;_aW6ZKr?dJ?qxLheF^^f`uv_JVwsN16jFv&Ccjmqoev(ZFDN$Ql zUp}+V^ImhnVEK*^Ll%u65%0P*vTk%c$1PgpwY*5)X`yZ!o2pgKzAtkxcF5g#TD+*Q zbGfL0v-^&V52Y7W?motlADZW}o6{vW!F#XS55Z{*cZCTGi}tQxlFJ@iFz?&Aj!ljvQyt!*xfBgd*yS zXBlhRIm}G|A@PL!?#-e=#`BJMR@@Q^cAK==a0V~yf`X9Tbxn~5v%{HU4@#v^YK{$U zt26dqXS`+iwb!@4EK0ZA&AM-D#+{Oftua|W0hiY8S^3rNpX#aE3vNl8he%DR+W)&R zLE7tkx@U3t!j?;s?p_k>tVG=$+?JMXD9BxO`IzuWWu=){FTC~?`f(ws_hlPfqkI%+ zh?R^{iF%#aHl~*!maaT{YReX8?s@jo6Tb-@n#mnnm|0&KnBMQDubH&W)4w6Isnll0thE>P?O%9LW##za7}BJ`ByS{8{*~!ULF~`q#jhf#tUhxn z%J1SK>4!%WE_|4ydTZ0%X{WZ#OtZ>pTctha<;&aI+#R=6-c4mua&KU++1$=JP~Id!*@|NPf|F{j=N$fzXRW zyWLpt>N@D8wanM(=lvJvqPSU#jl=>{z*CMRetKz1>Xi53(hy z&PH+xiTIaftlHEtqjb@<3Dbg3Z=J=|Be%4SW2?FRoQ`>3b2a>#OG?;J3ChYo@;h^Q z;cSjGJ_o}l>oD!iPVY}Vur@r`xjllR*5fwEZMOCKcLgKbel$ADKP`K6&2DFN^a8O& zQ!VE7Fs+OXGq(esf{)fel1%M$*#ri*mNOZpNKoFz6mog3}#)o%6)Fzvc&Xr64$c1JztiwC>1qKn453)dBqFf zh}Rx=N7fe|7YP;8amq9$JGOhyOC+Q64n#~6|9i0XqO6+MK_~Fj?{#cSj9XQcS48;KG<{#-v|yTMR+IfLw^lP7ugwSdxYjV#MQ`22 zxMGFv556@__xi=2-g0d<_B$Ko>*{#r0B@)!i_;A+kz+-Q4^4k_m1azuy|IYZHma!M zoTt&rrujQLUw+-e$az0GKwemP^%c46oV^`Sbeg@Gx0rVwXxw4lb^1ua)U6GY8lQ#p zB3B)A`uSy{i1u-TGjcnZE1q)6`!avtrO=G+Y>J8dLVaaVP0YI=bZM&Vov$ktW?wQX zYO-1r;Z<=c{(@iQ0*^q(2}+V$j5o!?`<+bJH1N;h`2KO@HGZGVllzvI+e&dWT-DmW zW2cwc_AG{!gzX0of7^XV=(>ssTl<9QnHei17awHPyYh9db9TtT0P7_k>VB4oIi#5E zT8{8N6OBKfDpTlG%Q7z@rcJKc|Cz&P$urImws{AB+~oSG;aS^9eXYf9pI*pKPpNqO zB&kj%=3VggGKU=&qE*RZi(Ya+X}GjC`r@@t=79eTb6I7x=1ne+a%Z#Ia+Z0G&Mj~E z7fp=90SW(9kF0oW>F48TU3jr?)p6Dejyqwiqf^Veuco*x4f+w7D#(15+4)e+UVrY3g?yjn3)z*Yb z6fUg@In>}#9C>~hv*wm5x86-wTA=M>dG&Cvs5f_^x|+fP^Qg~NZ=Q!4y1Z08v0QHY z1j(v5t=7?uA8XACKO-zdNtdgOKTV2-!bj$hW#^+B=9R9>)dCwx~byr)8v>e&s$ekED3mX z_s!g{J0@<@@b>Q2I(745Si=gjtdKS54PX0~mUmmmMdfZ^p5$&> z_O>x>np{EdcB7W~7>|I9ZWhp8Atlf#+^X-rKtW>Y^DuE1EBfPPHt) zCwwJ8$<~6S^;T3{5bKPdjcTa|dZn%ZjK6xiwnk-@x%WQ~e%t%P({;1v;>JYvFX#4V zt9hx03r;<`^+|Kqv32HpQ$Oo2?06MWW5N?xwo1p(DakFu`ra0Hfv>Z*9UgD(j`lci zV)E8CDejnYq4)XpbeC$*b3Qzqv(*1N9eA0@;=PpFpe)qkP(D zsOQN^dZ)Y!S(K|#>r%0YoqIxwo%7c8D3+_+wl8)nOl_XcRWIu#{35BZLD8jwYh9-| z^W4i{MQj%Yrr2}7d2o_5i&FyIS z%S=#{Y1+8g#w~KS`+dI%9>%?%Y3{BQYox8W{`G(AyQ*^jhZlU&7bi2!ke@32asw-q z-czNF`%43Sx$9!5&bzk4X0J(_m)ThbN3p)F_rf98276|q=l%Bnwo{k(cee;AiZE4`Ia(=Mjz1x+p z%wu+Ddpuhk*1hU1-;|(xOGW3_z0xaH*`IbF+8X-RYu?t?d95ECmPk)6k`KL>ts?kF zeTBGpbUXK)*z=c8CpEm#)BVZ!;rogU6MnEpRdaGgH`FdZ%l4zY%Vdgg;r1)R(o7!( zwO>WdFo@{CBs@9Vv}%odaK`fG!CcXzOZT!F=iPEXA9!HZ9+qc8e|36x{mfMNyE7FB_4>_Uf;*( z_EO{b!HPS)>Z>Y`>vK$4^^!4oOLA$~i8Xa!DrPT@EpUE$W7fMqCRM$+x*bgpr5(~< zosI|J7Z%@`e$%4s((B-ZJHAIA{pu>{$a&(4^7mz*ROAvivKI4t|5&H9Q!Ie*a@jP~ zRhQ$nZe9H~GxU*&vvt&cog-PfulqTp9Z$V@IB$jJ+Bq#5@1569&pP@&cD>;ZRzop? zz@L>{JRa8Vf7rjXAw`xVn4~B)}0fD0V(|cOpOTO*yg$cRAGRG4IIymb z`Pyu)t`My?zTOK@U6B@Xn5223is#COH7eH@AJr)Qqcq{#qUE}$I-f3>^uI#>d7!J; zY^#i={~dSryG)eRsnCwxve`Q_b~%^Lla%Ymbp<)g7`R*~Tn`I;UU-Px;%c^Z{&h7? zkNO)`7g9|79~35Dd3k-`=_x{XFP~j=u+ddyzRgpq6sPmnEkNX@S9eN}lW5^7ui1b>scmbn-qgHtzsG^qD`Voeqw3nEY8}yy}Y-TkLb0=;(wQ zO>5mF9|(D_=2Vc4Y2CI>c1P6F+1tCUww}#gtPtu|opSce_SL(zUnFvF7vOh~PQA72 z(DenEG+P5V1ga{p%&wd*n0@Q?vj)M`__=GeqjK{Z?nvBRHp}9wq{h5V@xX+2FZUjH zaoyv(g+b`;DgRZjRwq@anjQW>X-B^ar^SlrpB4yzwNJfJB+I)-OS;8OvA844onLR_ zW=2jut@+LtlXyPfs!aUqBHm$nJoDzmXHH>>V%s*9mgsJ93E|dYYnr=z{pSp2_e(_? z2d@|1*E;)|Cw5;yzuFs3=J5BM7tXbaS+_0LAuqa{ks&FduqSKZ?%Nvep=Z|K>14UK zGH3fF`6%wMmrgr;b1$99J}*>aDOa4X^r_}I(2J z`{-+|+_Xp2Z~K?(Lkve=6yA6ky2f_qw6ya{8xoh#@%YoZNdA_o+P%*elS}4`?cA&t z^Wx3giTmar`*^`MlwZ!{N3Ahug-U^$1JkeA^Lpa;`8SOZW_ScE=53Uclsm|1cCV>y zzxK|Rcm4&JbesxXSAFu!*AD^;K`K$RKkt}(`SFfTa&eEQT|b<<achBYm&l zM(>^7qqj9S4srH}Z&$Bh-TGWnSl#Law&j;p(&SPvVoy#qJe>dv0;*tsfoE^ zvVoBS2!KhhAM4dFF)%QPW8G0ykz2sP5YhTJ(tFb!1D-ptMJ$^prf~Kh=Bb#zN~ieG zmM83IzN@aB?kdI;nvk>e>aVJ-wz|6ij`u$Zrm5}p`h2mQSFLR0t5ZjBtlH_j?bM+? z+fF~dq!4Ye*f2PDm0nQE$3;vxFRER-n11}2mfh>9s~=bvaF`i-PoCNxQ#<9R`(d|e zrVhb7q6a{jEtFoHf z6*jKD?NAU=Y+)8rdi>roC7CtcPJfSBimr2?!J%KUP1hiuWw{XBfxQ(>1veL!G3%|1 zy1VNY^PISa``P<_N+j54hFPBFt-jH+hRtr1a2(hDJFnTd?BQ@ddG@+8&$5&z_oK(8 zJS7Seu1oUh^Jr_dv!ypj_nlhSc5nClV+WSMZQnRsYghk;_lrNDt1n5YW3eh)rkGq7 zTzaWT;Lg%OR;^P7=|>klk-3+uV&fWUE_KyJ;ki>$%Zd)?fB>HUE~Q#kp&L!HZ@W5n zayNdO@;A+|wk^ov?ortZ&9jtt&5+#W!?Ma`MaCY{7%z#C&hwj}alE}EEwd%w-hAf0 zn}5>P(&wE`yT%bEUa?>Ok4SQX!@Z~{JW_d|YR=8G+$EvJcWqaMS_&`6#KRi|y4=J+ z6fFIg?)9~;?OOZu8RZ;DB({oiXJmBg6?f`28_p?bpDlIpT1^$dw{_kR`(I1cbAGaz z?cP~>BC&ayzeYXR-nn|wQ#h>(v{IW67TVco8WDyBDySNQ{ zRlQm(kXs?LV$I%thyGgJsHplB@rE;>wZ^_)>gZbc*4wKiQkku}e!8~&wyUcDV|(y< zVC{kX_4bP1XXE$3h;NwVpnid~glR4&^+lfP7jBOaW=^~9 zf*Tna7#Q=5{6ZMdFDjqKz|g?p>EaktaVzBS$w@h$lTbr6y9(!{)QN&131DfUDK6E$li!R@wP4 zj;Od{92~q?>CmB?a}N*y`2S=7zxXYsB4Gx`zfXR@XLIL1PxSGm(;2N?OjDwqFStoB z7rwG<-t^CfGnW`8zF*DZxv;Y^_Ix@2_rnDl6V4n7@>;pnU^9PcX}5z*+O#MRW`;Ja z3I>K9!i>U94lV){XRThv%qUPek%{5JnWZ8O2FB&?JW7F~ZbtGV5As&6TD4SUW)_RV z=AFt$Y%VVQEW`x`m5hvyyE%kZlz5Cbb9{L{eXY`^>01jlmohA5in3svG1DV8&8hHv zhkR)S zm#aVQa1d(l;3ya0(818a!NTxp)0ei6vm$pSI}Zsq%#`I3)Z|jKYS}$|X2%X@WyR)5 z0V74WyngKo9ETLHZ)mfjls73jAXKQRJi*CDo-0di!K$=l z9A`4jx+ZiOFe+5(2^z8}7Ad+tdFbY{RK%dX++AYTu5G(!Pvl^z%KmI)oM1keduq@z zi6agIE=O8iQp^sB3a%Cj6;(3t;NW%du?s7AR&mhVRLq~CQqUwIXk6^T@oHI+f>MN5 zaOhP-9iu+?1Pz%=4UAp5 z6ef1Lbll**vv+r|;jaKE7Uob_M+ODO@Wl)+kD^>!l(IB0?v7$FU`Su6l^PkwW?-!J zYTMsZ?)f`+Rh#QB6%Z`!>)wGbEd)GefwvBpS*Z?^+Klxhc&Bum>QibDpy-H{kk=6Y45tUXNiq=zO5cw z4w^?f=SuKM+&sfIdAIP-ymb-_RwYhjH~7C^!0m|5`921QsS}p+eO_A1u)$_&8lQ)Y zWUG+{1A|hMpbL)*Q;HiCf3TCxDuyd==DaQ}B`zr~QC<^QPC2Pl_5O8L+OeRYW%aYu zvm&RNxhcFXNMRED!eE}{aHR4U`=ptI0t_qo+7q5Bs5Cj;KfCpNUfSuMhQ+VBmE`!^ zPX#UQF%)4IT+3h3d?>f~nv{|dr-Gre@S@o+5=kyI80VaSE&u<|)Nj{g3g=8!=WBUd zvM$XnadB(k^yldeTCV3_Ybkkrs)>6i_fFekCA&EjgP>cQrmM>YkF!@qCbr(Zt1A<7 zP?=dQZXd%1Wex$x&p`?+omh@YE)g(1AJKNY=ZfI!-Qr>{DMvaI#3nxCaQXA`XtT|o zGa*yEEsjVi-*>26c==oWzN2q!rlqZ&BJ}QE?M1&Brh|f9PYh3Nzj)>h?;YDB_JWhS z)AL0y+&!QuqbQ)D!E|Vj#{|a?!Hpq~izF5Z+Oe^-tdeGMXh?ie`Sw9nin!pDhwu87 zm6#NjxEPF|M@t?4G@l{iZLa=E8 zF58`PkC#rGbH~-iW!YUjo-Ol3wtl~QMquU=tBi@BTNM`xBs4JZvKE*cq`YNX(}`)~ zW}A1fE@R;M-m{!x!7~q5fkGbBr-A~?N>jR>4O%DWUEK5C(CC5%1Iw9r_evEub@-H~ zGbZ;u|IEp-OOnAuvuD!l(`}3gPUW@92?}agu?R0@ckggmDU*1`Q0@M8Ev20onGfE* zzVvj+=T8#J7EDTFGaqbPthVN}&H7vu*(^c9SLZxBEWAxSuHBMoX3#t!sjPHad2@|N zpx4T+LQT$VUaxw{@Zb$gk4?zji;N3gMVt~Cn%AWrKI_9ZJG){^)R{xEt=tPOT4pvn zI#{%nKYP7{wW;}%yH zhu~my7m+0erQyd`q_QzA>r6Xy^>~2_-;{?Hc6u#)*7=?-efc@Y_MC(O$MVZBYod&` zmnr!)u{61`xR{7AB+M|LZg@+Ad8U`9V4uj=1utLmIoH0}ux*y4@S4|J9HCqcyamfP zt#=JQy!ZQ=&ss{W`U;HA6Q_Lr<8y$kA}n|9>r1s62R1z1eYmE&I$_D@&%rm(-zs&w z$7j~yILH6_I=&ghlhPS< z%B1~KL3UQ8ZqKsQRVq=PGKw>Hav5%BR^;P%;Mm;3U>v+T<}Z(!Rom`m%xuo#@tOz%Kvlu)hcEg3_;sZL6MX883RQsh>9S5Zi^gDCt#E+{VQ&2BJzIIaGQM zToj#ot+a8&-#U5c^Uqm3XQ)fZa-@kAavo?%5L3EzEqm>OE$g%yc2-;KcRb(9(I&58G_L z7G<8ct~6_*&zlZqrp`NxOa^y&?i?0$GELf%zf($ynT0_>kZFgz%e~hNRy{j#hN0mB zLu1EbHin9vK<`jxnTrj)O~)k8r1m^qSNrAY;~igYX6*g`Ia6|((o~(Sqa6ZGHf>wV z7`#?6cQ75CEo{2X;?1dd@5;=t9)CXF@XMB-sMlxD9xOF*+uqh7wsG6Tdwcv>NxgWu z=HXs#!IfExEe1T(oeHX}8CKojBjbKOoS(lvmi3C)RILRcs;u zXC6It*igPaEY88hC4{AQ_p1wyZ7xp)8C!UiWHN$O&fJw%U^vMbs(Yx->Y~_*GfCES zSooxcSQ*|)7csFFYJOMh>6l@8s48OG^Pk#siOe%^nkG4KnJ`mXfHC4iOttj$&oezb zQc8?B+wAH%;de-cKfr+k*9G>+Fv^1u%Nt`*8vp8Gf z$zQH#QVb8p8W=lP-1>ZM?bcb%988JtZ4J$PF6fw^(dy`(#Gj^4JPV15rWQdj$RC@Hz<%wJP4aSB~foDGV_%tv? z$ZRO@BI>yw6!9d1P zUWO8fhNs^c3Z64G==hnlD83F^(&3P>dD?C(g=wWTyHxf)dFSgaYr+(I{`7}{G%>*s z4lYVbdTGovBc4o3vicdJuJrxh*Pj)3^RCU_9WA(X_s!c@Beep3Ih z|NraPzuUvh&3DyZD0sAKn_-j9lk)l>#zD1p-?t_))a!+E2`kR(z1B4G*bBj#R}-5I z95^N}oMRv(c-n-Sfkp3@PzO}=EBW>D; zmIwOg#Tg8Ir&Ib5-L>6rZai6!CVVqHCLqYHu;|*Y1ebee!SDCV zsWE6MEfsl^aDYLbg{kDa73YlASwA+c>x;;q>s83YzQW|S#O1eZzcMa6)8T06+gh*ycpMUcD;raQjS7A+9bCh4%6< z3fFA-B`!o2f3MU^E3;bXGUYmt5>ukZwrdPW6c`TF3by>+!`)&r-$G`>^^3<1mT@th z-Zk&q)1P~cgpB=N&-|&eTfJ)cI%n>OtNrup3wHFLsF}j>q9$+c*WbUZ1@qQ2F1(eX zu*~XwsrmU14gtwK$0ZJ|xt6td&;0V;vVyYx#~=SpdA07C#*xb}vKPqs`=9=_gHQiV zu_{BxTGmC1D~;Jxj_|X5ci3`7#Z-hrXz9rrht3EeJI8&XtygGbHN(W2tutRSFeq8b zov+M_y1rC)^@lq1ulJ5ixXw5v8M>`T^^A{hh_9^50`}RF(t>7M332lo4m5Qf@bTF+ zF+)yd#f3)C+g5RFZgL!t%6@3Ig|)#tCh_)~9$NwZmWUbg8{)&Fy)+pjB0EmVc63yv zt$N1H!11)?*M}dUif-JW#VS~~ee29Bp3=<>iymI{eXjI;>-_8U~2MgFx!bH^){hc|u|bZ_0B?KxfCL9a3YOURCXyJMn%t)G{%dSvykWN4_~ z(f{X%r{S~i6`Cxn^_&c=(jJ~m6KvIF;yfX9hxv}6?8Pk2#pxm3N}m?qE7i()wKj6) z?)~;Wo1~$D`^*%(Gk=2WKhM5j z^Xjm?SD|bzl${iM2 zsQ5_i{a&+1H9usLgG5@g#*EV@!p`QQr5)2|2F7jhyu#2R@=C5m^WY~nwK1yVwQX8Z=dWm>3uLzmOH|G+V}aAn>!7ISYdVzk)!y!R33Rokp3a@ydtVX5ExX zjud;A^e(!yc}}^U(x(@xQzJ6qvYUcXM}0Xzp35 zs<5reJf^nuuis;5P~}xq60Cm-BR2{ z^p+YVB;Gtf=in8cYg?Ij+}g(br)ib(bcHvEtKPfJd7rq-HF@`mzzVUv*#7Kn-}$F! z9dVO(&ho6W_P=}K*+iFehZ#Pf6C;-^Eio%rT>jZ8E-vm{dvww_2B)xM1{FOW<&+dmB#_Pc=|)GdvcP z8n10=<;5Z-7-;yu`{mC)C12O+UW`p*V-jR@VR*61rNeWnv7na=k1@O5+}$%77quxD zu__5(ZtP(6ICF$m@a~yI0ixSd_>~!K1QY`p3swkz$l`h-cyOziD2GZjhkTyKrrG=| zNAH|_UAg6*z~w#s9hWa#o7a;lCV25G;{iQJ+Z$2R zX=2aL^z8ZAB>3>*ypmQ{7R8+XO$?P6(?m|KZc-3r^?#&yX7e;(PbbY(VeSMbX z2DwHL!48*}GfC4vmNM6EFEThkr&6Kn!cO-E(x3i4`}TCto;`aOv}gYQ^6=lsEjq{l z{W|+R>&MaX`|rB$+zr`WS6#n(_g@u(`}?bP`sT}&oql;EN;r4*?TY^oKi=Ir+e+E6 zK%sZ*{NKNx+t)T4)K}zn9O*fJ`SiL^w|<}g{PH%x|NH*G1>Y}J%Rh6uyt4Oru(Q=y z!R(87_xY$!x@@p{>-Ia9Y!BZ}-e~l_^vUL9TLT647WvtkD9XNkAh3LCa%6t(Wf zQQCz}uX%JQ7pgdH(@NsVQV&)JgU{EZ6nANKYY6`AkjRq~oA~6w zF~b~>qqo*CF)>!Zw{}(2m3B78hF0c{j1CGbBxhPTt?FC1N^9#1MFySIi`LrO*!cX8 z&Hekb;uC{Y5&P#Q%~vi4&X{pf!CNe?jD^8P!8|-SGRq)cHnORrLcsO?;~KmDHu|}H zoF5ctySTJ`-V=R0qWW;dAtjbXzu?H}d14(B9BIrak8Su|d2)4mgwe5i7u*-_U^xEf z-RoUKf(u(5bc=QkaQYj@XF^; zEfj6v@aI`wV-qkwMrT)7aQ-l~4JcCqLb-8R4ylGISyg~oIozmsy>+Npd z6;*J2czbvI=S?>9@o96fm)g#|e*F030NXdyqp~%hUR@pTVsNd@#%_Pz-(AXef6qp{ z{rdMje_3XgrLK#J!u@^r+pe{~u9UpLr&hY-0bhgij{Xcgh66fh^aNr|6r5aAlF~%9 zj&wwSoYJ^pwpy3bF^A(Z9}SP0h@3gg!?-M#wQ1YD;|5GVTZ9E#OM4=wxpEmMhbLXx zS2lBb&?$**X)l-a$3M3pe||Twytw+flHkPI+1lj{q3lYltcsaLqJ_j-I#`@sME36A zBh#bf({AJYzo*tY0APpp2R+hr5+Z$e(za**<|NxWr?FZ`CVKD865bGmCtkY z-3blm%n%efB5vP4&wCCP=sSiK=gV!~#x+gxE^bY)Gt`2-nPzT9xH7F1j5*6IjXZuUEpd)4rbyZx_+&+k{a-rD!?+uY1GwF;-F$JcKD z_j7gHznjnR$4}eZTVbhp>zB7>*rDt5@7%qAmm&3GOIq55o8=6bUzW^Tvc_9qN$w#> z%!)Iv?7i0vg3j&OW;f03l?`jh%Llr97$;6<7Oc!kRhz+IE_BW!B0Dur+N)!4x!}v> zC$4W7?%XB0FZ60#eMWkkTT1xm9TQf#beccSUB7DY_sm&B)BS@Ep2=F`)!HUTmG+KXZ!Per&s>_`~F{r z&2}@937l)Mq`qYCRA92z*t1EZQJv+irfCm{?YwiRE1%5m>X6G}NdB=Z_JEE46%KcY zn}R{%sxGD#k<)H=z4{QKct>DWIH?=YJOfdmuq(6 z+irgRvU#M!yldayeLTE6eEqE#_iO&vS-vieeEeYLx9-|sjfet%zWRZZuDM32rVY+=v-x*XO$S;G(*8ZNvfC_7Ja z<<==mTdzcAt=%LT^7Ltq)%(bh^0#jP zLGe}3KU@BvJ+p9L+0t#-=EcwRfBostA0D>t?X%6-=M_q9;1G9_VUX~XXplJ4An|a4 zmy5uHg?0+(B`03ddgYuYI8j7ncQse|9YqCIIS&m*77vs6>eI}m{8O0%zFx5w5fdv3 zUo(E-n1tWr~5^{R>>$iVrI*ul76SzyL`!Y zmGkVuk7n>qTxF!0v&;3W%K_u}VuDLvEe!QqTXeUT;lQR)iFXrN!Rvj6`3Wsxoo8&eRl76%<|{QkI$ZcceZ){ ztzCBe>aFDZvtN5|yY9IwYgLI*^~#_JMO|i`rL$os?x`&Y>{dxlOuT zqNSsyw{zus?P)5PPn>DlbSObgtEEHYh;!+T-U9ao!9aeOP*=6h4CZbQo0O`{)eo#W zziB#?;BAS>hUey=uk2)Q+`XIcJOeL7U){mt#(=!M@4x@nd@uO@_IJ5JiLrm}%UoOe zyfi0Hjl;+PrIqpXNF~qdKmRVyD{+}2+o`;sgH4SVKGp9#bdI=8ikQQ}Da3FksN=xq z^U4~53U7G@SAWQx<+f*=mLcM&PT+Dv4Zemut^;XT`{N7rtdH%~U-@Pl#efQ0`%RRGsq%O8GCH^~g zGfGu#iD%c$mmfCG*4a>?@-b{_ir?L>Q-kKsn-{WmYhiV@w}!VoXV=2*9BVGKFudL+ zHjzVEu;tggySqzc%Xi=X_bi|*W`Y~@fz-%(a`Br=%;aC4JF|P`y?gh%PJEcS)L)V9 zp-XIp!OQzT8`Pe3G%V=Yk?_MoqO*Gm{zP{gy93e);n0lY1;*{H$@C;ym~L`|{8oQ(kVF zbJcyV=#{6e|Nm0IEa?09?OZDkzkB~q z=~0E`_vZ=5#50$)>=N7INNwkKuMDCzdm7>^@Ch|KXhKPu=Ls z)+y|!59fBK?!W&$x~pR)my%n;kz*%SMCx+yF}`m<%qZyV>~b!*w_~gM<%C6^5{$nj zOhg;{c~cLsd40p>3*%*zLFrGsd@rVb8TywajkjFD7pYc)4u0i>bj< zwP4M?`jvdVN=rXJoKSf4?x&x#orI=lB|mHOVVh_n-LRNlcxR5!gJY^nfkCI{J*;S+ z!Rw@ZXKw-Tmzq^+nrjm|jHOrZz7rx4v)Q6}Nq5Mx&eKbm>%R%|&;P5mVXer5nQg1i zDz(?Xef_h>?z++GGrO%A51c*w@9CCrjWON}Zhh!j&|g*NzjSh=h+x_qD1g;D# zq3-q@N=1ooF1+4~i@%BqHq4tfxjn1Nz6Bq2dMNMIzcP0 z-I_h$Z@G`;X&c|mAzb~2zdm}pWX&{A`f{(`Oz`0@@n<`)Ebve*bukc9ZK=p)@zz|h zJNbrtXNAf~?{|5Pol;LPxwxD zim&~hv-v_|UFmO5YlR)XtRblmxzE2I-ce4owzI*G+lcxIZfm`K7_u>yrj7p;=Sm+=A8zw*-Glw}F|C5bQ5?k!xHn4@Q^a3;tr|IpScj}~U#{I@$+WvK-NLvY!ar_Kp- z;Y-&a^;pm~VW-Ew9nM>p3ZME|A@Z^Q`>*E`T8A9|6zu=~=kxjdd+I`${o4PhzW(q3 zpY?y!dAlTL9u*g4QCoDtTZsMO84&@&QlVu+?;advI%&52hpoQ7i^`gggDjOlIt>|$ z7q411vrfE7Y~ngiw-c+&vRPeNic|L6U zA6Pzic6gu6_H>z+>UlE#eQc=Ua-9ducBUMfy{fls>G$q=?zg|muk$h8w$F9`vX>7Z zG9Ipef8fZOL$1dy=1o7+6x4U~cV*A=g_XN2R!wVf-yQs8nqh)jno#b++Q*F#lY9hf zu0G5AIX`oW(W6gWLz8$o7?w`St9rZP=ZyEvw{El){9Le2=uYKoIS1kBd!DM>1?AQ$ zC7F3hOyD(8VtJHb|Mm0T7)M^_XmRlhVT3Cmk$kQ zelxIWd?)|z&f5M$8@ZW})8-wO$$NKyPj%g&U$c+-;d0D0(bswR9DiQF z|Ccl3Ud^|HxOsiYAJ^@-pSyc^`%G&YzV`3uj~@vQ(r|K-{_UnOJ+n~N$H<vsYzavc^|y>@t>)ssB3 zEqT*?OZ%vI(?x=3F*8_HKI?p!Cv`G@`K+gl4zcm4q&f6*@SNadaOmM>5s;U@&fDR+ zrEOnSP*`JoWv8}oDobRXq^a__5ZK`jg=j{6T?%Fy={mQ1IhjwfVQyTh@JX5Q1 zHud5R^ITEYqiD44blaz8vIz&y_y`ETi14~tuvztfZmJ^F?tMR&9%5rqaq@UA!ua9y z&zPB6nt2Zmx9yq0DHtt1`^8P=mDc6&7eAfY-y!ig$!gK(9ADvt3*Czywk>3hS+XVa z<8pasQO^|tp*saC9<=$!T#@m(zN7x7@F(s)(<0TLZ7VHj@G@K;FLnHrP-*!Wjz`+* z(-v*XeLh*mb7!k8L%@X(PE!Q~R`3U0KP1ESy0XHcp~aAcxog+TcGnI4iB)#v+dir&u8d9uE3U-caen=u%k%#|V7LGAdH(-BI%~Jq zMeUmM88TR?yPV zdy`?p{gd}yOZfRYSorVo6zr?cPcHJE-RR4pz`)pZdyS>Y22{a(8b|QU*e7{GaYs;E%<4?<*(7wqn-=uMJzt5CI&LFbodGiJ9Y@( zzJFG|K5_xW4J*aXrE&}$nul0!C|Db*Dw|TI-sDS^!uqZOLm5gvv?F=9Hg6nsyYIfEa|9@mQXRnmwWeD*Svbhv7(`)WT!Io zr(^CeC(@lN<>QrhKbJC`nCilH z-hUIv(DvLWCoOsVd^x`Dx7_2|HV94pF2=F;>-1Dd0T-Pc)1S8Ize_7vbNSuVl7F90 z=TAKK_UxbcbzfIMzI=J#f~@+AueK{yXI%UE?Q8n_DL=kFTQ+U)D!aY)<;L$-YWDp7 z_B!8g&ZZm{CN-x5MXNX8m?FOae*XA#qWRnJzwQ4#hU#SpKjWB{`HsH z+uzUcf4=+hy?xP!u|aj^J4DizADnks`&v>}6E^b_}_l5$gJrBwyH_t)0dC@B>ew48mS7oq>7RI^p-j!rIT(4;x< zL|T+Q1Rp-Re$*>eTdD72!?H7nI+~sAfBy-cy7K$H-V5R#Z6SMS^L@Q@qk_}t$p-tPE|LMW3BoIMz+`if>>j z;W#(#Yk%i-O9u`{me>OOGsb;;RD}=sGCp7V=L!4#$H_?#Hj zQ2(W=(Oa`ekY|FC+&Qxc-usIU5?r)S?{9Xb6qp&!0a({=MDpRkPdr#JUSFYpQ;Jc&nZtCvNuo^&ag*t&Dl) zo4=RU)vZdkzxwIbUM=e|GQx9e@4*8u@wt&*fUyzCQfdw`RGw z_BvKSfgH6^H4n|LJsd8{&!*f}QW9Kp*U5`XDR@S%q*G`l({^qKPJs)$SMEL!;;?V~ z)PGx0MX5u0;vF6P%QK!-Nv8PDEl6Bj{H^TmrKyZfkqlv47pFtCL>QiBI^DDL)-~}g{Hs!_qIdc8=gqsNgX-8B%7W)E4PcnT-PM-8BX*r1 zlaR{cdAyE!v(HRnWlRWMk!ogqs32VBMXt}%WwSG9K07k2{@Z2wX+gJMFo_-zY}Wge zBF*LyV_Cd&NwHm&EX$L*A{-AgvfB+5Ll^`PDjYZ~mRVso`M>lXX2HomY5ohViX-S8?V`kUPKP$myZ-pH)%4xB59d{x$v>20C~x1pTbbdo8vC9@`kdc{ z1LrQg)6x)U-fpU}=-xD*Da9+5CNqDz*RCmi=f2O@Yj;h2JtRAtlUBI7+&9S3Sn3iX z;1_gkg9|6Kq31sqGiAxoCChy_Z+d^=mkZ0xcP(x#t^Sv%T&rMw^e#7Nt+9jWji$=k zOJWM7kF-Q8xK#+Q^s!=NXi?_mlfUrjkdv#6%G^*dK`(Ea{amV(-y13x>n~lk>gA2A z8cHlI>n?{dR$1@6^Zw)g|7ZKpUNU-V<2$)yMbxC*KVR}J-8zkhS?OKb?vRdD<^-1) z1xxzf<{c1LQfkecQ62YO;@) ztWsoc3Rf||>HR~u+-r>9n#8OxFHZF2V_;BFNp0`hGJ7@4DrKRD*|!XMRrcIG|9j^% zUfmbPx)Kd4^*xOmt~Xy?poMK+~F4jms1Zk<_Z!_*+t7&(Eba{{wX)xX26 zhq%?$&Q0+*m|CWiJogC@tds%>5v5Ax8kq)^f$L4;&7+7ubF^U@w=?;$U2+SzMHMe%nEV-X~l3 z3i$f$nzz=~*u$#Zb6T0&wAr^adjdO_8|_{_yG={EefG-ld96p!929NH)=d)-Sym|X z{7??#2@^l>mKl<5ObpkXI_wf6r_XGeXtm~Wu3@p$G^uxXJrgq)<=i{+rOvSXoy(IK zhuKn^ja?%|(%4xHf;UUtyrOl&>e{I_W_L^u1^p3j>=8TSB#_y5F>06hY{_jqr)17p zx`0DAAwpEAQN*sW>?&)D=G$kBrSsO#=(H8^eAMVTcBDj9?x`onS?m#z||V zUvwBLFXBtrS`n6gFsOrrbB+1OcfO5fOT&_uD5?3J@nvW5=yJQ%D?PFNjK=fNJ6|*L zFa&UEPL5Ov5)iv%W6kK`qNKBTdSUXOQzlcRQtkbNSMI(m!jarl{QBX94b{hXuS;Gh zTwM75qHev<;k$c{-Y%EQV(~c?P>|WZ&dud{=}Ct<$1W}v5n^dD$;!#ya*>gNwOe_r zkcWg?$StF6ZrW+{+g-2?`5Vy?Otx zS?`8cl4ep`?Ha*}eA-Hr&RrD{XaXij@NlftqkrG z%_>a6zx5WEG9({4wCDEQy6?aD&xx+DsQCKxb^FtoPv5EzV7Sl^Y?Amy#9FjbL6XqE(chB0>abI>2w-OOgPAV zkYkCEvXPR*Ua{FTthI0JDG3B-o%P!9<$0|rc5=3npqA5_m<%?CNw1{ElaqPAmN1Jh z@b1@Zy`U0g{7&JhgtGDUyLsY*Iwe+fO=?!S$y-1A8xkTK!aSwSX1c7)nJuU9(AzPg)3nKkmV=>nmJjf#pb+!J?O z|C}ee^h%U=_sdsz1Kbli+26j(ew?}O;)G0D?uPdvn|d$H-j$uvxo~azvXg6sk4QM* zZOB};dy4;-)V;6P|C+hkdB&ptQdK2pjim}kE(C z$Y~u`8;>L<=B-!uZ@U;S%e0R{)T#XS-swRM)n?toq6{lKa#yVEt^vwYwxqSyQ{M(tw=r8)04%+u`0%k zhga!%#@juOr)P3BE@V#FkbvEeQ~W>Nz0@Lw$5|r;qCIB?Xb!* zVxq{|yKn#4TtAe;soeXyYK8HPUq8=0FkWc9E9v&`H4YJjYglYn8*o$B{b9WKB7@UL*rxw7CN z?fLKiznX6U`}hAp@dC+0|DSC5fA6(*VgWn*toPqT)fPG(xLb8|y8n~V)*1O-GI?=( z>#fqJ)mq4~9hGPfkbL^AOkD8sm1nZrE}QLqE39@pzxn?A{=aYA?f?GtumAN}e(S&E z{Ql3)^X z!8_((j248XJ&Q{FI`QcCw&m9AJYuh=-D8`gaHtuXKvb_kgGw{CuJ zWOC1`CXdg1Zhy%3oB4SjQ(j)4fXyMM+G$NO3<?TAt7N$$!LVWev%_`qGcOsJ8P}!6 zT{l&l;U&QN(8RZq!N+ykp*y*A)x`w4B$8PcdZ;dXF~^p7w%Z~Pm!7<(O3Yz1TO=KX zi9{`YO!wOM-N)^m!Fup0_A3ZzH7?NAPWIO{|+ zt3lz+XNN4_uIg}6n2@8T-*8H>@67BK8^zdiMI%=!JTmchIkHvZ^0P_v&#e^qFutV5 zanZg1z#Fp}&n-EnH!oBUxq5h0(v2uVRxjXS?fki`~ja2Ea&I{YFR^OL@w`9VVyDzu!9Cl(7XjpY7t*uXIi+zpAmGgTf zS94xA@!fjOFv?|x)eNIx-Ho@WE19R2+08$C=*$6gCWX0*$0Ss0ytlhdsjbuetNO4h z=yamiRKb|F0@HV1snpU5RXJR1Y-+5|H*L-eMi&z|tu=>gW~oh^RQO;~e3OH~&E9oK z=g!bPFjG>drCP!`=%I|NONk)20GH?Wn_^cO@RT+yA}y`#696pQp1A8Ogrdw5I;o#|Iij>ET(Z%Dgqw|Bq0SYS^4-rq%;-zLld%SbCrR{PfHS7*0= z?|N_PrBnQ$9X@^O{@(uuR=WQE$N!#n44r=Y=evjh((V6l{PXnn^68=n+P@x&i^|^m z=K5C4h$?l)Bn6)PYo=;n=WuqJw8e{`LF0&Yb)~7i-?qsbza{RLK9;n5B{H|8fpuG1 zBa7S0?ZtUJUaPEZ$EuY0C&_v^S!xSFa^ay9Dpt=lc(w{$1mJTvVL^QD58r*}Md zY<RrLFbq~JFeZS5*bYd^(jBKX4aud^f!?})}Ios2CwoNO_yjUiqtO zHU`WyTpNlEX3somBCcb4NAV|@n&3>yAPt>O{*t}|9R&s}XYO98wJTMpIk)T1x6-9G zj1Hv-RAv3N(*zY;mTte3!0x)qM;GJgy8qG-6{_B=R1Unf(lT|2OQxTh z%^jh~1xi`DucI>9*0S&Rp80xSn$Bk%VJq(~2j*OzxWL`aTrlwa#8~dn>-?5-O?uVG zAT3cLk~EEX;`FGzC)Z4t$L3tQ^(^6q-vgyd^GXB_Z8F;$68tBy7|h)z=y>&J?T*&n zeDZmM2Q~QHD?>v=MV_71XH?9+`f-ur&3XF|F)6iH%!p#yxTGL*Sy!Hq#Ox>@-Iy59 z86BH+c^E8o*R+<;TikWsO}Quz8~;V~hH9xqUZJ2|wD(uV6eeh^t}RT3yz4--|Wpa3tuR zu`TB|kf4a?Im&lCk!IcVgnN=FbdvanWFy@c9sr9?LA| z-`7%Kz0)ZwOe#}66<4PBbXCTi%FOuEbnxMFmMz&S{)c zFMj8dNq)?$Zmzp|g<+f6+QjsN>dz zs#A)LIJ`sS-)+3%wAua0=}md;yA7TgSuYW4K5;O?)?A2VaU8M>TZgdsJ$aXcms4h$+%fqPbkyyL!RtEMR+kXD}{-3Y+|9y7% zbbj6U-@k9~-@Vt?qLaPpTa?!dF(ri!8?OY(m(|?zU4B?+slxR3_V(k)kMIBgcYXc0 z?)X2y|G$m@f7$-h^!QqD3wMPn4CZ1k8j`%h?VCI23v7BS6?;}k^&yKyqsO<6E8i6{ z-1%F?8}n<`rhnZU!J$`c&TxnbgltRPFUYt<*C*}!GX>Wx$LvbahjR|ZnPEUdqIZi4$wMPk9bOpHd6{@g*b6{|B+G?;_ zfyYJ6QHgnZl(CJY%Y+qWt1gFZ5;0cTc6Nr{@$x4gt5&_sdNnCnSirmRSwL{ZiYc2H z1*qJXVz8O)%Q|IOxlH8@%e}8mI@SclL>1WB9NgZ~Q8|l4J?dPtiDHtI+nnb!HS{eu zed%IUo3vGbcAb#n`xARaTeKI91Fk{!t4F?pC2r4o*iZFOdh&|Z(mxFP}4Nq?t zaqFKS}yYHa5) z=;sLa?GhZiv&*xVylsAuI~X|9%w{n-U>##yFSr< z=#Vj6&sd@}l1K2>mAlIB*94w^$Sl3_B${D*l;sRZ*^ApeAHTcTZg}|GP6@t-z6Y;v zvY0c4GB?>c=vN3DDNo!j*|OotQU61ivQHjZ?93h$wQ7k>!fQqzX!6Gfa{`IN9m$oZXig9WG0;O>F3Vu)C8vHfEObe_b zwp}asi%hhX6@Ob+TrI8OG5gN#NZo zu(I;47iPz-&Zf>f6~fJ;G|40D;N8;KXIvP}KQk1lHw9_)zRh}2Ci$mR>FOQh|6L3U zZ5tL^iEvDuI6>sXtWqVR2Z_^MCoL9XV7qW}-Sx_~Ter?TnSaIV?9;>T{D-B2Z@yi= zZ<~sT*~0~qmnUvZddD~O*t13IX)in)7#kWy-dOIQdS-#JGV6@*Zx|$!6FNf9t+V-h zp;%jD_s(Y(6Yl0QbtNA^q@!eU{{EiY_){e_TWs#gHeTMu`)BdBe@r)%i$xV%I=)OS zKR<^-P_l56=jKwuLX)d@uWVM%%D?9|>#fe>*JiK86g+ROK7YV%vvT3em60ynIV9v5 z7UWj2#UKoi{mSv+PllHTCr@j zXu85xrQ4~oy1v|NMpaMai$hFuEsGxVHKm+6<8+hZ#XBV#$E|)7Yu~@=kVxY?r}{VG z@Ew^st5Z~uxUjh1oiQUo$@-^&vJJx(B>|b7?X&;?61T6ftc&m9|1E3We@))LX2;IG z$y)8doxfMqh25VXKL3t%r(3|n&lNJ=ojT>^^Fy9J%{YC@&c;rjul@Gz=*mCep8oy) z-Q&vHe=oo9kDh+?_=~;rESCar-t|Adb1U14cXi(`9lucUwROVto&U`1zi&T(e0ibO z{kwPn-(6q#Z~gwikHh2lbC+2nNn5c~%LJe8o6BuJCf#WZ%yDL&!Jy>R$hq2}f^%Bt ztg_#cT;31o|M{@F{C=IQ!0$@&!ZOzf8cYr**Eih{{j$UM&GWv zyno)>wB=Ys6SMCUu6)jQt66VIoc$Z1CCsR-*eGDt!Xcb<&b3FtWA|M@7ru7q#~XFJ zcP~HaX!LOA=A2l|j;w`e%S@&QaU9ewvIw86mS*LCJMBcoIRmaEGoDv!g>Gd2(vhHc zb?svg!x=Kn$_zWTvc9pO+3K_O$)1)AIiD0Rb*A3gk1cQSW>xi@iv zNE;(t*8?AhiTAAAwn%#PEVNwXJT?2KV8Vf`8WX0ae!S84mcdGiDayioVZn)c5iVyk zr(4DarcCFEXyH~aR&=|-8@$c#!6h%VYi>;wGr2FQir)QmZ2fnyMFKq`UMItj8d@1E z2kNpq9I*W>)HXA(SvhB3@rUh~(q@_prQi7Fnk1dOZKm4{{x`38spUj)F|#Spux5Mp zLgDt7nWeGDw@+KK>U+g5Ee^R~IvCEs zGRw=$+oWS=9LEwVq5t@xat@CNALqv!yVI;Qnoe9?>#;D%`?O}ts|U5 zH_Ws@TzcKj!LWAg*}v1b-8y&I)sD|m>BBl^!MnRwE?Zcl&=K-BH`2fT@0Z8#|Lc^v zZk~Pj-v(`^qPX9y{q1Wlx7PnVJO7Il zhSEtJx5fYal3xF{JKmwkKfYdQv90|4cX{9He_yxfy|8{?Rf^b>Lqd(SkF>I|IWABR zlwg>|D6~L8_|dfoTG5LIE}G3q?G9_;+&8)JqPfM2bHdIY6Bf?kO=AhaZg_sTNBheT zr&RmPH#Zw>E7%{xd5%T!Sz_zUfB)h_9a}mi7H)dt_W#f2>)Xw%i)SqR_32Yk%cau= zvtFw#Ranqa;N$ZN@K{2O;qdY2^Xvb;T)rOE=)Afq z`O&E!i31bd8fG`$HGjN#<(oUQHqT;B4nqdPu_Ic5xj`wT9o&X z3h#i#<-xU(5M;=MKYFLkXcmCq=iLFPh~9;<%X|-peLzUKqOf+@InjbMl$)^2}T(c2-Bj z>A{IJmg|0AI@4i#Ncmdn+!C6a@$xz;I zq?g}$QLG`w>-_0`GbPS#of0IG85v);+gZNMYVL7~do}BJ2kls&ox}a@V!Ve-i-y<5 zpluy@c6SIIusL5mHE4;*x}__$tY+=HXtp4_L(IHMfkSYf*V*6?A3j|8HA(sLLBrzk z*IW|qyQNERznd3iI_GN4c9|id zJo(p$FZDeFV)ECnef_uLfByf!`?<3hS_$8sQdC|o%u&8RZh!6XKd5c>A?5G*f(|Ng`I}$&@3GRCm#f>o0yLj&KA)HW?f3J?Oh?YE*Z%L}>$}G7 zo%z*Z-?c{CV&>|JyGmoA>MYl~q}u*&YA)s{Zw_@%_hBwOknm z7YIy0b$E)R${j=YzsEwK2fQ*9xjn~g;@Lx)&3_NvJ-qdtu()Gs+KnIg5|1fNs8XEt zhE@Kq&+0RRiBcNThh6m=LU^7mEvaYi7g=A?ae;gB5NP z5_$p|hHRA*3mMrK2`qFtB9>PCZN9rG+oMqSMS_N`Go1NKLbkv8mDVN`w1lCR`+C}{ zjw!ajx1TaLoe<1;5ZYy3z-XMTrF8DhIXS`U>SBWPHaRh*Oq;`V)T6;Jd}Zh^b`E6) z%_p+!7hRti>Y4xS-m_lqW2LzcNpdU-hkD|=IFfa3d{whgU6W#cs?ZYTm8}zG^j+)r zhY}NG&ENwwvO7E8{R}y{>)g^=y?YLGc~11ocp0zc5qE>}u;k7iFAa2cir3D0n>nvW z@i~jAlAi0GSX&P+p|ii4lV@bISTwj5mh-tesEH)HG4)4!ZRj%KkT0ut;VEUfb%zw2nwjHJ{q|JS<%*nPH*8@{8VL5ou;?!cXcp&1ukBuxsN2MK)u_6eXr) zYbgznmUkfz3JaE$>-<_7$>KA$(20A#R)5~i1$XY<`}h9;;rnm1=GgBEIiyihU%%si zVe_wruj_v;umAa>C`C(I_H%F!Q=+G>a6g;WiPfwJc72gw^YNLS{mzN!@7;OxZSs_$ zXFKOzFSC$|yK66VJJ`j*fbre;>b*XacbDBVVwZ66oOtc>>5u#W{I!2u{{QXWvu7_~ zW>?b+$$4A;`)~dC{l90&|9>oRS7RaA+R>5Xcw`;(!OKc|W-W>@Pr2B9vM7xD9T(cB zIl+AMz3OMmoovhtcUR_!$?xh+=gQe5>5{Vio;&xQD+!`s?^QP_UaOpU`^D^7-NLDJ z%v>yweJiQ?ceB6#({%m)Kfk?IHe`wUb^1~o+rcwEoSjV@X1Zoe-{hTVZ2sx}tAb-C zIq!X1XZkO{yh-P?O}ymVBO2!K+YQ%+-S{cfCM}uM>Bq$T?)pqi29bphg>zT$+sobb zJ!#?y#5de|S(Qd|F*|kNXVC8Odb~ z34X<=x$j6CGD}t%1SwpzdYzGb@t&W{ru)}R-zIO7QD7`EWmQx==&f|d=Q=-Arm)ML zxEmY~Uv)@iRa%6a9{rIylaoQ|k%x5dlXt6FH~;d?SET%ItGfHAHab*Ikbo92hL0YNaQ2bTlu{ER#sR7o2_V&P#pq zrB6Kjb@qSK+j(P#^Ri~|XyGFsb6JHIFKpepJf+;*MRRG+E1xOdX>FW>E2li#aC6q? z6Hh-b+r%x*u}esCb{ap2(4&OHjzl}(%b||5)r|NY#qBa@EqfgsajK=m)=V=*ZKhzL z@VABiZ~Purd(2pRz+LP0DcNs)41Z)pFNMCHyk6+`65(5WRwiHOiWAlInXOsnx9uQ< z+pOm|?@l#Q%FF*DzW>M1BNyWQj5~Y3em;L+Chz;R3q1e+?f-N6^5u^k_Va!Z%2p8F z_oJS{&85po;znDQMZs7K4=cONx9eZf&E?bm8)6{b=qpHcD-~c3oZ3 zIH&K=j+q|GJJj=Slx%9WOa%mXYx`X`@fDr)%6pQrk+uWoOD zf9NaMlDI3=YW}m@z2!-tWHgtj_nF6&tlJZ0HY-ia zKcI6bbQ44Rx~-EoXip3BxN%iMN#}sfh0XvD)5OS!2J9aGVk|z}(-S<^H445kq^Pi$ zmL8OO+WVldLFA-#%1rKwmyNbnes!Git@$?J$)(>5coLdSESNb;(~lgQvS?zzl~s9Z zax#Y@_m=|G@^qGR(OC<`Ci*(QPMe`NYt>B~zISI_EKfGf6ujI!XW!#wiE#Np3%a93 zjNV;#J)I#H&zSieeHLu)aB6A{>78i1?O0Ivgtu>M z%UvgR-CrYZsG#u3=x}25`&Ji=Gm{hiR98K_AUN^$+5n}fSwG{BS=ENOy*P6`&?Z+a*@Bcq+|MzqF{XN3#&a6qk5)^SH*Wkpn z{f{^d6g)cSYAl1kKOtZLb|(&JI>nJ02Q5gF3k zyE_iukUg{T#Gj+LkN^KM|KGpq_kaJn`q$Tf@8Qxn&u0HC`Ezjf^?Ubau3x@?~ z-x4S1yf;fdJ#+Vy0@bv(+^hvHuWubc{W){H{T>_n%|2$vNjg(F^{ZXVo> z_txU)g)^^b-qkJIrX{2j(>EhaH${jcO|FA8-IXQo7Sp;=S>xGdf=V(krG8wDuAY5@ zD_s6tw+O#o)>Y;S+w$YqNAIfo&-7%C^dcGmCo4^FKN45sYG1r_W@P-eYqL+EX<(T5 zsV3aHC-au_(;a$TruU5wDtPU$P*rp*^sTCt{<~x4?qubnhek7Wlq4qTUzHOKPzrz0 zz$j5txahw`Q?0kedf{VtV@m}RZ#~{{P(n(uShpi$2E&{{g9k>!fk#U85R~7|GLrY+~AP6taIn{Z{AO(EK{^ETs^IQrGvv#EA~!}tx}Tt z*~=lKvI@H{)Xn24nBiu@@*uV3wb~oTlqCv&$0QoPChqLm9remnHc-Vl>-gF?ep`ZF zR!tF9R{HeD<-pG^$L5&4XYESZm2kB3p<`T^>f0*4vjXp)<;{Fzut-7hqD0l&!%4H7 zOKviYb6@iIVJms-!pr{n#tel?9M8cYI)16eZ9W_ z|J&*J_voi`E%=*PUQ@Nkhr`gI_TIz}8-?nGncE73wt9&&e%?0cJ-=|1_c^Vu2D>dA zd|U-KhMb(G$LT4OdClg_qt}wFq~w&G&Y17M8=ik(U;lah->?1g_hpWqPTnDEU_HO? z&&%5G`G247|F>^zDi42)-8SVZfe*A6e>3rHy|UR_@PX^0`Gx;)U78wgV9;^kZinH4 zh=9#g4HIU|R@|(A@GvWL@u@RTC2#JwF}c0x6WEbMtLu@9Mt`nO@k*nd<3Mp`d6p|J<25xZh zKymYpW1a_&Gvuc2-nroTi?+ryGBOel8=kKHUAfEl&2fqBsFO-d@3Jx})NbiZcTDGT z>2}~yaZywF!tuqSr~AA?oJQo1J%@R%Ju)?wmQ508&^c&z$l+cQU&CtOUlNNq{CH|J zD^k=_hL2(P!L0^WW0}(DL^rmAIhn__aeOGes*d-|Vi{u(*>8nLo4+tpYo-t% zXNV$CNBo}`?(^@+o|1SGTb*3w-W;`1uI}$_`@cWmvag)laJP2<`rL;lZ&P&yCw_jz zbe7@kBc6zeRX*a4l8NW0`}VenXl8F%(BYBT=hU)$WgqtecJb~Iz1hO%O5vI(Qqpw3 z{(bpW{@>I6|Gu8D|8qEee&6TIPkMV;Uwx0eFZS!^_5AzwpWmI#iScY*eIPz`WBSU8 zLMDfLA2cNvGMbATGRW9)1U-=zuhDIp!JR3;Zkwry!n1;d{al9_l9Ha?&8xH9TOcrF z{&{Z}mP;RZtl7(L$nb=(zvEsh&)*MUw(Q@To%L+Pma|3+uSqqmn)XayY@5z$iR4wK zuVcG&`MC0YQ;gHEy8SRdaw0WO4mK+NYO%r^!#CaX( zaF^@gp-C1hj7OHLomg_YsrX1oe9m+Rg$Y-h1zlY_j!Ur3vnq8cC^&K_v#eM5dTQ5u zpSF`iHs5uPm!E7~^{(u4S()D3oa^`RE{b89{O|7le_c+Ta}?a(CpNb(pB;Tu_TSU* z@q7Lj%v*L+Mx|QZz$K#e%xAydX&bw5-`Mmv&5ObQ`lD~(zP($wVuBqMoFv^y8eqwW>2xqMS9z3)q^Ot>k}FvF6uXj`>_P=mkG|@&_ng+jBxC0! zmIgWh>Q)wdnVYvbl&VjB8cByQizm%AGka@hjGdcYm}^e9}|*WsHT!q5HR-dz5-A=-v^A1`!dKW#X^P z6Yl(JH7M92$}4{%$z$sgy&R3j1`B3pzL!uqA}1&|-N#Ab;zhlqzA=fbj}$3*D=pDs zT(>>Y@3Rxn5rw2fuMbRac;Dlvyh!FJ_mLOVc7}U-O#HCHK1J8VMNs+Sf-{xDe;i#7 zJm9$AZLB91%;uAv=$^o$q~!NO*-W;~K`rUs!KRD)=Ptc-Qedf@d9;o3<&WqU8$8~p z@9_KJmA8IcfveI7ja0Ao+?&@{oL$w*8moM`kMY)p7?=G#68$Hx#e92jk)o2_kr5uW z>$Sq7Bh~Gi?FP^KJ>NZlBY8$8JH~@=k5A5BiF6eQndA(un=@i{<3AfEaWEWE*mSS2 zJM?~N8DmRAfD%x|T{W!~o$Gh%vUUZXCzH?V* zRly9-i)<>fYYqyn_6+r}V^7E~Xg>Yn;pa^*3#5CNS+Z#HaD7w>4VYG1Q&FWC)c!JU z(z|=r@wWon9dfK}7G2}&$huo{`{4cFq(Vu%J((x&Txu}5d+c-0b6tyL2Dgtz@|-@V zx=W*D@}oslq~k(&{%qg0ysEfwPt2lKt7>zsO%Hl~F0neZd>8K$zea`PL*6_4w;yU+ zan0&{v61QZLzT_@J7?_dx)QZKXp*jk^4Z54*|%3-$$ozD*<@1_QO1|s|Gkd?KRMFI zZm)muf&H3uUM|+Xemr8^Lb*OhiGWkf4)0!9_sGFT{`6w+sAoHNRtv?jU0^<7a6NJQ zhHmj+jd~f(XSb@ccXYVSiF&Wyv(0wJ7N3IiX3mO2DW~Fh)~w*=W7~gEtdO zU%gPKCTEIHjf>@`8TWQc>Ti%1^i3`H*;#Z#$;kI|`N`%f>WXe&Ez?pXr#IelOk9v|~Zb_0+v}$BaB@s+7*Uy6(%P18>rcJd>&w>sCdVpLyt0?Y?H~oml>TO6M|{h~DCgoLTv;FQWPP^ZOTc zWEv-bnbB>(T3zJcIX#aRrvE2*PWJqty>H{@KR53txbNSt-*NN2h?rthhy9PA=i}z` zU(k-+eeZ7N8iv1@&)ffha#?@h&v%C(KUSKQRw$J=StF0LGj(2Lr=X_nNk)!#h_xUrzDO*N+CA_wM-Z|8sSE{{2168WiS5Oj>RvIB=f2FLY| zrO|6m&vYJKwD^HVxAODCMCWS}XBU;TKf1Sfclq``$EFm_TDH~veV?8jLkr)tdou%7 z&fA@Mz}K%J!o;&_sjdM-)%|DZ?#)`8y*n;Cze6#PrSFjQyVnWNHgqmL7CEg?s(UMo z{M2qCVTFlXUX=!mvPkUc7gS{xOl&Fl{#IncRP_DBizzw@E-q{fSPHBo-p_U7oT0dE zW~R$~t4@vL_jhM0PMCRH%)Vz*()^R2VI zY!A(ut@6A{b3&sDZ;e1m>r4*~1CKTdxjl+(f?HV34zwPqW_{oM+Od*dQAyGJ-r-fB z%Kv}h=iA)%{oj{@o&OWwT5a6(_s!>@->%mG`kH_5?p@*Hz_g}=o0e2ZB3FBI8$=%apAnPXVzUa zzm=~knQ-ZBPoU0%4GYeF@9(ewC0=hU|L@*e$8*#SQ&%qJIRm5k-NE=L?T@pc?mV326u^>CM&ZV+D!-&#pSPX67o z^@0vu40(MoH}o?x+&$}_b!hVo`a>t@G?%-h;*73!dJH`C>bi+6?=bLuaNJMs37=NZtd2V*^`(q-MxF5akr~v=J)eTrb?Syn(toz zd~=ijvgaNZX=cTWMT_Q?s50D(vYzWF_xJ4V>vva`SATwX_IUh%sSCwla?=)AU1;cK z0f0x!-qZFf~D823j2NbtY+)($uEMk|B1Ry>UG{2KKG~0Bjk&!w zC(ROK*e3Vz*}J78?8z#VzPxr5V-j&{xDYd^$;U%%!skR65vTrut39(!jGf*IdL6F4 zyPHupn5XJ?2A6V3zA@Nz;PIrgkXLIhUyjD1_|I;#f@|=5Woiyoxx4BA^>v$Rt?lYdt;4no=Aw7}t{98+d~Kn%i&*TbDP3~Kuf?ua zDJzA%+^ao#sUO$&oO@BZ&BE8B6ph(tB(X1>CAg?MeA)5S!J4dwdm`U|OS{(o`}MAM z&o4i$+PiJrteYmj*O$8ec#yV$*~8_@9xL_zmA1;0w!S**s^Px>gpbmd%}oz&6$<7A zD?Oa@e5=OyxPx5Ei+d-E@QCMyP1IYYGl9jHfkl1E2S!I8oeoZ0+xp`#?N_Fp*E*SA zpupgqAahTDpT6;_m|GKidHke}lj21?f7&P?6%?DcW=~0xuYimuUwdZkb%sr=4^~)g z@YFZY(Q3QPE4Y|z#?mlO!RMJt9BK+jB)E4gZ?R!Z7G74dU@@~`6@Njozk6w-qf5zx zppFxZm=pG{t5mxr7tAxy%I@k(7U4rxd*XhT{mMU}xpaYwwp`k|qdqPfD~@FMoJmZx zo7?w#Q%m#eh|dfwuCqp7cB$nMtZ12dpgaC{i>0sN@ymWI-7ZbIV>>f}hvV7Cqq1sV zYzAxUvbJmH%sahn+a8IO?l!mckC)o>OUwwKJX7y~W#86oUYZQwPVfI*&L_NC|MX$j zq}I56zRwQ}I$l1j|9{#3f5CT?v+7^ITq!g-w@CNG=cR^h%2g~a+D-{E^XJw4=VGwE zbI5o_(G$a>18-=lLzq`djpI(VoP4J0*@SG&qzp>D@NQ z?w5DHZo7XvVDjX&^6f*K9gH3lRvoKeZJCOMXXzoZH=8 z_L|QxeJ@|>6xdL?cI&#m-`~0D>VB*G^2F%r!7Yum&)%K5^6ISB@`AO>?}Y-Z)=oKO zUa}`T5j7kn1b9Ppr7T~zi-}^IW`D6M2H{1X|SA{*1Po7c0;coxy({cNW zXZ`vnw#*Fuwo~%ai+&-88pD9MS9d9w^q0mK+dQ>95!!QDx>wRwv)fKd!D*U;#GJf) zi+2gf-Mw{Ty7E~a%g^4jeC>}vMtEr+`aDPU(u}C*b|)*}{H)Mgs&RRVk+|-m$){c# zOgCg$ERvqWAL2E0*&!dnX1~eyCV{(SQ#RCJxsxVY^H6$no%Kh>)TckM9*dFsQ>nFE zw($Jk5Eqv-slT4C->1{+zuY;{&pt8DPq4>>^FaQ-fB#;-{8(X=_J6Ok{q1v-{~s`P zuqWBY?Oi2waFdSGr>E27p9y$$91puApva)WraMQO@!prii+BX=?mV2Vc(2`=fp2!Z z&A~#BfMc&cZnHmmGb{3Y`lhKOmf4ceEe`+N_?_`RLs>^d*>evM1FO^LH|CkoO!>F> z&&!uG0((WD)^>Qg*p|JxH0fx+e!<&6?M5WhEg;%}+1W=L_UMRR0|FBr+k!%Ti|9;l!%H&&)Wkm)`vud%Le9&Hs)6 zBn|7yf@|g+zqrt671M*~$v@w`T^*?^Y-N6r%dkIU#*|^zD7j9^KTl|IfFm`ux?c`X_YdC%8mx`)j(+YUvY&jb7VZ7yLi`{}uoF_Dh`0 zA7@rZnF`)7M|(x=~k+Ms*-}J z0*hMv3C-^jjT$?5RlQf*tYWFTQ@h7d%uwm_hoy>a#=>c#Wsf~g&m1fF5a(uM2W{#N zYTWSpYf*9W<98f?zRtb-_xJvP>i_@RTa~Q%YBhiP1W_NvBJ(NTlt37@xMwfC-$ANWf22*k$1TinQG`t>_JiAoeyLZ~YwWl|! z9X?;vbB-&q=)ood!9{Pl7!}SWAO0BO!7V)@n!(~mz$MewGnPG>>Atm7n*C$jM)kc` z-K!Qhu)6q!SXuKdsi>{4uH3OZMsI%ozsUDxGoHWSS1(egFXZpt)PFrfEz30MBBW1y^3o&Ye z5!cf8e*W<~q|4{B%M?+i$qPzfq4;?glW(fplQBC%>+crE8sl3Jw&}6h z`Ck51(&;1Pl>W@$KmE?PcWR$;C^x^hM!R=rXNw@?+2i7YSCv8{CY`xt({JasxV@)DB7twIxqAM8 zS*50JowJOB8lG(CRQ{kO<@Rosj^e#F%1L(rV~mv!`Ebg$7KW`tW zI-Zyix9EY>8lRpikNQl?7r*Fy@|E$*fj++E3MsL-0$Rq*A08i8^%N1bReDyr?ri6Q zBo~9e-;*^p?j){%z;q&4=1a1QsHc-`f$l?z8n%_U+qN!S#8MWz_j%sUiL56UB$w0# z#xJ?)CYHWbA}UjjLNTPc}_7{rX;apv8}`f z;RIH77NOK-B4L5Ia@`JS+*(m)yqPDJg@JwXgLiJT=O`Vij{mNtSH>?U$j#k-q*z|= zyKl1oi~+Vm8)MoAp)Xs@R=iuLmAbg4^4{aQVJjt199kk7(IIe1<4JpDU;FI-^8Sm`!eURv z6we%A)a>6QnPHHV)nfQpEK`E9QtrvDA37C0ujSi06vJfKnmhR!*{1J*FtKvE;DnPU zyH_cnRQdV;wDZm>mv*PS>z!F2w|7^}ym|BFqO(2O_!V9s&T#3RsKYSBospGcUgu8D z=#GFEu4F&E^G{z{-s|>VJmCuSz2(gL*UHApK40|w^T*Bp_x}9* z{$5!8RUoH(Om?>JflD(AvX3T3oJ~*UJGUgW?X?G=+hW-Rxu4yRTq|9v_T~Pa4u(d~ zfY8W*m@{E%K9A;Gu26KeT2^tYll^X6Cx_yjXC=R0_S@H&&RC<{@y?dv<)$)~i$8zN z|M%$n{kp$*YpSMBt`AaEQq9Rz@igXKl+xILahH~ITeak6kp#t(vhu*hFA641<=Jc3 z3bu))sjW2)6`Xj8Pk`YuhjjDwE&c+to=3K<5AWKrSHaaqp2JA#X*jEpS+-}J;}LfS zv&x7+=lB2n$6xpBX6}W@e}(TITAiHv`s?xed*2?Y|NHuW+;ujFMM7sUUVbR9nGj*8 zD6~VPl7-!aa!umpEaLk`mdCwd}3f& z?(6ZYQ)$A@@2wx1b_oiuH#=Wk5yxFoY}ab8+jC&+HJ(q_NDf@F_1 z(VP0tr7fLuY4_UetxJBqYjixq9OE_Z$qe&iQ;UXv4pfN*5QdjWeR=FeV6vUQKIuKYeE3 z&iFS^a*p)eR4Cte@6z8~=T(W5KAn#Llas{r+UrKPb*ug}|Geeb^Z&gu-~a#HVdhgW zS8P||J;b4T$cjxV>b=8?rWVhx#@;_}?=+6AmJ~L$5-fjl>WILBb$(M1WUYPe#B+q} z#gsEDUncFFt6$VxUCe>&hS09bha02=W;{!_wQ`=&+TxIq&3nR7%7shq zqj~)P-R~Qo-neEr^>g5nb>;CjMgN-P|NXce&hBRXgh5zS6B}B_Rf`O&UDUUoN{M@`Gu7Rld7*PHS)DAJ>xa$j&2B}qKZeo ziGq#K(~3F#?aZ5(&5me!8{)0to_fbEuJwa~@5>){hW=-ku2{vOJb|fjSM5$_X6~*g z1tre%d_Fe`!DUB8Bz>Ds?1`4<44gQrYtQAHkkE@Kl?`&z1T>9b&z}AH?86DGbLWL{ zhHey5WRqgMHm_^tkp)6Br`4qs_I~dyO$t@yo9}v3`DoY^$J^!ZTuBZdX^KfVbbKUq zX2>ygSad6~a0?tvpI@7Hwf@Jyzsv1^UAbHQFh-xvA=CBimy+5Y*N%I~PB-(nGUVE^ zN=b7ehasbqYt~1mz~@P}n>M5@Gcq(bR5lXWA)qB}e5oOz#e?HSl}k&s^7h%6eJ=Jd zUn%q9TiT{HzZU|lmbx!i(hSyEx+2Y|tH*|+EkP;F-6NSl(5Pe0)Zpm~sb`w+X1$&_ zW$N@vfkJA6p@Gjlr_StO{FhE?LJ`&fo9Y*rc-J6oa0Vj-rf;$Dzw1m-d^T=TtlnRHZFQo0&dO&Z4}V51Q(D<|WBuD8mV@fLEOz-HC!{gvxcuwh z|5=rD$69uk>Z(T@mfn8&t!!WA-|6+AueO($H!b0@I%xCKB=Z~NA*)MXy9CwJ=DC+B z$!n<0*ut6?s^<1g#JuZ)NQ;hYRIGltR&>j&3l0K}kB*tkTjW19ITs?PrgBK!tz6nU zZT5q7S05FgInE&*0uwnD5;n6nq*~e7-TVJ^eLcJV&p+w*zqWk0n}7Q0zeBlyk3P3I zDi^Q(@q^=xl1tMw$4U2IvrX8wo5NeumQjJ(E#g*aQpL>lB(waTH`h%syqUD#mtT`oJQu|j6Uouk*}aLY)%n)ir-G*r1s|LHelDYul1x8) z=*rW-?nup6bSql1csECzbdo@Infl>bHg0P|a|+)w_Svj>WEouKmVZ~dMYU?nwTUtl zLk&Yim~I@dUVTQUNtLCQamDu6W)a*>cka&q?iudVaiY*}$pgs+rQL@^LMnsYww&z~ zHjwQXbX}6K$Ke$tyY_of7sqTyYxR>Wt{vT5|L4a?-P(Wu4&Sx8x%+$h=^{(HWwU1o zyKG52y^BqtSY3%LK+IX3L*0wjNporF>ogaOY)|V(m2=J)OnhHQxr;74)Wi|l^~TJli?>cj#Te zBGzgtAR_2%Gy6q>#S;My{moI@Ov`*r1o~%QUG`7bk}F!^*|A-9+tMnhRkIK81gY*3^=_&-JNq2Z~a7<)-Ah7uRt;i|2EmSr; z2r(#5{PeawzJ5#K3 z`98N`%htPh=0qI`zrXMIySvJdWF+{s3k3?d1@C_EEx7l~JG&1K3@;2X#J)d&;gH?` z0{$Bu?Hv)XlhUemqRYe5T$=t`FdWmJ(a0tzu5`#s=gg+hTB%9TN;ut)oVe5ApyW~g zZp$Sm7mo)b6|q&tIcxv^%&+rLwVLnuOfRCg_1UMz{`SI}_f3Cnu-W*)j{7=u7kYLJ@KeoNUGYs&fg z6XxhX`ug!@iIwE!t><)P93G17bU1#%@^-Prms5&9zN`tFhgeGAK9r1X;+W9x6s?uS zV6|Xn(7x+)bPito!GB@C(3%r9b{-4=&+>V4)`XW?XM%)_M`x!aliD)9HK(@mJv+JP zX%lyTnd*+hv~8{Or&p%!;8C}4lTkTv&l?`+-&MKieffbO1pRbw~%=5P6X`}gZywYpMeW@+`Ay=K{FPS06dd zxhel{iGb3?70Eal2?aarAMY3-Mzx-M4>7?s()%>OzgYumI;@I`~_t%RH9%5yf z^_8WIA*gxQo`-w3v+sp@<`XQcPvG|jQ7sMEh+v`@onrt+%lC0pA#IF%~e4lyZSdR8|v_`n{{po;p+ z9WnE!HTW+?!OZ`jno8zrV|<)xaOQg;h`}p^UAk zLBc2G?$=i5RWl>cdi7`LJI*c_KJcMd^?SA)*)3FmIoaf*OZk0t_X5` zeO6%3M7`Hc6U-w2Y`FIS!}I;~`0IbP+wXfAR~>yOHuK+`HJ8ud+j74*DJy>_r$<}5 z>DyAXoc3Lte4<`e`5NnrsT}XHkl|aja=&!gGG&8|)iTx&f^)m(@PB?jfA8Pp|9{B; ztN(v=|G(GwYq}S$-TJbk;o!4n4H7>3r!J*GUiWs{HJ6=6vr4B~_P%{q$rkbI*5scn zn;bHpIr%8Pz2&$0-=ov|b)T>DYnIA&D$Y)hzqhKH%Y06~5T{9kgS38hsP3N9$Ax-q zLRt@PUjDSW{P*5vG7ro!ZyH_H_T5BAOPGqe_Le6irNPb$-r4%4K7M%Nu{ zPn_Ai)6~7kL4b+tc*d&W*H>?ESozURbZWVM&EH>Fd%ym8vswR?&EI#c*L%*0eD*l; znx@h$^?OGd{3b;U&S-x#C3>Cqd{`&jp8GlOfSE)(-cot za$zny^dz7Ef_%`gcY^N{uAMtR`&zie8IeySJ6La+_~u5wy38fGbjpW+SHt7y=I75y zUHSS!W5A+9xo0MVIgRI&|9Q>u%W!)0?4jP}ISOs7%;E})wNJMvpSN?$+{69b;7-Ml z8EUGlr%YPwEVO#tFaCC4zuZbT!*?ec!%l@%AJ)}U|5$bQr*qogMD>(AE8O>3ew{5T zSn>M#=gQdEMO%*_KYnlTZ3BmUuj0@1EuS*Uc*&B7LT!vo3gQK|p zZw+lzOsCD>dQQ^Y`Ao)3)-31Z<0^ukyDmI5Xt#FYn5g%sBpV|EX^xWS1 z?Bw;=JzxBL%s>72Z>OgfyJntyzW>MD-{tc9eoZa{Z4QDB3XeZCMC`~(i>YMD(|<8@ ziMPu}R{^DWo1UDFy2@-A5nw92V1lQLgV7J&)_*ZrGZ+};W^!!Q zS+i=g7$?7h>Xx{*+vESfdVYWZ?%)6ZUjCcv_~@Rs%5JwMyCW-hz1US9wEj{Pn=ONb znbWx!#*=I(7`D8d`Po6j!}5rddG^+S2leYe)&HFT@BIJm|9}5ap7*?Wx9pR-mkvxj zk$ixUVT1n_4uRq&V##W$d&5^psfGk-e0G~s;c;N5@*K|)(T&X0uAIt$t?FMNkb2y< z((s$=QXP)~uYX?!&lq$yIQsJ}NjT;c{v`0MXIf9krOTIvC;kDA-hYk%FJ|!X!Si^g z38nSr-#?r+zdyl8NmVKBT9&Pc#D*zMENeuV0+}aC&RN=9s;IMw?_!kxqK0YAuOGks z^Y3qU%Ed#cuPQNJIQyuw{m6kBBXdKJvpzpI#JnzeYJD$h)eX0=!W|Y5&9%y1_=;h%0Au?ZpVD%tm}ys)&(su${fIuqbStIYvX)m+o|D~~ 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%0AMefRkc`w^ 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�E| zbKKe&yW-6m4I=_v3}Te0l`Z%i)mGQM;^0}Xqz7^hmcMUDwsCGQ^LehJ$Y%IdhJypc<2hOm zj3N#yJ%<#QZaciY{Qe{M{lBlq%iP~%x&Hd}>C<=b-u?OK9*+87&mQi|JMMbt#NnmmD@V6tH)M zWf?S9>Ix~f-Z-Psv_vPS(U|}J=}lkm9hbQ5Yp%@ZdCkVN>Ts}l5eL6Rl42PjL$G<$ zAH^3oa6A?|@#akH5r^j{4lPcK)10}F6gjuGXbH0jr#rc~2+j~t zT;V19>`;>kQ%!~qJ4;ANIe$3!r)@e6)x9l!kzR(ppC>dg*`M(%V=dR)R#65Po=UYD z%LEF_%WJ;pi7wg|;wF`R#4IIINvP%C@>Ws7&7O^CN=)ZQpRdf;OpTm&FW%#oN%k@; zw`*EYEPlVM`0?S(9EY@(-PJsEows&giHiL?^@Ob6`Q>i86|xkxn)bISnBpy>$GxQ zxXaGDaP<@oQ3jjsW}&|Ru?j_+FPZNs=O5*a=}LCxb?@R}FENsKmNisL`d0tZeE*Nx z^Xm=g-w!?Xx$NcZr~e+lo4)_oRFB{0|DXN;y4!y5>Ze=2T5j8U<=4&S^XtAnOSC*O z+nj&@-?#7Izvp|sD{fmT*Y&VvGp%<;P4#KZ-5R#pH{yi5t=*2@k3!_vnkI31{Mw;3 z;h;)`f?nvcTg`l8R=GQ@nSEP3ZfrZt>vj9`70Jg2!KDQscJX(A<5ErwGxy!&w2jd% z;=tvnpFVsju$s%U;Je*(pVjg>osoeRG__)a{Y#(Cn~m$^J8T5n>$5YrNN{ zu_-#lap^t{@qS-B>BCzS+rsrWlMX+0c+gt7%W$gBf32B0#^-yY=Y86ueSM9UX0 zSbO!drqI+p0lq~JN-s;9Cn{YpwcH|4ML>Hk0Rf1j?evs?e%F!#&p8LtyH z?f<>>mzTHy`Tl?Y|7Yg=|6jhVK0l(IW!|##kie}$h_Ny3w=hI0h(W8wJLlsZ;X|z*T$L>xSHq&R9h?tqdn=Zm>(Ezh zo5D6tl|zp4>xB^Y21W(Lsb?6Z10UY@6JT+E4ZCZlm<-tosD@21XO_S4Vk;3bs>iUtSTj-)h5 zbS&)T>M1BSU~pP)Q{dK}ThnCjVbDf7yJ`zeS z4>!>|ar4OBYG#H-qD2Y?97FwSXlGWEH?zy9Bcf38eoD|da{y)?JDK#@gw8Hc}(?d0dj_*?jH%s3#bd3h)6 zVZ$w4Rty^}W+-|*3~J%LawR7}iILxHr{D>}EL|5CD}|5~j0eBDsOO(*QdLrLvR%Kp z&2)b}E`*EtkqOdZZ6OFIsq7O%%K7Bf} zWrk$q^e!ivTik~i1};3%8XEfC<-+BU7mjmqshx0=Xz*MS#9??rRIu4bylV4_bcW6U z{N2}1wUkYta>@2tWY5O{me%7EoHwnuNG+?~JImKZR8LZUSr7*k!?KX_biJM@!Hs(j z8dvZ5=1}%{jjXw`mci9IDKl7^I9E$^a~HG-y|rivP+!8q!H}-tp!Lb?BIgmMtsc2c zXGm_|Q5~22joEt9E0@Dd&D=h&OuQB`<7WW(g5Aq%HO@43uy8A_v+{^w+#%+Y;d5ff zetY4<00#{w!RQDVg=Fm|FBk${W2UyuKF*NeTKDH@dVJl_wwcp6c|5%L`)AGFb=nK+ zKi#~&JzxG^|CxEZNS-J<-IT}^;}~MgGk522QQXQK7IG=rr6&R(^Q==G|jPn=i#_p z+N>k2VW-e>>x^=$Ak>T#kcNH}or$?>M+VqA=&?sM0L2$F!%wi)? zwX6w`cNE+gX;@Y8ey?`}`{tI{8_nE=9CUhJ1+(_sIe!T1axdWu;%~Y%YxiQQP_6}@ zZEZbARxx=@Eq0mq?C$4O*Cv_kSr)8S4%*$X*R-yF@M6WPts7luL~c`2Oj^9HT3d-J zvq#bASb|zi#&yL{YsE$`Rd*RLjh za1^Pl+-FyB5kF7=kSGg>#NFvy6IZAo(DJQyIglM%%dYRrGw06T@+c)Xl|#0l*Rj6l zaPBW?6?mAnkj+Dor-31j?ZlBB!5+TWt4teroRef&z~01MyvQ{~*TCIYk&%b>ZV8Wt znG%!AG$FwaO=mw|umAg5f8URsIr9(l9*-=CK3m+^Nh zvYEq|`t#Z0-LJQ8Jv{po-_~nfjB=r;Hq_3z^IWF*i{bOqDTm^^t5XVO4=}IUnpUQI zEU0T{XkO@{XB{7I?GQ~owousi(8bv{K%@ncuz4p!@6w!5?I z8aD;an69Lw5hCgsny*sJ)54(4q`u(9S&1_3kL$Qv+L!&kf0tRXLqMRz_1m$|Yf%lx zj~^CfwLf-QaA}>@dHvP>&mfgGV^{=~65654)tDbMK|2c;xy8hRf%fG+BUtazD z-Q#Y*j!i8k&u>jz{JZ_~#}{V8H#Q~P2{IZm`kOEP!*<3?KrX?h&p`3Y9Gm50@7Cq< z{w#I5P+dN|BVuFWExjB!hi`YL1aDCo)6E)F*S`PxX-iJi&peldf9;RS zHZe6QU(jLt*8IG0YG3RwkJHc2?m4s4Z}#%(SCZQIExqKE+3CD2=d9mG_pTGV)3=8U zG3{)g$<5u+Zz*i2v-OFFk!H^Nex*AN9UN;`MaHsEm?fcpI5h8~Sn+w&?v~CKUSFFs zPv&|vemK%KGjg?PN!NuNVID2le(srL7iioOZE38iEco!q-;jOvQ_amo{$08LdfFa; z!I{Ns6Kqbdneo+St*y+X7?}+s$a*jWKxbwEn`c;mnA2K+pFr0h) z%Lw%CUG(<{fUB0_d;v)c@vE$c8mC^K3-$3a@ba3)3r7R$!`x;6cQ6=L~qhp z;*n`o=ypgG>Ie-z%&WP2{@u;Xry0I3nyJ9Rqm%da+4_r%ILzha|2%QG|MQT)w(rBs zteY-}asv-^{CG0i{PGL=e;>;m8av9%m-EZdza;(0Y~rIES*t%;`QKYx(y{ma>#tsI zLBfUoN=ErYY3nAelz94n+5s=kGg}3cXBeydSxj3hb!NkDsTVuM?=#Q5`LJZo*0}8x zWoz1R^?g4s6S8afn>!8$hfDG{c(e$Lsm}<$oT%!gd@Z_3P9c6b}6Ooc2`u40oRB)dz*ms>*j$UUY0&BeVPGqsP;D0%SQH zdA53Lrt12fIn?p6dh?}s#qOa?y%;otbOdBRoqV9QTYz(ePPgio$cWI|H{raRC)>7O z%35p6B(PBA=i~YNem>iszpc)+#cqntq0o-cD*uY=e(CT3*Z%+S|8Mo5UrMJf+IUt% zrNU%d(Ub<)=?EmGOKJr__Vo;<2%DM z4wr(0O?%bY?i{|`!Eu}+xV@zojCmNM6Z6*KW;S7aYDsqg3$F>#sqr>!yn?lRUGp_xE6SvQRkmv4UkjhqKE5?+grD zK3<)V|88y;-x~BeY$Lbpp_*upB59{`m0$e-f9}7dyLSEUZM+QXiHqz{*d^W;EX~vw z5fqd(cFoUUI_#khs8L!78>!V?`zAWIKjs4n0RAE5s8#7#TR0Bzg*0+TL5bdaC5{5Cxq}Z5gR$ z<;B0d<7+-1mj5Sk<}CZ7sa?sTO2;0*oXvl~?$_b-b)Sy%+t=8LyR|9w+_O%wv9tSj zZ+q_R>s${yjy2^OUtZ=U(kG&#anRAl(L-=+*qq6o>DxXDO+B>K;K0566Qf&0P6`(+ zOnRadF}ot9fV)^}(V@u-G0cliW^SJCE2gq))vJ!yP5OpKmrv2TN~k==eI!Mn zy+VPd;kR~%l`lg_PsEH)22CNuDLm3gSuv9COWxnmaxj6 z`uFBnkB3`2w**Np-r;;9VeYHs?aGq{mZ>%{9h70%BJH zJ?+dcWu;G5ar-Olf7<_z|M&a<-T%MpKi+)K-|_LT`Fpu{?@}v&e=YyASikPu*Y^9h zzyJMB+!^1uJWAX6TA0S17p8A#Tf7LDc;e@BGG3)2$VK2nvjErAgJt@;=l@8F-aIdo zHq*dqvCe6>4+h;ISZewW8I6CJp6W=C75cXLh!HcH;yI z)_ZajZ=X~ZmpZ|b@KkJ}RDhC9=DEUXCa0@9HZl`8bo6X|YQwVWKSv-(sA=?4fkOX3 z3%Wci^u4v6(xe!chANzr3s(|JoVe%Dj~Q$XJyX73EY4wQlHdr53oiQcH{RulOQ@G# z{YxE*D#^vlNxI%FE21@*ZZL8x3g5I+!qA>Eck3?6ly$dzUAk)a&zu@mQJ#_DlC<)P z+dEbUog=L`4jxp#Vk2{KW`mIQD?dgLfuHG2?A|U68)f*8FKzQp@W^=Z`s{6{37uzL zUYXzD_glfJedf+9mwr!sy4Py2fzkeZ^SwUEy*Z_Q`Xz_D!&XKHo~Y@;EMQM`OmGt@he|~D8A9UeHmBlr+Th%T*PHunx-0;}l z!+UpMS8Gpx8Z=>J{Pmzy4@BaY(hRlAQr?z1h|nxg5!&@Yf? z7A|P``b@;?EkOz^`bBlOCBB*Ve0`7?@nW4d@ZLf2Sdw^ zWnxV_%1$n)SuKOrYCu4GW+3ob~$CCX-S_VFvb znV!3yygpRkS9DVQu-8g$x)7iH`J!JY`<^|To+rc7aN;zFvccO-yFI;a9L=6eKmH%T zHs8*BQqIrB8#nLRDjC0DyXsxt=>%~PMj_v{NsTQteHJPFeQKhcCg78%!YFWnr$O=z z*O@()>vykyH%angomj1fT%hZOuMZ>lzl*LkR<5;tUS8*|t;5oDP-6PFDMD+_1UD{Z zo^odw?+1o*w-rhm4-bnj3f-wQmEq3eL*KvO_2oPyJ3}CS=@SNnL!8BtIk}mFJ8};; zES>V+{@Nr#Czzk}Hy2 z4;%ecA(O;>TqoV3wt))Q@|nCs81w!B|D zrRLK?#bAA2r6V&_do4n|*LH9uwsbf+Jd}IqbwI=5n8Djj?OLB&hC@dGcqHy9E8pu_ zVX!T1#yY-ZA!-^fCIX#C4vX^gra$fSk*khmow-AFk#!?&m z`03L&9*bOdVp}ewk=vI0A7VW{y;JaBf;-HC!SES^!`S8kk%TB#(&_+a9)S<|=buALzuXlq|z z^RIw;bM9?#P1lV3@ptbwy}!M$|KFECHMMnG9rtVhe7JqOUrwauVoIhqxA8;)k%>X4 zzP_9mrV*OrVj#3o;%DE=T@SX38sDz9SS%vvk(}a|)bS{=Co|`*w>E7xYNuqgcW7l5~nlSTwuCei-WxISD7A~xKAmPTNq$GJq zu}RJ$Pol&iNI=ue)zXw6nPIG0PNPYPK(fxl1l3OIdX?X=kd8k6KsCzy1A=ZE4X$onwI>`^mw9I!$5pLW0jpUi$BwOt&X zliGG44hSkO*aYC0ZV$cdh>Jsp1qtpK~j(-N|R-R;pW7=R}LCl@9Z|ezS-Hm zd-CZ&_ZT}l1-ZF5@2KT{-pTPyV`{$t^zHi9t3TE3PUs0+!qr}>Fx5%7{q#DeEg_Y= z*7e_Bs=Iaev(DXL=PE38)qm6Y#344eLiN$!{@mNIwsm@FP728M3Yxa{>%*Hz0zQQc zFLg<05xaD;`u+6RQw0ulGR)vIeDbr~Lxdr5>$JlcIPX~gl#=|)`C{I@+k#FtJ9WIZ zfA3q`+`<{TV`ih}(-i^>HV8P!@jHZgRk3tNH!v$*i!#z)IpxVpxAa2=X+p=gil%W* zxo7RH<62XqT(K)wU}8YVGWU~jcdaT~H|g~m`_mEw z)}~~IfMlVj*hK06*SF^7`}0>VSDItCpJTJF>1nys#xJ`v!uPH_-M;+x+p^cS)m2qe zi~IKqJMAzEwrKl&`!_Sk`q!zsm!g!*UH06~OP1i=fAjFMd)4BfqOx6T{{2*)oExR- z%rQs6`2Dm^wG$8bZ4OaW%G~;T-qRDerY+s3Q?ogPt!I0M_0e6$cHvpiQhE#{nwq}} zuS>BLm-av8za>mzO-F^My4j4=ZY`Un910Gt(8<=le){Xvr>%^|??eSf8U*^@sR~ry zf8H%1Se0_{_yb3c4fU)mCpO*5)o2o5nAp;!zRruK z%`N7UlHjMj*+5-U$?uSrkg`$G)Fy_9FP62O(D6Qcrq^?0#I`0S#s^D1qP?b0+H?MP z>DiNxEIE6PgMIe%v86L*ERB$qpP!dJW7SvQsFi!mg<|qoJ111E4Yh4+HFnBAsPgvP z^!l%#?*A?S|7HH4`2QFA@7rE=WBhvgxcs4d_v8!zzrFu|+y2*NhP`W*_Jw3_GGRNj ztm4`m2?-u!fm3JQgxgvyChAOb?2eHzeyzjA&}HE5xGeQ9_vagHH%u^4>M=B3RF+_I zNWzg}MqgmT-Wa{<7jy4_ugO?@)hC;;CC^r=tAlguAz!}P)#aZgvs*<%yIQR$WbAxD zdtYg=?ot7bk5UhNYEm{Ycy*#`Td3N)GiOe)Y};~(WyAThORVpIYfQ~$No28TFc)N& z%9FTIZN6D%u5w^rn$4}9bK*p{M|H0fJGOke_Cv!CWfLy3oVBb?QRjJfoW3+o$(4cg zjLqr=Vb|l1zu2~pSJ3#Oih%LY+?p_s+*JZsMa4rkbsto=UA3 zbmp9}e^$X1DC8)e?BXHRXKZ22SyZ>9-kPEKmmZ7Y-F2^b>3N*}lClQhMN8)ca^gqXKRvc1EA$6EB5{y}8)>u_}^bk3ctvlEa6ftcIEVnk)fXGucu) zUj8}dEvV}Mh|R;9>xhSW%m+UShLo;ePwsHRWf9ZU-Z?OK9TxFtpLix9iOosTBWO>7 zpkR_hH%AXo3|FvuVxvbjla9;urE%*|^WU$psjik@{Q3sx0+Bq`x$kfJ+t=T8kH2+F zDb(P~zd!c>jwZkMQCaPBBsDiaF0M>gNy#bi8+b>bru%%O;>*KtQ!do{8 zurCyNzhtF%!ZW3qRXU}siqAUTWm5hbwe{LHDTXOpN=3U%X0^`#=BB_Wv1s?IG!%gmY$SKLni5}1{$9(W{k^E_eMrUhl0 z3>SWz9dkJJ`st+gXOt#4yPSwF>Q(NR(hN?SVKZN8?X)K>#*B=G4-WLL%-(%hPeLqB zFwlGD)~FS`_wTQMmU1^woZZ;C^IF!=hnLIq^Yh=WXE<{vv3cptn}6%rk`}cd%53flD?|6Ab&P8MGPG-feVFHD>B^a_MZVlC(`AaEd zLi;VH)JVN`hR1g3t?rm0^7iwl#Am6IN={qP+?^Dhu^~Bl0{e62(o>#2t0e_v*IiF; zX={yOj>?`Q^tX@WLX_~tsOW3kes&&{2tLxsaAH+k2a88bu4bNZh{>Pa>IPSIdPg4@<(^zYIZqqgOyWTUZS_H0cRnZ9a znt0FpTgM9Dgv~O{-7z*LJ+}QEE+M9&x*jSj9^PxWekk3UvzR?=gUkGk^oB|nrt4A3 zJZ=Toc^uqa%7y3s6<+f5!8?w=DIzH*clD>A*02Bg_uIdBi<>29=r6tIrqdS@S6`<* zDb+Dy&pPjj|7Y{-KL0E8*E_n@)L6L4Y0aTaURA4eZhu>Mx!$QGcHgJ4Xyh` zt(%Ma-CrFBX*!2mO4sIEWeRl*Sh&?sIdyu|lpv!`)oXfF`?_adJa*w>vcoxkj_CXB zm*za3mc*dYTJ&Y+q+=zGUeh>@)xYX3yuM~m$W}4W^(+1)E|aO7#23DyxqhOqtkOgd z7O^mHy{S7LCTLCAv?-8*k+dl!t>pDuDGZz#%>MgDL{wHL|uIjZ%&$Ly&_hL+HY`qrdy>{!j<1_B(xil2s zxAyw^`Tn1G|3Cl#{rvxv|9|@JY(hT?o;p9>b^q>w$N#f_w%=ZSJCEm%y0%(JM&z`K zA%0Hf%-5HC%}8*`mFYWOb^H6;YqNOR84}bPG(vX0+_moZHbo}!ygMB$uCB7H&`rz} z)5v(&abRiGuSaECOGVaI=S0qPX?gi2_CtW0p3iPA=55yuj?1mR8uh()zw(wKmm|Ke z87qV*bBnVGR#xq@yC3#tV$6eUw{~qaJj0kb{p;VqvV!y1O*d@&d82B>@wdD7?fd(u zX1B@#iHHj6{&(A7UosB1yR6ULuqPyH@4NR6E-rl>97;>pPRr|h!z0kJHiV~U*S)%* zcaI+}aeC7O(1|p;>5k7*_w+6C+U-0m)wM;NvD10U@}ns=g1g)U0%qNFdDQr= zLPGpu&>?nzA!C7uD=YdL5)V2sA6Ax;;V9?&uCOrmSfizag##~p=9xb)oGpZ_Yz2-o z?lGRO*2<~KaJF;p6|F;g9UZPKrZ{!5t^4M{V9MfOLH(mb=yMeMd$0KMRw%_xvs_?boh#M+EHIpZndeZFa~r0Ui-9g>{#VzE@=G zGF;N0=z46~vN)~{0h+E?1)Vw6r`l``b1c*93)*n)RaRvRThW=)HgDavy6^TKJ$J^X zFj7iz?b?-DiAic{Yk2EEzFfR3ck%wZe@x06n?q)1-Fqqc;^o5J;xH}tA2Sbd7yBr; zIq)l~99cNGz)fu3*1h}pH&;8PEcTo{&w9cp7ml>H2M)@grYvWEp7Zp<8g{>ZF8}Vc z)Sqip_@C-zp84%S&+(uH!JY$76&63wFlf4*x%5Us%CC*haLwyk*LE`=VM%^dx%unG z%~h@pEgnllWNtE?(TTaElk0xmV5{T~i5-ZTRM63NA}G z8C+9ay7l$fsBGEf^0yL~4}3fS?~VRH&7=0e?Ef9UUAj}GKz@5PuMx{GopAoW`}5bV z{AT2N`EJzZB^vh`l(;r_c$|!gY`Z2DG=ECU){cZj7i0{Ug_rqGk})pIe7ElIo#T<~ ztQ;&=9`}F${kBMOVpQwS9>XTxP^~Pz%?lb*BNO&3e_r!x##GCfleTskE4H=nwtn^_ z`{At?4uOo7livESo6s>~)0S%!lGf(l&%AxeYe)Z=8-j{1od$CrT5j8U{m@c9!Hd5G zu9n7TukCGVdiPr_ajD0P72O7(4=wOZYin(nFb)?~y1(tW#I0$w)VTvM7^s}sS}v59 zmT6fMd;NB3)(zlvbJI|@#@^jI6)Az3C6O&S3iH)QE zw`H*`&Pa6(q0(um^3`U;n-_;5Cy&qJzr;nO*AhC2Vx&bSgW^ODj!V znmVz>=FCscqW$%@FJ_2NoFJUS?T{A6FmrF_vZREyrUKs<-rjaqZK`Uk!cM;H*Sxed zwfJUE5BKu<%Hy}cA>^6MmY`*weeOni%ew81RkWFNBBy=Gobpz1YUGj|`=$rUBu3>b zOmgqPxa5bq&i~2E>TDA?gwJTUl+!wrqHp*;$My3(fkVmGzPS>{={Dt6P6oo>Z}(1j z5cTSqmMkaiDdM&IYQbEEhU#qz$DN;Wuzi;Z^D5+LIJ+$)H?*bd`o-mM`wgPiCNmf> zW?*D7W-^$#WL{G@LqjHedCJ2j+J+pHtXZC@Or5}VnDfh~Ee=u9eJhS$?Gh+vb(!`p zz5e6$-~0ZX|NrqnBwyUP*tjsm!{^J(pUu)YYhTw+39@Mt+}w0pV@Ap0)2q8@PTRG^ zxGy?`gkqW#Q`iy;%8xCEJngw9OV7gT)JDZ-dEWA^Q!>8i%|tn=rsV^BE6(8suS zgP_9#k6C9#T9zEURAQSP;=07aVx^tJk*rF+2To@=1O)~96gV^_TI_T*cODGz5}W;Q z8NaNHOV8f-3>&vyZI9l;aaB!j)w``97EUN|$#HG+DchBFIw$3`L(-BJI^OfPF`9+6 zL_2Dl9JBkCvGi@SbBf?6fwZ>7^c}9tK5mV&tAE!KF=KnK_NKI+mcEJe-YYd_1W7bS zWm|>_Dis{>i7Qx|yl%32qqE9ki|&PoCaqdgc<1bM&$&At7Qa6+hg;=AE&2Pd)G~0R$E_RKP6dVhWW3b5ef&+YRXR4oOG`+WF7PQ z@}REW{rgmdKK}Xhr)1~8z0sn599kj|R%r!^t$F8g@b)&%Pv7^~?26H|vJy00Fe~=j zvvuEd_vc4nFWhb0-H{of%JAadLq3Mrw-^+p1^w^qF5Hsd$zjUeP#SwW`eb=TWX&(V z4ILRXnf41d=AE%HvES2G&^NQ2FT4mA)x?AtukIQ=K-K-$F&%Qq5`^Db{nkiDA>}Q_NOWLyG zz>}Bfw{d>2-5HZfd& z{EvZQm8ue#;UX{1wR#K_!VET_Fw*z&l4x{dP~ns`6ctlRQPa3%Qg$^V;YdQu)~h`U zciQvzdM|YP`0(Y=uRkky|Nmw)F*M!o)`yq8b*GNs`CGPk$=P3Di#k6TPOa+ckPFOa zy%D^rnlEY7H=fY%I<F+gnt}<>BlU&>UP~*H$8RlmbZ-}2M#d=T+cqYbX%_TGN+z{@^wEC zzkU4c-@lBdeTGdhcEw%U7WCs|N1~q5?Wb$zNO-n07H6{toY6_LoRsC@a)q5?%B@Ki zu~EL?twM8M1r&44vN~$m5}z(!*KpWrg4>LaDTf3rB7R?#P`=a6QEb%W`TN8QZH|OR zhaRny$_uuc5^f|U+OY79)UB+81%iu00vLaN_+q(y@s2YyxaKGwS+TU@7Q>P22Q--0 zEX}cf;_>oaZx4s3Z)3~jM>`Itopi|jW503Ml+L9lyn-`-{E6QSHRGi2?Cl#i_) z&zz6@sR&q#rGMWNdg_Pgr(`3O-Hloaj%U1&uGKcat|k%Ca);ssx|CtTBr#QTSU!*prt(qsl9*EA^MI zu$;K&H5bG8zyD_6ul;$w{)7AftN;JW+t}Ln|BH~m`#tW6_yYd(;%er+3I_6P*I6<# zo}YBhCBh}f-DQdK^=)@Qo476Nm^pv)Turtat0M&#oIh)M^RY-ni`$fG2Zepu6Pixn zy5rmOeXsf*7O@@=p3KrmX0IKUjO5eLPuIG6o#T zou#6JYJNeB1(XfM1$W-f(||DsCyRAyzE0-2cHJU!(x=G)AuAZ#YLya@*rmeC{`?gRbQ0@1H%?-t-pC_(b zdT8l!#n^AnaZHg*1%hVGJdsjq;Jnt<|3b{e%vH03G!+wAni^(4^b%1x(-5~jRj%$& z!R{EZ6>GQ7jC{CBNB7W$$lZd*-72+OirF7B)@JtH-PNJssO!{mlHaW-YV}dcEr*g{ z%ygR|+U)qiXWG%@wQroy_V7LDUzk}hl*lRZ?cQa*rQhe3-`n>7t;Er%-(s#^vkq$T ztraya)_v+SC+PKbUq$2V$Fj9$n;B*bDm<n?;)c=G=2(Omh?NDEV^Xg-7d$6JJ$Nr8%i|FSTF{KD|srv*Xx{;0CV_ zpVzxMF4pe%R^&eAt;8Y7%=~F7m*MnR%Unb{UX}Cgw~d`8;$FaVK;rPH6wjOLUKYE0%F(}H^=9sC+E-dzU7N7sr^BJ9BL^QO&e%9Tx48H^ z$BNd{9n3sXncE|Rwj9;tZ8F3%x6+cxpJ6QRj=bErHIIE`V1f`eu|M~Z=LBU5` zG>@gQ;q;8d8o{m)4c(3{O-sw+Yd^M^d-DQiN9{)<30;SlKG+rFR`Om@!^L}bgGG%> z@v`?EXI}X2l)P)(Wb}uR;n2csvoh}mdAQuFP82+{V$Ty!Z71IdF41Q;e>+b& zf0W?wdFYby({}OnLorLdg5NOicwapuT+?aKy0?Y$OCC5+jNF=if4%>C!G{Lv{7a`8 ziU`b_;(PU~5%0-lhBLc#-DaIX|ILZqPl*|u3egw z*SuD8>d(DwH{(`;d#N*DHj&5V!5oV7M{?M z8ZI*NVY%+g3~xrkvh;$6DVv1Vj)ew(tM!=K7kxUYL&K+d*Q?1Jr|m1fe867v-f@v> zoDQD^eLntuxOUo^M~9B@ZIOKbX!&G*##<)Q^S!SB$yq8gQ6x)RN#Mb)v@aP}&Ly); zmO2!$^jc<_%dc}2jmp0M_0x@af{lx6b50fPT(eZ9`9`^~%hLd0Mbnp!kk-Nr+gqbHSb9B1pW7Wb^yXUTgTlh5H7LtC+&yEd7vsdvDn<+L?O{qE{{CeY z;mb3dusE!+{d?`}-wEH=DLcCwn|!%=Ty*2sZMo4xPo6kbW~~)i_Ug4(Q1{BmO+j91 z=Y4-^p3`x2h@a*f8v1rg`>tK9xAMClXSwMY2Q}-R(5U17MJu~vig}>iQPMYt0|L(xjLayl@ z9$bbSe|l`bx~_WdRNH2&2M0uJi^OCk3$F9M*lMzMUb0hKk`wpmA-ya#g0Zx|;lE<2pP)BqhR3@6P>R=cZTq&PEgWSxFE>b z$_LIe@*u4qd8(fK9>o)u_v(w`FNj=bUUM*5=pKYIH$ICP0L8j;_lb@#~?U+_&CC|9I?W<1y-M@d>jf1~FIl;i7)a|j> zG%!{v>vYg`w?n0wp3|&U>`RoE6h~>R-Y*km{8F$sXhtSa&HYFB9?t)Ny8i#Kb+3zz zzXt4&y>>fwZL9sAc@Y!(?)2wRWpv*u)g$9;CHtpkv-T{5k13OuSWc8#bLX*vRV0&xr~AxOO^58)Pm2_V zJ}0*-tXsOknzcbYDMoMl^XJcXvW|H)N>t1c$y*z<{`!`Awxzpn3RYslUGJjyGk;!W4( zS?>)t+qnGbSaECf=99xEmam7X=74PLY1Nr}289|PlyO7;T} z-_8n_ZfdxB9q(<8zqN1_mWyQ4=FL-aal5LlKZvFmvU!cCoV0oC|@Qroxw1D zg_Kwao7tV9)!cm zZrgi*`@b8;il|`#% zzwq$%Hk0nMUSE%Uup`Io`j_y`3UDcc=?*m#mgEU*0daW*A zXYrmtYpveaOCP)YW;3)(H2ECP(K^?X&630*tl*L;bZD}8gQKA0TBZPtSz3+T4{g6a zb>`d-!xn}<6IT0AKPH_B>tbP5YLgOFP>xu$b?L`&(OF8O8=@x6JbfvvdFe;}{lEW- z%iG6Em75CgjMYAN<6Zx@xVG8Bcjleaum3vtc*OKiFCPm}yi#@2txE0IB@?F^3errc z&)hq(z%^^$trJWwK?W6Z$4+nBa_x$n{O`YY>QBO^zkWKcjxqW6_T0@JdXx6>7$+}0 z#PZ;fjON}?C0vY-0atgK7(4UI$|@90>~if$zbKh;_n5@VN^Mt$LpcH$Z|t6_cr0rD zTHTJ6XOX?FQawLUb15tBk}_mJaIgLKF^MUgYsEgSxco_qNl06-;Z1_zwI$~i3w~|= zb-}W3j_L`SV%8Sd1eJH3>AgJ5Hx{_aGxPT)hh5(Lz4rd}W54&!|JM_9g8BQ}`}gHs zTAVlzNId9c^wyr9da`!Yd;RoaNyEp_1q(~8vkWs|8_Df^*><1BnJaK=P}bQ)EKO&A zzx*GXy_Csm%__4g63vWf{DZe`EZ516y>8~a$v}I7PUW?&U)Md{XS-{+{`J+$f4^Mz z|NpQ5-;ex1k1m()47%CgcYABEg!A9WpV_7Nz2AMJw7SG5XUAE$NshT^=FWd#aBQRS zB5Q|Bx=Xe$yI|blv}9)F`^PV|IQBjKmX;dc%6RySYs-PR53*-QuJx6jpgFra`NQIM z0uMYGUmTZg**wcYf`NhWWI&OwkK2vv`{l3p&XQDEm0Dupc3|IknM?D8mv`##EmP|Z zdG7ejzFHyYui$UxLoQi;6F2!era0NR7coehzkPlrC(QGt!iFzRM}xYm{>7=xnbT{> zcFpoyS-I@rj}=q42|QiFsFLQ#}TBAc4&7IW>l$DIt zJ5Fy>`wQ)I{Yz;FWCRNDmuH&3 zJ*M_B>CIA+knq5YBPF>->1hF_EXCn-H}web>5|x6?iO+V>!vr!xA)hu72TJ=y-O#8 z@$k&b+c~cT)?eq)cahJ#9{=~zweSC)o}OO+f&c%%_^uZ z`M`O6>!t(OJ@`8~)XK^)CpXAz^!TV8v9eh`b8?u#!kZgAml$O4`mWBR%aGvAs8F&* zGsx^{@%$wdL+~tA&;Bz5kwj`^wta8E=#87*|R4t>emE{W{M3@t3Q` z*4pcD>bGwXNoQ%1x)5`Uak-6Q;wlVxV_li6vW)ov}U5$|$0Kh%33 z=WWRcy5q>n^3oSuyS6HF8==e?>uECmUnk&UW`4C~5dqnC3`f4b{^e`)6Q)Y_j#_0{27-LtMS-M7`P|Ns8a*;x7d55NCE+x_02Z@UZY z@p}(f>=R~JTNIVuG+oK*ki?bW%$^M2bo_mTpTAF>C%RYQ`Gn+^Z-4vC$KT(#B3H1r zmCG(e*E?=0Fc_1iy?bl6f ze(z(CnDwyp#O2Fx-@eV=Z@*{CQnS0eXU>f&WmqtC{!CfUjDDG!P12PLZhik`57eBw zETeg#&y(?kk%Z6a{D1%M|K59OV~AKz+1l^ESGV<_-#)+on;)l}{~=K)2MaEChbWE*2g8hvKQNSa z%y3`yQ{oi|qg`i0duZgjGQayrgl8XP_|j3(qglq=QG5TmLCUE)rVjc`r+yOEK4YY- zWYE^3kbZCX^q_6We{aj}_Ar}zV9C1OeL<@Xm?s|ykJuwp*6?szVY!2ZSmTm+iDmom z@8mdnP*dT=%nv%#&Zfz}dN|?GVat;ra@-q3O5fOiKmU{E*>SsNeV!LjuIrfhW8+uD zHU&k7rf!pR^TQ<<5BL23acsiZl#GJ3BFo-@h@x4aab zRvXSB_eD`>>9dWdudSbbSz&h}T3yN9o2$0LGqIr4Kuqqiz{NYCA9IVBx1Mo7mBFGk zEkz*7#pw8h$H)6UHvV6JJTUXszOSd>2Ca&iue`eU_PzV>7fb7>-mjiz!RWKlPC&tN z<)XuP<}ye;f1>ZtV{9xhF_R;sWh&!A-IoF)2Rs<2Ic?HPja}cWR5GjQTVnm!+4A;v zzZ}o5zpl2V{`vbqZ*RYS$^Y-@{#u)DwKrBe=v|WQcV9GXX=rKp%Uiu3JIbfdunA`< z=*a!jQBhfY{#VhAX-}@UC^_r)c85&ZRNOL~-`J-?W2f}rjl9pI)=55^c4a|K&4X@Uj)##b>vM>9R z(81yIi6bq}tJcuHCv4Ts?NQ%q(<4K+hJ-PHR*bQIR9mg{I`l;RyYKe@Kg-wuecgY) z_4?@~uD!oMe|^Swzn*DxVf~qxPEE{v%WYbIuq#hE$NqwC@$Ka5kZCqYm5LA8@LJe9 zZFV~I+a|fEV+Dud0(FT?U+27=^x^SES*H~o%k87qZhiJ6?V9+3hZEJ#C>`=RG4mQr z$Bw=EH?p#3pUt^lCc1jo$rayT?EN11I><|N?Opl&-}{#PMSES`@S(u^jCJb_2i;7? ziESAtm-_G-Kb!LSMd-l;FHg88SnN)e$f5PKn>qBqOD4e}|#^><%i@6dvl+S-( z^Ls*NnvHDzonTL6EX^lmpeY(n;7qQV353EUh z7Jk6xP|Cv48+Wg7^I0@eV(H6FmSZBj6}|Uf>DX~UzOGJE-oCc|fq!?5*uSItbzh&p zeLMeu|NmbL%hlDy_-k_RGKaDo^|!BFHD%hbn*9onEy7CQ)+OKA*MB^@y0$*&_cXs5 z_O<_Zu036_dw=fq*IV80#@}1BQ)#n;2BX6&<#VT=y;$7XpyVVmF*ta}BTs`FOU%EmtX3{R{Y>pG=l*R`PQhA| z6D3anto#(aDvR%kU&}9pw#~V>=S}DLnc#d>vpi?(wW!FHy?5T1r~i3(dAWZ4zBkd! zl}_JsIns2+U^8Qs?y7~(X}w}~w_Yl4Y&_oSGV^`^Tcw^iY!m$@1R3Wj?wERfuRr6$ z2~oe|3Z@oCUf-Sn&^Eu_W|p_+!&&=wsPxFqoEi~fw?FsW^!?ed??=okXziDuA9ugZ zsq%|s%iizXfB!D2npLs?HRI2@OBZzn1O)g=6dTNt?6Rw2KmPdzV}k+vG98!Dt>1g~ z)`pZFXSiQ8zwKT6bTOW+P!UO&-{`&G38%2OXLIZ8Op{o&q8hxj4ibtB;!!~aVQ!+bxONIUM)-GPriKVfh*X_OkKDp=k zwe{v7WVR~g-7b@yeARfHGK9HGfLJ2C)D6`FuWqSYlzv9}Dgig@2h1cQOZvq?OlKSO5OGe0pt0_R_6M z&!(SdZN0szuC7{IP~N`oNB{nxua8J<`YgR{!K)WXUhsEs$oQH)`K|Dy^t9RTKP`Lb zPqS>z{NZDGYR8nItoOxJ>#ok-Y5nAZ{$YhlkJ}mIULQKc7SO4bbluVA^)9ZBH+ZBi zJR&!3xpcM3ro8L6OxKF6d4Gy!l~|rF>~C_&d*(cI5;x7(Gxz{bX)lzQ`OsJ1VM-Ta1GPOW$nLDP%E@tT^WwQmxB+aB=q9r)9f6UtWK` zHN^9b)Iq_RjWlE5O%#0kK@0m4gR`nnMef;(zkCUu!=O=DcZWiGy zX}&UjYEP>1X0_DZ3)^E2YmZs(QDRi^Qsl^4J81*Q%&e8EZ)8{h`SyJG|AqhW?*CoA z|KHW`hR1e^98uVE&93C~{XfqNN{UV=_*?9lr(PAD-D|+(bD&G|_=c9qv%HP`Thtrx zwsMFmP-yT4J$P--~4c$rin3k8i%{nNoFRceW{GkQe98Z@Md2V zb!?j|%cS1{E_%~B7te0Z7GI*a)X4r=^)tf_0gu893ZBilDR`)`eIxJMr!zuVtt#z0 zH0j^H#uFS51v{4HIo>GcnLR~IEoq6<9`4)QnwS2!vV7KhS7755t8M4D&h4Do+A`Bv z$6D~`8%O1y3BGeJ?%C3&9f-K9eoM#F8 z-quKR;8OEi`RaEv^8%eK4+@sumfLHpGu3^&%hDIx%@?Y|%f-c*UM=JKY`fy81oMT- z0SZf>Tv<3}(LK(^7gz32zG_!;-fQEONevabtlg6z zGV2~(x6E}}^03y$K-jA}uTr0KFrMvGST7#@2uv+~+;vy%TmJ|?@Q%%1s2xBUIGcU*!J0TCR`%#-*g zT5}1WHc{4jx6A(BO6e1)GTz%>^k13UvoKAnxNmO6@vlWYb*|5O>^x73g~RW`@s%wu z#_HT>1S2`7X}wVN_`T2gbI-i*31@Dk#!Y7xjMnV9QObR2|G$r)_x~=h|Frvlomx}s z)KbmUQC!FM>wmlp-~UTgu-9iS2wUaPg|pVN3Ck`+9S4SII{%nWta(P4%UC^z1ERj7s`~Y$*p$Jqj+I zwwh_vQ?Bh>oHcb?Gc~sgOutgnl3cNDhVN^yBZkK$f>mZL_vAA?b8KhmIrl5qSZ7Si zn<(;r8o)ER!_qiC;ObP^5%KK=ZG? zXQ9iHo>jJrFL|T|C#FV5tvtO+=d{sGdBI!hKYnn^`>tuToL{zYW@_ZMYkAvmKP_s^ z=jqtgb>_0o|Gif>t;^n5{68(z)upU@r{I-e2Ib94x-!fP$A7&0_w)1f^YiVMKTOUhi zgUxHJ@64ZP&+>_1e!g98&9AHL_q#v)|1$pXzrTh%LJDTvt$#Sh&_At(ulU1ZLB|ux zJ=dpiXa0H9_(M(PwbM__cH62uPBi2?edG7<-@l`-r|#UdZ{KPwwg&c!8yEhFNpRlT z!hbz-L-YZbxr*tTHvYGnMQxV5|I7=HOiC+YdpD1L<@w~FI_HbSrkKXu%DfWASoq%7 zJ|%D3lAo()?@qtI=+<+GM-J2W#p<4%9WF09xwNmsllee-&>Lm*d^GXo2EuVG1r zPvZe6cx4xS$jw{8Es zF;_a$N3VT$tF#M;BAfe?CX*6_g#r>T;kT8Gc_$q5HJvEyZg8OXe*LdMzv|>~^>$8J zvW1mFrbi+xkwe?pW5+eAXSZ@o!nyk|90irwIZA|<8GcLI zHt+D^d(}H)ub*D3+qCBjE3@}X4VOvHs#-@f<~k_rX#P3Vq?{Gqc{)_eh*w_ji^Q*O zZ)Zn+zj5W;yIPi(9fA{=B)(6s<}a7E-J4OA-n5fn_IBKghJcn>$TZD{@0)G(sQ*aSup$NhJ|K2Zj0V#87wOl@N8&Z&F`c2%dzvaWmrl1 z1{<#qh3lT%^9-)AD~T<6Z(kQP>&HQE6-QoI_t#-71Q-Q`!uj{SjxA*nxZHJuW1k*3 z%c@nk7wiHtm@Jqnp!G zkJU!Gwf(<(CT7iabxe@CoTfkZ_YI~qTa1_3S{G>kmdO8J&2pwdYD3RTh|em8e-j@19Z_W#56zdt;X++$MXv}IeEGuN~{*_m$F7s@?jb#l~c8MTL1oiTl`+u>0RypE$<(St-Ce<+S8!Z%a`*%4N^YTzRYK#LIH#4L?=GO zwwYG))5;W67M|%4VPQC|(ZJL)fuUa|R?~xbMS*5edckZ-#b8C(?v4vD6=&tF3zBk6 zU|Fd1dRE@sncBV`FK<_zlRMLseY4#)<(X8{AsZ2gl{(r^ZxYfZw48b#EW7K@{{GRs z!VPyXt8(pI$j>EY7+@5|e0MJY$`9K^>&4P1-zl2nxml6Jy+BFj)C!;Lc@G0NSt%K9 z&b{sS{+*rA%+ohL6_jSQODcW47d=tfD=qCz#`4ma%kKJFu1L2yw=H-5`uBYK{@1xZ zE!U^2h%cr0@B45$bVb9#->-k4J{{elyY;QJ zx8_;{!I)`BO#*Hlx~Sy%FzA%Q*Th$C1sQRZ(vIgO>Uea_ym0!=(!xD^^7}8^%JmCjaVpW-++xyvxyLG5_a@FFsrs~=2>cjR&R^#iq{dhPp#?r@$0AevYU(!%QDlR&Nh*@So70AukIYjruo_4 z&z7t2Yfry!dMx6VR_k$z!j@a-Yqc-^p6B%F_~VIBTtkn~xN61lW!>#)J9b=QWhlFQcPb;3q0NCgov$vIoa;Zi;-RZb)0MX>JD=Z{Nd9kK z|2*Gz%l{vb`_KE!x0fl_?6XyT^zFvKr~7}J>#yhMRXQ{EL7JHR?TI0+`}iAjRyu~( z9)7-D-MG58_Uf%uT4y*sP8Yq5-L6|`>5*O7k^IWdCiHBG|L*M^k{hbe1ubmP!t?Z7&lgsnoWOEn)5o1=F&m`H?ke4Sm&pE2Sz@xW% zpW)?+`KGSh4p%KK7I~P~cHp;Jhez#>>&3IoYs0Tp-Orx-_M$Vd}MK-_?IhuV+1IUsL1uu>ZV#-2MC0o@=vmEZ((m@1CEE zclb|RlsbKwb7<<)m;W||NUoa6Dm2UQws(tXH_L%7ZVC_G@5kSJe>=IRZs+~&wGlH| z1(!Cg+d578r;7fo4^fjZYIAYTYfAhWz;mIXg@I-Jmy-?GZr!?7c3;Aik>zBc$iiaP zCo7F|jGn49c5Vt_4d-qyX=!~D_K0P!%)`~2p5=7ODl1h8o%guGJd;zw$+^8y)!3Yy z$!&{8PVTvvB3aM&nz+dPc9YEPKIp+Q9T(Qe{)ud| zmE%9q`DBN+Lss~+2e#`cQo=Y7_S(d%tU4mdf_yJ2-8r zt*!0e{d>}Sa^>zbY6Y>*3U@wPpncQ&;Ykh;IWGfa|K=y_4a6A)4!yWrTUhRKHPPy$3qzsTm*Uu^ z?%w}&Lu+em&pJpLUrwx*7ff8opsRFj%J=ED_wUMb9Bl5qaQ6z+!qvRNp~{bT=>+wC ziFYXQ4r;u$u@A)Jf~YQrLLgvSA1XgB+Ho*(tHzm_j>iRk#qEh)wTmXd%X?MTbLhm$ zyZY0k%kTW!x9+t;csKJ3SBCJb3$?_uej9vWc%X#Om_w3d{dMgIv5CjGrEmKD`Sa&p z>*m|n_jTN>y*@Yo{+Yngsip7vL%&*ndA`m~YTd`n)3-nTe=^6${_eC?PsihLGanvv zcp;acU#=v?#vjc5y6c1(pM%n;3LCpS^Mo#@si-iW(>cV@^ueLrWy^trZymX}?e-sd z{rvm)>ifqHoYR(dB+fb_W-({hCcD!4MY_h94VXS$SZNzMO*4>F-63V&R;QL*CyZX* z3FMTi^=3BS{BGj$*OF?fX`W?0m0ZaJOiD9u`-mrqb=LNC%qVh|*d@MD=NDIS%WTFS zeTK&*Tw5MJd3e-DD(9b-i-?G$l8YM4oB++Hsh4K37DwH7_xf`%`S!Nl+w0`#`(N`_ zNGNh@IP+%7zq!es9VaT*JZ7?({?0Hkd709?dHX$P$o~$TUbJ)FzNIhZH|M54<0$qw zmq^f?Y4V_ALd@Ifj-OK|uHNl-h2?BWEX$?vD>6m?vRbv)eZMnV%OnyHZr?M-;?Lcp z4L0qSN_JI?xXemoZ>L7yb<5ROI%U^$_kwX-aHz7dmgEetM<>M+#FVb)^z8flNPNFW zklp`3VNq{)tzSFw_vhToJNNJJfAhMiw(8$I{r|7ZmDatQx^!!s#o6q`7TVWqkEfzMw{ z?4n`I>4!UVPH#$JW7&E4{`NI9J2(WRvUe&dUtsRhyAHV7Uq8}BNpTzQog&_{#}R`n*gK0-8^yWQz1DSTm`N_w#Dha ze){z9*}Jy9$(l1Pm&O$YcrY3}vlKt86Vv(3W8|ymymo7dWUT9v?e6bw^&@<*Ij*q& zk@$DRdu5L8xhr1f8CIF=T7PxF|9R^4)SdQbp?hQUG>!^Z6+H=T5trS$U}=b*r|G+Z z)iY+Y_Xx_*Tc^MNyZvAP|1YoG|M~T}J;St1eMV43 zxj9Gsi@md*qrDUhx94W0CM|R*;V5uaaZyW3V{_7cQeN(^Wc2mTnM*9X$K5v{G2{Q- z(_*uvCoiqwM3U|wn}tqm<<&Xn$5a%DD?6y08(aLCxbEcK-aIeHsfW+-)e5JySOr;b z`M7S?ZMLNb3mJMYuD!CpN^<5lXG<0SB?3t&KfX8l+q}icLj1$_^er5&QzkdC9536- z^GIqjSGqvg%$?~Rcf;lPE?&wJde*k4@1mcD2*31^Vz=k-lHd0nvN>%1d!M-2$_b~M zQ=*>FGUQ$q+4-FNU9HS~@f{aV-@dCOa3H5|!Lb{UF5Hz5_psl%c=GMVV((7=ZGIfp zd%4g`$;ic2^VZM%UYbGPv!=HuzrUMn5V&fl(xEF1QENl1PM#NPR9uiy8NK88x@yS~ zFU3!W8TZYC)fY7ca@7BL*na!%x5xeQr!JLzKel!1ua_@{Zprt5pKrJS&!_PH%bBy* z<^`TOyLF1t!wFZJ1K#h-{VkDZ#eHx|`%42g{|6yi(tjOY{Qdp&_1{}=WS@U{=^k&p z+V6$8%|icvc-Z_h&z*Jh@xX7Vlpd{msb8zd;1aU+_df9?m$EN&o-Chv!$8%o-J@&2 z^8^{uAgebO3=S22jT={Ucg$quWKfm3=gY0wcd^<(^sb{r!=DozHno2RJa#;heo_QIx#373wd~$ z$L;-OuZh(MYT}YPi}&x{>zVue-u-*`;edk9cPA%FQbLC)TUER&^f6Y7g zn8b;{+vUy>b!VQ~fqsUM6*-c8^PZMDaV7IiTa?wYV%{9dltU*|R>h9^t;%H1C+8#iRX{atX~G^*;eTT7{M z-DY_DLt@v=YMcJ=2V)qPm8WIv&Jki`<#9XtOSRqnvcU zVLrR#>enx~%yTajbGR9mTdUsl#L@7xgfv5^k9NJ1-?`4{z8#wvAN#!BAel*);Xup; zO-YwCd;04ptv|zbVBgw@=b}%Y5wJPX!B>4f=YFHBOGoAnnGJGkUyD7CJz(23Zd@8_MLZ~s4L=_F>s zx<4OcxSej?zJ2?-{`w;C&hu`-xnczAF|k8&{wyPt07(}NlB3_c%GdbA)QEh~S;%-0f!Rg~-AXgvAc zJzI~BFUrgI>(1A^-qkuMJxf~X!gS=JUEA8z*FVg(X^rV^ta3SK$FxgecL-AmPokQv z!jYCa?1j@p_@(|%(5K&}gdA3K;bcIM7Pf6yZ1c{mNAKnx=Kk4jc*2FninPm>< zW?rvx%e~e=dZo8k2f?`vt!5J`_=a^&9$2=s`MzMO|~I7!|6}SEYp=UckayF z{q`=i+xxpFcP6jO5>Y(E+A+~c^!bL9hYMtq9io_+-CX2nUsZB>_rRgR>#tx*hGu9K zGk@vg$CYy1_kCTLweav{V=ji+_SWwk?pMOr zJ<(a$lX%zYT&mPhKehJiwNt%MDtSl6PE%EicroX$>ycgCdLDVZmPU&!8TJ0&ww&dl zY|w_y*QTYjGYWBC|5Ra9_xrZ|%%4R&W%P}>4ULtLZC-!g*lEYKN%}$z#zC(>R!sOH zkzAraZGPRKKkD^=(lw*E?*0GhusYLfpKRUzzmDqXPhb75P-0ch(&VRmS~?>3&HD57 zccNYlOXFS93zv7Kzo}Xx$I0-;ZsClOmbq6O<;&mxe7nR+Yijtr_>aAp(`}{-bj*-% zeD;*tW%W|)$fvh?pXdg!v8(jXM)Pv=`NT2O%Cq*va+wl zh&^bo{*29%HX8lyORjZlJohxql}eWCogkBZz_9T2iJe=%-_xIdI$Y)Z76vN=N$Y<{ zJ{I_AWUTYCXJ*UjDcpbG-^{T~^iqxWbSvZUcKS2^oDlSI@ypI61Yq#EhJ8h%9z!n{DW8sgxbbO|it1#63|NDLW^4GtATR-{u z{PC_+-{;RO+5i8`?)_%cj3Kv853^0%x@+~#jI~cbop#^8rOjgI)8*5YZa>Pst@P+) zMTzX%t*_rI89l!-eOrv)bhTEdb=PNF_OR^#`!+v5E^godf0H}D+D z-SEVe+_#qgWe4`H&YZK`$n@aK2G6ZFES`I_N2GItfSb%dCd9!WW?e|2K!>FF^1 z`;k$%W+KzaiVF-34Gf+xjv-8mwrz6-Z%@yz+qzZ6X^rm>gSiY2ZfXkBX-3W8h1Pw# z`)&L2-{0g~zHj`#!Tj!CBatxkG_gg8oD8P^`n``o)Y+tCUhob(g+o^-T~*d-Jrx!G z>UXFpQ}U0Fgpy?%zNdBxu~5x6|{^Z@W7SU!TbJ|7lv?u=sV!f)iI~x-+zWyH^?e|I_pB_W!!? z*Ves_-&b?-udhh*5i>LKzh7SW``@>{x=W`<_qEbgeeb=xYlZX!}U78S{R}{y04weviqInDV7JCYA3P^@3&X$l&&o`+G#8z2L^&*}S2z&&;_HH_ejWFxjugFUu=QD(Lj|>D%r9Ube5l^LzUA-$5&l zbed8arS|{$_uKz|{RGj=UOtCsTHf4ud~@q&pU;wSr@2pDz9gHcSeE6WM9ace%hH)r z|J7s}U3M=GQ+h7&^a8{Df4?usDK$?pI{tW#{PNmXOTniJYTmJe6CJ+Xc-LPO_H*^t zeqn<%o6JvM;Y^qJdS>bTODgzJ@3Epq(Fq(rOP|a${Jc`s;Qum@`HY^G-b-h%S*f(> zmBW!t6}gGt>$Xn2Jon0zRK|H{eHZ;zH|lnGda}`Lf8Ey#7oK(3z3aYiy%yw^X&P_* z`tN7OIUM}P+k+0E+9p|9)M%HE4$0eJ69hr>CD^pTEDxZ`Z5GA1&fPX!r@yip7c!NOnk`fIde{0=BFRvS?HuP^WEvcKNhd@{BK=p zu736D6yfK7GS0%20*gz7H#=m!o;2yrl{w2hQ~VmP>h4a|JM`2*==}G&)_)43{+;Tx zxTCsUH*nMIPahRqBZEXtOlKAgHXJoM($T^yapADi+bxH9nK~w_uGjqHP;P8nt#|oH zS)!~1yYNKW7YFB9Rc(<8Hf*zGUy^$K#y16@kFBed9VAw0ad$EZxkNPl|1{nH&#U(L z^RJ28ux!|HXI*%Cef93=iM7%CJzMO5mDSG)V>g_emDaLk=GWZzpJB?oe}6u6Nb-b$ zAY+H-8h4kp%A8-9PyfF5_V3r$#sAmF*Zlf={CV`+P%eWQ`?WhK8<#4~R%B2}U@+)- z!uoX01db-A!m5yzRZ4<_34%&fK1<3i-h4*RitppX=b26SS*|h#IDJ;XH%mOoX~O+W z4*p8h<}>TLI3_KLoFwRe|p#tN32tJGS2j zT1qjdtqYdQb<8nJ$Xz<^WVia6L#zUYyY4XDda={_*>V0826LvQ{xtW~S3mx+M&Ve2 zt}#z&_~8bV6fYgS{ceJ*d_$JY@OUyP{M0IY;#+@|JzH~?@*xRB*{w#b{Tv?|((|_8 z-j=JH3HFPdL z+By5$C8k4XuHBla7k;sc@ftgWn)1=R)1&U+iPkJ<+R7gShvfJD|59Od z@BX*nuWw(-Gj~2xx_9r}{=MnHM0HMos(8~e<;>Zg=^wO@KI*u}>Egn_#DT|ULBkv+ zRe!dbHizzdxK#6}aQ}MwJbc-;Rachot@-(KxBvb1r;q>M;BePXJb}IcL+$wbBLar2Q0JAX%dXrIo!Znd6KQM%1%>o!BHBauRFx4-PMIcawP+5?7! zM8QTM2V-M}oGAJE?7gkxDO@KKC&=?JPv^2ok(<`As!Aa7eR9L~ZO3NX)VMUrich*| zzHj$)8IRp>7cbwmu|fYE&!8$Uq_XD8|9yM+Zr!GoS7xF9Y~jhCPIpF!$x23+ir2py zeXXl}yz1TWeLAM=C&-*OKDE;7+q#E&X`x-MZ-3YA)X8#VxNqm?BU2-G!gIN;bZv*j zXTgbr+DrznU*DaxTkqp?=X)aGZ?h0tt~%+1wSLDK6$CC#W?;DErg%nXuYUYKnNSXH zr)+qL{d=)awu@!|evbcP6BU%+$udW2J1?BQ>vvJ+>$g_N`VVa4S6;L>`+~)O zK?jACb*gQemUF@^-`Q2I$huV8Rwk&aclv9Ql=j^CLZe5Mt{Lu*>yVUtI-}&EMaJUa z`|iIlXaDo=XW5-E%nXxORPBuQUsW3xSU6*$+;ql6Eb5$stEV&scBMQGEbg0>wo~l( zwwE7#e$KSMq7%3vg)2pNnakq$9+Lk$fA4!=t0>-2`r3`ck( z)s)Jbn|(h_6u-=FU7=2bnibnDJp=`Uu?cMy=d ztuXsw&T94GT(&|1A%Vwd=9PXhNb=dd>hqohnU|d8=gqs^Ve$Ly_wV0lRO+Z4e!hMEzWduN_1P4ZYA4s0g{4I1%|9G8M~~Ny=cMBwv2(L_X>sdqU2}c@ zy?g&3$JbZh-*e^H&(r;XKS~~Ze0bNrTcQUqm;d|ww^nSSNn&r#qZvD8d?Ib8IR0F- zh5w;vN6a+Ui5oo@U9~x|FUjI(&Ss(Qi{roLXQbS}Bj00S=wgwPpe|4tr^3lt*;2LZ zaD@Qdp+a^B->HkAhd!O0f4JCl#zq+tBW|xlD?F@m5;I-edUd#wU{_`3@WYRI0sN_Qm(tYxCYi*R5XW%>Lx~l=+PC zlNtq=PZc(CadApY=IQUIq>4Tia9SAhzlyKJpC>rfwr8e&&5sQ_r>{Hyz1Z|5{|Jwi zve&eNYbOLBx(htmK6}M$i^QwOPd@2bM*sfR;b$?u&)!g1HNZA#!Gt@N_ARc}^6TG! z-+o-VecIM*TSb+A{rmm??3?HF_b(99x+<6zJI~Ih{>P6@`G4Q4t}nh4`d0G6)CouT zy|?tXXzyULIrih-@BZnhCyAza&ECb={laS7zW1KejT;Ua9$UxU=WO<0ar+sGsxM_` zwuQ0luWK9jXU_dCI@dcZ{?vs{D(7_;e}1OCD5`a4r{Uh?!7ek6@9o^Cuu0v)#sB79 zKRI5xu%9dS&i$8KE^ub?luKE$)0R$|lp}6@`eIa~nwRp?iHsM|$UJygHLbSb#J&d# zB@YfJD))TJTK4(=vaL0v_YOaN`5X~VDZ`^WEiuhi6Csy1~-E>T3J$`|tev-;W;~3e4#Ew($LTccU}s z)?0cickKCU%it05YRTU@CLeA_3ahTM^uGAP@R?#$M~u`Y8?(Ru_WS<)lmB;f-JN#V zh^Y*(w?`RIoKe_SS8ltc*+kcSW@Lw|@x2+sYF2_Xj<6dh#IE8nOi8;C8TNZ){@Xp4 z9Sh79&&}^;cp#E^{<>-xr)ZkwSKXOUW-}g^QL#8Dog^n;)-qkz_Ta&S=Q@s?JlmCb zJ&0cMx8|>k>T=e_9-*Nwx4!?~m%nrS)vBpwN?(8Qa{v4J^XJQ#ntYmPAAWgYy7*t; zeQCiL?XmrGdoM1WDde~9_8zu#yl(C5MGl1f2{L^!WH}|3eM-${m4U0zv|5b?{CCg# zWc6-Po{_)AU!9ZxwfeJVs|3q7#TvHOpW(cb#y_|EXLj?9ElWiT3>Z^S|H%C`W&1U+ zhxz}LG?#u*@!iNFI5jH%-u*J!yIcA%mM+OY{rB(3fB)+C-#1R)bnR7^$HTn+|Gusl z+&TM@VBG#GReRUHuKIhne|~*cWxvC-NpAB_?(669cenJGmXk?tS!~^tC|H)+9oduk z{X1_{$lCAcTp1i>1tQtM#AdGgzbx|5zPTB4LY|XmE%P{)$4odd zh3TB%JDuB-0?O=lb2ePP{I$%ldDEtorHfu!)s#5994Zk#6PhcvPEIPXAw6oz?&Zx! z!b|frT((3pCgnaY+a05Sdeb%j^V?tN>gsr&JNN2abZGvsOP?l&PH)~-6aHEANxenT zp10bQ6iiQg+AV(`KebED?AVH}LeAN&%yRF)AAekU%`SNIsY5yp4b#G%GUTs)znB|) zeM@YyfqRE@a!ucib6kf0ioD+!PJFkA^MrxODGPfphNN#R17<(z?JzJ%;c8iTk9S{9 zWo>Qcj>o@mA2v~P@R}SX&4!wVCI*JnyQ^V5yat%6i@y*D~|Omd7P6+YH&JSMe?V+|0ZvQ$ls6$(ZI(|1j|v}wKW(hCni*X(P| zx%6@M@xUkN4(2<3{B+(@@NBU$J5Q(d8C#|qwk~0&$voln^**OPk1^!?pZ?_KHgA`m ziVMRs9o6%E%M_9; zn-vU2Br`HmfN>$l%tJJpLP@z?73|A%t^e0f@IA^14zcJ5mL?Wvhh z{r^4juls%7ew88vXTiPuobpdyr>1?+&(GI%T2q>sRl7av$H%(*`r2xnZ-#Gt?8V*O zxJ(T=c<=X~;Swk|JpFss?%lF;CcbR=Ui!$?*~Gs&`|F7v&vfoh-e%za{=?_n=`-Ga z=g?DHd&OzbI&H;tZMP|#x4is%ZFgU+p@&d_dXkFesn3h=PAr+($KkkZjkf%PJCTPu zA6_o4t=hFMY?Ig$hPTX-9yfw6I6QoLMq=W^&Bqz8s`D}Uec12DIX(Gn$hlon7{#Dqt`OdA~cA&t6ebVjO z%_0Wd7q0Shz0&iMVa;LxO`8Sg-+%g(ardp{+y6i5*Dubx{@PUkL(uD8>kgFcjJcJ% zcBx@;AKPRP>zbsAM;us;S$Q^Go8_Fqa3cSg#HO>0f2wpH2%oQis<`5rwVZRs%IV+R z|38%ffA!wmZx6p!@Be=KcCMz+{rX=Y{?t@gRV|Vf_HdMVAlUxbfk7rwZSzF?hS+lR z(iFxL2m8#etrp%5l`=ni=A88|&1-YcD4uh@OvWlc`02UQqOiZuKgm{~XEW313HR2l zQ45}Pd9%!X6(2^;0^9kbOBlL@Gi?tpWfq@R?#6ZGkYW9qGaWy2jFkB7zB4B8+qduB z9`%QwXY8khtySG+(SB*wtT(c1hW(ePf6)1?e5l5cb@}cCA0swpMP{$o5xG$9Kl@~m z*W@6H?6sM!$<8w^eSdB~v@8F&M6$eX{k^;T*Pj-!EwvFpklyKhC{2u`(&p@PcH!x# z8GY`>r7vE#D)~+2gj7$CcB2D#g{v2CUhd*&wra|x<@4IMKMjg@KJxnOE*)>XrDvp8 zpLTUQz`*eB@5j5}<*VP@|Nrw@;@Z_~w*($<+NSg+z5ds6`@iqDyDWM3^Rq}mwyKv- zYL)Jco4XRn2#-yHn zYsp{Et$5Pi{8-(6bN(}h-?!zuOzP3J+QWLjjBDnmyP2+f*~&^Br!&uf`!`MQaHYi= zX(mIC;MEJW7R$es{Ztx#H1V$Z{SWm;J?)H=p63#cj>XSWqNzo zt&O`T_wB{weQ)nFdn_t9ac4Lg6Z7c6`gPYWr46KSZ%b{^RsT!Wyw5((db&z@Nxe)z=fB0k-k75vlYOyqdfbAE369BH8^Ki{?ZJ!z5p_48@z zd*{nNeQ6yvuly!#{BHNHm$RYiy4up#s0_JkwucO2B0Dy;vQ4lw@M2L2ak=@ey1`Py zcy-l#sUv+4WLl4wu30O<7`AeHHlK>ejxAh1TRk^<9pLr7did_GS{-N3c= zkoVf26lcf13fxvr&F*n4Jok$U&R983{mNoVUNxomm`o?f4P3zXHklCpxLDf&J&I?W-2f+9XTAmYxlI6 zcfAGQH@`UivE$6~P21AH?pkg1+3iQsx%Ypjxy(@zXSy=SSb%TcJkR^9x~}BTE_$_9 z|8mW4J6qd&&zZu89aZ8luWf(*|7-le^UE#&fB2hzG38zG!JRjr+9*ER|9AKOf4|K8 zSKbj+Eoxt*b$8aXkRumFt1jzZ+a`ME{Bvv5s;bI$f6HR$PdwN=b6KV53}L0l6E3U) zpI(Y2=qwU+*3|s5bMN;9XJ=~qs!mmU!N#{`He1_q1BYVY&nrs}T?${Ey_&H)_U;Tx zq2-xj2M$c*bqrm0Mss3EN#_|qp@~x}EC4TX1IQ_(~$zB zrn|OkJtj&gl%8isPMh>8(B_w)g`}PIi?+qPT#jVwnbc_eeoy+a!rVA`BLefqR%`iI4umI<%7Ki>CaPvgcG&-4`z6FuKd z75P$DQJS-DJM%X~x4t8>EPhWfPhbE4q5Z$*|9_d+Z=a+8{B*6&^WVEa`|tZb!$^7t z^O7l9`R;IUg8i2a0|&F)-K?~a%rcjsEzae7dN4YqHeeFN<6Aeb&S>Z0Ylbi368vzF%e zRk3|r)&DoA^RZ4X-f(^W_1DkO&-a}pq?~ly(=_XtW$VYU=QsjhntogGHk-M8ofd;n z<{91MXF)-lnR=P03RBpn4m8eh3HqK<`S1hx;YPKUT1x(xg`6)o8_YAdKGxIg{7Ue{ zd&5OOnv54pqpRO9+qd=BlJ%v^ev8Cpi{FPX5q!1%e%C=(AS(i8I zSo;6}(7*rJpP$7YIW8(0d>@;yM#)}@U3y(ll)-QL<(D<$k3K|I*UVY?^TUs5p@%D7 zB+9s5Or{GN7nk>6y8Ye7IKbQGg(J6Ib)}3Z$AWElul+c@FuT)UAYj$ZmEUIYv+mT@ zyVN|R+F{w+t*McE(}fvc{F}clbJ3AAZ#V>d9$HV#XJ>2nmZ@!6Hf7nIbEjo4s2WR4 z814(3zu?RYweR1*yD!(fbuHR9gTtyR`QwJZ2Q7PZx2|=JVQOgU<6JpgR*mZwTb|#I z)i?as^t@Hfzw|UW|0E;BoT_l8`;~JHZ3?G0`thY7SytH8vTWn`TTLq_w5=?EmN{!# zMHbVs2Qwyk%NgtOg{mv1xx86r$i)%UQFBM~qfg;>&o|Mc#Vrp_^|uNJ`UT$?HhWg- z){uK!AvNvYr8~8e(~Oo*vshkpq(|U%V^Z)*oe|~%G>OFh**xd1bRvy1^A471o zxge+F>fOev29hQr)qBN{A6%=q?6b}6Njtka4B5Z9aJ=QU4XH90HkfS`KH1~Y;p+=G zHt>f8$XqK8-8d)U>_Oc_oE>GK?WFuHHctPv<#)gPoHupKN{dz{I(Qx_v%z|@cDe`Hv9pFD7MieQ7=B_+YomxfL2_I64M^abS3J{2=iK7?|NktX ze)?(o{=at5eQwJ9+Wq}I%ZZgWzrMWu{CxfP*Rz6xCQsowd3UP&yUj+z0?Y}oYxRP) zBpE|Pubi!oJl9eBy=8e+_KoJ_rJ*yHO)+;CXW8f(^6s6!(q5T)+CqOGD=xL- z+Hr!B_or9OYu+344Ga^C-H#kdIre8-@sDtKxp}*fw<#V8;%Z3u>)a6*P}r}Sn84Mr zPkl|)8QrrkRTomOZ_T?dt@J5%#;5KRx3o0ZZQXWlj+@%W74M!;3gX(ID#*)K6O!Av z;98@=9j9&09X%ULIqUgD6x>PzLphjre3njm{k1CYnZevX<3hd3-9DF#*3K^VoU%;w zeiU!pq<48YF3z4I+0ZsmV{c;OwOb*%v(z?nM9UX@^$0teniVWa&sP^d*l;In>5-*A zXTI!-`~NEc|HH?}`|sEN{`m5zcUn}uocuX%i-)IQ-75AkeakL>f#;5Etj|k@&1qw3dS_O4#jdG4uZ_dY zvu}c7@$_r12b~l3mQCr}_3c`Ic4#b*!HLae{sg`s7^w(~UTKZj2O89O?UZ|By zifnjJcJItm{_O=VM}FSQ_i2`YnqSxYJ9>{e_xbY|rkStY`mFYJ_o~8xiK}#U{g38_ z2fkgEbneVnQ<>(rgA?WymAzf(_VoK2GtLWwF4a8W7GLKHQ9Gxx$TH)|?=w4Pmr5|q z^3>COekCr>K(*(Nf=YjsLe8~`nmo$<>m+7b%XcJhnyc=eU$gJtn-qiT&0dWi7Xyrz zhv_a$adELzyY=jq)rm?@&5DG~>EC1wCeBnnmo@Pz6MIdg-@0e)r+b~s5}&ZJEqle) zaBa8TmnG{C?PWhDa*L@j*f?$4ti=An|99}I&CagxYKxv{ zip`8kf4MC#JaoyCCrkP?r{1&vbwcpT!DmIq-5y_F+-X$U^*%Yqa!03wOJ`78+Ku7^ z6X$kVxV5_IW!!C==HL>v^n0JC+IEph?|rSq3UwFA_rHWoEx>h&(ipk0eiYj7Lq}DU@ zC<)6e;SGubE4CJy z^ea1M%{Vn<|FbDOAD=#*`zLXU-<{?*z`U7S5S z+Sx2#?Qd=@(x@og>GtH@?q|)c=aZgKI#?>fl6LR?-h;*`j~wrozvOOX^Ni2sP}9GIGKM+=#mUYR zS}M}~wHB;ztHN3DSM9!=r}Su%)N`X9P1XrsJH7hf<=A|`;5+5vhh*y*38&8wr`lgo ze7(0dL;KYF$BivpuD)68d$ulCF-hvasuJhxYgZSp{{O7p{-1pP&+z$n_A@V;_&(se z<=3h|{qo(vKWi*oKm611YzxVKzU64DPV8cq2YVM;G8})A=5l1>1j(6;LT&bSoJvq) z-)<#vs__ZWFP0S*;ezthHhw(fv_g_M;M~q{VvDB>u*)Xg+Hp75;pu@{-zR5ytYc6( zfg$HiWy{#l9$ss$pqsY%k;gZn7zfX^(%zSzG@6stI=b!#7id**c zUy+^u@-s&`6#q@Sb%ODVM1khd+5Pgzk1##R5L+yCYWLb{Ggp?jJ8#yk_IY7p$~WcH zhBX%N&M7ZB`mN^p_HIT&W5X8=^=DcIiT?~v#1&2!a*8P6F zT{AGdccRcCbNTo7H5EH{pRx4s?>~S2c3QHiqh_Z-tZpIaU124*W%4qAY9%}!6Qnm~ zZQMBh;*2DT3tPV)Q9PvKuwb3#at`N`?=GH4D^8Tx=C1YZfBDO1K2uR8_luQTQ{SaM zw3=r3D#}S&x!lkZ+~u-Kd*39(sO)@hrO(rLbbQ&akUyn8 z?Gaxhiud;Ob%<<(yqS!QdxgF9<%3fYC9rky2 z7(0bBv-TDXSUi0rbb-;(Tz0ZyXfMwiJ*ETMYj1DUo&L~3rf9jAt#R+SjOTtUR?QY> zp62ADDacT=GEHcu?Yno20*y*u@=0wjl+M3lnDW^tYnr#=<|9wqUhmR79aNpYeKzC2 zcjfU$^)}~&HFuh^Ha>Y-WZ9@_u`2qUq~sy78$ELZ$|e;FJd@FUp0m|@;>u$k=kIdf z-0N*_E)vMgo%p@r_b>Tla%bN7avab2Xrll0myyM9t~3XRSJzgll&kxw1}Ehwa1`1u znbLIAjj!oO{rwkj3%*#t5_e!$mwvap?TlVQKzYNlFI$)NF!(HZ#c8Ry_1e9?)$KFa zZI7Pwc1cC;L_|Zk{ze>&l%8LAQAKm8tV= z_3>%Bwfp_@l?@D!%sXrJa&JJVs>Y_xb3 zzL!foT2_W>9=&>C&yzJu=k@)!KcD`!Ou0RE)=bOdo4eP)NRRyf{wT}B0OhvWN{2Ht zY0^v2%)IQ_^k9mUYvG*bAOAFG{P_3q?(XvM@^bI=PoHs~&%wagaVBCJr{EP)37#n( zQELr_3NA!l5AsrKGe5SyJ>ucpc~;jF&)n8scC+BaK0(35H=nK*QQLQ7#@nI?zlFAR z#Qw9~(8O?XdCa5yqhG}~1TE1oSaf;MoM%sM{7)Oz{`rtMPraN! zyXkMg?)vlN`tgs|JH0+W{>}gV^UX8MV!UQ{2EEvoY$e+(YkWQGdg-D2ouPqPdKmJGglgiutZ!5f$IJg)N zw%ywGW!=MWxy)txn(Codkk%sIeMHl5C zS*aP--;d3)x%uXflvMgxY2(GoEUVcL8m`g}D|av4@lBVJkBv$A`IT#D55!)OJQK6E zx}|#Jimh5x8E0mmnfAQXd&Bx-rqx^7TGC%7@y+O*6q#=9HfsB0Z->$xyTR_)ZK>g=HI)Q07^*KR%5FKjjAtE&F} z+CL39d7hXo4W6}4t0hCI>F|UqCB~-By$Kojw=LsJJbiC=o7wf56Dr^51nhcxXz7#N zXBlg>R+gJPgdTO#jO$?x+!q&a{GRvD#F?Ls{(o5c@74DBx}Tr(pJzILm|5(3dGYUQ z!i!FCTYG!zv!r_yUY_B&bmy*1^C|_V6t5n&NfXv|FAdo~?Y)tZK$TyJAh){5G|lq$ zOnMDXH+UivPp!E-iP0+H(AlLId_LdFiY_|E@zh4~@^g86RfQva=0DhBD!4jzXZ@bI z_wV(l1h2olh5taigRI}_BbKGFSDXzoTz+KbwOJKqTVMkO zrDra4zPc6U} z$rIa6YM)e=`nlHLxX#efy?bNFimfXkkm|NpoBe}BB3sj-q#i1mcny~x+Q zn09A{hnDFJOGV7QbUB?pfR? zz3GWgnEZ~5GjNNN!LFu{5=T2a?-FHH9lSL;U@+UG>4n%^A8CZ*h1Skiu6kb^ zDW;~gbjr;6ae2uB$Bv{rJUk*W??RxGi?C>{{esm|FP5L-*=W3ZS7YTVoAaOF)$QZI zcA@B8;_tE>AKi6Utc((W>{#@6ulb{Uze`u2u9#ce8z=5AHPV%WFe~I4tWX|CYCtT(Vo|zbIVzPFt z=5$T<6s{K*Z+#yM6!jPHP-6P5V8r?`?yk^#C9U$WhBK4TJ$RwE%+_Tg z>Dn=8mu}ly8u83>qyBPEbK{Gj-Yt2RksF|r_$1!TgoUp|B3gCb_V9w|zN_UtIk%ji z)z`hyNAuXWLaXC7dLDN=96Fb;RCsjx^Vg@_d@q0b%O9VofBpZ<`Tuv8re<`0-o5+u zwymAQb$`2h9oA|`1UMEwn)9m4_;|&|5x!0qo_C0gRShYZH?ea;+!p7%{8|PeqTV`-P;rxwac0+kT zQy(q0oOTu6Lar^%8af5e=Xdz4ar8S(ozc{FqVTG$d*U7=my}};GKMulxP+@phILo=Z~3ZIf?C&z;q*)Y$1>FEb}<$(ps_ zamVj8jw0RzjXWDlU#?Q+5K!{WR$k)5v-HWG4h=<%A4fX%)oTln*iCEtDxtt) zQn{x=BVqU6UG2vLB&L0Mw#7(1K=|sxS~g}@)4S{^Ed%I=v}EJ(SSE5yOn)m)95<=^bD>*v+6Y z$A5Bjn`#?_-Thbb|DON_hxpiULKV9`jz8%ufy%@ zRxY{6t?u$cFX_>{pxf&V>pT?IS#KX>Vvr&kw$)BNesVw68Cb-????nbjez z`W=TGIAjiV7M;m1I_GmE>#jS$fGnHT%Y8R5Trl9};`)5Xa(dGV!Ha$_dVW*dIHr1h z4^S`+{cyQx|HEq=g;mq$Kfk<5XH}GIr)43hv5m3vEHBNqrl#53`Rb(iI5(I)wOB8B zF|>NUYO2zzWlEEFl)Om0_UO>H3o3n~d*$Cbix~WAdZhmS;#8*@!Molmo0~Gbc+U)G zxzv@jcjZfw*5da^B7K*1&(uBr>E-ACIWHa`@87w^KYstRDf8s_@0%U0m~g0c?fzeH z^DWLEIBK|3T0@|}?&O;8Z7!B9kp)YV1l!i!67;b)etxLg?Cm=S50SJ7yVT>hSlzkl zqGOS8DddQY>itv+u@pPs-JaqBYfI1XaT44pCt@)}Ls5xuwiut{+}*R8WZxa~%e-+Q zVbO_f&7~Db{`Ll6Hauatx#h_2!*h48 ze|@$#?pb9Hms-i4EM^9SujXON%M@f?vO=ogy)(;OX=vhgvoPj>ho)foU5zlFcWPW2 za`*neD6pD4!!l`|+vdp`dQT17%FLB~I`2n$%hfE~AYmb~^4Y8prXlYwCOr!2{VCLt zyHzM~o_b;S+OKL4pB47BCYopW&g-ZwXRZ}~F6-C6+gf@0l*O4=XJq;3`8*UUeCVl>Do?w8`h8|D~waYmYCkc&h*Q@|O&Q_~XCLYOWsE>?uolTQR%5yj*e3dCTV4CcgaT z<72DQvrev`$lMBcj%rlo2v zT_Uos!{B^zMog1_wSfGgkM>%fx8GhB7B^IL%Ui`UVW-bd5j{@Lr%rPwY8K7S`LN^Z z4Z$7SJXx=~^7@jZs)N~$cjslV75#aY@o9rbfB8oBU%fm=cXC(XZpFYi-DSWw|Uw&EUuU}ujl&m)KeRg5V z>Av`*|KHs&H~5=azFjWZOGebw>*d3Ti8H3|+Lt=>c&*u;?^oDl=f%alA5mR4r{BVO ziJ(k;b~&?;%#~#aISkn4<4>L0b}jCxf5%3J!;%JXYL^B1Ex(*OumAT-uX1K%{&}p6 zzDsNX#nIE;@o9Bw|vxW1;m{CUWt2Xb@7m*qX&Z23>j z`1zzovpHY(zX>U>7Iv2?4Px0SVX@RFGs9*r_s6^t$$gUqH_Tv=C^)D3{`&TrJENSs zZ29Gu+v}eGR8U?YHEmAti(hV=-IRk5$yQ%Zcp+`M;Qi*WccUKk#{16dn#t;#J}>=A z3di%qBfJ5>`Vgt_lpw)_6w zyS2aHZg*T)^qoWZ;KbicZJMl)t+lpO$WRD3tWfky%*jks5}U=mXrY?4w`6niPpt*k z6|%v4%PV*8zax0iQ3 zZL7DkZ!-{#7WCerVx@n1lg{_=-_y?_3esN}o1U$?2Mu()x1Ty~K%_zssmGVSWWx)Fe}k;lMo)Ki*?>d9u}>!==$+is#;PpEnG8T^&7BIH#w2 zcE*^fwI&J*3i2KNC2t6@m^;bhd&2oX6q zbtl8G*Rk9ZvIQ3&hS*#=vDPa$iD8$&K9irl+roEs6LcHfAL%^0xwtLQ+W*$w$9uJR z99(TGz}MV$c$>PsgaU(rtFO(U^-QXA-;?(+OFcA|SaPc=XO&u2$#j!B4~mi|-Roo4 zPS*?8Qf;(klNaU>53;`Je&Qfs!wOH^Sqj@1e(G3r>++@fqCKPXkZ zH>$}^w`xv_rs|T!2k)xA1+&ecNk*>?Lp zXHBc*Yi2CyGIF`f`1$#I{r?~B|1E!AYOBBe$eiFwTIJ=>+d;rO&8O!f`^@s$8|E_o zkV=y-uXwQP;f4mjiCj*3oSMs>9OV9d(QBMqeA4&VwQcvRMOgm*_Bx=^xINcf)39;& zlu64p&8LQ^sVf<)E=!H{TYmXx&9RSj-mXZzYkP++S%r^X(p*b?^9=SKZQe#lO=izL zSZHBsJi9MzS6=LH9`U{ho!8aYwpm6UX?`+?Q}U66|IPxf&ZXw%=F`;{t=hWoqvY+p zP0Jqm{>qJZU$AwH#nGs6`5#l=8+eSCPH70v|5dYBR_xlt6=k*+W;(0vlCO((Ep|xo9ea=_ z^?6=Y_Ilkoy;;+PPEFYsHc```RWLp-Z~4!d?70&6=04w@s(JYj|Ng%p%kAyDDu1NS z&U~+HlzBXLXMfp)-eb~pik?kb`E~8qw-sNPG1$Ff`mN{NzrXs1oJG%+2cJ_9+CEI5 zRV4GpedmPYdC!IP@0R~O;rOQSw~>f_p_XvqmQHug&KC|LL4B{gXWlhU3TIxr@=|mS z>z5mM&-8dOtXOsHxWvx#y}875XZ7 z@%`djI6Le1s#4G0eHYz5z7%AwiS9lxFz4@!Q~^VqZ+&44wo38m&3oQ2Z|}Ax?T3k_ zQ^koHj(2~(-SyRQ?eV)2@!vbIy*`z<_U^jdj^(Lo3{tD!9XTVznwHJW%j_9)_4)JX z`+x7=|NH&^U+fOZDd! z`K-L|ZV8;%{6N*}<<>d+tpKB)-DYSoFkkQZ6boV-s z%=TRCxqdD$3oe)N%l{XdpBjHM(EsJfY5gg!0cJj`@AXSg+*0+q@aHwTKZxCnbdhsR(%~uWaNqqZQZM^*j^c*-!|tPwoQ>)b%kLT!^ezg zeVH?}`d7{Dn3j1jv3aj?_2L!wYhoC5pC_#O`BFx5+Tv+eeEXO`if6K0NJ_omyQ;v@ zc3$)y6ZSS4J?@Sf-d$hZlN=0+J5M#w^onk%Rj#8_dDJeom(i~apLF?ojwEIhfl58nWjn1?DOQGb%@b!wbr4ISJ}DJ zWy{}Lrk|Q)u=$co(x$a9j)JhC*|Ddj}4SB%ipDrw2aiDnD( z5(|47R$N*VpS`>6u1gfp`m2{rrcYUXFy~rlpsUYR*=Lqdd!Aq3vv&Iq6_J>Op+CxY zuiY+guH9MW$Y5F;p1oQq?a7spuuQfN3ss-A4?kZ|cFXLX$8m;7d(I+;k$TTYFy#Zf@ zDeHE@4AB=|5;D>jOIgxZ#a4Si2~IOUQK=PkL05TGa#?`slgpPk|9$`eWB$BremPh6 z7yX^Jw(k4G+Eb^!vul**HFB){&cnmz!oYdhVg5{^r!6e&F8grI4Y(*Ha^{HRWM#!C zlTO8SbgW!4TQlVp%M&A}X&1j=F$*m)XAKlLd|g(%GHY4h_S?O4_k{JWDNFi1LHW|# zvf#H4w@ObR5&8ILO4a*Uk2p>|Ij5|6z}IDqv_y&@!?YV0OZ{%LtZg+taLaf8zM8k& zquE~V<+s(%*vi(Dz;|hLv|@^i$r+RBlb$_YcP8=w$yKt&@3eedgPGDqWzHH(Fjk*= zDtJ+rC02GyRd|EQ+V4hFFL$0_9^{p@Bz4yGOf@C;(k=IPOL%OT$eUWpb?(2Gua6sF zh}XOgk*{xE%UwL@z{i(ApB`TyZ@B#PPnEr?N+~9(ADXjlB-ob;SZ;iF_>IbxB$4l$ zo1~VwsNS42f%k%Nm#dhDa)HtzL$id#oj=9*|M+=#_wM`uzHOfwynEgE&qdE)^UVA7 za{d2@?e}Y{N}j!1)pH=_?yIP)g5HVedJeEI>FWHxjPcIWb_A;_|KmOzmc*8>&7=W}L# zRa^W0m6qnj-C-+Vr9C(zX{fAX;J@m?Nlr#vb&r;w1!=L>rKL_UYj@}F?LH)td`5M8 z(7Si<4)FTy`o8t*kA14CPO2d+GoM|lW@ZreIM7<^mHA9(t&&PY#oLl&R~-!<3T{kY zZKbrAp)f*p*_3zZmRHVunep_SCF7Co8eu*;;p+>QH7N@wK2lbCzN2yCi96ZZuXizB zu$liXGxECWnz;-LPVR?;XLlTO_6p5@Yq%ot>e_YHzpWQ-?{~Qtm7N`}AiyqhX?9t# zy2+L)K~w7Z+9Z}8w0JtHILwfx>)xtsR;zl<4+O3*s#&Dr%%IyfYfiC96+18I_lcVC zHr80zD<7H=&)RjIA$Qj2O*)Iz(%vy2-F-K2zJ2|_^8f$rYig?sPdIGwc@rkD_cY|k z79B%RV}^-x$4k^qCMU2QOMaeMrp7YC=wM!cLiO2c!KX5>oV#ee?)I|06DE1J))sU3 zi$AGmh`*Tjby@D}kIO0~4lKP`JG;@+xLaCa+qI^@M?F5)^3OYLXj9(OFhg0wnyd9* zwM(?TveAu5nU51b&%Rmodg_5Qtd&$%S_|Nj0SxR8I}C%wA5-&2~HmS3Cs^IuW9-TdWKTv%)`1u~x5%59aZck9>l^Yin! z-`=G2{(k+8=SO)%dJ9GNzw4|1D%ITexCEF)2IFm4ITBhEa8N#j7S2yqk z@eB7wnVbSK<4)QZo~7>?(el{A*IN zUaDE|%)KkK)|yT@pulq6de*X&-()T*XXl$7v6y9caoIKRnT#6egS9p{*8lo=`s=P` zAr~@d`ddzos$TIcezQPMN+WBlP8yHk1ar3}Lq?8ZPM4G-2G(+?G`};FFLPgSnBV^5 zU8Pjm8Rg|`w!Zkyzvc7WY5XEp74iZM5he+~$1DuOJajCJPg>5Nu}bjM!l!zNKZ{0Z z@9kbGuWVu*v8?mr;rNVM%Xa12rTrCQz4`CS$un7Rm#F=Y+&o)%vEW46#RV;oC$~Gw zHu_r5pMKL;=KiAxo9tZvSDZ1+O#goTb;#8XUmX_ADfXGecQ0iM+xFXUPfyoBXsNxF z;legEvwwymb=v0iFVCL&(j~JdX>$E@KxjO6I&!U|@EKeE}9F^{{FWm4o z&5Un)=BYUxCl~8?p0S+nxTa6_$EQ!5)@7Si8J>KvHN_(7r^WIKZpS&R88SLNX7n{5 z{w~nkyr*zMX>!%fA4NME7D%)dEYneac4-+y3&Xlg%9kE~{P^?#_x*p5UVr}U!^6LK z|7L1EE3)-{d3V#FKfCA8dGF_!_P{az_R=`HpI@0Rk6t%cIFPtjL7f_co@5B2~cW1xZq4J3Hd1Y7q`*#*-z|{{9c&iA&slS~PQ+=A}Bf?4wKbOTX+{IEi1;Z^^y~4bNiQOJ0;%Su!ja zS;O)*(Nj$JSEKo?rX6>;m1dhxyJ_OPF6=gkr+DUXS^G~CE^ZdB&4==MMOzP<`1*)^ zpZ~@0i00JHSzcb-r?2jIYns=6t+e~SsOatg4ypw#=Q20A@MV;5pH^Ic_iy84-6>~I zPdfj+wL{`2Z^iv>E+6ie-#=5l)6Vwam&e;V_ZS#DP5=7uu=)PKcbC`y_?pkd7Of-2 zQJ(IlshWHDD&L${s|)I7`I#x9LeEfV2Ler>5vJ~i8@f=#-(tuiDz@V z_P2Xyr!p899(mRK>YIm;kfOuI8I#^=@ox9X4mCg5`TfN78E!`=bIv+!G3RPiTG{Pa z*Pkzc{WU6kZEb}zlf(PcH9u?jG;WbrmuLO%5WI1#U%TR^b5eZSj{W-Oe`KsT`b@k0 zB&&39te<+o>%Hzhe?3g4#kQUhajROk%Gd4Hm%tlt{#5}}7!q#3`da0ES>(`D2OIz8 z-Mjbi*|SCKv(5dxcb^vUmp*q%Sf>>D~b3iaj zS?SfaP=kM>4-Ox`yUEA)jpF0f&k?g+O3HUWnQ}%lPs@I?@_io49buI5KICi(ZTQ_r}@U);K zzIXDrM~f#uyLRNvtQo7iUU77PDV((H_t&bozP`tv3khzmV_ARLVVb3e^ET^3qv9X) zjd{cx8du*8d;M+)>(VLh?cbCAE~~!T>{i-pvh>fkV>XAjJEs|zEdO;)+ahe)Y^N28 z73a4|eeDWym8_H1pU+t?6#Q<6n;g%SImH%?-5iXs=B1VLoV3x%uu$4jd;MGc zOJ1hg8E?I&$ee6zI2|P%%uwt$D@adOaZ#yYrr_U_2-e&4d__JV3fO;KMNCjQD8jWh z!YD0@{p_s=8_EtVW7+qNYQugiE{BC1Kfj6&D zNBvf4Q`K8~X5%^KcfB(;cTB0ax_t4aQj*K6E!Ub%#Bz<~b!7W(I_BPEF`4r-C*m{r zirUW)|4xWpeeG6%wRMDhe*3X)*Do5{bGm5!TfM5u!lErV|D2^_?6k?-tw65 zI{P51$ggwJz7sQpA1P0>vO94)_l)N(u7;W2eLuP{W~K1`-s$3U>)x#udgY$+*vKsGx7GDwvKZ>pKaWqhkHprsh55cf6j+tUDhS`ZgYuU zTkaPui)AVM{eod%SS&v4rDXKa@BaS%{r#iU ze4oAux@;5fr@Gs=$}jhbG$d+FGWIW4 zV{($2d?~DYvf4z3MO!<6uRE>CWP0!WImd%9o-LC5Y}r+!E$KeLG5C>!;Ec%ImRlFg z`^FUMshhDG?xfu%^kH^gFXm{Qkt+n&a$5}-^S>;+k8MGEB${t{yN0A?`Br`#sbGpdJ`8H`G_2ATv22-S8uwv zrsr$7cRU79YXUMJzmR`p_vq`j`SaKNUJ>j_m{t7Dg(Y>Cl53018J%K>&+Bf@+_}W3 zfqh5G&X{xG%Xdy`_U5)=%6hgm!sDxu+>;fzOS3~iJcwWYZQZg7%ZoyeBrdBzV0XN6 zhxkRgHumPF2^DCY;%73&Ocw4-&d?!8=7pnjKyb5e$V3v z93EeG9G1{5zO^!dBXIiL)N>oN*ae=q`Gi(2(8%Ao{>T1{Ev}By*}Q(PZ+e?8Dtcz! z(KS$T>2H{_IPT>#_V}M=yI0N~c5 zwZG(+Txvc#Cy|apfx9$ckDl*<|Fzw;re8H+KOI}>b-W|7o zcftCt*Jhnkc%=MoSMA?lKexrXs;JNNvfG!La{HI#vLb#B|0>rFJ6aYUZ;F)?J6?EF z^5WU{7jK^KTDNuYo;!Klubzpz{cLB=@3-5h{5bAEzwY1V`7>8|X6HV3;eRi`?)I)c z(ayUKMOJhDHu@EvYYpD>S7FN(qo@flexG^s?a!p*x~i&IvC6KtAAVW<2JKPv_|hBu zQcUS)cw>6Snp++=>xw;>XSx|En18tUdu<*UpO3?uiKoQf4sbH=xw^LCR40>_vx}kW z=C#`!n)^6zZ+X4q_&x=r28q5s`)bPtIn#gN|K4HxTIlSfz_UDurn;*pZoj?jnx68X zyo8>-4ChA^eD2&mxw35SrN@SShceX5_eQ6@k8D;6oU9BRA0GE} zIVs}PTzOGWzU$NA;Kew_(2cUTOb3JE;ACarWoYxV3FK2?vW?cW$1gHBma z_25ZoaZ&%$GGT%eo2jAlfyX+Ogz>!4V^X=4g&j|&r1@5bKd$U^Qdruu zY|iCdZ{q*YuK!*B|HtmNSIerawu|JOPyagq&!gS;KQGZ2B(WpO@rruVs;xMdcv zr>8EQGGX#B?McF`4%tq1d_$IJ>3z6oeB+?zi+9|L?=!>FeX=*I$2q`LnTL?Vo4N40rOjfBu=b z_nyn`%ad?&m5A}TvYbBbD^_l>O8eOg3}q5 z%|+Q=_?$O5m&yxsFa*3!aynj9q;EAtQ@(`h7VEE7Qe~UipKZ;Z-PP@3ZL{;9!Xfv{ z6E1Tul*-h+-o@rWXJ+PeR`q9xI%hs^nDN|+W#jvt4vCjFcJ2&sD%LHsOux;0D=M3# z$Kgb#hx|$9-5+OsbvG>HiDlom{do8B2MfRdyM6!PxA*`5y?5SYR^xp4;FOtXRf4|X zv6bEbeGFV-YqA`ruX{lN4gK$%NMiCn&;im%g)yAI=eW&`hJd<;2UE+x{*8|0iB!DgS%y!QW;o)JUvokLnoh_cZY|i_0%fEl;|6cz5_vxJ} zb`|#<_Z|D5pR4d=X7I_MC9_UXDLS|JyUU*4ynGjfWA0^|tD3Z2i+a82(X$h0_WyW# z{^!2GMZORrSQd4&s=%InKSkua@T*YcFVO|#<}u)M&We>)j!W>=f}mx zC4?W4`&T>B(Z$6d4B8o#4*cEQ;rH&wZ;AJv5)yKW(MKj2xeH$leY8i~_}<^G^QOJ5 ziPKwNyytktB1P`3>O&b*@8v|4vQ4)YRJU@HGpuoUQ4u*>Jkv|FQI#!D{LjY4E(hk8 zNj#Bfx&8R@#Nr~S0PcF0^5+{<<4(?WF*#qX^>b>Rd&93SN#ZQqTk8@rS>_&=iVgjH8a}2_Nh(e_T0Dg=gGI8 zjop4*v_m5`l(+uKD%)MY#q3L5W**?!P`)>B>&niR%>hj3dV2T*rp+vh%D)zx`QqP0y>urRQ-_4-EvieW%;1>0t~6Wg9p4eA z1il%;?1th=tusUz?dtYg`h@=aK7U{S+L^mn?w-8be~yk=wWP_lJ#Zx96XTTtDr}mv{ft_y2f#{QGpLT~EH` z?c4FoX0DP;xWkdzm2Xq-9{P17#5Pf>G=L*r^EKOx1ltMPSLJSWTW4+9qxW6Bv)5eO{qDnFzVf8t=^-~x&aAXNTYU1?F3CMPYqd)agB=dvGg*3IuEw6v(}HgB zE;%pQn;?6^wlb2)V=2urmJzI0j|MR21y+41I#?Fu1v7o8UP*mYa z>rESuYsWZN-HqXx#L2LF#jPFgDj!~dPK|rEJ9E~#=}UcLmQDG*gpJRJ$x$?JUjFsx zvpet4Kfm1aIg>}$rI_C_mtrJhjUMgsIX&gwx#Bl}Evo=ZnAd3> zU?IO_x2A%SfReJYpJn{A&zlm?z0#Nxa93%Az|5xKBG;}}M%}CbV=&);`tHxWzdt{H z_ltV{ujAj#`Stt#<>l_Bn!GoEt>5{w=KI6_{~z~f?&@fb3gCKhB=cl(zEGDF^Yfm0 zJi>mLRgxTzPC1ZyYO__x&!T+^j}lvZ?`(V4HgodUy4rnfL-qOJUwQlYczbH3Kw<%F z@vX13UqoNOc911j@MGC+fuAp5dWI~YBlc{*jm@3uH$z-PFUd{w&e7c3=(Q(3QB4Zm`eOqmrt=|j_L_`yiew^vB8Hc+v7Oa_RpGhCSB#g z5tFa$&MjGxsC(yN!z8O~??U%>$K|hPRG7?=x5&5TZdQ4*N}Ao%)(gEdLW@^#jp`J< z=&d<#vrxy(4qk2piO3y$v){h`_*1HQQmW-b>-WCSo|5G^RnocVo^@ zEihqua^#HVbO)_F6Ey@?1q%;fi;9SNE!{J_?f$eULcX?(Zcn*vQ=-M;RX zU%C9grDw`#?)Uu3eDV11-ShwcQU5$?>DtSaf;|t0FfteyG~YXun^4I9Q)gAbZM4O6 ziI5ddQSY;ARW8fDm}dRV#O5huMuY*=(VhAEMvt3%Y98$GD=6PN-#?upDq8l=BlFCO z?;e}G*IW8}&s#20IxWcOuFHiz*H%d>3O97@&5qDzYgn*q?a8ue-zVSxJQ6CNZTKK` z^VX})or$}X4&PN$c)#F`yaBh8U+}rs`+KXuzq`9TbC%LumW$3-`;xE8o(b}re)(tK zO#SDV58wU!&`14l+`8)(FLPJ3tatHbO*q!x-ae_w$Bse4mvzR0tv;NB5{9X}V*KRo z_7qj&;eo{_ea>i%+X;q%rf)}sO=+G2?%j2;*VfV4}``g}r{P^)fSVswi z#r^w|7OJzB{i&(_byL6opz8g+?Q=f={rmT6k)_(SjopWLsWKc+=v(u8*S)L+bIa$M zr@numUjF{xQBNL z^4afe>x>mkOwOw>deS!2yCT;}NLh8!$`m!OR90iAEPOfF`bo;zM|dH?;o6WsT0W6phlekljD8aq z^DP#-^_G2x@GCmNUH4f z4h`jXT9BkDYjq-o{oVe1TE$Y+w>NfdQA^wK`N)#I*jf8*Y~}50S81)?oxyQR;N#P8 zPP@bGp8N1P@+!KOt0)LNx0}3prFF@OnK|eB?bDBo7aYi%E3_kfrjX!NBj3wLzL#yD z23<;>Det%ZbERkJj+EsG7dd|B*kHr(rJ_f*_$14U2VN{kelK$3+5TK2Ou5p_)Aw4b z@}f!0lHb0KGFWni@5stMch>pq>mStlb5p<0uVMS^&v#SZa)dAc`gG}9@99rpEB`(` zogBRT-nNI@4KmjpVxzdYqaPi}F)m*4@RM3wz*hIK!U-1*70y@&IwfDg3Q00IXIL;}Ml9@kAKLm$0?HFuCaayg5;}k0Jcc+wH7KPP&*pw_Foj zvpkRO-(kfWWzX;4R4rCwU@3mPcHI_JMuq~e|Et%rX_mK3S1{)L)LEFII2*_UX?H&KDd$(x+f#aKzv~dqd^C=bFb4 zIjN{89m)_=xi%M&br{KCU)YS2(btDI;`I zjowm&{cdabExjOf-bKYmYU{PC-Rmr$Yd)Ry!}s%-S8R)`+9>c zKg;&6Q)2SCIPbx`Z6c+eSHdjjo`3$dXxW@Ax=HmF>Sjlox+TxsoM-W9&ITE zlCh@dD|f?|#pbU}pX@g5I#L?DG<)w_X18*M56Z!Id8ZD3D7(MyZPng=_5bgkS-1Up z>P(^LZXZ?s&wG4M=kNRf>3;p++FZ9e>!NKXCM3<3oPEl~*TbPF_!OsRS3t|dcjk}Y z_{OMCH#u*t)pGHgiSOx6S9b0Ca&6nrC;ad4+1LJ*;`+Ze&+)mf%iRTs&a^2kQ(Ru) zt|0dH_N@uR7OT2;mfbv%GV@dAOru~WozC~~&QCwB`RA(t^59c7v$)ic%$1TpZMW;~ zoXwg!+0rEzCMM}V8SB&D&yc9>*~dAzELPsqfbaO)zzYm*Y;Q~Arloq#+_fclcj@IF z1v%-{e{OgyRcH6v=J>46D^K5Unbn}>&A(4zM8@-sC(cOUs!N=$pVOlxoW#B0^PLPb1VJHGQj^i5qY%JEsmMDfil z|LxOXf4x<$?!eSx2#tgW?(AE=}=X(cN7C*Xs;Z$IjKvD;byZ_$~DAQCYCP zzG%wlmW?bkjf?%J2&a}fEq@^Edf=Dg|H|K@FTVR2-ehdsHAC1qR_}T0%;~9pFYWgj z8YevakjK3Tu6Z%%LZb}lr7tD9^fekvR5nPIE`gfxxf>+AD32q;B0V5Fz{K*bTF^e_y^7-3?Q6QV2h- zId#s=jZ=i?^(_!P{_*D?pXrxtq8c(dIM3eRFY|K6hE=ELfOg2|-cD27x-Mg$C1dm3HE*L2W&RFs7-tTR>E*EZH%VKS?|IuuJ z!qVQ(?%d}yEMK>&xfRc!e){s~U%Tb)@MVt?rex2Bv zom=tE)zEHfj2EYB_@7zNZ~8W0o6_+}MX~+#OQD{g7Ypy_EpnX8(6453$%LP0LWhtd z^DHi7znycYr^YQ?Fpa_ClncxI_wO&fefmo#Qb9?``R(?$Cr1`s>-T^4>Pd4Q8$%o0 zlV=ypBF@El9kPu(TWj&}aG;UeDNFyIKEZuE`d)A2TO-Y``$#vhX3fUbNWbSl`Tu|5 zum9OSf3E)W)W}0VUrH*MpUZw5AHVLz%g@*KUvDcFm8yF)r`WHjn)e|4^9y&kZr`?U zZ(fhf=b~pZNf&%Nt#7O~buIaFVg_$wbjY5xX&sU$W8UkZ*01{+oW_-wS20P3_sYS* z*k?tSb00_Wc$S#7SsELqNQF=JDBqr-xBp>vguvp(%a>_7C$TC3`pH&yRGo@(&ZBd~SF!BClr&oWP0 zsy9ijH(sus^?1dv`-#HS3a2}tkkmo z^8@ZruS-Smx?SP@xVK32gvj(ljV%@S{~o8Aiu*mD*wu5OZQ+?t)qU&VH=pc#_adNZ z*PQ7`PH^;0Dc59syIr4O)LO@rj7y! zEz7z3-z#So&%48Ts5jZ%V1_@p`l?)R_X#s@u(Q-RNO0YoeeG8B;j+DP%%Aw?Tn^cu zJ2|iK!u<^)zu%=xJ6!u!)SDUVk<$MCtu(h-=vt+Njy>BVTqF#pmHP?5D|@smt~y_6 zTj2V8r;oo6UNp&Q$N9@GdG$Zo_y73t@$cJjn{F`W+53NAwg2;uoxSK# zfhj-NoSkaVCKTjfkJfCRaL~}i_Q`{H6MsB==dyB%X5q}|H!}^&YHsi*@NjbkFN(Q- zHspXw0@uW{7aEH6mrkCVxVFj%cr9&n!rlyIx$4R_8dgId9t+BUXhZ}FsJf|tRaC*?QpHnQ{ z`+td5sw*kDtM#oZHMkR;zj$Gf*Xtj?d%oX(8}>TND=^c0rkq~+69eDVm#;s6UF!Pe z_O0m2Yr`DY>}7p!r$p>EjvHr2~@n6Oa2}T^!Rq`uOnccWQ zv)HRxZQ8YiCW5;)7)0gxr1L-a#z>r8u4(+fDf5&n|MBoABC6613l>{TB+XNQCd2fr zu~9%~&9cMlV&AQ*=1qHAGV7U)`8|_VC9X4eT~9L_g@qa|SM)QTV=itu_53 z{@-WY|0n!?y6!=H`|r<2wVxlxs_Ceag$D~rl3KhhRxyymh-3YZs%SaxSCH|Uy#8=b@k4N9jBS(-HMiqEE3uN zVBVCI9T7|Ox7Pjoax%u!@qOv6pC;mNT{`U%yXJJ>IJaC?aCekV7TcLQRqq$wikYKn zC&K!;^}-p68HS1lUN?Cv%4Lea3vO(GmN&&h)o8Mw=!HXy4xvY_Cdd~g1+WP({Qj-} zpZNcpd8el9&-e1w>gBv(^Ze40fA8M^|M>g7e>V5J>BnNPR{PG3)iqjLo!j5ve{p4x zs;*o6k@Y7J&Im1ae!bWI+T@NF^Bt>{g1lT^euqhJ_BHjq3Jbt@tFo5 z&lB=)oxi;2Nl->h{Szau1sSf7jO_Yc6nG{&ZqK_Upi^u$|NLc>Ua_;K5%1r)x4g|P zS1OA1+xvZe{Qj)`v~5!|W3MmQpZ|R4nP9ggpsl!mGcP+9_g_?QSm^xWLXcNWXWZu5 z3a%UTZgK9pHkH%joWXXjxfes1UN@Z1%;2V^B+Q?6{PN|?*N>lfN#SC2lj(3jXZcjI zh*RSAg4_HOB6k>kV~=j(o2_mvEc;|SgTo>od%3e8+{&fo)I7FcySDvqaY&814M)a5 zjXCYdSOS{d_h0tw+x69A4da6MeIaY}7xTY0+GXkVB=M@qtbcQgy`}^w9XkhP&5x$Uau~x$MHXtbt+T;jdu{o4VN) zRMn)Iv}GzavvQ_gx}N<%{{JI;?&tA;KFZhB{o0qZclVj{=|3O-6H*m=w4i92OCck_ z+U^o_1*N2p6@{`~u04ynau)a>tnZjBuvdpcz;W$F*=rq196zOIzp>bpo4wXxll8MH zofoHTx`(m#3jTU$xHVnU?ML_o)^7{~0i}=jXbWS+@Lo2uT_OD|-SxkAWWG@CUn&QS7 zz0AZn_{U2o0cF7t+j6&@<~;iL+Uuz8ueTlkzWa>U9nE#uUoY36FE1}2>{VV~K67$s zk=%-8l`n;Smya))@Gvm^)x9@nE3@`q|CL_<%m2N8`Ss<`H8)ON>XKHrGv>^LW$d)eFH0Vofjo0l#}WmX zGb>$^KYseu)KREv!Fy=m8^NH3Q2}q=yASs~T+Amh@5u%oZf2og&2q1kvK)q6_zXKM zD=Qs~%)K>_epvsc=7im&lCDcCh4<9`)6AYlw0Iug*uHXg$BE2Y&yD7uI?{SIb=I=f znTM9Gozm2JWp@AW`4{gVj(l@NM_J+hgn2DHiqw1FJbjk%Yu@?iE6S>}U+>ztd-wlm zkC|ueSnbLGam~ta*Z3aI`T6kQmeqy{^OrTKK3mvhd}!6yt+}!JyEWzu3ti%os&mkK zb^dkfWnK>65Lwrk+iqPy|N8W4ty^ZXQynfO@=GM1@;%eQamO|J*181;_!9CCSA3E< zYj|l&5EC=ApfPLaUEiXL5=*87KADS}I@Xpet=f8To0XXAvdcATEinbNcIwR0NL{e> z3E!k;lXi;e&GGUpTw}oSbk58M4w=T_S<6;lFRNh)PH@hi+bM7$MDoH_j-CshvUinQ zVq>sL!AFng9H^ z|3Cfz=W2ic{r&y6^Y-^rf1f=3^zrWYr$ymjzkcL73O#%=tznA5CXJ4rGp^jbwTn6R z1jB@zXQK={j@cfW8O%A)zfb#QqjiGk?Q1MItaq1<6^n;%<$cGsGsdsyRd`Z&XIR-m7c1+p1My>z$<{g3; zQ)e#k+`VS)&C{R1{pAl=El3gH7|m*GTwk|y-K8tBKHH--3-2b?us(lzq~TfbiDgsH zPyhY7^5=(lMYB%X%=NZwl$^m(=B|+$HDh`Z!^+jFEayD8DDKLbzwD%dtI4K|!ROp+ zvSUM6eeqqQxh8vW?DpsD!*g?*J|8(Vqk`!$V-?TMoy$Y+xc!oxA-JK-<;~3_o}#`F z&;S29KUVHk)$bTJg(FRsUKYNW)&3p6{@VNP&l0Qir&li7qLZn0dea8Rj<)4%I_}K> zoH;Y+2&?vjeoOWW=GS>HDY1{(qOO1a`1)e(6s>^!~8_Fmt*%UYn~L3qZA89^rt zBi*dyydEl6ITr|TT&k1O*KxLBb#JFp;mqRA&oa)Ge0juqLEu`|Wfp4Ovz+Z`x%O@%Cw( z#Eo(j9gk)0TdQ{0H}J@=X6bpSCdr-;_LQ8sV2TmD(ouo6Yyw?oX-uuV)<)%*gSJYY zS+mu5LA2J&GkeRKMM_QIEUR4-eSM11+cUEFCU3mE*W0u-@XYexSt}ZrZ2h`6@3q#} zn;*U!76zN^Notq|Iww6@d@*c$)a$pqV%J|caO82`Y+_T?*1P+?no4y3;lH^qQnZJaIp{c?COG_tnj9k^~0=D}Te=X+m^zh(eKF1xs+HvR8#(Dqe{q$#W znrOqbz9&d{;?}DN-W@&5xjnbKYUi~mXYqOkkHe0P2FGSBROxg)60~f7&F8nr``<6_ zkpA|eC1%qq;R`l%EtdZ@`TybN>;G^6|I0u1^YP!~^JDGp=L*};xwN~TbMx|*d+NUn zvd%du$eXLNd-1*U$2N~v&#;uqmymrsb*Ieb%@@7jrlt2WJgDCPRVw(q)y2D;@1A}0 zsla~I;YU9&O9%-}e7st!l4tMH!tF1*0u$F1-Kcd-&THw2&d*+b{q@&fxh^HU^X5!u z)T;1$`SYdLR?qAw21_@pIF-rYJ+@)vWx;JTdmlf};EU1huGWM|L0bouMHt+#3=PF>2Y zDf#lcd6(#G-W$BZN>}Yf|9r0hb)5hC<*%i>K^{`|J7|7OiQ zdD~w+T(sY$fSFfA=lPu@E-6vVQX`8z558Y>$=Bq}_vEVYMZ4=NtKLZUG3Lg3F&whK zWXrqsB zLY}9WPKnC?;d)5!jC+G>DpQ^DRM{E-UY49vo#_>EXhx(<<1tPF>n{r2Gf$m99n7JS zq}<`5rTSsMg?Uo0ez9Z`qs-!o4S6A7i|WmNFPq=5tKJ#o#Ii2dV3KCD-Yg*{)^yt~ zM>MDItA6Ejr|?J-Pi$+Xgkivb)4Q|G4_53r@J#R3K_O?g+sEBA5+22#Ia9^qdNo@< zu>Rs+nPfeek_#DrkE`VmTw5W@c}A2o=vn2w<(Wp3*HnF%u3q72F-4PS_VS5bzH-dR zDzbcYbGNR%`|4U0^PwqT!q!bo4qrazzN50?A`3&T?3_NIrhC=lnoAWfPCR_>jLgm> zzjgX#l#11tikMtrzR!Q7@73?L9A!ChZh>P!m-)iE~;d)1wVajHXOyYchXR1Gbdu490!GVE=EoL4P|Wa9 zW#(OddqR)Uask7h<;i{TG@l1?sjb@E?Om~N(aY$pBiibrp=SgoCZtT)k%&66KEeEh zf>PjWUkyVGrKIAHVI)$|Z9qG-&#(?%+s!_UTlXxpBw5T79)+ z9idhwyhR6ImIw)N?R)Y3a;DzOls=9FmGjnL&lYw~5iUJ^xW(|A_v+}|^{nD~XVgQy zJnz?hKKrsr>~i$_=zROS+M1d=uA;jK8M)Wbm{Y9TT+@1@?>BGmC;$IXrk{4Q|MTqn z%o(y5bT-e<&z>q@_opiQwT&*r1>e&~%U3GwTDMGKnyg^5PSu2+ z+i#x@eW#y(uhZP{rOla{kGmdCVSC;yb7Ha7j2%xxyqYd&&MV?#RCs^w>_hgN^2!%U zQ-1t=s93fuTU_u~zlbbfGM``c2m?c>#+xkW<6mswuw8EGOx7|z`?!0? zi?G=pHnI6@O<7rvR5WF;esnFQ{m<+Be_j9o?S8ra(=9f2|8kpN|F+(> zfA)L5<@xvOjeR%IRIIwXgS9X=oIxPi-9vM4Rt>|!QlCSe4DOlw%h=?O+`qX0bA&_( z-$iAieYOr4k3Zb{X31WgsV|lqe4MBzJF7?G#*Ys(T*^&Yc#Bs2pI10xW@l!&W5T)2 zd49(~{X3~&|L@)5YQdMj`TEzVFIRWsdAF=lT0mL$&*~MiNz$@wpPVpa7I?e$+U>8E zd-D_%Ss5M#-fXnor5C*K&fNWfzui{kar0PtWbMf*j~Y5I>*~C%de6w-aCuv9TA0_a z-Mgiuw%uy{u`74`twW!mUtnR$&EH_qraX@=?p0p>xAb~02K#^Se!EztrhH0`l(C$C zxqrUg^|g{h&zt5PW~-d#Z+MaM(-gN0Gj?5FTd4QB$WOELTkh>9HHXf#3uonA;%zM3 z75n~HwXde`Qkg%`9#+qo^w#uBVz-crWcB3BMOV1?mIv0??XO|Tn25m4Ra$Q=4>`Z^%SroQUcR_a-mdV>flFt19k>_1cU_`^l9r}%!i43n zQJyQO2(_j)d~y*vJn2Ez)q6>Q1(*d_yt<}x;q)07md|*>pv zFF$_#_y*S8z3mgY?=s}v2~tw7JhrC1SIX)Yv*6jC3Q3D>y6)baIa8q1``Jo|XD4=a zJZ6^lJ90BONvXu7x0*APfhRwGxyzKxB^8PCm&)${e7SXdz{j^w&fRTG+@9H8^y7ne zdBeTkJa5legzo*mceg}s$sg~OM^kFdd#>IHvD|9=Qcdn`gSScBd{gD~+b-+8Ir!c^x$es(88;QR zv};>;RzI6!th{I^v(f_IMPE#2M)I6`r#IcDLM2b>P~w58?P9-AojGH;V9E36ueaqs zu<*USYisG=bDwLh?mzwf(>#9vpXdMoH7|R5xBYuqzP#Vh!*^@8`@ENXCkF!W9{qbZ zC**FUYsk^FJ(m_7WXXQM7Pl(Hs zug!I8mn9)2Ypz*qU%fbSB2tJeA^fG#OOuctDUX~lOW)q(9=4fZ^0VRl zueImq2QeMble%{6gW88n0<*e==lOr${d>2-<-hzXUWdZB@1An$XN{OV&-M$Itj*aC zyF4tP&$(pcyL8FAq=o|zgEw*ClI{Cj-C=Up{c&rD<>rj;yR``c59;jncW&7A<(}cj zmkX}%{(ZshdtREecmKa_Wh@OAV)N$3+BX`gEUD0+k~|3RXIuih_;TbB|8hbl!2>vcDUS9Z`5J z@MwzC&wK;+xj}NBVp-dAqq8qAj|Y#4b3~>)_p3{SRIDeqUdHTy61*ML#SVufEw8=D_3> zx_k;_uYqKM#Ze(ag&nO&pKD9j_}`Ih2--7aMO|Nc^1F_dyrol4S6a;M19>FMX6d@ImnP^+4^JZ0Ja|350CXU|M~rodF% z`GSdiip{l*E^p8B8u_lQXQ_HD^c6E>`z&1jI6#Mkz3?X?%7 zXB#$bxtHYWxPmvRh{3PfvU!2hvR8=`YafZ&_g}UVS8`%k<6FJv+N|zHnQx9MFfcSQ zc)B=-Fbmq+*+t(zG-LYA_wU|KkvzHj%Nv=3S0_FfDSI(o`t^)2G5a}RGmmS_!AHI~ z4J@=Hkr9h&2(Fvbwm69Yf;(i2VQ@j6%;ge&d>ONr~h})toirl@SNo< zmjr1T2I}qq`S5f9{J6Z?DghHum_HRbqVy=Y_x*{((}go`-`eISd}G_KsM*h*=3Kpa zrQwcX)O&6V1?Cc$Ge-oDr#_PVxXoEnWw&*j_l)XuCt@~OESsPlvhkMt)rv3OQ!J+P ztez6I@9&3?PX#sfZ%HfUXR9u{u-<~{;cDgeJ7V9x<2n%hJE=kSd0hB{L(0VD3AqLs4uclWX5S(KWgf{AdniL*<&U z3va!@G%IF(xM;AU!Z+Ij%Qx(IY*%bt+i$^lJDKg+u@|zkhca~bdQR)lykzt=^V~bR zO=^6$;Z;vfkL|oUYlfxjyF@QW0pWdWvJ(%ozY==z@`bZyVutx4=R2b14{qO;EoZL% zbXTK^Nxr-P8Q)@$t$j~E+r;WU&zzNXLM&d_kezK(u^Zn?W!FNHyDom33f@*Zq85@) zZ_nO69J1V$^TE>^JN?s}wsJktOckB5?7`lvT605xybk?${r`vQ_wDWC-o3we`*LRF zz4&|ee+vHpd-%t_{zrHDckd;W^IuE()JPm$zSxXW?OyKX{&<%bpA$Atb9t)Y8@}%{ zO?Y&0jcvdzhdj2amkooq%v-u-;KGe7tCw%b+Jb%vSl0UwGYXQyx5)`!y(s84^X!9d*Bn{A3|r6bTP<*H*V@wT zSY9^PYiE*f$rv{`$JyMNztvZB_v+05pXn_G^m^`VUrSN1qZ7_B$Xs1h+AA^BLqqx8t+lzP=Z=2}a&alR zvn_X)o15>64esavek$TI_+_VdMw4~6YE_{U6W`@yI@fr=lvo)$rd9oaS7+&)$Zy=j z(do>>z|i@~|aMhf*mFyngkvg4KQxjBDz4}Yz?ot3?H z`>o}fm+}O&lQ#HYudz7##&gkQ_IsUd%7Th!e9Hw_IlE+f-MlJr<3PrhRh#BViX3tm z4L1!hcQ)3nOy7KA#?^$R;NYth*8TXvb?t4zSwM%k%$D{&IG^{p#Gg z&z|;8Gb;4@(q8|C{neM%^?&TY@na>my-9(eR`g$wtk zJmXE5i`xHAsa*2caPv2{y~mdFG4Q{?7wgF2<0A3Bggw1MP~_a1-@194h4Z=?qH>kg zR&9@Rz2hRk-!9l~!IiB>se-qwc3a1X%;ne>vf#JD{;svRL#wVoJ))X^Wa-w~H#h9+ zUM~BQZH9u*#N-Kmovu|<5iN2qYi@YGyH~2b=-w~;-|6*!5A6T_cq}t{+rF+Fzx(fZ zKKlQ?{&#s-XRurZJA3#&zN&i}FD8b@R;}N9FYorYt-DN16_Sj1-wiv$!$g>NsLb>~d>TfVs`oebqdJZ>!9&-^iC z?bTJ&U;g>@NvZVmwZ$L$yD!{*{FvWxvGr2J?yAbllMZL@pI_LlJu%4W z{X-+63*rhp7Aw2heb|=!{PWH!mP)A%*{I#dvffV zm-cL9s_1x7F?*z+F zF5R?AYxSz#7Y{UubOp4{4p0yYYN~tU-(&9rVDph?RL_dxzxP4`PQE6%dXwp z#(eVYwyn$F-`}I!Gx1FEtn=@rI9^u?SAIxHH{Y=K>cUOZk0g&IC?=^m@D^_0yFK^w zhKie67HSU~<=3s<8+Eek{gif%jxCZBA^CULert359wRedDgU0|{o3z$pUcm;tJ{$m z(XMoHcNx3Vn=IQ2yW+op3t9VP-~Cnk#zu{ek@A%ids%~LJ)ikN+1R-y<=*dmHS-Og z&oM6I%8EL>OPb?z=DZFU_aucNg&A87`8kwbc-#fviJfU)Q?`Gz3Y40jIEk4>e@aJ6 z3X?qVMCF$2b$7QfTg=$+e7Wd6i)EX^-e?Pj3C_v3U=&$bD79(o*Uv433M^SGg7*GmR&e)jy;?Mzi?CdS+Q%jbSpOm!^Z z&ns@dT;kID4|5`lm>LY&7qnmgR54F5X=c$Wp0us`N=6D|Y0Iv)=Q{Q8d$q#)T4`_97J0jD-MTH*?{m|Mho3&}`M>@D+4}GM|Hc1Z{`~ae`E`FDKP{TM{L6!< zr@!-e?EleU|Np!Ee7n_W9UD8O9{4q!T+=kMT|G(3=&OR1?+Y9M<(D&ee|K|=Ic4Lq zQ~AuoyNj7l*%S-$y1#Wf;F3HqaP9VGmi-ghR&14R^xZ1Xt+3~^Swy|kYq|S;t}Du< zcn&PMxBE+SH;2P2-lPr3tp)VNd+*B2$;CbI?eJ_kZlqbqC1B68lFx2wi;wf%o(_S8 zG^b?#%ry5O=U?-E3uUmqveEsu?#}OTw@y=;!0VJ?k&i6`F5G|Ir;hd`~QC5|M&QN zdm-;|*M>>UIb4LpCbBjLxZhDzP{`Zs znjj#(VD_xpQ|5S|(%kub*Sqyeyn+|ko{qV;*71ja-M_Er_y4~6^iPJEeNBbX#M3_o z9Sc2}&t$K4T$A@I?DjSvagmI*tV{t$2Lxjc&uzK5#BA%mEmJnlS(X|pvEhDdopRKr zH#=6$y2X)HJ(s&zR!i{fGuvHzwc@MgwM_k(McNozPCQnY)V^`8YHjHR4P_^z&GH5Z z@AXznh(v@sN>>?$OWv?@%*lEz;J&CKt?x&Acj@IPd@Wn@OwY7RN_@AnV=XW*FXIRm z^?!V`xU+Kq*iq3V7p@1aAdZT9BucI~-i;UOUP`-Jyv zwMXl_@|BcC_<4^P@GRh1Q2o2j#;$(Rj`W=iTMo`RxKiul#2*%BCeHJi8Qx91)M>f) zc9<6vKUn@)zvsXwfYySG2~)AXk=f4zL%E`BTED*L^OKbI@^%*_10 z_v-6i`L~yu@tiw!##fplU~R>LD?Bs4U1MIcD$$td*X{owZtL%__<#0#{Qr0S|35rC zd)xf9skSqEsHe6%WU65Ea`7nIK&*W0)Cm}gtU%ThJT?~`j2 z!jzceu8Mc+J%3ttc%3QVM2UCN$_WWZ9jo@5XHVvj_rA*4;C#(9L|$+CCY{&!LIee) zLm95T`+YC$ZSL*kX98rmd<{}ry6CRp+>WgGEkcJGmrs?f|9STM!+rT*AO6u0d6;|q z`kphpKjznezinSz6R5Bynfd-&V}}ZZUki)`{at}uKS_0I0OOk!Wb?Qa_u3pmn5ES>J0JN+~BcGPMg#K_cW7v)I=MJ@1f8<+-#$HBD>G7z^+pxD)0PK<^LYOKc=qMn z+sB7*e=dy;o@mm!*MuQr+R-f8q%(j>bhLPRHZNN~ODQ2zdEf{l`s%bjnzExTXc{a29k zSAbKUMe2?Gcg4~lp1Rko?`Miv9{<08;vOaTZZ3hlPNBzVn_i1?YQMz&z(Y{7{pA7! zv$sbKUFA+)>&UpZ{`EW~>n6u5_k5HE?kxDu*OfQ7qo_po2Oo~|Cjjp;qm^x+m}6ywg_ZdRUL77L!^=M?xXwOuigG} zY5&m#FHeUx^7ebY4;ofA^`=C1 za4<{UOnkgMG3~wVJGPwt=bMz61m3M$%h;Kebn^0+InQ5zdy@4`@XIV2_Oy#KAIySglt*CAA1@a8J6?>ipe ztA1}*p}YJt=i%L9nwIA$Tg9wjy~Wq&Tsxz~uq;SNhI4l1_qiPfJ>4GzR`sa9-L)=QY}ynV8IeFI?a4>N z;^n^@R@xuQPv9_kaP6VVq$UC9yNrizm=||^==ab>qL_~S^XRG;&x9m zSiL+`Vnw@&Rh!qo)!*{2mHs#&oAf^Sk$|=~p(;4n>dG7e zL74^5ZhgPLYxA`>56wW2uQ>YP)y`eZ9!#8=Ex^83WTTF;>c@M=-hL}z zXRmtHnDcs>t-&FA{pZ(jEUUf0{`FhmU3+c`@|}1UoA1@|;Gxt+KA|PnSN85^S6Vv7 zK$)?uxnstuLq#hlwd*9_{~q*d^?`3Yc;2xE8cQDtEOu65k#bcO{y0DJR-ApIg32Gy zEq1?puFYD#Qn@0*)7hlWaHiY##h+yNKD>N+`BWied)xY7e|}cp>0ZayvUi)r!D)KV zKke@AO*|z1Ilx$c>C95u-#6~r7n&5bjfvHIY>5BvYD|9^YWnfrCW>uUea33lr6 zw)?z$=7Y&u(bpHAeJ~;UmT+Qc#7#~~hwZt}U7E5Jwq87I^l<@8zu&ZAzV?T8NofjZ z2G24%xcru1&b_+ujasNu-l{@>R>_x4HU9$-f4s0|cW;MX;R|OA{WD)ZQknMzGbSBK zyfWjN#xYI*)0rLIlg_kcW{62t&N;eX?RaAA8OiMRbJ~@!y!m>X)#1UEOD0cqAKzYk z{abK?%E1X|FBnW%GNqx@b4Ch_ZqLEYd^V$b4{yBgJF)7xKqu?jmDgfj+LW@_o4T{y zn4z#+o1s-(;s3Mi_c;3EE)vs;l?>c?|@4Mgq^XGe}ZOOaF+R1QyV|9Hq zU+C`%<^B8%zu$Y9ksDdi5iB^7!>HJ+=x74dw8th5)jj8Dcs`4{v-e&5TJg?rVKWz8 zn^nxle8gGo;kuPiu08&0BYA!mvrqGvx*goV?}dgk?ktye5mQPC$liNhEk;A*%ZLBV zr@Y&7pgL{WEOtKM&tI!zzpwoo@m*KonNjcGMh2Bf2RmQyU3}c=tmf&%m5ChR4{%Jo zz~QtmkJuskUIJEQOhWgL3x7F@8z0@$=9nG@sUi51h8@H6S zZTq>-Gn{Gsr}M&4=?;IGlY+_HM(6nQkSDLo=DYPcwFG(His7@ctNJV;7{ z+HuoEeLodg7t8#WX_N2FSSY6~fA*}~Wh=X<_kJg)^Ov%us7V!maE?CGJm+6lWKFAW ziS3$b_J6_&f2tChJ%p4tITkku7R;1nem2LKIc{;9P2ZY?&0$w{4%921@3CI<-A>}Z zb5)DTR=KKchQ$sxJX|g|I+;pFQx+VqH9xnKQICDWy*C}T|BdZmWi0oZdhEch_i4#f zIKoT6NJw4UYhHX_Gxj>K(7Xnn1*_JU)^A}9Z1g|xFK<`t^yJK=EcVaZ2cp!M{WQt8 z_Yb}o&KPPSEB)qr`qFRTy!v-Ow|EpDUl*F4Z*A86dfT7p|EJe~)&IBs|L6Sum5bk= zatU_2@Z;I(?eG5nmba@ic4D&mHG|`nXUoyM!CGrIg_mXCdzHty-s&O)^Mv1%manTm z`+3P$Mq!1%9Y5YZWW2nkV~rA%!85+(Lw@VOaO}T5w@qK=xzClV*T45EPg-J97wK}w zW`d5{wPxp^*Vn%L{cd`z$GcBuH}b9L3wi7;=Wp*Sw3(?K`SfarvA_cRwV?v+@|sF{ zuTNjl=X>=3wN2Kah~1N=I!_7wXp_@4dEBo5{BqguWmB%`E)DwL*q6C}n_9#CBsV_B z3Yk)VW7UWI|8?JAaqaEXpCM_}Zp7KG{$?^&?9cZ4@4v6P9e?Efx2fgyrWxN1_kZs= ze6;ev_3mA*t_N<+EVO!LIb zDQ>u3?JTr^@v#fBp%)*EuCnpH{PpKkg`&$9FXtFu%a!I2O?za0JG3n5t>Bf_4LqeY zr%o!%U%2p{=O)&5(SQ5vzyF?o+js-Rr_;Ifr(K@CoqxW4y@|1;VXg;z>WZV4GdmYt z51TgWR7|z8xolDT=Yty^mKh#6z!5ItH|3*tirChFQb%*1tzG$YMP&OF2Di8T-y1eu zzxKJ5}1j~JJCujWo^7Z0!X^=A@{U0bAElnobs2-9Zh z2+*|ou%!I!)rVWe1Xb!(8yPNKXuKPjH*4iJyOcQPb*In#%6rl1a-;V^qhy0y-bIZk z@#5Rcr}GL=j9M}!CilA8AG=jg$*PY+_rrGkH&K^U zu5bH0|Nr{`PyauR|D#|3v3vcmeZI}B7Rvo#HMXz)k&`AjPj%&u8JYh~7#%Wh+1LGv zc$O&=|5%>8u>bL=b+K!|{;cT|oc;9SyRF$Q@2azZul=sv_A6j5tAgzD`7;kzoV5`; zy}+aMO@K!=|A9H1e@Ap#85=FnIQ~WL`5Ynk4?ol<96B>2=-lORUzF}FZ@tqWe8%L} zJ?}ul%<>~Qc39}z*znjeFkVdH5SY_(MZ`^JwUdH@yhkT9OWOm!*SY6AW&SJ=6%K4M zU)!|vJY)Qg-Q7np_~ak){r{F`U*9`#nkDmnya9+?w*xQ#lV zFbGIS9}EZ$UV5-kRn61g{Qhohe(eKux@Rg0DJmCP|;;$cM%;IJ}#F3-;?Bm1T){iDe7%X$x8-AcLP0s&u?H4AOBbtS~pYus>S+=+T z%E`z4!R5PySBAzKb?`L=H zjL|M&mAwK{FiyLktG8$H;ea;)MU!>(B?pZz@4mworFPN3GA*J1PH z^XAW+uXj3g|F%in^VWX#JJJ}i)nC(DX8W9(6F={L{o&)+)Q8vk(~mgLkQ7)pWzvPW z+jb@9|1kgmZhQ6pS+8<;vn_qW;`9G%{nz{dyX!yh|8aDCe8cus%?(<9{h3kFEcaI>TI2vy?T|wp_RqwYS+ikzL`?DevM#s zw!eS))datuD_c$*=3Blym&fLkCiYrl?2qO3fBx;1R9ZTb$9c!P?HfyPYi!Q?_+n-_uh`Y*zP5woMTgIswA$F~sk4+9Wy#NtaoKXuE$#4(NruG@hD&Sj zzn^T#@x8#c>;1mJs;qxLw##Q_H%@SASTIvaUBok}L*vlAdFwh;BhwleG#I2mYj1Ct zum3T}ZCTCgcXB2!sdk5a`;(rYf4oxn08hV*X7$_+cR7X4_%;S{G0&QLd7;4TRYmd3 zm?nIg7r{2=i$!0XyMEoNiOZ``<+yAK`@Xkt%EX1M>-Ju>eN(#3$fumap^af(?fS&M z_sukpFgP%_=oqeJb7z7)awkLYL zUnce{xt86`?ZfN1{OYw^`!3$QQLD_!*(}|{RK-y0%pTI=U@>=E(6l+;UXd;gN(B-j zKVC2=#T?3ccui~P!&u7)1~D0i$HMB4cki3!b!>@gqk`MKNsF%Lc}yxY+vF)^P+N8L zdUWa&hM;W0GfipsKd-Y{mf8rM<$E;GV6il-KucA)=|Yzm5AT{DQZVqFo*F3@di4X} zs)d&RJSTsh>v8x~dHY{?{m=hD-oCH%dCDaBIY`WlU$g$}(b=bO|16qk^wDK9OX7sq z$r}zEtWxc%C-- z{qr>bpPR;-_owG{C@L)qJ(sEetH1u&!_?ctL5WF94tJ`9d9QM$vwTZC*~u{T1)m)= zf8_RCzZ3c8R!`i>e(ze<-PfnTmE7L8_Vqn0hLF1*4mX;s+yT=E;KUp2*{?+ z+q`6^7t<#lSAoUbZ&|N+o5u9LLTr)6hAN*kclPqjwwy@t|G%WWpTmjgyZvqNJ$?Hx z^8MVrqUv1boB+%8W6fSxb3U%mejcdJTX5sx3w5OfE(RbfpGb5j0O7_llc>^tuP}{lZ zm)h*KPU6wMY~p);I`1LQp4R!-f7Smw|L^nv$N&Gu|M?pK-)g7PVyJ$sFwPW|%w-CFq$2`4YjSiR}D-=?nJ+kbSirBc)l z?PuFE+4(ITHYC5xf4J_|Glrg2t>>R5e-k+XS_xE*t_U(&*9+qs2V%)ou>0IdB z{`>V+yLPg%7i6E)IlDb}cjM|;CY_6w9lzS~&25}kvYjF6V4haf;w?s-bJuE_D7^LC zx7PIe%I|enXKzJ$#Z)h?&N*ChOrXZ&%2{0owgc06Cupd$F}!UP*c-jpmtltL@=HGM z4~{qV@I8{`Tp3w>fA+N~M!B<#%B9!-d3``YkUcMd#xy3u&g*Oc{k{MH->*$=jjQxd zUz#;5k3awZo;&OO{r&m#?<*9%f1CGxcH2!Qj}32I<}CkIb2mHxL_{^W%tEz}7UtlO zyDFPDW}7-lCP=e?NYr-h4;ML-;L~{{qH&Ju{&n4P^4A|a*$Tz#99w7YHZ7M$`I{Y| ze_uysoXe%x%ci@q?WwhKY7x5VR_At3_;=j2%2{fOJ#q{8_BQg*P=9preo=X|!!nhH zUJQ0^ErJ(!^Xlxej6wP1UH>pb6`Wv)#vmV&Gc z%iXy=m#)4&_teQFiFs)eT&j{^mfbpQd!_lXfMbUU#~}ej!LskQck?ti-k)32yp^%h zWASx~^g9=K_3dfwkjQyAQQRs>WnxwMksI5Ob)G8uQr`M>!D|-=hqr%Kk{TE;Uip42 zaY95L(<`AJJEhnX4roYR&Gj)*T6C?>?kwNMs&vlX{+C=-0t01Vsa%=x|IPnz_d^=~ zpNy|F-t)|d!{PqFH`3hqzyA8Ujk!W*##4tQAwlOJu`w(tToBFP9ca5|J4#Qn(TVH@BP;71B|YY4;^p&ojLsUxj^3m6~VyOvm%r1&KBSOrl?bS)~%2~bn^$B zTXCU#u4~*2y(E-n`P|5V-}Zv^V~#~{_ug0RUXi7I!sD)c*tXgHku8=J8dVehB<$L> z&dhOnC$0R6Lrr92R5@pTW#!D}ci7%D6&i#EpK8AUGHvU%sNW4WJ-1hzJHJ^kDY{p3 z^N!#9@>9L)e}><0e4V?$_V2gaFU!_#eec-NQvd7tdwbipQ;eIrzbU+_&Ms_74i#nI zZRU``vLNy-+j%3IvsOloE?3za9hUHIEk7c|D79{d;0wOXCcM?x8G1Tn zvXW6aH>-@A)B%QY)}-w(?uN*^o3R+q*t*PTWwg2Rvq^lXtT>(rb+Jc12>ZRwoZTx` z$}#G3_hRd<_s)2*i$H5Exp^dqdbR*4=;Pq@cJ+;QMcno#n1B+}l6w4OLT0%uW{kJ=se)so>Fq?|Mf#Oni+C zg%XV3JIoL|`u7i4`61=Z{*IugTZu88kKeD|d(V7n{k&qP@4V@Iv}E~LB)(rdW!Lv8 zZQ-3WGkJE-bH1{-J7<+wOZ(wxdizwIR+swRl<@3eJivQq&$Vsa?Np7Vr89UIOetpA zcFk}3=H1MFnim`n&XDGDWaVZvlH}oKQ&v3t?AB_&k}13HzUq6uRe?Eid-9`eA(DxE zUa1<&p7sndPcsY7eY?V~Rhh3qLgwK2_StEF9xYyRMm08ndtyZxZ;64q(u%!jgE~?q z>=+g996oSH_C(0{Wjz<8ZodvF3~a1XmM;uvEKPyMU=iP+o;a8 zY>cjoJ-=)BZcB&Ytqdlu6Oxy+vOKu8`&wG8`^t~Pmt+&}sIn~1^!t0x%zX2@_Llmb zscT*ze%v~7+uFWIo1Yy>sq^+CFSs_U*B;)#=`5dM5O0W5gY55{OjHmmRG7Hn$PZe_kfQvLVD$-#&og%cX@knuRd|ylf%C1VUGbaQedc;;=e<$br`^2&|uUGBfVgju*72NK0?Piwy)lsLuzE^4G zlbaL2-BX*-av<9INWz0D^YdT#%iHJPF1s7Gz(_vn=SDdR<@>w+mAg{Z7By&2FO9AL z_I3LHA4m27J^X&Z{?{Lq^Pe~A{6G2s%l!X$^Z(b)dp`a3yg?a^I24s)c{%)- z2UowIZ@2%<<<5YR@<}3ht4;oAW;gMi@aRcde5+W7dH!|VYuj?u#OnThcg#3@MrQ%v z&s(-^KYz%yg}uG7-?HaWk;?s@lE&Mwc}g^0+snw=sJNke_iNeq8UOAD9udrLbm$ab zDj3UVa(?O7TI(E(u8tNF5jpnsDH9p`9KP8L|Ld2N@ojE6^R`a%j;^A>;$L!+CvR_C znKkeE!Zlq2Dlf9!rbY982w(C2wXV^Q+Ew;Tnf7)`z2E)Mx#Z534}4-vttazw9=~|M zpH+O-ua_aqGGFbzUVHUgb*PKVxn~zcm2U~yShTI;vSzs*t84XZpW(K5fAa*DoLoF3 zv!$2GxqJ!ax$9WbSgv#D?Jb<)jm zX?$mrmTkS3Ijipx-^HsgDyeKX*M7%}&U82^I3wu2t-%D%2kOe!i41KXS-W^+H!Cds zUH|$1-`%h8{Wt&r(q7*wU(RnWGyDHHf6cegUcL2psc|raM`X*zGgtb)Ur-cC%<22K zw^zA<<2T>;%X_ONj3-Q)BrMqZn@>pjvCk47rzyMV`YjK-wEAR%PO=fF(k@qX)%1nT zD=jvy&b#gQ?(og_j^&qE=55ctz3lSIz0-rfO*yhRTlVAA@1l1mxXbZxo@r{@^N`!c zc=5vzo07Nw){1?-m)}9c!D15go2^{O19tPzyZ=LZhGlw9XLQ5-^W2{ozLwB*TR;1G zA^VKOhT^3fmVcjx@8?6|g_U<5~VD$IW^^;EAc^)Iv&>`X^ z%QSPQY|Pmc@7IgU+)Q!boin#^SwrpK$`|_ke}C)NzgPR~%R~8pAKIr}TJAr8Tkh}c z|9{p0`SWFo+_J;(n74*qSNr~=r2P6b)u*YE=bjh47^rhF98u({wCxrMobWFF#zqUt zTa6!;KKmC<1; z&gFvq4A5`{JHe+zxMWa_Tb#R@BI9C^b1_g z@!;iEl#`cR7UYxdm6PV-b2#T})%x5oU4o5vY6draD@~5KYdoK)^OnugF0OUrRFR9m z$NN5SJ;vK|&zgtpug8n9(sIu$Wh$2EzB@L^+h)JN%X@e4R-rF2gbM7|rsCaCwBscB2p^lYB zwojA8V_TX1S6458cEQ@3?byEUmor~{yvq9`AhLJnrH!Ru9Z&6>{3KCg(pE|<#RE41Bn(T+ZR{0*8nNeqPc*6M|);}IUPPLpr?I&Y{!i6()1Ok-eg(;U0*MHRizj}If>Vez4qTe$bSP0MQ%jIX}5L9@ju(p5sWf=yR zlbti3s&iO4oZ30XFrX+>~eQSl{tG?zA4O})jV@n)~@WV_ORFpr6{jX*P06+ zIS)?v{Snv|T-NzQG}LzvL#Idf-N)zt1%#F+dHnzT|HFHE{pt1puJ8ABoutVrrt#tH z?@CRZ|9pgV`=o4d!~Q3&SaH1dd`qp_BgvBFEJMwz3s}izwn!{C z4wc>2>`-&oRK~tcL6|{7hsP=I@>}We{`Bgs2@|u{dcL@R|L>-^v$y?v_fauNxhePdn$Co6>+b*k zlkKPK@T^i!;z{oQxzAEhHFdQ0ok-hw=DFdFeS6oeEnPjk=->AG@4NSI|Co^CB$T#f zp^QYSVfxCgtG;DMMFn$2UH|%ax4W_P1B247jB^^R4c4zLGiNi~ye_IpO>AqQkA@@1 z;fdWR%GU|;yWd_FJ_~wgst{{H!gj@_ze!NQAO->|cD&R!Oxiynd~wyU5G= zzTEw?48|`4f3yX2^i1OZetg#S^aR;E}!Ctt!7$B8Nab__H)0o zj$KBqQS<4?mGWH961kBb9y|J(O+PvaSSqM2%hQ_F)p|*=|M|n?|MQjox6X{bpi$QG zrp~Zw_t6uHM}B{@Qn~pw){MM*TrJi`MNrV-vB^~a>(diXbza=^ zHTTpXHKvGte?BEvRV>)zG0XD&TGMjhBZqdZkIp`;s3HEyB~L6wuYc<||M~NuXGZ01 zKl0$a=GhabGVfY4XU$se>~N%ked!bsB3}223|U z#zlTN%-*KyfBW8yoBfqeW!7s9*P4oPZZABsDS@TD*F{GucYUg!=0}_9_s$-4ljvgY zJhAP)i^v&<37=EK1eI;hT-yJ0d;NBwr~3aNJ%8R_c|W>(CI96w&psXSi@Pedpz8hE ziJ?~o-xWz2Z2qt+aee2j@4=!$UXC6tzxUPVb@A!Rp)-Col^v@r@P3Ay=A>y#qMzZ_UFeY z@z3TQEp}QZ>sqy0{z7GYKaqvE;5jn{y>) z>69?@56rnK>po98n=@E;BawpRtox z__`qOxKeNQ{_n>hI?V~TtT~nSd!PLI^A{?QC)&#i&o<;PD);-j?d|rpS+}?Sy;gl) z?o9Dz^+QV@?!A5ee$Cf!Zy(#7*Vost|MhS2Ha-KV*}WXK8dZuNj(7LGQ~j*)ZKAcJ z+M_KZOlxk(Exc`eKkdccyS#tpxKFLQE6^uv%CvM+<`UlJEL->SZ8@G(taGew)-lDZ zaF$c5m7xr;?y@bqde?t>@cX^*LtS>c%C(+^fB+E8}!_SN{iffr$q1w<y-fO<$Rkl>DhZ_}`z$>?wxt*;W|JILs0j>PZxN zqO?vqw~g!ay4v0M9?X-`e#cW)Wl>(*-*#`A()7%UnygOmrv-7D@_JWh?e)LMq;z1y z=5{|$+cJhTGv=6{*mZpWuj}=TU;Te#zF%X`gO+{o<-YwpZGN}z-{0@{*Y?hqTkGa_ zsxxpxM-=ZOp=$5Wj78I*Pn%Kf_2SrzcRUV<`6e?c33N&)Iz5=vv}5M1oAGbSBBSR!D!YWT4=b>k^G}@AvE1cFSoZ01w!IxKndc&Hqt=?tH*%f%q{y=N#|LKa z6|2e?7_c0%e!0|4bLB^pyK^^beSv3G0x^WOgn3bl~G&PBzJ~jAoHSQkmR8e)g@#Wz<(c!a zPr7!kU*3LC{cpP-<3tw$ex)_Ba(dH0e>radFY|VpQW6sz&sKr+mb_0I+n8Br@z*g- zHr5PgI=4#d(5&ETB6&V5zv`)UTv;vZc|>9vL!ZmmOa;cw)mx=aOp`R17std~7=Qh- z$8P%5ly`#ru8TG_v0mVq?0EaEq2ccXzKf;={o4D@@)zspjh}sVN(}fP_H{@+tCLaw zy75ViLVJP;N0Y*l<`O|cjvgLWRfY^-!3i_ZHZz?~U2AIGYtS%7`1F?dQh9l80-hF} z?klPlS`YM0I9#A`uC2AAN54qSZ;BDK!$I^S{^g_s%|l?uEmg%ZHBkHf;FL<>T6AIb&7iaR&A`cmF-h z|NrfM{r}_pe=WbVd*!SNTbEU8-M#hUvI3L*r@R%V{RW;7`=04eP2J^FTdZ>6+VUw? z?_cQDJefYT(qsPem#6jj|NHXMbE{|9zHkPnFRAmGY8S`dR-1E{=j4PKiF4DRKK-dI z*c&Hp)A8}_Y<6z8FSma0TDNq{=buh8sk6>KPmK;g=4ijQ^3>}*1`Q?O+9N803)xvL z43!+bJn#Ph`v1rO&JFQ@{;ZxXR+iZ*`y<3(u}Fo zy8fD+Rk`s^t7Um+TFbWeial~YJ9N6Y%?rxBduNw`veCr3pNeAhE%jf8DHtvnG}xf? zJZiPF-_o8_`8FrlZoQ~`?)l}PHS6kM@J(FKw4hQ;pHD2u&*S2%bIXr96`x~URVDE~ z(%@l7dH~OPm+6(e;?^JOVbJ|0p;eXd9A#dyI(f;vwy!3LwQj}=3Nl|?=h(ya?&GF%*68fte`@~z zd%U?gaCyNivFN+4n!cJm+^GpW6Sd~VFw1aCG&Uwgaj_^BiP zn&Yi)RzEyAc#bSBF!!BQz5elMcNYb&6sH->VzT$1o8SnPC zw_X(7Fe7N@w55Av8c*0hF-`m$dUDCDz2&>k`z)W7CdB-C!>-ph=WqQwuy)1ngAr!T z@||JH;>}8vBHe<^cElPfE0xFPZ(qjM;;U`MVHi+w>axyx{kJb($hHUzO`ag3v>_=# zNyx#-VSd^tg(O!^DZRQI0&X$SA0FJ*zrnJU|ITbNUZyR}{Qb9|w%LALcj>2pcg2@Z zNhn+L%+PSU$@N>Bo&h|!qJrIzth&0JiD8pM(7E7~pI09fn~)vM?BdLol9a~i;CAb^ z9_Raw#g+4>PuWy1oY*eiU?j}-&F@U=JipIdZ0vma`JbPDeEafY=7~EbJ&jn^POU5n z{(daP<&p_!Blo(`Hp?|v-Y$tcR^g)djcM=C(p*dNTT(1%KhIh2bHjU?dA@vAuFn~X z%O;sCoM$u_+b12n(BUAHcgo#r~iDzxdj@R`^;n@cu!YW}`Jj z&h55B4!7OWN@p~es;t|l;~gCBC3k%P{laZxA2;QCUhS-UuQQ3$W)F#iy;pfXWRliA3$pKd%5leb#o?bO&*%J(t=qTcawDHe;rG4U887UO z(d$f&y{bG!b&ACL6^Kbpld8z{P*GT<@*2Y|DDyZ`+B#g>dx$Q&o6(in#=0+_Sc`H>R&ZG=bY>3 zXWluZ!#&k$vdi~v_iZKwFG@VE#cCga_Dt0K#yP>cyPt4!Y6z-xx|A}j3F-w43BHcX zRu$q9US|1xovdKrHJJvcIfp`&owl;e=3T#-S~$t!I`d`QRX%(?ysf*RH+KXJ9cw=I zX>v}ELcvVSrF*Yh`^l+ZoWTE(qx_uBw*?Q`cNtD_U!+-Vz-KSTS#bAv-0yvN_x=2} zT1O@d$>7A3L7z4rauxAZet+$5yNsSvfKRes$Liu9-Fad-#qg+xBLx zU!|^>D&jx zhY$PQ72Nmt>3X^Q^=jYF#|G2xpqOPv)^N&6W?|J3x-(A~Me!cd-;LdAW zO36%eg4d^q9&QZq;xqMpw0rxtthEih^35HXH|&kOud~@8a`oPCpC8MxIe8pVP2H*y z8I|s6GgV2c+J#lQcU|uNL>BX$>fikP|NdRNB!BZcROUv>}QF~n-lA2y{UTrFSUHX2nbhp5P<^J>S>i_i!UVqK?z|vXs>BEN$ z-)!Wr4m9yX_q(jEurB2OQjc({Rzk1?*q$t-HQ_ZJv)v z?i=5^nr&zPx69W_xEm-5_pn5zGPE+7JC$dPnD_^4C0$f%GM@RBqxN&fzt{DjvfrC4 z{ruJaJs>+e|76VE`afSj)&CFw|L?c`zYllw>;B%=ulu%HUw&H1mQdC=X(~*9TiM@E z?O3ACxbMz7Pp^6DduDGaKF2@(%<1Tv@^#;r+qYfY)$DTabSF>M?LZ+`^@HoGXB9~W zM4S3uTI1fb&9>+8tOXX0vuBwFWZyoQ6S1jDNDC=eP?($MQAg7TcoPgT1F-- zVTFch&o3EBCAq$q8<~#=zX~ za`euOja#?9x1MMbpycRcX#DlS!{eGa&j_4oR64`{`^?#cGY;K(5m3u}fQiXT%xeC# zg>|e9j}D)mqpEv)PK%MEBS(+pPQOWd&xH>i+^sL!I){~Y%0a#2SGPq!Kk*1w+M#oH zO=$1->D#BfO2~;t7EN){(|j1>z8&9~o_VmcH(`Q9J;`0e49tDGVi zs$MrQl~}j^^=JD(&h?wmverdY4%yQ&2dwnnP*%BNjoCSu9oWm$~uuPd9Ff zij~*nZi_v))1Z2_W#OzHQxe{74RgPgQGNJYR8mz%#SXjre-Hil%WFl=n-;a2qxVL0 z)OY{+b$|YR;>^y?<#%eF)A20zM#qK2Tiz-OUAVI|d;i`mF)3cp6E|xt{qym+J+HXt zuF$uF9QW=fYE8NHWY=5m2)ES^3B_rQ%T?aV3S7`;KHGWuv1{mZmv>$dmoMeWy1l;j zJfn`L?$aj=>pz>}P*IeE9I`<6?tZhkkwx z{Pf_N|K5D1O>-{AdArQBn60n$>D$}K%jNBDY;1V0>bKa&ESvivvA4DLeVVeiEIYTHpRw?h3_xOX8H2r z!-n_Tk=$LY{$_5k|C;~r+jsjK9nGDq8~iq_EUKJ$Hd<-U^v@qs3Zxj^8yNgpBd?q} zD`;49W3Gf^TiLs}w~sH?u`JY_Ig|V6o;jCICK=Aro%L?jzFq5oKYMp(#oGmYdh3t& zoZe((*JQ9cK}&PP-1Gd^YkzN>8YJYs!t-49YNt6fHf{L2Z>HeaTV`q}InO3)^>nBP zYewGY%Ud~NPqj-={=T2DJ{DD0{@Szsmap5YgBKo{m%quYea|!Be(SX;i!?>Y{V{&# z8>aYOJ>z@dF7DB}?|zl5wq6PsmwLD+$Ysg3U!RJcA57vXn6>O=)Z5%tE8Cc~N8e*h z@6MZf=cHt7#;dz~w3}>W&ap?f2Wt3~_orQpG+@vFZDr8Wsi?bj<}wwRpPQy!EI$9# zJG^;9rKZxB+-R?tXLl+*{<+EJbJ*|Y&!=BoyXxw-&I~)q&O8!&bsMi;?AqG+ z?|d=0OP=Q}TU@~MXwGJihr4FooRIkS-rAWWo~qC1$R^EWIS|_TUh$>#KCk!p`95+o z?$Tax`tF*TNgV==e5aWO?C1Hpe0f)>_-m${^jXJKF)hWD+CQ^CNMCjC%o!sciSnO1 zDvV-DECs9z>{7g5?ip)&AHKa%8}|14>#M#lFG}W}jlEjEefsm+GflG@{(SoRvhd`E zGWlnBk6n|RQKZ$`crcjj*a_9CHY@ul@qRB2E>+|AaBa4%Jg|OW?e}MIf2YnoG1uVR z+RXvyEH4~a`Xx}U5xR_F_NFy1YhTXb(X;eDWU20HqTAhSRQ&dLP2KLd;SByE9SKTK zDhvxH@0$9j3gumG-}|n}YQEp{^YiVkkBGmQ50UPS=rY*z#B#Z(%N<76nU(8Tr<`+C z`t*7J-opyHe;Wdnwj6)PmvzE2_qLiQZ)f4vyt1O}n&~Wd^ZiyyC2GET%x^!>>s9ed zS1--Axt~MoK7P(``fz!IR=r<-dFAO3&L6J7zu&`|SoJ?aOd#l)&8Dy? zr@#02_vhcYdp2kOJo_`Lncj**#qrZxB|gk6VN6_pd6LoI%J1J+uiv*LZvE?9adC0? z_SIVZFTelq-Frrb^QX7%O+WQ}-@4yv_tvc5J^SmzO&!dkD%bAvZu!QrGw-&@%CB=L zwrHGjun|x+QJ=M3CXnqx>kQj7QbIpWd{t$yO|H#9GiS4p<=)C2SGKWU5U6dMZOGix zapv=#*$=B#7Tq%A%(-=b`AuH0neu+m*GbRXUt4qgn{AE#`Av;g$6kN_bya`=to{Eg z_wTLw`mQjEL;ivA^$yFWj_fCw7ED@b*VS=cf1gyXuXFg`9WCM9axwOLWdo1caV6wXp(`u}IkR8Oy&a&mGr zBri@-Ft?nq{!C%ZWh3!P*JeFQiMwxaqo=z0YNcNqmADU z`kblAl-RfLTyjHdQZl3uZb;U%cH^E+-k=M)n`Q!K0=lojmYR0Bg z+qus#oO9}YT4I%ZTg@_ed+DrYI+-4U>$ZzBWW5zEtgP6acYRy_^V3C@7PXvLZt+PJ zpS(4zz-@{Dd^_LZC0{BkD-;$*z1BYPYD-G7ghuzmZ)YNl>CnDL$9HQd4QcgyOVC zrLl}MC*M!nc_Q+X%Eqm8mgU`B_3gm(*WFf2rYzcSn4OyT)y{3=_SMz^Xy(9(JRqr%gJ1yDa+R%Cd_hKHK7>U=)H^{#YokesBZ<>}ko)s>nK zEHiUi>g~16%4d1Ui^TqqX(5XJ`Emyq-v9Ww{^RTU`M22^{=ED0r)I8&s?m~8hUGTz zv)|s$di%V+yS%^S`Sj=f_v%hK0#lYrdL9m)S}Hu$Ga>)XZ7 zS?BdJ&99@-O4l=F$B$=EN$I+D)t@`A?%&my zTNHx5?qpl{#7>N8==e)q`pE>H)o(S7v%Z;mqJkypO zIijb=&z~RU^?M(`a!e8Hrm;<#4B z_Gdx$&R>V_KCOuhI{DcqyZ!m|=c#c;$4csUzvm8~wM_Noo@=&KZR{Q<^S@V^lx@y% zEBmu+RsIS0jKKTVn%*p4ZoCXW4M!{$lP>c)K7DKLcV(;T+Rozjyl3umRV!HuGc`Q# z{T{W~)QRQGwKv!A|NZo|y+82v`u~sg7u?>KJAYCp|7$;%BMYpyuIgC%Jm=v7moL*4 z4r&U&S1*SBgJRG%j$|j%Zr{`Gqe+qUna1#-at`IDK zeQnlE-AYFn-z&f0F*3zytlhS4?Y#5pm!nT*o|-c$BjeoS<6JI$4Zliq1uk!TGWpNp z`E?0R^J{-zt*YicaQpJ|nU>a7*C%BzoTsvCt4-$J`Rnw>8ZLiwSdd~q^L()81Lv9I zpKYcGt$DqtW1Fp^0-KLWo{$o#Io{p32C283Rmy_{&rj4kAKb!=F9n2 zCTY$r)||=kJnr^)`x>8{r)n6TwyIbvUay;dT?>tyAwyK!yd)_nezPv#s>+`-iD zUvdBY=fg|4UVD9Sx1sSd*2TN0hFtnuHTRR}w4mzgE+h5Pn(5{3 zF7E`Tu3cTbf6s@nT@7EpojtrY^X;@52W6PQ)#^Xjk>zR95KGa0dZ|?Vr%Mh$KmU=3 z?@EN!EPOX-E?#giYe(s#iChM6JeXU&9~PfFAi)^@MzcWalADs#As(5vR!-(Z&XcAE zG4(rMd@zNFX@W+~TKw975K z%TfB2f>+V0v(3@&>Ti6e1f9FZ9I^UV|NZ|CIvppSOoSv93+`%7VstPrmC>AP6!2If zcv<9(6t?ZNPN$wr&)xlX+O*ap#w98TqO0q6&3(S}c=|+6neTfu*&1$KmuA&u@Cq_M z=_=C6p|OIa>YIZIU&FQCg0onSv)R?SLPhS~W!oX>WX`{y??Hp$wKKUEm(N(wYVTKw zjGT6;N3iXHd2HvOv)AMQ?zaE;cTFZ*Q~q!f7>emHs?=3*XuJ z=KC+tJQaJSlg0mKNuk*pBmL7)=1sc3)jw!i=B=o+s=muxcUt^*k-63r>$0G1!#rbk z0in8^S@&P-*33)im{YVfZvFP#-nkcEE!%rtd*cGvJsDH=bS`ZXUN-6Tkwv+8CA{sV zb^kxK|I>K-%fIgJf{C)XepPz?JYC-JDI>EeoNbPZx>MEpV|!LV&9t1WmpSX(J+G#f zDoQuDm%2QSS^s)le!kqkni>8c6N8c(?nx;vJF?v^Vcr`17`;d>w!M4qc=(=6l}P*-?RF+)Kst}+WzGc}7 zr#BgQ7fE|ex+gM|&AF*VW?22)nu{g>Tw_5PJZXR{txya_?Un2tzNQh@Ws7lY8+ed zNnK=7Xq@3~cV1oLPS*O@?;2PfR8}rn{$yR6t$5`ezbQ+u&Dw0RsX?jY_VsttyJ~)Y z=s&&b$IB2eK{Z#`FKODWiVUeWI_gTlzI;4BM=eFs{C57mR|{T#vo`d)bW-5@tt&k< zBahr!?|(i|DopXr)?^lU^F`aIZ)RJOHSg2Dd)IauPnp6Z5IKprBISEfnzDF`kd)`l zCw)ge7>uUd2v2ODDtYVP_0rY`M|}K(q}uhGs#l$-wE1uZ{5bK6)vl>twl?Qs zM~CXnG8d7P!FqZdX9|5(2;^U|B{^Y2SwF`l6XVwC_qGQjgU|whxt|XoK4dl9e6PCx|G&T6YK0z}JPq-h<#lf76sBh??2k@fnI#qNa^&;dpJ$&Y z@*H`7IVd|<|N7~tOdgi$496ShEWdZ?F8BG402cvE6TRuhE*ynPYtp8OP5Aj+eqO+( zr!~_$zW%!O+Ran{`RA94iYo-JM#rC;Gr{PSkmh{3qM1vlEUYPa;&wf9uPk8g^{-WT zE67(-;Ml@)W4Nf*E#0)KX+|emKpU-hm-eD z>irPuiF4%qHMlvl^EtR|cWGWvVO(44GVRdb$0oiWi2@6`Tv(K4e6RLa2E5L^Ah&#y zONPopQO_3(_p%0P7N0tLSNLPVR-r>>)t+Z=hu!5^dBdS!z_sBkTg%cVnKRexZ)ZGm zCC`IFhvCAV;{_KE-<`X3hVv|=;v(siLaqb`4})k6rSh$bX-S%b2QyV{{lJXZ5^VuOH3X(SPKP~OG`hH zSd+lSzkMBZ-LrRUc4AyRRdh1vJy(35kS21iKqxKgm7L*(IrQXtFyve^<;(``;!u2R~02 zA$CQfq>!_h?^Hi`xLmM!y8rzDUyj=+L?j7KtlcXVsQ2gD-`(38d>lG|Zn`sX-um=j1^bwUbBRmA~p(!Ph^>--z>3$F}x4yfe3~;`HJPP>!tNm$yA| zP;K!61}+Z!O+1#oQv`a8E6!9jRz#-x`55hu$(}ha{@=6f`~Uym|9SuK+428h>03UH zDi^#N6}s6()K4f&*>K`9HRGBWf-`RFGRX)|?l}I?=S-#4`YHvE%Z#6QF83A^5@1cx znrya3%1Pqc_Se&v3oFb_3~gVhBiOPqFn|8GyZe7WoBd5oCE$WUw0kkd2SOmtFv?auIA$bD_3g;a~@FXcC@yA(r8xw{n^>Ay}oSQ zukE?M?r+uJs5QD4=P!TFtc_ate*X-mOP_woDLMTCS#mk^8ke)g4}Z*ah*IYK{ntrKy4QJro1{DWz~Pm$OLUdGtF zXEUNYPS`XQxLo>`&GuDR^5>5q786uDB(BA*{eE_j-}LUqli8V9m51`1rs=6Bdp&Ot z*4ngh%dYj$tmVGS7EZcWW|+Iqs*v&fy>H5Yu3b6PVEMGkC&6y1&*Kg9$N$>)|KKa# zIPuQhpk1FEFECZUX%e(GIm2ur$!qY$B(;EhNuKOX&12WZ1sf;ckyVoDoGEyyZ^rv( zLED71!*_*SmT{Fw339cv?{JkcU}IqOxjZF8pqW$V&Mre^1^$wR3>G)7OJ%S3D$3bt z%3s^`qICVLvs(n)Ox=^tcpp<}x>v<^{cD=w#V{t7MW27p2|DF@CCYqtuB;0mpL0^j zg|E-v&EZy97a_2Cics;b@3vFtc&&TgCARQ753jPEJ07ORrF6Pch}476+1BrtGo~aB>#fVz9yE%w?0%^$vXwJUntuoqHEA zaLy=ybntOW_S_apVKIa64DEgI{OUIFTraJyo+*FsG{3yv>nRM2xBdGZU;lmg{{PMY z-)-Oj>*{)z3_dwOdiP6Np@6q( z;v_>Ig99fHW?q+loba(-l1adEt?5*sg@Ox~t@`!x@$=hnx0TjbR0vq!eI(`*y}IMZ zVs`Z}8NWpjEt~SJ@|>lrCx>3Ag3ppI%U*9q*H`Cey=ydo&1m#;ll(6df)P35Xot>rdz{gw+e&eBq2 zxSGeP@q34sd%%-AXC5~%kL9ORUHqP9TKTFgnFSZ9JaU}rCe+I(^YX|BVXrN_vhO!{ z&5_?UWzO=iPjAheJY=Jl2PuEtiLyS-1$bNlVwfQDS@Ug>ohnXa={Puq)6i=%=QT%!v z+jA3NzbAzPLT5z2dT4KXd+Qe4i+0C3LJ~W6f4s5MszM>FN1)YU!+{5Z->jIpm;)KU z+$!vtDQ%K%F=LjS{<}lZ%k%Zt{*8!cH!1(~>vewJpO5bUf8VeBefE5(fQ+D`VtbU# z8v%*!?{+lrerGso%gnoi%oh&(?6zVs6jExb?4Ov=dVJpj9wj%!z6Z@-Y`(6aZ1it$ zTYvui>78E!Bp(%e7Wi}h`tk8`+A+_2cKb4UJ5MrC-f-Z@YS)v8Vv77!Q_pcS7-ZPR zUifk#fN3Xl@gtQJO`4)!&Zpl9wjA)hSNAb>i`%v9dyP-+Gmm_>J%2xFO{?P5)LB7` zw%vbKch=Rw!kOY8KlkiW z^{me45KeqG&t^|*?Vp?H{qp4w@5%jLcKdtn{(Iki?f0Pr?)5H{2|u=Ojd~rmE~xL#BaR6nS#J+~c^YnSY~CMWJ!!p769X&f zVTt4by<2ha=a%kP?_==qS+zG;|M%Nl-&c9`M5|3%=~L`fba!^-^?QP;ai*oFZlSSMWJeiO8aA@=>C00GZ zwfMtDrYlizMLCZ+G3&feT=-(g-EUKt+njZn5hNrib=+$6>UYZ4C-;6!do}4YBhOCv zb^ZTc?*=gP%xGJ6sZrpN;T%JrMIxOCu1^qa2rzY-6yl}%_`=QY(W^^6RN~VYo#_!| zQ85;-cwM#o?zXSL{=EFW>)ivx<~P;R`M)oJea2=KP+#2tt>~}MWs|eZJ)ad-)tsoD zH@#SabK>gC>$cZ+<-X3XuiVx5^xwYqx8GXn2H(A&s`q)5fR>`?DaPK(Pc+Y`7JV~^v;3m7w9o?V`Qe^34QuSLHO z%m4fErzH8={Hglhb{km|16Vj!JSX)p6}Kq+;G!`nDwpAc=$*{FyMpVD=gv~pG89-8 z&}L%Je|MLsm*w2>8m{9GHgnJ2|MThk{@;iBTYi?raomkAUVS;{-u|Cicjs-HnZ3C| zfB(O>mbp7-@4Eiwf9k9A`&7L*eX+~wPb=%&eP40enNw@tykcZ94DjWMP;hAQX5#3O z`j_Q7<%H@)5eB9gY8UQaof;b&Y3X)k@4GrS(TV+o>wKBWAf^s$9(L4{l$sMctj2 z8_njW@;qj(n&0!+$Mg69kIlB`?&xUQQ~CW_iIwT=SFJ6VZ6XyE&)g9$7U{h9T}LZp z-_^a_KL694V<|9Kxl`cBF5Bz(^sjHNe6r@V4g1BigZ*zUO6R2Nm9snC+n%~tFL(E! zZ~Ffqhxo0J+wXkk^ro+SnHn^FKd-h~`fl6yHg{frcJ?W0vJ-DGPH=lN^JZTMpN~h! z9oa=U!(RWa(OYVu?HTlt68!BOt$vzk@rTm?l29G~yidRcnQ9sJn9 zk@q5&(?j*lvqckF&gx%(U9-!6o*eUr_RU?D6%wmAs64r2TjZ5@HfzhSQ@i38swiou zrdb$Y`g=HbeVg2>Uthj_`SoVr4~w54kN2lttKPeN_w3VCx}&~q zTl+g~dFG=SmrGx3GE485|NrptaMjLhx$)Ot?=w>>{VvPu`tje7cW2KlJGq90PLP`A zE?@IYL(}j0jODXRZ1XSX7MQJ{vDV<@$Go~&ubIy$73bCZe?NZuY0%HSd2;Vc-mTpA z`(uW(pN3M-?VkzX)!7z&{duXEky}+zm!EaU`%hma4X);PR;Ajl3%9?+Y0#NgE`G+j z(b(Xv#S)iFtA)4s z&AWSc?fW$U`~NKbmv7JCw?psz!)I~b%gpod)zthyxV!e-&GgK96TcQ{b-d6Qs#9Vr zP?~k;?rwo;K^h8YzVEeU$;$dFwkx~C%i_|lgE9w?-?*Z4$GVhvMN`v?*V{ySCr#N-ZT)cg;g_S~`)^b+3!b$+({W;U`L2`;*UA{a@5`^Y)mNLMyW)55 zgxZ=D>*H*7rk_rauRAK?a)L!{pLzE+%?;ZX^b)5QohrI?b9=7v%rFh3``ofWE*-di zLsG%3d(&|hPZiN~l~y(-<}N*d__AZ?+nL;H_WknDWbdwik4f|9&p#gRVZ*(%?2b1M3ah`4?c`NrsM=w9K_fPoMe|-o(20=O(_A!Hm`x%mB;UUv9A z=RbdEt$XZsQDu&$p(}KQ9!*((wA#zlr1t;ceue`K$~ndvNgW^JUYlugD8I;iy=z;k zz9-w?kf5S=otIJ5m-@1F`0W4t?sx5d-G5yjJcTuT45$2HxVQH0?kRIjzwiCFHac5k z-I)%7+WZqwOD@GoB<_#8J-G0sJ**jf{Wt!Ws<^8nmyT~hf zZ69OB{p@R3qDr{kBc?6AW_SKGhrz~MQQ5oip4ycqYs*l3Uyo&(;M%vp?`b?qkFWVx z{_mZ9=akB4%Nqs&wg+;Lb z&&T6)j5+P%_3zhw?maPsF`$9du>Af)|47jj4BrjXmh5fcF8*9xY2$A8zgnrPPac=3 z^i+BY@@mKkH1QNf&VT=oul~3A{_nT%*X(}twZMYWHRAp4Z_hq1-2P8||1WQccekqq z+ANLt|L>3gKg%_+&57lA-0H8}a+n-CW~r{>ZdCZ#5)^G<$}Gs#^E|h>ga9R+iQQUJ$ zCs1UuS83jv-`mRiyZYAVAOD?M$;Wqp+uK{ef4=Lqm8-bB|L3=6N11o>|CZPP-5^MS>}6U%R}{+<`CSAKj=)8n&-`HOaLwc8dUX!t;0@Lif_r}dxR>+k=6_V-); zpXK%OOG=F$XD)yF_4RdoyV^e$arHl^-;X&zH&{XFNABnS@1zyBUlYj7T32Cs#dvZ# z_l~8{wq0vDdH1Y)6yF>_btS9$=aW~fDDADjTebPB)!p;|j{iS;KV;p?JB-ZLsS3^) zjBbWKZ&u+p*fno@>fQP4O;>XX?%>`yQ~LKlbB3OdXR>SVH;7rV@U(Qi4r8}h=1`yd z*ktu5iFJHux;wVq*jM%a!}0p(@BhAAU-xFNyj+~&*A>?{lvVxxw!8cPZTr9R+3$1j zPfmG#kh3aZ*;BBIX<_x7wOI^IKAO$Z+kb7IUpsAijF;fEBL|vTZv5&n@7%Rk;p848 z7NvK)4VOOq_By^kS9bZ&FL(RpXNA6Bt70qH&*tdWBJr%l$L036w_BAq@4lL+{CIjt z!ixiksuUPk+;KX{)Znr$>gL{zLxRgW-3}$1$M2H1XgYj@|Iz8qwcXWC_P_Ho_T;ZJ zHj!iA&8XnEVr^#-L+_Ih@AemdpSVio0S}AZ)_2W1vVsx{K8!in1ls#%MhFFcF?e-O zh1tQuXv%_)2U<66qk6AN_0QNd(e#RJsmY=aN0A3Fp7DB~Ib@f4&GwKrW1oJK#T=I< zTg{x#eArgIh z`QD1%`lo+w|NrBA%N$kq8~2=pPS$nrk9>VE|GV)e6W?oPY3CMQl~Fu%m#M1fsOGxg zZVilnCmyRw7tB}^5lHU())K6UpqWITEKZNs#r)c^7_`YGPtTqtb>HF?t zUB#mKGXWou%b$0A`ta=THI-UVCmeS;+|j_=IwNxJt!;1aEu7-Hc}3Rxx9!XG>%YJK zKEJkA-u&Og!^I0UHaa-0vRL-Uuw{}#%iBAWH)YQ3-rFN~^3L-m3=aye9b`|)Mt%Rd zWAYY9v-0mB>zB$XYaEV0lKa4;!AL^dPUxA^^B1}5OletbXI5^`vz1}waG%oEk#eQelAu!>XU%&S4-tXT&=_xKM&Dt2L{=!$~ zsf?zmi%1cZa!6WkPD>EicB=%t+01duLKAr!qY{``Z*5g(VBp(j%d>x1YrWN@Y5D&aFJEu|gtIukZ{peR zYt90OK}HVDYxlahFg9E)&3!(1{p-316-NJFI)}R5`+ZQ_So}@%JJyTt%aoNCUf5Q; zIYakOY^#QY>jBM@yA1>HE(~$7CaB7V8n9oEhPegb!9 z?f!j_k>OBu!_zfP9i0aa6{c;K>$l8&m3x0%?fu&o2272TcN6cIFVz&ayLzzVmSMqm+lH+C=(X>hg*#0QlG0pE zp4xnHiM~5MH+-haN1O9&&5d8|o&8pG>E>9)86ka}qmI8$H9!9BS~d5PTd(4(E$8w$ z3bWiy;MX*sZuH!ScPEpR%EGNb73MRrOYD?fobcH3-07Pkl{;f5avpN$QCAW^{rB!+ z_VR5T8ji-yoVfH%`KmkLOrt(`Oni4rh4I3gc6U?fBbNp5*Vg_2@@2}`1+%WLwToS6 zxxDo7w(ajOuG;#pCFA1O)dJQJOyv37n_m=XU$gDdIFt4L+1lue_wT}*yn`n0d;4~m z-QMco^ZB8`J;4socB`Jq*>5`>omj5SWje?>p=0th zlkL~)zJ|X)`TxoOU+VuCIDd_s`r_leDW&D{iueAs+y4)X2)t>#LqqD$efhIFtM8t< zR(t>Z+?fJ5)+#RK=+{*hw6(9_vwwg5v{uRE8k$N)^S0&6&s?l;sK9bU%=GCZspKtL z`!)+t{iqx68^gw#ke=K~HL4#x}>rc5k6)}*{O(4~>1%G~&aqsi{pUrdW; zUcJ|$v1a0dZSnhly*mB>^Zq~Kr$43C?ys+z!4-Q>-Z1Cfar@uD+W#N_|GWOrzvZ9r z9&X<4dpYrTnbzjCt5ZIk7_xb9I*`W3J<+uHTg0>7;r%n?r)%!5D(>%aO;2)QN$YF4 zt^W$pCWfymaL_e!dezQwcE*ueD zqT@8f)bU1*g{`e^uVQJrxZv7fvJ*ufnE4F6EH}Z1sihirCR%3szIDc_8Z>ZE;X7#VzW-}T#D;RUHt_*&bsrPWn zx!qs?K6>}yK~s&5+*C)EN^^%Se|axWe*W^`+ur6H$4@+@dnTu$_}bp}wZT@)G&{Yo z{QFSB(vZ57VeKRXRYswLU5k8!6_r%yKA*`U^Q%PTl+m|1={qJls~$9-nG}P8(PYLeJvwL>fMf2rLljh(1-aqEoT*`8DSLFM@?>_P+r|rDLH~pzpQ=Z`2Qzb4Z zFL$zLAJALV+l4LOmauHx=+}X>*aFT0rjl`QfhS3G=2`+&Q3Wv{dcTlcgv27!-JF`UdCe3Y$un>AA@7oZiCNqVRYR!-`j34#zX=EM7kT$bWBd zb@3kS%!|1@uk2DtI}i})5)!I;bJf#1%OATmZGEqK^6%f5tLOiJbN2Rj`R)In@Bi5s zA;`?3Y)~l^+?DtDY4ECRC-=byuVev)5kb*<0qMyl1C; z$H}`>EA{p)-~gTQ}`8b$6K5k-#QsdG2Sy48QXB+{nG9Prp2h^;h~N^v7?_syvQ)&odQb zKbHv1-T(9M<^BJ*|37X2DXU#9-^xGHxTCii) zs#{8H-o~z+Zy(2hc{M|J?vqoOUfFh^`+R2B8K21-A3fAtYnM+nICQ3I`&3iT{T=r^ zJmPe81ryhqX8Bnj+LrqHpvE{}q1vYv$ecs@Xwt=g%kR+S&g3@c8Tg|NsB) zes3T1>~Z0|^oE3IAqEq4np&1wsxOO5%@)m);YFSCDo`sd@nFF(Ax zx_Bm|W`cmAvfv%9*Zv&`9zHGkzPBvsMC3!>mJ{*+e!Z?dxAe@aSuPWk)?83xU|@Y= z_V-0B%iWDBod&8ZJ&WWRJF7Jx%wMCi2a^>;O^*ZxD<#JCh7COkF;wB)hqV!@z zbVQnp*`oj@ldAfAXOun9c@-xnCG-eRxmtCu^Vy=5-Ma-CCtA4*EL-Wqa#3|b5m$Sg zNMrBOsTZwGggZQXz8{aAd3w^Rnv`d8;i2pdNvAozFYf(TyIttcT_#Ccg^V7zC~kJ8 z)F3w&hhif!jTN4js(O-bD*df(zKp_5p4lJ2-EH1_b?wz>)n7ke6X?v6ds@EwcGzm3 zd+O%tt-E*ces*w;(u1?7ru?k&T;#JQI$cGbE#s8S!>$EB?U}b`&kWwI%fzDDxo4lf zoKQ_oot^&Ft@F-@^K8GiecSeXGp1;`L_{*xG`N0`T6kY(_x01;-p2+v_9f0pt-AMp z+uC=I4IbMC6&LKTPGjL`@IU1`aZXijw>YP*?_qpiV){4yIM1$L%}t+%olJA8YvJ%3+B_Ux)BU*s9f65R@1 z`QjoEa-6Yd@JMtFS34xZIq*I+RO7zUbVoJHKvmG_?piwP-A0=SY07wrKVH z!?ldH$2nA;S`KcXRa8}5KZoBn=+-pt7S3b^3yGwRBy)vB9O-HtTXzKqTnt&;q{O}8 zVEqsN{k$6gzs&!)F@O3#eTl>y_xD-;y}SSGQFH#M+xJ(`zY=z%NGq7_y=>6ITd@bZ z9VhHocD%IYl%}BZbWhIH;`Y_;_o8p#uaDVlp0r`BaubL0v&wm8#yQ(Jy1&hpF_%c& zyYBR_FON5`dbZnq@e`j@N+mPAH+-1trD-Me#=QTb?sFl58JvN#0W*08?{pq}QV_th zX!pv1ep$iE7oN}m`)B_D>;HG;$w%K7w6eV6_I20&{gpo+9zL;CjE#Yzfx*+oG31y1 zpV#&O{~a!#$)CKbcqoi2-Xys93rS#m_*w;{TM#lY|Y zA77_%8@F;*N|4KuSH?^E9@^C0eDiMWxAZMyM^;S{66$#HLd)pHifg-CmHSU?c4!26 zglHIwUR62eF=t`Y+lP~cXCCLfoB89=Cb6c~Cmim)%Cuu*kcgP@AXk#{S%Snl4$bVM zmS?-mdk!qxxrC)p)A(U-XyJ@*164+Wb#k6JWds8~i~Q1#^;pV3f61qKb6@1!7abmv zUg;`}j%XGJ`LHZXeYMwH%RGEmNviHj0VNBsEpjVPo!({MuVViBR#b6Q%DhVut& zd9F3prPYNMp6}KE7_mq!t5J04I=nz&>YU4odOb&e$5~J4w3sn7K;_`|_i@ivzWh_x zc>nv}S*xhy9BR(|EMg1Rhw3H~!AOt&}wHdF8x-^V4(l_wGI(b15b$jj8Q-@+$4WC+Gk9z5YKBYvp0nP)aTAjhw`T z(2hHYYx3_-(nyxD+QcL%uyo3Tz8`#(j_?+I5pZZpQ^}Ck+2y?KVx}X5w}ZkW*8&D5 zCMAXz<>i5_yGsqqH%siF8sxoZ#ts$P)PD|+3T#TMx{caN0>KUo*trcCbxcuFeH{}r zGcrG~;0Q+phkMtYt=IH=1m{gDzvXH8viBSlgVs#RUkCsHt^afR*=zg%>Hj$tqxMD= zv;X^a_k7W=)4x8f|NnNr%q7D@t)C(x7pvc|JLNtrdUNf4LCsE~Qndib)O!~JXUy;CkK4^NqFF_vrV@M3w8Tl)I#%jL!M)aJ@fkrdqi+KDIl9HWa% zqy&Rf#~$Oy-R!)KvJ-78YP=Z?Yu?QFxF|TWf=}<6h03GCa*w zQgaqoy}y{dCT@nw1=p5C!jm0T0@KWG1n($*z!^46`iQc-+w zw&n4I4!#~GrM?4u4L36^o#N%?F)^UygwnTr5}h3^rdw9;=BnPxq3kwIfn%YE=4v&; zwWp_c^c~-s*Br-s%A_nzZvhVr?;^uZn*;)<${se{lhKuTbx{Ws_rs!CbGEK`7mvS_ zJ#axsU}i_LftfH?Nm_ohq|%rHfZYME3SGMo;&w<8D~YZdLBgzv}GCgS=-qgz~4Pa49^g zXsi`7p8TQUKR+j9i*VA(%r#bu69iZ_JJmwntfnw;$rk$i^0xf^2?sA8v^X4B?UHa| z%GN1UCfEFXHucG-w_nfK|J0tw(!lWHPtDG}y{;wOKbEiTJHTWdnb>`ELAmMknxvsQ4h8t=Sa8XaQ!oaf?S3BHvp8aPi1GHOH_b8*P!22Tk(mSC3hXu`4d^!hEIFh{`u+sKkfg&eBAWJBzNnxIRXdoa&~ZdaCASzPD{{UiT)0&rgf&Z1+{x)ckoj`@UVgeNd9I^7gpfPj|gr`~8KbFUtZS zO>QNoEQ^@!R}bC$`$m>aNyvzKA-hW1BLU99C+zAW*7$)XK%0?V8n za-PZM-3<(IIw7EZ!&BhK#5uDog;rea?0hLU`SM{UrGP%Deo^ z?oBnrgeiT;cW#~gX<|TBONF^$p}An;j*T%&CzS6bix}lyU7Og=uxa7iwhu2pE8hRE zJo!X>e?i>twMw6UK7TLw?%nb^MV_l>3B2P!Ub=Q|HNzri1`c624(Hys3zK_}bGx;@ zn&4*H7Rg>9$o0X2UE#aki_0AfK1wP&EEi|@yLwn;Oq=s{&zje}zTcbqmcYgLyhvl7p4Rbg?FL4RZyL#sP z!pXrdTz7xVb&zs=)rEpRdyWRuy-CG0}zTN#b z`mvoI`)gUJVl9u9m7>`bqNKnwb zzf8;y9L?+#rloR5a3MvC}Ubn6r5W zJsM7&-t^~B_xb<->OUSofBt;pR|9b+v4+1@`}W(%eET=Q?rXTWxx+E(&5zX<3QpWB zy-Ru0CPs^7hcBtM`{J&bTCzJlobv3_l$k%@y<2zfc%|N|n)34UD08j$-28{Om0y*om&I?v*Iw1X6Sm$0!q;2bgp8d^d}S_#+*F)0aputpEJ@b-yLG33KmYKzcd@^&!h{1c7GEET zg>kIHE6x4`=7^o4M@q-E_JV`ox!*H<#XQpD4W@_V$-gOtEjCtk+TRk&TpXHjS z+hnkOXU4Z{t&hL{{TufB56}CH({t0)_*H}oSkjZi(nLEu)?BJSzTm?-m%O=w5;Jw; zE?u$`+&HQE>6G2K?`d(K|Lo&(A=+SR$f+f&7jK)p*T|p#X~ojYz{nt`?5b|C=&-{R zSLPi#Q#Cb%-b<^5`MR1+E-PVB=lEh9th8XIhXtGAmWXSo*A#uPusHka>iK(WODiXp zD)O(|%Q<1iEXlUbN9PoMb)CU5C;za3h#*r*xl+JOor{^ueoQmx>}@~zF0C~CorKH( zfBN;}|G(FNKL78d{P~dStG~WWdL90rPk8^n*KCsh5LG<-5|O1ljhYy~ng^gjGK<$zL- zf{WjYo|P$zeW_os1u|}6HVgFe{FWxwm1h+ zUF)vD4)TgD|GrP&OO5ZfsOGG%rSSnKtBousD+jZDnRfeHR6v^V%+?u-a}L}IKD602 zghM^CB%?vrGxzw~x8>z0_x69i5>U;|uydt?qx!)moH`L42U|6C1m-YIDkyd4|L*6; z!_mO^Q01)5nb0Gz`#NNqTu!bnJwNIC-lVWJAvdm&AQt~7!2`M-oD4b}W*Z78MSM9@ zd~wS)*~4mq-+I}UHk2hKxiQIH>3Js|v`oP*>4n6F>hjyH%UYuRgq0>0n4X_ysG-Pw zbeco=w$pNYcf-2eR9Iy4UT;i9M-bR|)E>5E-kyL@I!&eF`}%|7VzM$l8T;n=cv zPK7iC3dHZ8LSL5w?<{3 zJavX=$s&m#o6p}@bXyzia_;%^-B+=sXkdH6C@Zibz@XQ+GaC{{Iy#R z{GHzI%PWn%wsx0f!X2OVcjr9LtWcV?^f)iW$t6lw8(7wE<=FjjS*^;NAN$@r@%UIS zPmP{3p~LH1^2~Go^5y;a?w)PUm{k;bZjq{bg1q1S#~&r0F)YyeZ1bKiZQIpjC67CH z-qjF1bLK-ZNI2J*&y4?<u7xIVFvziZ87&ACA1qF;yM z`O7AvzCIqor#{ErS?F?3XO_jPy$myBUAokmZ-0CEK7I3!x4I0IH3W_?yt|saCs%4E2cO)$NIHgC=-DPFu zd)u>{S6z#8oHFxnI1l58ImT`VhN^;{sdpu#dFle+Ds2~d#_|78djJ2M_kT%;&GzyN zUi9Mn>z&uXe)&+aPpAHuc>MC389|Z1oLp9|xqIeU#ktV0u0B4=ao4NEpBe5b{yxW8 z*k)gy?B1)}6q9x|yL@^z4}DIYsfz)X5z>*Cb0645D9i z95{0=dy>hf3+$72ZB~yYKtEyoCZXLKkP~?V72%_`_Oe#fdIL=HF!(xSqG^oysLKX|m+D zYcdPV#Xqi@B&?dmkX34b=F_{gf7|y+rMVR`LN~ zo{n>U9f6aKT<+wA?K78N_A`;kiA{~=gCM(A%;iIG#17ZXdql6@dd-mKMzrgZpi>#` z?cKFc7WS`p>d2R#xM5=JLJrR_OS_#z)1+D$c>VB`|TNgEEykcThpO2sb^-$ zudP{03RQLM{$Jkzx8L42_TT#dkM^gnc~^Jg%)R|p(yH2lC;wM$JuXGZ@=fhA@=fwdu{D^l<&+v zG*g39SYd}NN6(wc4Jt-Yc^EF6ysiG4n%tyvYxhEBb=L(Q361UTyZ3+JUcW!ioM+au zM|$?^U6O^++TeD5m?cxiZb=Ch~rr$1AvRhO!-n80KH zbId;7$^vpPyl337>P%BHO| z4<1h5kal-X=FIk^L7`Jy8D^|_a;7WQ>+ZorrB|a5R0Q3R_3f#bxz?B6XrbuDL?rpL-p1Vn@F>Wri~n)o<)hFiq7vn zlx5tH_;5OgPO((IIZK#zB8x+LcAA*&avdigM+t$vy|R44rC)D*Gs(@h^tUv0JhyYo z63wr9d$%wA_s(`oKOASzF83mfjC%Sh2P4lBTNx!uMlz!`y4iiA=P*~PY0 z{Vv?jn0G*+J?7`Jo}`9FD}zK^o)*1j6a000{r>;;f6M>h-TzDcze~}kZQQp$SMA-u z|GkBN^#30pf4=;AYSN6S2L4_?Z%#EHGMqN~%u*57DvNvi`+t9ptF^iK*e;vzp0)An zM{(9n#tR}_s@Ix!t6f!FEAHyI_q&SFv&$w=D}#Kux9{tpZE-bf^Q%O?wOi*b*~-Sy znfkT2Lm<*rc!t5g>f-jR5g znpeDL>u5Z1Lh!(w$AN)Q=S(VSGEjV$sHc=zDpHzcq`qkHyQ{nUM68Py_L+*gFg$$s z@>;K@6Gu|m#YuM>dG?eZoEVV8a^?oBdk?G7!UY}M6CW9ub9{br%I4OrkB6RFGA0SV zlKalT{Pov4%P*PxYjVcMhb?*5sH5aHt60-jm}`Z>yWerU8{N-Iaztmh#CSz2E7?4^ znL4RBPCtD6yr8J-+6!!L_N_Zz7b!k1@O=qa(!`AmiujezSUz8*s+tye{p#V4PYjh` z?=26S(V8Lu`-DOGqjzeZrYB8&r%Zb3ad_*!ZO>B~zh9g6<=*5?tIYbRoAKS5+sXFq zyMgFtQKrBfJWUOflR2C@_=1#d?#zw5JJ;n1&vK*o`EmC9eto#?uH?1olu}ODHZSJy9C%jI+uKdn-*$b|D0gR!hn-?BPbmvcyVY;|5g;yo$YK%#D-}YL8 zBX*uWf`XbBZL21Ii+WaRb;ii-`x1;bJqQy*TTPaNA6;aWHm9C!?pPg z=X%bZF{lvTxRbwaXLa~ho`X?mr0y7*&$=*W(~~)UpVw4P-uGy|O_y%T=k|a4u4312dqpL~`0Jq@a&qsgat-|#U$gcp zKc1OqyJ>~iZf<5a>69q*q>GbU1R0z{?J}1hQ&Cx+uw?749Sk#mhE?xXsLFHDbumtE zIie(T=#xt`N7yjLlnTO|DX1D%ixN@BHjuwYhP-M`TDK1JfOjOCfIak2rs4T(guw25bU@bcx$cOMljm+EvBmhaGxyuY{htU&4g{KG;iii>Py zbIhwb6<^=ItB_>#XldE)ZGCMjj(oigcQ`+m%rU;aFHcpQ@yybk-JKh_xbH`}iaEA) zrf$&ny-}UsvvLK`T;01%uBCING4{*-{b#~x)sUU!Ambn{)U?59X?eaJC_=LH93|e_ zp1hH@V3pIIjFP1y6HOQXmdo=qxngd9Xx1y09?g?B(=*Rq{b2bz!KY7gw}7#uiHD|d zaht;DYu|2tvx?c;KEq~rR@6+LKLr*`rrg@Kg3D-oTKF9oi3_`%4frS46?aPnPmTU| zQ&+#|=erV?hFbY4(tF~zJLIg5*E>BW-?TI+wwnLF(xDZ1*Om(j^7uF{ZgOr>3}7$r zY4lJQO%!xFV>x}!#EMzVuAQu!GD&&Cb*raF{#B*!{7z>qgB!lzelp!afr*LP({<|1&*) za_;{R_Wu{>-hFoX@8!zcUoT&t6_20y@7;Zsj{AS!&DQh|5>pBMmiKU)@bfu(9X=1@ zwy!-KZ1(xq@(nAtHnAJaFUvf_8Xi2`(=kwX-{@uH!?C-<8 z^fbTUTN`ftTH^PAkl+xg{X z9{qK%uGw?`Or)k))HVqt#`nI$(~cR2iB+?6&E4tT2@5oyuz z?QK!ywcR0X(vcA%BDMxPO*8U;J0&JIxul$tV0g$fCFs;z!Oji_HU-x;vpgmXFfxZ7 zHWUn!4YHZDr{(dA6<(ezRa|5qX*fnrpSOu8S2{^HG+e>y$gccK@4ewJulCO4JAO@y z#nxsY-}`s_w$BPW#9(~dvikMEqPumoQoe=l|MhD1;fEh~->?1t@z0u7s+sw9vzF(s zUAvuaf{^l~UF!tCy?R$LLp}7JZTaf8rpG1(xjE>&^z`&!6h8A==ET-(M-F+EmmfEh zjb6QU%C7Ycd~HE)N-JDFZ9IIGAI}zCE$P-18z0sy(W;eS>aVoer>^w>hXSi>xig<% znl3f7i}O#FRMca9)mv80iyTFNsc z-@931XM+ttPYLomnR~&+oze7;`IYML6I*O9{5d?q+2DDLh^ioiLNnvHG$)sqvrQff z9-O_LJRKTKujn58ezxWD>{mV-Yh0Amw3nL5pIRuQBvqKzeB|fD$CEo|n2V%53k+iT zHOKq*H@UmI9T`6kb~w1C%(-dH!wa^Ha670wiP^|3 zyJpSwe5HcZ>uQ%Lb2<)PQe5!wWPIK1(;xF|ejfXMeC97s-Cv&yYQCEPdm-Mjt$ebh z6U&4tyK*f(1tkx?dmNMXHpE=iZ_&~x_oST+Ha?SiXq&Uz*P-gY+~Qx84HIOq?Rfo* z>7Ig=@9MR(49j9VR&1|*;_0jUu_4GPzchR8)F6geKZ^uEowu+5%3lBB^Xbp*M^0#d z-L<1Uq3Y|atM~ru*L~&nQWN}OcdbWd#hf*7i}g0-q*<}a<{7bIFkM$}Bwk_ADrLNpsmTj5s$#idT|60iuU%qvVWrRI7dmry8Z!Y}V zxUoq?XyaS9hD?FXsMWvoY#Z__yahkjb-$OBY-{)^bmPyaY*z>NErJSXB@QtAG%|Yh zEzo5B_^C$Gks)jC+uwaBo*iiL@;uSm_LS+&tO859^iWa9atDW#v%05c79CF8p>tY- zlSxWRu!Uo}N)Kb-JN@fdPaimbx7+#c?;l6qy4M_8YbvAJe0SUHpzWqgUY?gjLay50 zSr8c>%J?y^)#3zuZ+i6l;A@J#p{Yj_dZxHWYSgT)&TbR9Z1Qx@{XLbRr`P}dy8nN9 zeN}z^uHC!U7wt6>O>%7SIJl?S*qFol2uJbpIWCRWxymWI7s6%)I(h84W~H=Y#?Gx} z7BY3;Zr;AP?cS?>xwZEX#7vM`SS~D*_j}s$zKF}+56TzJ3-+6PBHPbp%T+#xtlnpp zJZ|x3UpK5f{WHo`z1YKZM&P7pm6zt|`=1WbS&`)uaxZw&?%$6;R^*s=7%mM7k~?~0 zfq-@+hy1f58#%q_R~b82xPO0k=F6+As~;D>%k{QQ_gm7hTspbq&>Ys*?wPsGD`ea{ zl5abAcv&(1?K_@4bNR|8K|;oN@87o$^13A})SacKBzq(B+DDVwTXXk5GgSo zpmSUfA_^W#OCqD0lTOT9SNq0Bm1FnO3@c@(8Hou&AGU2xkh<5c=FxCYr^j6N=|h9J z(cx*!GS)7gGNW?dvN@MFt$TM)sbuzp<#tT_iZ_MnS|zeI9MMwxvq_Z1Khg3U0TL*WZy<$mDy!rpV?T z!;(v{_c8yt!Rddwr2PMnx7+XQT`#k__jO;t8y9oi-CMf?z4rgP%Ky9c|Lpzc=4UU* z>0W<(%g$z=-~R7cr`zxSzaw`pL$Oe?_>?uCk&}!XS9k5^{8)Hu?eTr)iSwS%ocTRU z)!Wf7-drhj?c3eP{eg`Bms#c(O-x-sU+u1KP3-l8GnW0WS?3}-ROBZds(!s!|MaD^ z6D4&oBwzaS>u!4ekM{lB*H2fParud0kAheJy*)oGBL6+ouRng7ljC{vwF8f3&QIU0 z)&6~A=k?%KdsEM>{d(I*Y;I)dg}ZihBUe>v2pdkXw31u4d-v`+9vU%>-*bjKR*|35?0*W=aCtc z*mBm#?bR<`BiUoyt{*oMx>zm1>$NBH%AwR1|3XqNCPZbgz3E`p-~E2uRyQTZo@>?O zAA`Cy422nXuq*R=Z!UNhU0R+hD%dZcJ03E=3#P z7jPA6`}gW_`||z2er-SWv|`WfWh=MsT_M24T;w^cn_-4E!;&e_D)l0dS?=5}Wz$t^ zA=`Jo)RFD;sWW$_?v&jv*c*3Ou~X~Trrob}pD|XQwzQt8!tgBSHJhWCQ-_n}j%^Aq zDM5z~qGyEM?CAI#QuscxdiB$SKM()@yuSaR>C!E+dOr(Zoqg|j+3<40kFB*oEha^{ zyxKP1NdH4kmCfF|+CBSc-+DGl)zDVv`R?Pwf8Vhudu=^`*~WK;kC$WMg0)TeCP@A? zdAfUM{N3~C{SPj=yk_J2Q%Z{t>&y#Uxz+r&V1RAYja9~KhwrjBtr23qHT}KZzX#v< zOTD@D{Q2^2>)LB9&apq+``t{o>xqcS91BzNlI*Rf-ai*;UfmYAE?mS?PRQ-FW$boc zf7eh(>*ts5&fu)kWr~&!x-z@j?ZgmMd#hpMJ=D(Bp98fLy{MmR^q;0pG5v7Wj2?UeA4h(d4wm*DmJe zea#{+?)~rbEN{-$oOAAZ%s|-~` z=i~Qsb4z2Vu3X$$&^ot?@#37E1>4FebM6#T>TBq@cUK_AvfgKrrm9Qq_H9>pz31h) z`%rxU_s#Y{n$LgxTIJ&zaH032!|%h7Z!dj#egEGMBh8H(qJb_8?oLW^V_LH2=w9*G zxv42{TwFFk%Ui-~&Ad~Zb>Wi`waSW`@BbbfZ%*s<{Q32D`>ktX)#u(ETe8yUR@VHY zmABvB;k=QxWQs+bo`+yTbhekL@NDx^v4y#}HBQa(e|PW7mR3oD!rQ(s?j6Q~2Eqb{ zR>n-t%1WO%t-JQ?p+RK=%OY({w~&Mvd*5Ht+R=U_;rClvg&7>ys#j9qU*9$_yZWA4 zn%{%&SozZv7fe=~@!f5$TbR14nBn_1k8*vog;%F+yLI92@rQZ30*1ye+s~|=J&~cs zuvlr{OeWn9*Qrtb%thUn`i<+-R?yYiv z?|jkM=irORhYV5=1(^!xoZ`?v(%_(S=D>q@M}9rN{Pj4$c*bbJ^1TqcV$&h) zd1(&cag#;*(^8$c|K9EFX>#YyZLw$g`O+^EhXkBm*1W!D`>mAwM~Y0oW~ai}UG;y% z_t*aV@bPcEy5G!=Yuj$$tNZnG`TgIY?&k0N`S5V%O{*(WPg$1;CulCcT)5AAo|k54 zzV@ulS$^#+Q({+Hx}5v<>}s}PXy}3333F!LjM9!=u4#NJao(%EQe8#j6`4hZ!P z@|w7Wi_zia-o*0nxwpM`{yn1hch|c!IyxN+j}t^UK0Bp!}~IKmzwwr2N`df z($b;Cc4(GzRZH)YGUL~I+t;QAtSxOgzJ2YknZi5DCst~)hPvLkd$;QC+UOmoN?t9~ zKW{EzVqkhNTleqk?e%s4ewM{fns%h=VWaF}|K;1)UcO%cRXXkBl7@MccHFL5m-kCp zF|m+6Nv4ZkP&#{IAXERU%TdzK4XHc7%==aw&id-@-|F4gPY>J7z5VmwyNcLVt19lK zH9H&n9O=uA$X)w;-+cEvkKE`>L7!swK3!z_JnHq`zlAo;t8P_g?|%RLn2qp6clHU2 zkG?EyNRbPMU?NR#txhVANoZ`2$tJB@4+9)V^nEcXGdUeh9$YBBH zZly&$mI4<~*vDVrI)!PreQib6=Vxcn->X~M;jm-<>08xrb6rxVuTgz^p@2PG?+NpP zW`WzgtXZ^GFHSn`@mOnVOY)|-?@FGRR-RP;*kK{V_ogb+%%$l2lGY}M!^}&!KI2(B zMWAc>>YeUACvzj-A69$z&g71Khi2j1vuCdF{r5D!cC%hLdxk-=c!uWl+>Q5Ze*Z1E zuQiyE`u6$h(s@NfUaH+4M*jLt0mjmkcs8>!lpo(&XCeLTp}~DwZ_oDp$vf8bZNIsQ zV}D((z5i2-solDL&7Z7lWb)FO#kReze*O62`}sT~UpD;+N@B5>;X9JJC_>Ui69loByqt)*Bo6qv|@AcQ7jF6b3xtSwr$(9(s z=^Jg8MPK=f@*9WAmz2YHmGCG?mU6G(~5h(=fXKNTKJh!;}_{=P{?g{P|N; zU-e?|`qx$xb}lWNUsvXQHJbFFgF$ctgYFE@iBZ`PEsQ3$RV|5jE=b}!T)4BZ;e1Dj z;D4n%U8)E)b+w|?8Wwf2Nb;EgkV?LTX3<@ngVc3yjX{eJzAho1k+_y5mJD$KrlU|N6t zo?4dv=byVY9^Tvad`>pE!pptv_bkF(4OLZ}u1VcIVZU#wTBGST7m;(15(O`uS?<}A zpmCpJ@y=ey3%k}``)wmyzVf_(qT;O3Pus2qak1pyewH_XZ|(P^)7P)Pe)*w`oZ!5! zyw_zGb9Fww-}hh4Bh+o5!YZzhx);8-%@s^iF=pcrEn^Q}zxCf&rui0&lvp3$;41K{ zDsaCRthYYSwqr@u?3t@S*@(N`;XT&$ec5eB8KJLc>ssQTc0?Y1q^9H6vF7E&cRwEY z+poKxI%}Dc>gQ`w%`P2ty4_sXn%g%n?wz3Il2aYOO|&6sX2*ivv$sCWd3aWtA&Xn_ zoaW6Foh}SdIK+cI_f(q~NX%^SV0_21weO`_ZB_Z|-`A`g*cYzo$-Vob*_-?3w<-aDDsgPFg7mSRzhtAt~^i<^Y#)m`5^6k1gF zlztM=mA@#lhToZAV4|8LN7A-?ogJ1D8qa+Wt@`uq?dtON^1=&+m=>D8ygU8%w(E8^ zx{)`}$Y`W?-pg9+8`$Fh<$}eXDVcY2HC6eqRN2Y$ZoRT& z%kE!O6{~;6YLedU`#H)iPHWC+o}Oa5k|Xe|wEcpsyVgffG+DXkqTD>ip39-k z)M>#cDf8^hud9V#bGp}Q_ifm=Fi7IMXMSmgo#2g!YVp;P3>PZAIFy%8**x!E|6a3l z|4#Y%yS0BJrgBPlwn#G0@eo+Kt*pL&)z)@t_l#?oYkqXc*V<;(I?rnqc#*hw`INR- zB|RLR`8WOsoeH~Mk(B`H(o8(^`0?WvPmfoz zGaj-w=}zi03NT$Dfa@T~Z|EoHy|ynX%7K6YMM zSYb=*{jB^oMk&{oI0fBa?EJK#+)@7DsoA!_m+>8$b0%lQwF`!;)jmF4)!o4mKh1l? zl9K3SewR~fQ=g@6=ZJJkKe+w-`nqpFZ|{ne^XFflx$PQ*wULdDo!f)!`+qL{o-p-Z zw2KRm`_$Dz{PMXQt9kmjAKoyxUBZ|@MXNaJ6uwqF}}Q zXLn~i3m7N4KXH7wXXAy=1I!#}_Oi!Z>;0H^N_67AUI9&qsl}JB<(zWuP*75J?4L48 zi@9s2n(TLTlT#@UAk+w;e|7OmvgRd ztBt(&_u<#q4Bn?^ecE?FPnjX$>AS#eRW~Ioztjvpmrl+|5y7BKrLhc$4!gLzoO`}` zi316)K1gpfh%ks_lU-%S66<#_TX^D}n|p;D zm*h0Ay?ORS%3Ig0vtkT;jfL5zBNlAtVsUwYSjkVzZb9+h?;mdcuCa+b|9tJc^&G`s zhux#9pSI}EDo_(i+qb`d$`%`Y`}fhtoq|SNw|luHilt3H_0r<}%>BRbn*V zw?!A8{k*4Nvh}ykc>(jax465Nm@b|@Uc(kY;idUO4kjV~s}5eAOxoWU7HsA)=KpZb ziqSZkL)Eu-{nj!@lTO@-W#1t% z_tc%)ch6qF*L$e{vZ0b(_46fXmA@%Wx%GSBd#xvI2OIMoZK^g!9e;P+dr^#q=ep|q z+s@t%{iGq=(P7Beb9A?$qt_y*ye}u!t+Tod_nlXDm)OO4V9Hg-7ioN^l6A^GDk*M{ zrE{-VmA{q~Tr{V9na&N(Z(@SYs|*-yS~Ap@t@1Qr%e#AhYhLu;dYij*)fm#J1a)Oq z)mYq<_b)plU-u(%Z(N*1u3SUs6YT=+>?G-&UJ0c^qT#=$5r0?nO68ZVNTB>rohhbWkHj6 zzu(In%X_5vpuv-d#c{HE+@ZcYyN_lBw;lLmuw37+{&v;ZgKDqbSbSvss&`zsN@zc< z-QV%--mIl1(xHqELgIlf_s#?bah%ZPEY@4~TKBWrQWYn`i{E6uB>4^-ez7@s`S0J% z31-(yMHh0Eem}CZvbdlrA+TKh(Z555GCxBtv){`e|IfXcmMYfQk(Zv^SN96p z*VVW^sJ*{D^VT-kSzF2$OKKGgz0GS`qWN_fzunpHhtmv2{*+x2dbDr;_gfQA-WACA zf0i5XUN9r=$l>pQYk8d~HZuMFvw8dX@89qL`!>6B9yiwvWvfLBFZ1{PO}s1hvgX9z zt<#FP-3s#J=jXp2)_Lb{J9q9GSNqB-$8=@*&ZM+;ypWSP(C}86Y3_&5Ki< zrtRK+r~3Z3wb$e}&f0oSih0p&1)tz`rKg@Qn8Ng?%9fqcSk|p+&yvrZY;A3MtG_3- z#ZOPIne^ck|KSTi6xa=tEy}kmu1HmMJK^(N_gZJvX1kzuh60Ii{$G$v&e{9^_B7#% z0wEhdbUds`tNH16=GVUWQ@1Q(sVF{wrM5h^oMEEBvA_p`iE~uCrv)X&DkrUx4Dy;H zX_e40o3|-P^K*ksr^jcrtm3p!($mxpYz{HVUieh}w# z{?9f_H_mV{ZaSs$hM{BT!8=*ib-z9w=C|KtG3{};zWwiqfBglneJh)B%4}+(c{Pio za(Bkvtfu|G zY`D}U;qkkBzvLR<2{zU~$=JU`!`+RGZDEE`>1M^Hq0`!T|8BfwWE^TI!?!i8lG9&m zZ{IT$zSk2C><&bHw#k>j-mzf8f*D0Cx0<;h`SvnG^5agI5HHR4%KP7^OzO@*KRq=v z%eegf=fruRGd^!GeZN<@KW%PxZjVp`hjR9{(5C^Lu1;%RZOQDiJx*ImZrMx*#sj)7 zKP~*rkAHgg@ZZbFzweeQu@s0{$nXhnyS8_oX#+#TtTZJ#_YeEt^B+H)o*%#dwM^dr ze-CX>uXgsk_WR)h#V^12|F3>oe!u?n+kVZZQ%>zTR2}X5DSkfRvpFt8(Ym%#N1k2C zzI#T^h39zK>fOtOK7~9x$Kk`#IeE#P=b39N<=(wJG<)XRU_XJq8{`Rr|zMQZ7`R?=ipihf#t%~GV0!?wa{{Iico40M#-!)}P zk4TS8l}50Fz?aWI&$wJp{PsC=ndQ_yI;USq<(G2ZJ8|#zuC5a&ROS6I9&j~ycv!$e4Z}?y3d^Rjm7hN|@lf=9FVD^kH*}jU z1Y}}EgM?PEHNE}a%)j_Y@16FJU_Bit7o8KH7nKEeMSm`v@J4V&Kztd)k4e`&d0HkI zOijDCc&ppt^$j-LxRp|_ZObhzm*Met?_l6}n>5E{`IMm5Y4LZHWBNu}GT3wtLrNv~Jm^V7x*JlU@fA1<7A>FKU_zkj`Z`#8~0)4Rp;$O#cCCT6L}$v^J@5f)6I zZdCVvV^Pl9nP1mD4)Dn^FxUH zkwK)wjQ;K184<_bo16_EJpS9)P_-WTV27!*>_uM{P`w3)!y(Tl`?eDnP z*Q(e4wpu=^#eVO63B#vK4o4Q8+MfMYY+6F^2Y)Nr7tr!-McIQJf`n6 z%K`dzo0ZE$_r_m?&Rqaptk0xZt!StM2CB-d1~G z_~PG}eGztc`Sssc-O}Vq(=Rd2olg-Y5{O|SI=vdLK4hNPb!7sb5FM<8AEh za&DXXUcOKlvHtYaoV5G(-yhHa^D=(M=gXg;zWnp?=gUuDzLe~ZQ~vPt=gzHC53fJn z8LFI;`1kehSFWk{S5kaT*fN;++w3adETO#Mb?VA$>nWL9FV8-{D{f!iePM0B=Z~w8 z_inZNqLqB=$Qi8?n{zCMv3k$uO6--g+!XLW*Ff>ur5)e(lwR+;7w6AV7nyzcP4f0M z!S)%a&M+yco{XHC$9qk^D4yPQr z)ULTaGSY7`5;@)JbIo9WmiwYfrCmneiRnFiaymGJ9r^`=2{rFL}ca8C8riN(l zlBUCR_&n{53N2@xej2m(pvm$%FB0`OzQ3n_eu3AH*~urCe7!Z(^bp%AQ3VAL4(5p* z7RB!!b@uYEH(fgA+ur;4Izz=;<+ zhS~4L?q;o>tyO;cR?kcmZqB&t-Mfvwwy#?C;^W^wXygAu-cO30a44irR;LM*tt6CofJqrp7lg!lc>|_mHam`9#7w4y4Yom%@ zADa_ms+AF>8uxAOyWb{L3o>ul>|Cc*)Ka;9Uugb<=l#&p_RBhAlN%FH`={hAd2qw24w)*|mwI3&~ z(fag(XQt$3xjT2yTwB2YS+-sF!C^-u;f?IY#?jfe_umV#`JLSOe?*5CJ6ZRv-Xr~PeO6rcJqgwAB8tKn~j{%^!d7 zzN@L6Io-+S@;TS*YTOBD7zJ1FX6tI4oaH#-U-zB88KDK2*0r*-6~A2P`J!B^fc?9} z!L19Od16`np7rqRTR^NVXuRH^n#se;mMN=7>KTcV+{T9qc@W_tD5#86wN>}{D6cf%Bn1jD-xxo#Qo-cotKB8ZuBX+qS*n{$I?;-@`dcI(QO z)h`}#gdK79j@1+}by6{4-*&)*=iUZ0-L;m!nnppVE!Vx4GVq8jzw98Xe1GqSR4bqV z55@1#*QZbTJngu!|ZRGrlNqzqQ3HT1>X=_Ysr6$Ile*Pgy3b z`7}4TfccD!vha$P%ck7P*?wE_V&0rvj`wRTKCkAN|9qe{)@i}K{e7w+{ z_ppNC^Gdd2K1UyRFU#kjE7=qrxD2;$Q&6%rT(G+=yUx61TF^E|zv}`sEOSpa)_oM8 zDOt1Q=E7)p|2}84mPC&pXa9MeiiI7ktoo9#+x--~J@?qfpi@epiZ0c$GvCo#7{nz! z<7=nG>zj>Br+j{Tr+eYMWR*qNZq-iiG3Yv3lVZZQk4*C86Uj5$x$KZVSgOa~y z$M4C`KUTY5Wc$?h(&gJOC;zHHJoS@z__ca{YncWM7rt=Itpcn{uQsVFDIE|{no;m% zZ3(Ah>yE6s$7+H?)tU3P3Quf3xoe%|$-6rjC*DnZ@OhKXytr#&@k`k!N}T!q(0udt z%;_AVrbiQ{E-QU_`7l`XYnis!|t7V;dq<5k!awL=JfBEs__5bJpfBrw)-I&2aliRhGfx+dh z6eabpFWbf8%&%G^#)FI!>P z^{DL2L0L(6AHghvIcxis#vud{VzI`*YXP&?N`Qqst zdOc>FOV|1yIiOjh)5$o&+F}Bi$;ZSQ8ZO`O$uBq|qRdvdRYLHERf1E?LtdB2@I|(`LPb9Wibq5ZZ@2)lZ5PEx0-{T)ocl>*J{(h*b-^R|BGdm0#cW({z z)?9i`;Ly8*7fV75itcO?U0>cb-}8aAvRHefMB75{GrZ>K1OLD3)L6FVzQ1OgamLl` zsh0~4n|}t)o*^kw5qW#pmAs`3b^Z8eWrhds^RjQ4Fk`1M^UQ?B;a~awY3r3dKKwm9 z?EMpi|9=+b<}Nz1(EXRuRIR03?>W|1R%U9Q*>k_wlC{|N`%bgK@&&idjz%rt@MO-J zrK_$+T@PFB^?%AS&8^q`mV1jkDsl){FdFtc`#e5iDA>Ab-D{iem-ME4uRVKZJEzsr z6NwUS`<}HYzUZ@2@Nw?5RNN#zD>ArQPWkPMXY1Uer-**LY|?T;)@!1sTG}RoZE}7b zim6I$O?HXT^42Im`4?Jr_c^7cfcbH8Ucsg>cAdF4t804Dvhwe* z_gssz4zG=E)tr$XIeS(8j@s?N&#r&J?B(P3?>)U8eCEG3?fo_1l{QllUoyZ@z$9&U0?Qat92dGTsoyVcdglOoooitt8;>{lvv2@ ztNAx4sH(i&gXNZ2zPWj@pmqE-wcOlGPYoUJ>@VFapqxE9Q%1R6LfOjq@Q)1lqz8Np z+*lSISijLO<=1A%U8j}G9~W$&@3bIMf?J6}sG7^>ZMo&L8M|K0ZlBS?q!gA}tA1C( zu$mXFGx(Oy*>C;|rIMaWPqR;mWg8UPZ%T-@g7nZ}~00?dI*X?|%OH>Dk*_ zo)R1R)n2D|ZgpobbILx}qcYoVwm_${P|$hP_sxzjN>R_Z?p8SHaQ0g1+lY?D88cqa z&fhP0{LiN!QpqEu!=8+vBrfaQN}_ zsME_9mh4wD+s?mg)fX1I%e+~;r&e(|cqo=`es?+esYm6Vz58o_|M*g{Gv-?9+xdIF zLPA&1DvrCpDsR;*jyMY0tL-g&f8GD`wWB3RYJ+A1| zM~Y7lG;aLN-Mb7`Po9|V5%lH3&-&Psde}4JDr+TMV-S+L>tJ7}~IMA{P_zTCbY&G+&0a{c&u^DJEqg(og! zYB_AcqshKV;L@!l2Ufq>C6f8d!?r==SWW|nkH{U9YZ+dj!2&V7b?2MfY4B(DAXwB*j)GR;)h_n+Ji-}279-oHA` z@b)D0(5<_Lt;!x<&AJk7Ak3~*^ZVJmZ)xlaXFfc9yxZo;zN;~|lNdXyFCLKj9r}0c zhuXP@N(JAK#4TmHe!rAg=heGwubV#VZQ&IumeN>elG3NhCn<_EEo#eFX|~_H zvwG>*ovHqN4O-{8M@nlf(M#tz(kW>8T&_~X^_G^_zKO3SYiBLH7A35`c4f2GHzDD( zhqo?GesDtNMeU}$-rugxYFhoO%=(VrxwTUswHT%wM@O&CVrXf!p0vwe+?i|d9M+-| zjw2ae9P8(9S-X3+8^>IKN4^EO@*CC%#~8n!UANv?AaL#1U#z!Ss_8k>*IV1AKk-o(XTbp2WuhvQv_y;rxb$Ai9cC|lyI+h6}1%!nJZx(sH$E&@AIY7T){XAO(_TmQ;L4AKa zXF1(?UQ=Ez5tMhDZE9$-gM8WHp#OR zNd=+Bt7cm|F*Mw|w(8Qv-WNg~$=^3GQAs);#=UT6VaHsHgtXs(>t?B4-ld}&a7RZf zBes{}0LK{_=^qxnuVxiTUH^J|y1$_tL)3LkwR4q0S0y&jVD-{J?_NC3>;I4T{T9cg z4;Pv%K6?NEwY*&4`Nungx1WBR+ik$ez|Ts|8{d1wIIQb&nv&4n`&lxb=GBzC1>Z{Kbl;xYg;w-zv80Z zGgquMV%@{_D$bH?AEVt}#w$FMj0`iA7fg6mwQt3%hXoTV@3IuV=sdY)>6B}ws%m^5 z_j<$c9kH6(esJoxvm%^Tjj!{TniY8m7de@!ESf$0?{od<(?7R9NJ|h3=DY1v*!+`W ziHO(aOI`{^;x0X!nu1ex^LH+m`Zf7#)a$JiS50H%@Vy!IO!ttB^rO)E!WK)X1f5pg zx9v*D%?Ve0j#RA`U^WQ$5@cA>f7$%hrmx+Z^}HP?LHnAFhOWs}YSo=G-*ww`? zYv~!|_vOmTGnO?P*zA>wkMpy&dtG30MBvWjmnDVG*G%sF?f>=dw!FP911q!hm)Wyx zb5&V_-p*oL^r)+2m(J^-E_@w+hPxV-C31pK?ej17G+b1^F}QrO!mp@jk3WB|u`Klq z>zi2dc>P4@bCn*I`=!6{)^zo24DRcYC}LxCULEAMTG4fx##-Lpjgm8W`v*C3LepV_vJ(r}Av;iCxnk|GZ?DGp+lpvuT;f;nny2j#OEEJ+_z8 zWkcqf7h2aQSU%gb_0NLmbrMQ?r&V3v^-kZq<@6q@NleqUmOq}EbmDQqk{OZmmu5As zRy<^qqVz`8bN-DJ%LG`NUnFhS>)CCWDdRMwZDo8qQ{j^bi~c?^+FUw!_u1p?Lo2;j zZaJm6#O6zK#qNq(EQbm`+GpRj=+kVCx_brGas8{aR zS&rFjSFLDdHgrE`(AL!6#sB;I@#o34 zT_+wE{N8>4uWq78p~SMkA1mG{=504;-Uazf2%1=pXJIQKW*&ipWCa)t{|Rz z;PU6s_y69lufNfMo zWXk%wXA;{U9WIqNP)N$}TJ0SBmIZTRQCS4ae6MI+xv(hX6{_|C(*6z>dFfCS3N}92((`nB1 zqFK|gKMPf`d-E-`!~gv8)ZDa#2V|!*Sr_K6^9o<@BcHZf-?i0^hQL?wDty4Gg-hWnU%Cns2;z z++DjSbZhV@5rGDV69)wq7I=sTyGtxHY-zYt_~lCc@>Owq)8o$WumAt=+dG54$Cp1o zeOmMKe(vg7Ni&Ly42-i&-@Y%OwJd-C-?GSQw{PG6`)&ho)GmvyI~OiAEffxadGT$m zk8bGOS#0WSB}J9F*;XCZ&;;VCp`(afix5DZQg&Vz>^CaHC%hQ{F zdfi)|ZIV-zEgWQwEW1{kcxy_2afx}+=fZ4$JnHG^=j~H2S-h2>KTqKCu64ivn!8+J zS5D1sxHfCu>su$qZY;f=_fe|;$F1wHT+7$5`Xr%Uobu<_+0{_zjxo>8p`$fGhTW-}yyJAG@3X z?^*ft%fIsf|Led1=h5}0Q{?M@Jbd`@^!509Ti?Z8du`@l-?Sr9^XPQbV;mVBHIe6* zpZ@u&_82>+I&T^ZLS0lsJ zxi|`Csw~fUdTVkB91^>AJv`Q4IrQq;?K;cfR=rqX?V@?Iz`-OD@gTgX}PrDSiU0dhh zrWC*vYsTgK^r}S9^$#oleO)?bLa(zy!Lu8lLVIVobbSB5{rBB3*Nd9>9Xt2DvVL;I zZbptIj%$q8A}5yg2SvIZbTxOLqpi@;v~-H!^XHeB`n1coMl9i|n!0UWc6}3*@k0N{ zHyX{K|9lkwEAIQ9C56|kms>v1)<}DCHDb|&eYL?#ms)T69*nKl3VGzf)3PFuAtmiq zfLH8wCl?8)_m7$$uYNRf-KYI=If4ua%x>*H)HXd);@tF~TkpMM-yb{s^ZLuv@8+2= ziMe$rSL2f>BiEffIgeOl_x<|ycYghkU@yne*HxOOj2xFMI23JdZ0_E_Kie_?WSfA~ z5|wq&S{dC7S{>fC{3 z>_7AR?Af#O|NfrOyfroI^(6^cFTRV*5_=5$G}njr&T+Eu6=qhnm?Wjlu;yx%_5Ho| z*{j~w*n~Zr>^WQi^`>iy^%ZvdnqJv&YohMSmicU4bNt@c;sZ0771qrC%)7vMN`g<(|UinO~ zRIH1GJI7oxd|P;Ijg z^Hj+|XE{F+Z@9N0T8G}x-vGFI$1@5?WLKCR#X z-+fb=M0-)yuUChopZ!?={$GvF+4%aeWfIGF&;I!E^z`TR15CTG!PxkIVRi?Mj38>5EH@h7)@_O6upF+s_gd4WYJyW*h( zyKN@?dY6`et1n)zUwrqRK0(Eu(<^n?J`=fns`8#zg5leo&wmpus;XvQ7jiNvU3Oqa z-a8h)d(j_iLmGGvh$Mw}r2g6uHb$@z~Z^OJ|)i=I^}5l<;icA%VHRTW6+h58G;*-lt$%Am$pt zYTQ>j{maTzr(agy;8%KfHMC~mn(3vf&ouq7pWK8BoC61=+hnqtosr^#1+gO>2We}DSvkDs5P zmw$gBA9wC_l+4P{OxKpOs4!(cxnEcD|Kr_gmBj+rM12_7PCFgB%Fh1m@#$Mbx%aHx z*(B`Nm9g&Y*C#GaDhv4L?D};vH&TpArl@54>8E#I**q)X|MO{h`S$9hh-+K|tGl*b zK0Et%eBJM><@R+^)7F_tWygO1-O2o>EHdPAg0GCylNkr}dbZqt`d38xkO5D;ocxS8 z*Z2SJwXFX>UEa>#PGs$pb$M+c-v9qLcjk7jvSTb*V zv5MJ;LW!l>4(%~6`+t92UeOzptQP8Ou%y@c-F3~d9>eD$Q7<2yJoiscX=0g;Tzh7Z z4Z{TGhU&GYahlcBRc7zDx3%5*x^7m;?UyA^Et#`cd9D5W?c24p7N6Sr<@eQ8zBn2w z)xJ*h#rK1kpWZo|zBcbL*&Jh7cHznN`p>)nzI)bR z|0B3-&wMpCnPUq>b-M2V{qR)Z{?7`TnV#!5ElN1c5t4G^aakq1g9<~q=%R{PzmnqO z&reT_AL2bClze3w+q>JxJFd^P`KC5+rg9kj!wWOxr|bI{MV-sgDz3F}UFKw1aO(3O zF-=3aLl3U&+>c8UU<-XKbS%85Qr_U3pZ$fvzgU#TAkn6Hw7_ot-??s190F59UuN~(%+S7a zb;2q`x7h2~SMKl#{?Ks5@XI;lrpeVjic3XSI2|(Kt39C;6w7rS=&mihJVS;eIjjPBp@D_*(jK~ZF=t-$+bg7L|NJ<= zVo}?<`i(PYhW_0&C#h%M3ZG{CTnaWOs(PcVAn3xsUW{vX!T*lyXnx zU~VXMNGfQ(w`IB&ZkFm^n#+4;joPZWSxaxuij6(dpKy1(lZ#qhwy)-Qo@t+B z%94dP9)9O`d|J`JKObg15)kG#xRUhlu)zv0R+fqS{r+q_rcConem143-f!AgHzyYs z^UJfC+@IBKZa?t;{$AmU|3CkK`hUxmZ$CXzyEIe-S78n?RgHrecO9Y^(jjO$IJfvf1e)L z=U;vE_V3$4x1JWg47BGE5D-x;>>h1)$P*-caId{*Ba9rdRJh|q}vwNj>Z_>{Byt~)=__6_;-|L|0SYh_*Vn3$l zwkQVih)iI}+_z@ct7YCRpI3^0%?O)yby;Xik0Jlc{X3=vwO3!i^=n^@*Ulp!KK+rA zocU5s{~ZCr z1HTn`ckMmG(`i`Xzt>krZ=X=rez7liPTDn|wA{T_;82A{gQimP2Q9-R9mma<-H4jG zEK#PIE8Z&`?vZ3U&jCY`SHe;#I?D(Qzfr1=d+vPqP3u$KE2Cfk5*xO*d5>`V>M&D z_N>tCwX&}ko&7aKByi@(tMd;@1a;hHxve^Bt%%@U{ntUa_L!N@Sv_akRt*bv#x<9& zEft7PQ86#+?K%F~`z6n#zJ+E64(2_I%papdvN{dFNxw^*>Bw*>eC>Db4mTg!H&@T@ zzWsJtX4f4<9kvIrcm2z;WPF|fOvnKW|vrfBv2O>CZ2Bd|jCw zF)PH7!6IJo^PYyC9bZH;m|2pFO*Z$-+xt1YeW{$+fBf;=w~6+>`+qz;{P{9-)4#Xx z>n`8Ca{l~z{`q#>x9-`wH}vW*GgkTX!%vGW<4?Ubn!fGdtLyjg?63d+^R#-}z4zZ4 zAAZX_K9ToD`}ynrE2K5F87c^^MpRa^0{e+;(_c#cdc;?Vqd_G##qg{#h=KfnI+ z#C2X(Q)9PP$?l&e^!3Kpv#J+ZXDeNrRmAiA_UzfaZzoDk)ZnX>VRtc~To?PIocn!e zozkl`wz&(hq;{+~vVWZ-XuR{^jj5SEPtyL1wEMaVuHRVRAuHi?Z9iocOn0+pbAjS_FuOmOlCLrD)B&M;CZp9eJu=1fN*=IqByqH6vpaU(0Dh&nnH@ zQ}w2wPPL5B%g;+#{9*Bm?P?4LyvYjh4s*Jsbj2FpVE)8VrN4M)_J0HOyL<=gwilNh zynb!Iw8f|Px7N}tORrt4>hs`8lqlA3+tsjWa;wvlImV0R%aXZnJ8!w^n*2xGMaZZk zMXIRgt^fnWMazKY{{@oLB_-nabGIK^sJD8i&t;XMyKmpUQ`_*K|FXq1J(jGsPi1!B z`(?9!>8roD`x_3||NUED|L-?zSU?4fVP59T@X$rw*^x-p62SJa(<%}w5GV=Y?O z7yBr187`9)E1q-RDT7Pr)!7R?s~?ry{|WJ0xyVpGLoV6JX3f?WhhFZnv9S~XxNXiX z;ldZ6{s{cobms08qxGqAx9tC1et&-YrE#QweSmD-l{ek(r)r!YJ_iAeeX%-32oPMdF*TGveQ8(^dsb)vUyXQ}9te#cc?Y>nt zlXJt?G`1&K9$)M<+x)ujmhA1S>gv;i&Kygn7Afa>YM!{#e(CRL6^4R0zjx16__;-z zY5DW#=@ZY+pA=N|eGYrb$2tq)v!7$KW!q}1tId6br*B;$(e#z`wyAh(G3PRwU6u;R zBBq7Texhg=%ecn0_`X4iOUk8RFZ@GY*Z$s~DDgX4%*kK+G2iMJzv~jh-xe~gST*s+ zS%E$Nx1xg=3AjXS?>>A)#3_ZU2eU=`_qWou%#*Q&LdUN2w1)O+<)#beDhrc{_jq*~1?` zd?>J+fB)Y7|Bqhh&y$zGkud%7-?P8l+n1k@pXWdQv;P0X`uFQUKm7R6(5>eGzufR; z^+c{Op#8 zp*1(ZXBXET{kxy(oWS~%b`m}-UuRYMww>jt&9gY>G=DfU%T~i zqP>14SlnD;k;Jbn+t=!<)E%AQD8(ul>?Z7T z=dR4W`d>9WV_cp*HjjT_maKXD)~XJTA9~LeUDdCYW+!$^Svx!(a6pB_!tmA#!O7~i~-&Ne}9-Axia_sQjrh0 z%$G8>Eb?QRyCjigZEUHO%iNRc4tG~g2#T71>xzsc@3Wx&|G%gj30~YO7HYD5S=6~p zs|tKJX)VuSSXE^ed+q1>`d^pJ^^aRF5j?Oev3!nkmCwV6mxT)~=1upHpJ(}0ZQAmU zD~{jDUOIoWZ1FkU8R|=18k5f*j#zg0LX`IDTc-RDJ2Xm+75|BTVBzWPNPd%}Wh|^2 ztER4GYgQF`?VZByw`JMFW!Z}^8{CsQc6;{hTPn}@=sew|r!*}{s#wK^KXFdk>7PHJ zZRno4+~&-5VfQ;6TXN<`c1tvCptJ~|>^Mr2AyTX+9x~XH8 znA3|@O9Yy3Ol!O&ef)DxnYsIms{Xrqua=%az3bLAjU>zCL0q5HG>_anDVn-0JbMN+ zL-2H-H14#v>+^J!W>uYdnEsmgk*NHZpRK-uS6u(rL|U|M&%f_;dBN7R-Z$SL+qy;I z&~^F!RaI47GFNq59^@IiEn<-1;IxuVf9+RtpoZW4c*g;mwOfL2{r|Q9Uw(dm`S$JG z6MNeK#<_6VJpHmdzUKGi{`u3M9*x}p>&L%$-@e`d_x1k#`EmE|-O>H*bNI)fFF*hN z%d_wO{rBJN_--Y?Ph8b&T-j4FB|Fz3yL%n#? z74I*%WR1g$&oLHA9Y4K?&rq?5BOvQ-d|tlOG83KCi<0s#`22qQcYStW?)UHiiq?s) zQv7<`?L*$u7r*YlE99IKTDn&xxcF*m?DW)^vHw%|Jvp-0)cM<5#@Deds>>GKc2aFf zdl9WAy5PT$(!0{;zdma>h*WsooSi>6?oLQe#=mFZp0(@uuWx;9z$5rO=JwehgN`$n zeD4-*lC7{^ddTNYkCI_=pW;$YmRX;qMXLitgN?aG?yQfx_u{aDoWFReM4OMx=bu5@ zvCl$7mD#xp*ZIy~VUqS}gX^RfQK5=FJvX8pQ-T9aIXqls^$VZw(JDM)d3~AY(s#~D z@4ns3!ngEE`qa5?247E2Dy_%%U?#ig#U(zEkg>E4)qzrVb+?7ciANf$I! z`1fyZPoQfEi)M@8l3Tz2@_0O9klE(JP@1u}GgIE==$xw1Men%kW<7sC{TPd;YGH7x zOH_8~S3f=hLDnZNn}5A5dF55(7r3T-cY*NYE8D7nKeId<6uokj&eLZf)Sj#8pYO2X zUA+6)1ZR$3X+hq@1(9i`-p=oSC;KzhZ(YrB@@_%7)~On{?XNcqwkKxq>wkP=;(>+N zcCAxgE_jn;=ke-mQFn7YIIXP?7(O>VuGsSB+q<(LYbt*~lP$8Dr~m8(qa~Yx@+Y-c zL5TvHw`fR!Hzjo@+kf87fvAW$Z3zt`I zy~gZw%I3V`F~54&LX+IJU$g#hl3AUTdm&Hf&DoYHZN`k;+U~;lpLLy67?S#w*cP1W zd8e6Pe%`WJzv_S><3+}^j*SzTOYfGi71b<><27z>oOyJHbd=!ITW1~=&APw8vU>Av z`L7%f9xq=$?AG41ceCNs={v6_b;u|O-|+LDI4xO!dvAuY;I|i34lq{jT03J!?f=8+ zncN2LQ``G5zkQp#wtLmPWiL0(&*S_5@c8_Fe}4RpzgPQb+5I*9|9@=XrMLU8heX}4 zm+Ha0_V2gfv-AIH|9U=_{JgwimZXZ`?_zV`_W%Di{rTfoC%xR8zRW=ag1drdyVmYq zbN0?Gla1H5nBMeJfADOZZy|%g#x1ALh(xSnNILKHQp`2Gje#p}yG6yf)v1?x&qi}R zyc)FczWlK%shS)+*MxoFcE)w?wXLhHgWn`2D+L)cXk1cq@(+1m-)BGDOlimBKQ`}g zs&qfw$NA;qRsC&8S!S|6(|fdf>niD}<2f6Yuc%3EXf~^^-u!!YcJ{9U8jNF+%G1^Co5u35?}8M5_?>QaXn6F1)X{r%w7)tQn4*2@!A zUT85J7qbUDBz*V~HS?&%al`N1+*h_HXT5gKW;R{!juS*Ke}&-`OcFoqhP><8b*U(@Jv> z8>~1ugFX7%0TF>aB6mVfkIOi581`NA%3i+p@yUYSXTQqsnVGk2(uuULCoLtR>`ja- zc7+9Ajp8=$-f3NKTi4+tad+XGT$$tX|38Hr^7f`Q6PO zGn;o^jXKM6pe^xugJSF5yzT2=oA|Qrn{qYbbEV?W4xLo9mzoPU#=P1S^Qar|5l_Wbj*_yo>v=$v3_q20THh#H^0R!~~x)JSM;Y|HBHK^{-dGI{tWK za?uW*)Tzai#jlD(KIuKuW54m;Q=egnl(FK*vitkJ^x3Mdt)J$ey1!rCMZQfXN+8gs zCU$1#+_MLgpER_bPzu}9cyiae^73-OyKQ-&t;+in#6R;MPGDHu$*kACOyF#e$I91z zzg+H~nYDGorzo%bt+ON5T=s;`^)WeO@=ZrkfkEK?EdyhdBO4Os5?+_Alw6Ug<~BVw zjzKfXV!r=f?cM7{E$8Tc-V@UB`SIh=b>>&Hrk|b_bi;Rd_)1Ux+Bp5TTJ^oBE=gO@ z_*&*=Bg5C2ymDGlO;wHN(mj>M%1WPY_SgOnTf(?N(#Fo_U4Gv4%6q+sA3nQX{@pw` z*38Pu=lNxB=AZ>Wi8px5%YUD)`Bnda|3C5LTlB)Bi&+Y$YVnCUWi-{wxv?y}5aPs9 zV0!J_g{X4ji4&5tojT4e{9nmso3}#uG}E%RTd&pSt+^=5o08@e%lF9R-ourQKYtYM zd(e7i_RIF~+c`F_IyS+{V}Vu}uVL|aS;l-43Q^MS~WGXnM7YT5)k z7Ou>#^;U0qD=j!tF6PZ8nKjp{=IQ^H>gHRpZCctp#f#b9HZ2$K@GiV2^+_OE=k+4K zBRAH6-kVj@t`!?R;mEDFAMCF$aP-}&K6+ubQ1+QO9foacw>G=onbXN@?O)$KW0tnC zP~iS49n6fSj2a78M`gdQnKs8s*J#PZ8k?y>od!o&T*^)2(R=V@m8IITgtX$wvj+t_ zGz6||We8YJtT%5JVKloVERcVYgW>fqz13&es@_|wxVt)cZT{LiR+aruj%Jw+ue3Lr zw1s5feS0iX;>6A=PFE^xe2(l@`|;)F<;4GO9q;n^4*yVV|MT$h>GJ#kS!*lo_U*B; z``-6)tHVWxWsQ529w>6P%J776p7oVH>vLs;iMzSM+)n~Q3H=NX4N;&K6$pP%RdeCyleIw?NibB8BP^l*-d3cfm#b5_~)r1*)A zH+|;Fh)z#^nI1G$R^OCo{c(iO)%D*q4zQlU*_b>lxGha`eVb7et-?OXDPj{9X z3I@Io;%%RCIW_jl^5a{>n1wq<7JPQ*oWL;q<@DpvOJmC(Yg}SFE@3>e(Tv@=JIz5Z zsVDQso`kF&IdLi8O%xpYo8-$TZ(DV3 zSKvN=AKb!JB&lcX)@|#Sie$bmvh&V1@C)4;z3g|yp}WdKEf?7NWrEu6 zayNcundojRIB`z#TA^bmJ;_s@GM_(rmi2DR<2}=kPw%ceFO{Wq?#1uS>+ApbJc(Yn zX}7detH_?x&3Za(yChtTOyu~F-d0euIye3E-+5Q!@SsAFE{l(&XeLhV<%IQ5Gk%VQ?--~`aB#N`m)dZ zJj>w-N{ySXEX-DJ(yqoeF+{o{(|FE~m1m|Kub6q$;+em`fB*UZ`!yB!Us?6!&QdFO zFA540H_Y9%?)5F>`eTJQvgeg%88cWJHZkm)wKMDe?v`s&964P_CeAtF>vF*M=mnR{ z6I_?9lxi}yj}Ob8m8`^Cq~Ky4HEU@ClNZOqnU>qOv89)<)=HL&oqodQN@@{X!wZ#5 zbLLpIPAG{qYAq z*{-hw)gfD#N~Uf!|h!fNY+@5`DD<6dPwcqhfQa<+v+ zN&B)d)~5uLJ8bycFMrOA)U$kkcSrO!NrC*b-#g~)ARQ`bR6GW#*=DXvLI@j!ZMi^!WO#{Y@Yj^kh{ThXRpp`)7M`$6bzT1u6e6m zZS9lX!;`^w@QhdH>!@X!ReN_VWHy?)EOqVi&qpV$df`7`zRggfT|3t~d+quD{_We_ z6L)m`Byb5VOPq22+WXD(HvZolZt8k{Xa3uX8%)h*83g^8U$!~FZkO*RGuCruPc@eY z$T=+$Ve_9U!1sIatkwe$_+IombX+`n*O@2%!p|Db*D}E`EGyaStoA;UvER2>^|C{a z-qW8?XIN?khR5fqo!M})^v;0|!5Sf5a=SL{~jt9i;+p1eWo%C0`=J$rY~T$UNPszbot`329M z%U09F&MG{YVK#PsCSloFId#**?5lUSulT4Jrs(8$XDe&x86Tnb}ii|{H=Ta4clWkTtxq@U;8T7=Tp|Nt=Y{#e*J%R^@LZr z+eBCee|C;PMH}k|=?k+2pn5`?bQmRtTj+|KQ z#HGk`t9P;1gj}O}Q!dG!U=+P`=c>5^Kf??ShK`8rvk#vaeh>RDuyo3`u<4tGm@Ln% zt*F;KGqKLCbf(7B13lYrCT{U~9i!k-{q;qXLSK#RkDnhaUKy_LIMJi@_x_9&7sC8drDI#f-JJvGVltH-&2_jZTJ#ByjZttl^8X0x}g zu{%5c^3$3dZ?9i+I&=A*!$VIOW#viP+KQ9BShqSfx<-kzUY46F?Np+m{)A7a?8VP@ zeJKxKM@_%QvnO-!{{P?J|GT~arti-`pZ*-aywf!HnbIedB>~(|W^_#z=ww7 zAT}}V_D7riebVeD3d`=@%>SBhD`q*vi{s(jySe8o^+E;AmtkN zipOLOp2-Mle__2<-ORx7x02&Z)XP{kLFNT&MK))BTh|x-^qcu|YH2we&&tQQ7&~3E z=bPv({dSyR*39Oj*u%YsnrG)aTim%*GLKV`O=a&q`5XNvw;LuJ9^2Y^nr)pTLy5u{ z3FQ;lRCZdvVGKj(4vXty=53`WAnt zG3$ft#>cMidQ`T5zwG_JjE65>o081(^VQ+M>-YVAX8(7&qh9CD3i8qn;5fY6ZtH2K?~fnU3VGjJ6 z&QdlIR0!f}3|)O?LXt46d+WM16_*KNGZty?IkjH!ZVbb(Kt+azMrC&4(8=$-9v@Je zWWn<6RaTH;@QZ7HE~fceNw+M!AFR+?b?b~p-lV)x+juYCWo&)R`q<8$*}CPLRI!M8 zW?RRlD2JXoZA*No9^^a|+L^TA#>vHJZWji4T|9HEg|l(x`uXdAzipmJ-4 zXXqNy!kcPQiY=wFH?C%7pL@o@(7@p7;uvz)%uh#(@yb>c!vN=EgKXEWTW`;ug>C$ToSf6Y1f=^`=5ESS-P6X<^^1omq_!=n|naqX~*8P*_o?+PkG6BXa4M} zS86)_)TuH0s=)T>`uP(VL~}H1Fyu^_R`+h-x$nPDzdmtCj$tNP{ja0?`z4iDt!er9 z_Bj7(oB8wPmL0yAvj2YX)@$$U{=7T;_w4$8KfliZ|L3_t(4)*Vm%sDhudn+NbhIte zYTl}jS&YoaLFKi)hYxtT9zD~rlle@i#!=ny<-wOEIyIFZWr#~{6iQAdk z!(HEeWKvqxBay`aQsiEdMBo3%8|4GFTR9tu^}2Vj?&( z;o6m$C~=0Jjf+=*eQnV3Gi=8U!DV$DMFkn&TBmbNOzz>!5Uq0}pr*4->)3vSA&Ru%cJs{K%)_^$Zd_c1-@6xBax?+0(4+ zKem?fPFGV(3Ci@{x{^)F*f8D6ydcz=m37Uk<&Tf{UOhM=Ywgj!;zy@%-J|yRn`W|? zrh?KgkJi<0ZB7&N6kT3D`DPa&xV`RopnI?gbFP)*vc#;lSG3+0%$z?xRdcE0qn|Z# z`o*EO6*be?*k0sm)@=_ibsS`vX?aw!C57v%cHGH;1~JX9YA@&Pvr(!F zOleCh7$NPQY_ECTB}!WPi{?NVt@4gd;Yupd{-6z%SQ8EBW3G;{9E22 zU>Py_aNhm(0lWYH`1C31)-gkSTRF*`v>))$dnx&)FTecY6lY*E`D<7W@lu z@~c={BIsB7ykUJ0yI|rnkB?s`Fcf~iKCf~6vUgj}cXW10Ui|uX`vKD#3-!xgLb4e) zNH3Szyn5QEhFL4bN=xTlfAp?W#>tOmR>g{~-@@4KXKjKTvK+zVuQ`6jpENe zmsw<8S5@=Jzy7~}{rBzrXFcb3=IA+HvnNK3b$|8uy4crN`tRS_t=Ja-_t)#7^Z9x2 z`FGEjum7WGRP*`j_4y_8lz||4=|E_;uxO=5QV*ZShFaKYA##h;w?e^U) z{B81#xayd+h6~EcKX-8?`?zel|2{s>ucM&4TB6;8<&5E$Rq2cB7+lg+1O(d|o?UR& z?_{)LNlIht_VLMyU0J!8iTTFI5~+5_#Nz#{ul?M#?Vngj%%88zuPep&3s|gJRr-TP z!D>hTy3E4l0*7}EVYiQLx;G#+8}#iiJd-`~^2;DE;fbYd zm6{wZpMO5R@ye=KCkidkYd+0ez0dZIaieLLOw-;2(|*R#udE^g^`=Bl~z z_V}DLK7tGZcaKfbY>8f;*5z||?v?WY63NGHzx6J=e{qK79G7hezE~XFdd_=u<=W3$ z|L_0*^S#}$=gAD;C0(z!+~522$JNYv^*{c7{=Hqkt&o3(|M~gRvB7-*zvb8O+3VTx zc6xmMjOm}JFX#UCEIV|n;HHHt*GwzkQP0W-n-Lz9w{u$GoKI@KE!>K%MMt-{gey-#@N~Z_?XP zs&eP3Qq*TL!K9wMg4d$0PDcssh}G9vb;@>fU){0LvWJzTfuRav!8`q?a;$kjyY`Y> zW6w65)B9YvUArVud~Q{pouki>hliutW(M8py?i=#;p3XA6`5Qs^AEm`dcfu3aHr30 zqpl_U6^H8Ujl0werdS<%@J?x4Smd-zv%X||rAO|cX0b|bQFiX*9k2PBEY_{vm2vLp zCcOn>%+t5d+GSz#vT@InytXg5{(RsoQGK_cmEo>z-S*jGU^6BZr z-aU7=tjmtQF2zw@;iviZ?cLd_k^a*+O}n;bu3q;QEv1ReuSw};YnxvAxoOVqcEjUK z@@)J3wl1vfF?3T@5?(k@`qhF5Z5sRTGTrg1vR^oJmFd(umi^Aa>=$3h556{$8@A&!c_V1-@V%zKXop~LUfA7u7npko6UYBQ=?*4qXDR$+o zujgW~x0O5gNqp&v=VLJxaPGOdWa_eQR#r{Tp4yk1I(GA0zAv*@;$oDQa_UfI+7WZI z_HtguTDmdh*_Qq>G zzjq|fyw3N{;j_rD!xLR*SzAn;W%^YhxaZ@B)bJ1e~oUR<&!YVWrL%o}bWUT1PK zH1=#r$nUGO&xjmgoU@)~qeSxkRp+L28!I0OQZ$^pRmrvaM@iKG`uhLv{~zYp*Y4ZX zP<7^T`2Byg%69+1{r~TOL5;f4U)MWtxSLn?`Pnpv|9_s>|Gk@EU%X?k-2OiSw^PlZ z1jqc=y8rfAscYYz+I`_S7*=|jJAE?_y7Fdy(;SgG8X`NF1;;omY@73B-@@#(nI@C3 zNg0)U9xeTzp7y3$?9QLUkku|OOjhU8ZBH323DOeOea_8bYWg)R?dfH8BYuIn`mM?= zPCPw6eX+APMmpcw(7FHJq`kM853J2#tI3^LvLLwr$=3U#1+RY^CUk6BC+;}?*0R4> zUKYuo-#Wi6NP3a*i-&1Br}x}`o43sNv;X?({nw)SHcCHz6*c3ReY-@H&{BceUS+{= zEUYe{-zkTEkO|%&)p=sqx~--uMPH*<=dF4=@8DHe-zOcRQk-2YC$C@n#i#r0OTjyq zN0vQXvGmo+X99_3{h3jw`YaE}~Zs$}ryT>6mq5&y;sJ#mr4o8Y<&XtqN`J zaD8L6zEOCG-TdjFFHhz?_jy+rS3)@7_upYG3A@gny{rGcwe0Zo<Vy zHPSi7P`tRCQxW2O~V+1=Vb`}EJ4WX1-81F`|T9ZznvYq)aE z!LOymceUg(i94bVtD_3uduF-JD0=tl=dB5Msx7Aky$ZEg;(J_Bzj<|bbarI)+~;47 z!dK>QO%;rMH7Tgel6UvZ6047=Kd$C~oOkE%GxPnPoq`X)z5DR+@b1dkK7HH6F2#Lk zY>poE;cJ%_x^w5RJ{Ef8>-Q3`KQ!bp~Yd}($J$bBz8pWZs%U?vHynI z?PDj-&gHQ3K6B>C?(OT=zV`ZJ_FQL4R*J^-=gCvs8dFSH9-8A7%~=%d-M4ZLuk5d* z>Dueouajgb2rz$>JAaOG@d?XZ#p$=ob|22vRD4!w&e-N3Kd;>SuK(}*f8Lp2{rLaq z|DW-XEFzmfZ`xPAeg3g8KfZkU@#VwE!@s}3zdt=FXh-hr&nC+U4R-ZZPAXvHo z{`cMm@B2fgT4S=`Y8|k1J$_(?o6EB{p1+O`E-y-COAhAKCl|rD zY488lvnmLzr@$LRpermf4B*v;SSPVri@ zbw>7^Z>7duv%6NZmn$covs`rl4S((Rcd{YH>$hf|^0>I6x%`umQ+BSs?_RCkMXS2* z99;QKZj~4Rj`#MdaeC9e+vQizlO?b_BbHJPyE|vD3dyWp`qh?&VU||w zmMLnYGxWW6)5MZqWu3f!#P@6Vn>nwR1+WJdl)l}(^mLGYecjCI!I~)n|`19rO z?QQ=Yzh75v@#c7JQ1;qiPfE7Se}7zH8UCg0`?q^%zCAv0W>K#IQ=&(t%hE`>&wu~U z-pRF?(IGq6p6^o5jHHe?_gd%mau~2aXgIK}ag(kov$NwPRyaIq42)q>k;t07 z?2ucS*Ez{fLj|w5yP2&Tq<2kUy6VK9xOWfq5<^Pes;YO~Oxe`Iz|m$axg-5XC&$qP zS7e_uFbL$vUe9~Z_IbkO{ExCtcNQ1!UZ`(y(SoP;cJ5k9#bTC25<=g(bs86}T-YJl znC)7;a&5My&q{Oe&{?wzmt325rRhcAf>*X*!$XSqP5YMfILj^X$!qM;Jk>vcnyY(X-=Bh*a`X1}^&g}8UVi>=f9L-G z-}3*K|9@=%e|mjEZ2LoT{`qxPbw59ST>bt1y}im49)J9C_;Iq3<(|sTxA*@2*v{U^ zIaf(&;W{Q?Ro;!)yrx`~l@(B$Fl(ms`;thXn4>Zaw>)FWn^67qdL_rga_-&od&Dn$ zbgX;TW_aORy}E$`Q__-aQa`vX`U($a$_F_dDT)+*I(`4IZ+~Z-+t>bgjy}zQ{PPz1 z-y2?U(W#u3?Dc#0;lr1?jhUSzO;!CYcUUjj8kQtw5OLYt$HuR4?)1{w-Miy>;;u8Z zuG_lH>PyS*wYwjsyjWp7p-kb}i_@8WGR{8l?nRxPa<_I~y70+~_wKwko~*#Qv*Aq{ z`QL1|E%_+ztyZWWTj&u)T_yEfVSy zHa*O=O@e85WiV$Ww;7M%+=dyPSLYfUv#I@ixct1P0Gpnc;V}n;r-^#6y-S5SWq!Xk zXbHKN#wBGSWTMKUxM0_oI@z0B7e*~jGfXjNE=yVW`Vm_K@AkybGLPvdx~Z9MVONhx zcvdS3D=IUy8FcpWDReD05%n}|zS?`B`(A85+qs^&SYL){;q_-N?%eq0urTn;42hPG z6<*woL42C62@l@QxU?YS-PY+Inkfv~zJAj`XLk!vv}}tL+LfKM@8krpT5)=X6!1I0HxHOpb#^UYZLtg$1{6 zTNwC;OYHrcGzKmiGl}glrp4c~&oFK1TlXgJ?CHvN*|F1>u048&<3rI-p2v(0decuo ze*F0C+qE4>W+;C&Sa$B${=4r_)kHks+C7D*f#FCG$H!adFTQ?Nle@ZXRYD)%i)FW% z3RCu#@w`o*4dmFdi|oBRE@ZLxsh0bTJ8&a%vf zJsUqW#7VR6o%h<+VgLP7R=@n+yAOX|n{$O%du{*CnUaB54~mp*X3ZDf-f`r>_wU~o zkCttzjI+15^Db5pY0llj=D1Wb(L;ubwP_jK0iOx&9NG#8EO?GzUb)q*Pnng8pFylR zZrazEFEh8#z9wW>x5M72{rU3biN|~{=OwTh?A=?tc$IzKPs3vs<;B%E-oC$g=g*hB z->3il@#W8l+xP#am2b{oYnmIsujc2Urwh-1JsY$C?@|4C`QM6HuFKWTm2v;>)^I6t z=?>2a$vvSh9cwbv`KB#ntvBi2UA;KhTbsW8`S8#6TkGq3Q|_k+S{fEKIx%?Ny(287{FU+Q z>i$j+{`SMS&A&Hx1|=<-{WsBk-x8U`mC1dL3sz6-UwXsT`?cw$CFL1sFRtbOqx$?! zOYYSPlV;CUR-P6#ZRxD8#j8E$eQOjHJR@Z(VP(8~wdCxZT5M^t^Dcz{m}V*H5Zbt6 zTj}IQMSO|^hDNN+3R!CngFPlLp54@8;8?$|J@Lb39|ld9*1o_Fm(zC_UZ0lJzXP$Xy=Nr5~y5!%BtF_P8@4S4&R^HG=_~NcJ(__y~{P^sffRH@j z?)UW-Re4*Zna(N{96Hdb$z&<`*Yr#`)AgujfA!?`MVd`ixnzEIIBeS*8}#@y8b%mu3Dx z^_EL<2UpYkWe*K{pKhA<{qxM<9>xm$za?6&-7b7!rb5dFX(rpthDLF|8~QvLPjWi@ zobh0^6cS{dy!+bLD^bZjc^X-ldOQyv;PJUSK}Ll4g88X;%NFcd*8aP6_xt*Ns?Mb| zda|;&a#x&L^Y@I@YhBZwRf*g9)*Nr>&`L_Xe*@D!PN{QUg4Yv)dXx=HWd zykeK!`p@_OoV`1H_VMG(=QSGk?5_Wz|NrODk1zlIdskHV@9FmYPegXvpO(6}zc!q) z{>Q_Y@&Et){aE63;fO#{%&d^RPa0ChlHbgj!u!X;W|>aXvwLUVWtt=#PHwf;&6rmt zHZiJ8amkcJUk{X(FJqAIXI%IA``gul=EXdU4NYc-SDD&8`WTj-@ArRwoL}>pgtI}A zc=?M>s|#gh*IYAwE~B*eb(WjVhtl^7O1Zag&))U$?T#&LAKq+DI%2=yBjwEov+ug6 zTW1Q22qr&xPfAj9Vbq_dg*zkLTF}oMzGQ)Re%7$BlT6U~1 zsY-Zp?MwH?kck>g4~jY+s*K$=tv-&cIO2KetgB1QQgd^)^puQxGzC_7%@ovTFw9T1 zsGaZY{BG5Re~sN7#|5f>d#9{5edcmwzsUQy@i%8B_B{xlr8%RqD`CMeuE*RVUfFkh zo?UC@Q7p<>^N!=p8HT9R+Y&Q*Cpa$ozOL$jJM+`TgnK-*vw9~wd1bHstG_2M+L@z4 z`s&(svX6p-T+Yi}5^0lrd@nRhO{wuW_ks&wpT!=Ra9VIc=CHP@!7+&mQ(~4Jo4~k0 zeU|XVnP*O09^b;bz)Wo2&gwo!hF>z5@?M>3`IzFDh9U-Q1d=Sq~1OmfehsI|$bL|@I2IB>1><`zi? z4>_k3&X*#OJPlP8PWE~1@Q$m`Ve85rrk8I;omkob<;=k=XW!hNy<3yt>?*Yq%!-zV+7XW}(O98O~3(uCgvjU@#Cjt~pxt)pOR3vZ5z* zgxk*+SK9J;z2=nFfUpzvP)OVzHBJ6%Gv5#>x*a&>($`}f|NhE)v#zTpOJY4!zZQ)No|4=k|kpJ-59&^7h}eXW#PJ zmvQA<#``@Fb#cA5Ddyku{~!1Nuc-h3*#Exp4%Qc2&xgz3V?U(Wy53Ia)~0Jwny#C5 zf0oI7-f_S4#Pj3F@8$Ct8tJW{uqcn|6@T3K*R2Qs8W%Qaw;plv^I6|2a%p!W>A1^fL>D2tVt+KY}`#1k~*;bP$4lXa^yhJB1b8<<# z_5HE{(~~z0Yj;jod=!Byw_5)Ryf3oEHTIJGY4B^LAbuSqSs9_71e`MY}$7Oc8#F*EO3cVAWc7J-{C z2VW4d>MUVS#RfO-gh($dvW!} zl03f^tIkh9T@!ij>(8^ZcSpV6qi1-G`Mrfu;tc)Q20XGa#V;p3=n;`A=F0o@?BBJ! z`)%g8Y4$G-^uL-Qm9%PyR4Mz)@{L}(XU{TRka-s5QX3gAD4EOE7-t!#q_5d}_^ivm zcguc!e7rl#)$#0$<^J){cFwxF<$h;oWa<1#O1f*8$yh8uGB1e9MV;mAxz#VePK#3y z=bL**l*6EK#wUT~HQ6tW6eDAC%Ws?BBx0hP2RD3_mo{zgnW4#1(_$d3mm<&d+>wBf7M-!N8e|%`n zSi6-)MKJprN5S`J-#$Jq^lHrGQ!0v@Uf2`-K<4I@miBpvuIZ=#70P{9a`nopRhIkW z92p#9n}h>J!o$=TCHh>Q!({EWe!A>|1HX!=I2~DS^gVP*HrpzxZTC5RzZUJ>QE}R1 zL*px#BQkRWwm+D=N(fBwAX{@Ze4_qPX5OHA(g#^8O& z&6lI4wr}Yw1`Q=&@lx~R5+COpsUwo%O#<(8Yl3gTFA9nGF*zTR3h3oaV8coX8 zy|5}TXzIEJ|0Nc$6k<}y4`2O`JGj8zK&9#2Y3D}`4bL9f9VsccQ1}!S{kmg{L!*$@ znzgHugD@ho&+g-lf%--@T7>qL@v$vnf0fI$Tg)Jo(b+r$+pT9a!{ytrBS4$A!-+;lejwe97pGve1? zVY{*RvCG4P9g@LSmecEunQqxBKVG~1qUGc8gR1Md9&K3ax!)=8(VPQ@ZcIEZ3}Lfn z&WpRP-72_r>#M~2>z~uAxgxT?p1is+t8Idkp_Rddvw2o;)C}jA9ezGtnNc8tt8sz6 z<*kWqw^Wu+X_E-bSz0FQ7+$RO=^0bOVVY?Ms-PHLe3MjJmHA*{n zO%#ZY5}jJK^jz0+!#06sb2I~Dn%4D73aj=hR^9Rq<~ev~gZ+xe=*c3<%U>LknB8S& z&=3?AK5I{%_}$w^%X%8)eQn?QHJ<6I{G8abde`CgD`j04&YWe%@by(hEK};nt?$G0 z+7#DJH#Kf-+T!NYaj?)?b+-4Cl%TuTzoU3zko-`&Znxs!U(@6NznZ+%#1&h>FgP-H3Qm#IFgv(&0sPDzmc&{`>pB9@|Z=I|T84EBol@YTI#{eE2k>71aT z)e91oZsnXv4i5i%AZ5WL55K)8FFi!r_SiLOD2X(coRirYxtT9iq2TZV&h>l^0uEP0 zwwJwF-SN-#P`NQb(=KafhRLf-U+z~_xUqWjwW}9zZCZ2J@$TNDEVdJ8|NJ{Fv|~4ZuDSGHII|=9shnq$PX!(2boO|Wu=H!Lkg~9ZP`S6N z@)y>cieiB;m6{B_Gdm3o7CJfEou7WRWcB-9rx~Rdur{4z%32b;H@ zAx=F!2?_gR{GLBvs&9Q{9^2&*+cuw;MQc5}EA`jzSZLL>nQPwbrR!W>I2K$}i!RT& z?EUSay3)2~OSe3KY~!};jOc+g7dv_HxKur!z>%y_s#~HoZkYC~2$q*kA=YW-e86U}2aNdxeq;`8%mnP#cF}K9K zjT((7&z#d=KD(ql?F>g2KR>^HZIy$};E18SMdGg<7$=aii!<; z-+p4;a$#Di)7`&UetKkU&VI>pX3g@=ih?|eH?*`ImcCMZAt-yNhhwIJkg(N`rreS> z`+sezpT}ZkymD36X0dg1Gz@wQ3o?&C{`szq{n?9s_A+<=noQrcW~Vo2Jo}Hd6=D;9 zZQgQrm&BR%=Eqk_Y`?JUI+vP(vcpS%nFiy=>i(M?iUt-+25t9BUDO(SPZdp7sYprd zdX^hGtyDKr;>yFHPb=cCe>Iw2)e&(js*9oUf%c3eYqv|rTeF|Z*>|)`I5695nPsXY zdw%22E}<%}g<*yz!um^H-u;~ZZ`FA>7dI}MIVMX@&KK9OOg@*$)7llEED%y^-(%k1 zeC^V5-wZ9K#()1~{+^tya(n8t&bbfotzMP(?498rP7j926KfQ+Gd8Jx3((Ek^mBjF zLD`)T`?$F@of0?BzI!IEG(0pcO{}5a+`VNt@2YYOW^wnkjcGdbY#JZ9R?Jv=ue$C1 z@7e`<%bE?WmPD*wsVcF_egAu5z7!W8b$Q__XA%>=B}7G^PT~Jt5+^Mo{IIIuxnuo; z@2gg6ZEU^a-q9ejt0yX3MNTQ{&zX)H8s{%BS=AHbRXE}GwZw!Ujjk?x%FDOg3b0*J zTw1a-MsGUz0!AxA-U64N>2oaFw`X^_NF0!G-hCzd?OU}Z!Ca}|2kvId#&TNBkd|WF zzR=QN-tJ$8<@dvf4=M(&xLj-N|2>=i{@K&Sj{e)d9eu}TEo=@Qjk=WDiGCRk5_kJ&P;|uo~8P3ctos}L^ ztf2JX%E3v#Xu5#FslNefX>K=|<*po)STH3>^UUjA{~CF?s^!kT7X8Bud! z)eU}6*0*P#{Q2jf&HD9vi}h6PZRb_3{~WXIXKzoL<^27BKArwPzus)gw^G$B@&9kG zzh}Gm=f~amjc=7jFMryRr>4+R|5?7ix^CyJ##cYS&j0)A?CtMgmo{D6|C9fJU7d}s z-8?zI;Ox?lzmNRux8|-DsQY4rS>s9tWbJqSpZ$5wi`Lmt>yySO-swY#H@BIFXE&BempAFwzuFrkL=`iKc?!zIs zUIgYUC}}7e95dZ;{>%IG3`&J9r=(TbSY71Jj+ihryb+!;!=$BjNqJ}K#kXa@-yN5~ zSO2f|{#TCar(Ic3?GY$GCwcLt?ejhHt`+%va^^=TY)NWxabStB{bslH)8YR4&nD%$ zWbB)=vV3QZUVCNd#Jl^Z7~NTH;pLTi$}<1!w{IUc6$}{yyz;VD50u7U-+E57`PN5+ zenTI|3(awtH)P76Ich6$Xr0#16vMq+*XG8$+;KR0;Ec}&$M(0}iae@ojX%p&o<8E{ zcf@c-0ZS=!dw}=sOWSfk$Hcs?v0I(xw|jNxyWdf}t5bu#mR~;k?ScJy%i`b9=lT08{=a)}|NB#ghKqppa?PhcKQ^zoOKyt0{rTyqXLm1)zn{17_uuQc zM0pPQ{$BevTKd=Z{~v$f|NAdEu1|mZ^U~dQe?Bh$Ze?qr^YrX&^W{Nu{q1YxR-53Q=so6=Yca$!CF#ghd(@ho0XZGG&~P?WaaiHhy;kQO6@7kg(J9tk$MhstO4X;* zDdpLj0}BMI{pAzCCK;rwxah^q*gTVQ(_+aAt;n-;gayuiUgIoLu=n1U((K0%OKi@0 z?==;a);ht!P^>4c?lxyxjg6jQxVWp80ONtj4+R`W?m_xJbP z%cM#6O1SJ+mS9@KIA^UPHg-!qZ)5-nq-R!cry7{F%+u!#{8B|6TF-2t zwg33zhYt;=cQ(oxHcpn_7gziJs{Fm*F$zsBE(=XI?O-m8jPP8UB9y&yN!;$p9i6eE zj=d76O$;j^&7FC<+Psu`|DC)ub0!>l~+s@uieRJV@i^}&MtM}gWt*|(kFDKvD z^?2!8gXvp7cLy<=8~bYN9&c`%?R@uF!Jkj3_1Dj?`F+7l~L1FEzT&i z%v#3dus!$aUY+k7E-L?7_?z#esqyh;EDSNZ9bfKkU;FhIL(VLz}UEHC-JuPo-q1VJ}nfW#E?=m{iIpf2@^C0DS zHhT$wAd`m-^X9$A91k4D&ss8XoVhr%$0*3_7kg5RE2qzlJHICgwlAC66E?FlSNk*H z456#3k#$!L&!%b_oZTaH?{41o!0nl{&ak{cb+sT!hR;}V@9#IU?{80!|MzBx%)Wnb z-#3X0)TM{!p4tEVtoi?sv*oWmFwl?RV_*CA=I)kX4=?ZB`t7f|{lA~{|KE-O^Cms{ zvd_-#AHTibc=hAa^!d+=e*C)qKIH55{r|4s|MzYC(kZvs9_=!H{^!%DA{)6*ix25Z z7nt8qwq((IwJ5gg{i-HDzH4sfWb2XoxZ-B%R8fWi&%w zt(g7LuD8W^r(Ci*Kck{YF>ir^PLfX$$3*3CyH4A&-4I`!rF3XYeERI)o1|*urroo? zxUxL*dIr1TnMx-?L)nh?%!UU=nx{Cr=vmjyo*A5Gd^_szhoXB+P4gTl%zN%saqE@o z(krhDgvPqH za@LBn>kpU@ZZl<eS})WLvw=9<#HSo3lIQd_&t$ z8NAo>yKziHNs;$LbrVa{n%6tN`|V>m@cDD`EH0(1yMzvZEby5*tx|AuMqALCf@EK- z^TjF-H)Maf^T_0ts`%Y`r*WX=3BzTRxzCJTyfqi|hUPkVoUlBaIgv@t`QNPPoJXdd z`ug+m+ikrc`#P3q-rJTNyZwgr_qgK{22b^NtqZ-v`(b;0{r8`b18q&B-J~QK_T0&9 zKfWpV_O5kX!$R*KTQ==l>Ey(;1q)m{H4NV$v{hSG{bsY^kH?u|p;PMuCr(^;CRi`a zIcoDgjt_S)3mfl=$&p_V8Usm91H`6`iB zz_gw5$oC%tT!|iwdh*n`)H*6}Z+l$hquFWMGi?IT%a;|iW^hb+zUf}p75&pTlW*TP zU%ow`Z~oqTV^yX@<@xhoT@(1UEmyIY`SY!?^8bIjzhD2Vrj&ng@BX;o@Av=H-EFAI zyrVZY@|Z+?{f>XnZp&}iE1p^R@2YtH$LIA-QGz@{VY2!2<>dI{Hj6?6cnI0eg%S)Zlz4_jFulD?!cPG#CFm@WL&#?4SG`#EO!IrgvD|nhJ4xy7ap^3dz~g6k&}~Kcm3=3{lBf2{;b+-l@Q0(U_5C-mxrch z*eTy*M&jR=Jj;02U@15&=v?Z#+%H?NY5n?G!z~cNQ+BXbfbaCmCp)iRtTsPBr#NQU zdiObZU&l*6>)XAF!NjiE`0*5rMDx2*<^_STYdhX5@!ep&J1aP_Sa#FbZ$-N`K7Xy^ zUU2lCdaQQft*X^)pH`-^D9C7C&)%QA)_m`sYa!3>R^4P5t+BD~;80xEs($iH?g|Ts z1f_``Gm~C^Sal-e_NrCB#{vY+U)$cgtU3MiOOb7rlO#159her}Q$6;!Ii;Y z6;`s>qP=At4LM`B-|J}5;4PM2H1jM^OnfWX?n`3dY!ie~)H9K>+ z|N8J@f~B;Rbk)w7cZ-B#u9u$tEU|dovv(~of4{HW^>?nlX7H!#4*@la*QdY!WMFtE zE$o!fjMzONN^IeDIZZfOGXzE(R_+s;hjm^P_aLTJW}TLr>? znX{JV?%q2m++FMQo$Vk0ynI>|E8n-=?AW&4(;p37ve#bTCCk@t?*BgOevsGJYyR`~ zjLLqk+x7az_p18)6S56b4^0!RUun05U%^9Do^Oekb(jY4s|1czu8U69pPy}9J)`KMe`HeYJS~HEF`_yi^c10&5e|>yL&cl#B zKAa7I&aw9`J|c4_C;RYruZEdFUlw?1uAH@7p0EA+=VNC++r+PbeR$#1%yqRZv~~#J zJpA#?mkJ+`m>)Lxua!!#eLg4f_xie@SLgrxrKN^Wou{%twsx;=JPcisNTX2W)yA^en#S z=f}(6_y2tR>)ToJ_`h%7n)vSh{`zaxzb`uP`uR_r{~vxeH#A0{>MLc z<1?+yzE!*Lb_AW9JS(%t;=5dY)%D+A_4n7TYW<@u_*U2~bXo1$*{8MdOWY}35j}m% z_b<;Z{ywi%D@dMSw9x0X(N3OC6N8*mOr;m`?T9Wo)7GVIa7kopjMn;BLF!ZP-tTr0 zxO4OR+Wy(cKQ7wSe&lFUS&!nzvs{G--~BgJWeH9w`d&HDThq)Xs@*zR!%8kR_WIVX z-~QcAfA{aKxxRmX|JtuczAY2o-go{mbJI-pT$r2df4q4oLxZ%?o4TtzGG5wfJMQ1!mV4>- zHrx1h965a#7yDc;v5;}87J2cm?@ZFH;(5<2b2okO3p{&QwKbXN+?87e&XQ5t{rhrP zpFI<#G|}!@fyxt^GmBEvRw*BAVi1TcX6urBvO~se*RQ|poEel9)ETsX^DUh+`uo3)nl^TJuk!aBf6#ooK*6QIh0pSy z*+q}l#iB`REa5YkUwS>E!%_dHG*<^5^HD9~YN*dsV{}p&9*#F}w~9D=##u?siU)Sd`GeUGLkLoo~*U*GR3B z5;jxknaeptZrWuFbN0~AwR2WS_Pl0nu-typg=N8tS3yOKSR5+#R=m8Httq#c``5iQ z-{zFu-YC%ctyXrrqLKycS6K#*6Pk*T0#udU<*XjfvzU@|oLOO|meG`;ppy$Zrqo&p zyh`I{E%LPStvuD*J$KsA<@4>f%~^gV$x8g+o4=1w|9tkY_VvumFJzllSyWzK(lXA! z{p-t@FW=7o{(7zc&qrp4G&MiglgA}et!n;1ZqMKUpHX4?lo_sD^KYlUI{osX8@o)O z1K0a|d;k7>{Ivdm{SW(p>HnYB|N1GO-*0(8@A})apRZQ0_gnt-;YWenYs~ZImQUIL z|DE%OXSZK`e0W3l^UVT(W_uaFwV~w_>nqEYUO9AhTExw_t?#^i?rK4Y)A`0Dy*)Bh z-z6rwJh824WqY8hDy*$)$)K!zdXvuO{@0dEClyO(#U6Qoi``gy)kUTit9Y1~Eogp! zzeuOIh_64|&*oi}QAx+K*&QJzE83jzvBs|yO^lFFOVsAvxx9Pdxp&7Ujvo&`w{z=5 zt)zWA+zdAH=I1(R^dDb*mwn-9o4MyF6wg~eN76mN@C$p=1?MXVYW#H=6y6-Jp6s$m z=lJPT-h+qu)^T`;ds&&>Tb~iRt5l}rNywxXQ7x?&e!=hd@{5KBn=_f0r`_AOZWX&< zA4|tR@8vcU@~^ku)w_PA-_ZP<&C0r6F?x(s#RY@2*0RmZ@0nPvbgXpkvb}3dSMSwn zzxFG4?XohfCo89%ejD?tY2nl2>l@>Frf+pMbK6tWm(!5A+;+~4;!nFz+z64`u+{L) z-P*k|u5zYlqtrRG_a+TS|ODXe&3(cCZxhbffQSiEQIC0(9U55pV%Ow|= zpPc#qx_^tXr9*yDweaP@)%_DY9{*KLW6D^2jCbKGz8OJ++i&mMc1>dM)@}XSFHG+< zpSjD!QE+N0uVC=oS@Ob4Ws+Z8xU(7)D92l5@TG_fC1rwZv7k zW)`2zyK&3(NS*d3jmGwxOSHhEhhQ&WrBpS=-G)%V< ze7$;FU`X?+ReGK&l6X)eFlCOO5%{O)4S3Cabz2Aglo$;ojy z$3|vnu&qD2?A^TJSG-n;N-n zO6XP2yL-EHa~0AV1ehHpmQ6A0Zd0)}mSzw#4}5Ut*N+b$Z=P8eq#`74vtvJpNBLsC z1tE~cdXPHnvD>Bpl_x^Zn18oS@Nv4bT`pDsYLxry+uKC_ z^6&5CT`n(peXZyH*=v5}3Vy8F+flh*+3Y}UTT_18^xNC6ndurcto*gyUp{X;w?Lb{ znOcc;&?(h5=XNd$7d^5oZ!NF;t!>|4US57Ls%OozL%}DvM4vyFyOzPgcQQ}5?ji=; zA3u~feHBxdb*ZWQ_hen?jCcDkpAfovJi0Sk>O)sRSi!lIXSU_u-p0%DZe!%7)FboE zmhO4IZ!Uwzx_f(F4NO{Z=L%oGG4YLSRYK<7;}RWLqN1a7jm{~43+|1cx=$;vbBPfM)qZ07JCw{+OLcB@%cz><*n%<)R90o(1id=uNYse=Em zY>cZ_!_MqC)dIC0FDB&Pc6qXE?e(?$=B)4YnZvX8+q`1NhVA+Hk44^$G7cy`+!9~& zQ*y_@ABW2~w)yfjw9Q#R`(OM1zrTLZk7L_pQ5xt~eb(ZslC_2bA1iA}l4Db)-JHJs zZCg!4=kYzyj-7V-^5tkZlg#a})ieJd=C|Kh|NGhB-R2z!dbWmlUvV>cS6Gy|`1Fpr z^=r3YTjlX5Q^=p=c}q`P*zOX+(BcKQah`|%xdk3rHbrRT>?czcZmIP>GS|qFkejmn z^5MgW4cR)&eO=^}1ukyVS+BFdLi_vWQ@QgakNyj=y?C!?&%B;9KAqnk_q!W#xB0xY zd&XK`YF=Zi*E_9o+N8T*x4tj@zTm=w!*ltYPJi6M{q}C&Ybm9d20kskt5W(}Zrw{+ zU3+_$^r~MHhZ$VUH|yP*-1MyT`6TZ}rF-3!)~u3hx^*MnU2&GmqTN5uth)N2VO$6gc^C zowV{1&F6n_F0y@c>uy~AzSB>GZdX~IT@`p)bmP6%U;30TRf$R-zwBgxwDN7*giAbq z6H6u*zngt`TUhqqyLrbC%lv65IveTPE@8sf8@& zipONOPmet=@vi+^wMWL?!_kVC$y#%Y*(8HCJGSNCE=mYBVKra9byc(lf73+kT@l+1 zkJ+4GYie=sNa4&tuakR~l!A3`FJ7|O`zvG6x#gERL-uT|oF{Cky6>)Dqn&tgS-M}E zz_nn7lyjxGpBY`fwS6Jm%Wc>6#H{j!T@xfvXxUVrLK8hRemtyee%3?cY)AS@!ZL&rs3N+EZ=_3 zZ~5h)HF3|BmxpBgcRq3!W@Qg@aHy)Q6g+VFW8mD~JYH+E=PtW>x9!!otEI7xE2ez; zZ2$9A?Eat6ZX4bTNmF8xez(9czW(#u_jUj8KHmL(f!K{sgU0pSPxZ7{O4jOLzOv=n zoC{0y^k?^ECNX=y30Wp@SNG=!C_7aC*}VUAx4vEd_viKBR_E{k|84X4_j3OJxp$Xk zR(=2Stmtmhx!YyjbJSJWO_V+;b8y9pCwyvL4h&O_83QLf_9d^d-gYhNYZG5G&mk9+ zh7-0rI(g0((*qI}7d5Zs5PCAF|M=sK>2qcl^v~)Ij6OGKxsWP{GT+q|Dl!LM4@V2U zs@|>qpQn79qH*<-BMLlI6~(j`%-P&$d#-1K07Ituyz;L-4GgQIipp9H*^D0ANi3LC zoD_C7YIUYJOMid=ySlw`@o{nI^sj$^Z||+Svd{3?mNS9xS~?v=k4da8bGu;3lkn(C z!}?E8TQr})EQ#S$6}-6p{(l!9&5etv$F#Lg<_z-6H7QV4VhOui+hO~3O45%Wqcu~6 z{!Y7JxLonno;%xY{8v8dnA$nZ%~j=?OQDcZXNlm;OTPs|O+VjjUd{2b>YCYvJ9o3! zKDc#@!Jwe1ryJ>c3OVy(86ix8*WllZlckI*0$HkgQV}m(riiHe= z#X40ko$@&T^vB&&tGicNq@&o$lP}n6_j}k=#4urAnXq@BdxVBY9!(iZ7wZKbHJ@ z_Vdr>|3AX_|GOOj@6G4(_?my4f0j&qrmuhf{-0;&@qgc>x5W2ceAT`$H`h)sq-Ac* zo|g>I9{N3Ec+l{UjbYvHNz3_rUAuR!FJ(S-?Mc{dD+a|K8xGGo{o>gf)%53As{1dp zu97*G^5pXzpBKmd?Q4Jj<29Uegma}&aMYxAzx(2L-z;zvYB>^lBL4UL-;!M?_;wlh zZqMa??djlo{dVt6V{zf_%Q6nGY~~5c4fioxw0i5p6PYWFitekN51zN-=02er%l71E zuP%*T78mX!B5b&G&El@Wq!f4dWe2u9oSLk;gNb?duhP{I;$|!j_ncN#=x)CIu3rBB zA3oXE3V#0mhf8J}F<1t4%)YAdJg1LAS5b4u?qp8}g6d&-1FuYS})e}9>ib{+Ulaxy& z=Wr>=O;i16Z*(j-I(gA?j=kSk)Fj8%@4h?Z!`aKX*M5D?Z-33IA;xF=2gNxqxz0}%u?UwuW zg-L0Ww#jVj<#(=G`3g^$sdo`)p3w02%G$@)CGCdCwtWq2?`X+6_^z_0@82u71g(x6 zvp=xpt~P9{si?4!?TcQ0+9p14Dr57_?)5)g9XEwb?=JOlO__IklT4AOrmCQ@KlY11E^!4@IFB_d_omd_3 zD=V12Hcf!j;I@=Y#I8H%wq|C}_Rtsp`)K<9Yl-sqe^{2a#4VGVea7y~<Rq+SB znG&pd^6ayiJ3K*lXS#V$+E_lakuT}+?B-v)kFK=A`diwkOpqX#(|J(iixs{vo^IL7Z{X1P%msPHJ7IjHU zU^!+T)&EYj^qt(J6AsG-(hL-p{5nIHSIT`iJiRUV^qd)z6ZEEYD@@7!ck_JxJnzDm z3$m;D*i}#c-XOSe>$WG#7x?}+Sm!60FAGd^OX3i%pJC(F%hloBQoxX?k|LnmDyt}( zeCCKu@Is4X*CPT(4)V$&CYH}bHpOi5xx6dR+iT%`z0{)bn#Rh$ORrd8vHP^M=v?XT zB(KnD=BX=F*g_<$=BF-n^wKn*Ug_DHdPFby|A!~sf(c63rm)Riap$cvi{HXST!(IC z?A`i|Eo8clWQJtM2dGH$~Gwb@x?6H|g3_O9f8zMX?+*(YdC$MS97djt-0W z+~J3gur?m<6n*^e@y8p-4NO9VuQE2P?V2suWFpFSYy#`k9TOh8-~FmQWAQg$qf=YX zyt9ywDCghY#xV{V^f42-V4=D_?y|+dBMbY zd3&;#i?r_ax}AB;MDI>z5D1xic?$RbTQ`?yzTBHFzjpft{lucuo7>)={cT>okGFWW z^U><}#~&BY^p+PDI6mR?Q?s`C{I6dgIDh=}=}@Me!LBZzwQK9X{d~OT%vMvCPqUl&NGIE@82F?KK=9Qo!x>54^L_2 zoum9_%M{D=lO30AC_7?1>EPY%x02tK#pZ9{)fw?SX!n`^qPz%lAg@{EJqzWK}R3zfH^LF(yn{4MU zn_u@QWmW&_PvZ6ey4Uaj`{j4O{k1*@juU<}3|d`UI?6tZEU=y>sc0U z>GIw8yq}MEMSHE$j`p{4K65Wzz1sV(xR^mfJ*DdE*VR^51%8bZT*vk@|FAu$n)R?? z>D8$I_xIgpns(dU%1CC;dp;*Pf&W`g>y!=&8Qu%BzhxJ>_89G9ux1DfGGF#*)!K;Y z-d1;?^N;R-Z;d=tseCZ@i=VxUb9VIIyzCt3sOb^siVvkmChd_sI{E(3^}nCa)$q_r z5YzhoV0r1*g>lu!+igTA&%L(tRaW6krKd*z#V3!<>D2JwoHuv!`QnE@xjg}`s$Lw{ zK`W=^2t3+#MD^136Jv5fhxoqNl@IiO~T_y$-QNf#MN_e_2=T9$mo^Yf z*;5D0^ya@wGoL%lo0qG1utuFab53SPcYAxgdHy|tgEbL#KX&+DR^i)p-&j&JFe&BM zHeVM;E+wXy_&}efjd?pSO zn!4;>cbD<1hpS>yqf#EbZ*hCBEH<EDZI>i%5KDb-hZ zNj~@f{Q2ko^7AKNdbz{En2C4d=bxwM$les$VZhdQ+2KfA*14aTm-~ON5&8NxEcy?e&N;;y;rgBYBhUt8r$a^6OUL< z4KixIcI;=3+2+!<8(rPn@2MKC6ny#i_V(<%x-a#gXFp3~TeNGP_Od@e3RDgm?pmev z!9+K=Bd2KZI-kkiQpyXuUkF&oU;ljCYRmGh`}Kc*{CUar;Qx#1{~wf8{rGj8KkpQa zrLo|e?NevIzUI1jO3+ca+;@CF3=8-qGjsP|i|Xn%{8Ykqyt~@(re@=}o);~4(@*k5 ztv#Ky_w?t_KQAwze*F0H^LGEUv|jH2efs~(|9kEKR@eWk+`YE{_|L>|l$mu6eeN`B(G#qNsl z(fsRMZ)dDsX(gC?t~NhC{_2^9N&YS+=hYp`(@%L^s5X~g*nFSW<9F^ZZ@-z}DtcyU zuw{fRi5p62py)3qqUyFNwpn*MvV82T6U_cq+W-26SMD*n*Pxhp#4y15FvCRW7E zk6vBc!6@`R{BxRy<(QF z^4k0T+N_Sk84NN)#<$j~OC;w+S6jw+TsT>*XZif(!7S6;oA+E_yU{Uvbw@$f3+L>& z+t;q--OjPx&+N~ymoXNqDlZT7?0T`yw5mL2*{5$WAKrdHKe2=Fu41+CzTf|ruPrNe zXkd!4SeqUDtZ47L_N7PeZ=S8SXhxTnlHbmTs@tz-zBt}5Uu3~AROpw?qrPS_vx8^N z`IYx_*L5~F0$;rfi+w`+-T{f=cTcW^CfKk4hz|?@6b8@<)!4z$hJo9-JMzQYv=JNB)ns^ z3fq2RdY>U*yRhSjfbF-e^Ka}rvYD~PWlQc>S@(u-bw55{=Fhj6Nw2rDU6J*^_O9FO zPhZXLrv%+_NU%s;A$s<~)2B+xe?J`d=PRjsl>4^fm;S$_|35t}`SWkLysv_>q4lDE z#nUw*SJM`k3zkZ%y~^8=EBz?(947qyPR;6*YY zHcff)_xJZp+A~(({{JPselF+#gY*C9?5(S)iLk4&iMOw_u(kW!y#Kei>96woztY!_ z?A%c$6BoGi5#I}CuQNxSl`lw~Ily!0;8)S*T$ayg*f3mN?mvIV&L_?9ms)L}Jo9Sl z-q^5|g4S*8Qcq2ec(Pz6gW&gj%$w}n_yjkvaQ1a!yx{Y{qU5CI$qSPfR~fh!eVKB} zK>eP3clC_i7ICSzERQ2IPKM02Ik%wT{I2HrKfC2#Ut4iZPG0_brCZa34b#tb#CT=y zoo>{+ZP64V{^c_pck-|n&lWd+tsE5;&Rlr=*11!rEan9cX<>~4aXTY*7RCQvYn}Q0 zoN>GLXPdHfYs*}`GzAy#?qBCz^~Yw{bLMxo5z`qP985waG>e&v56kZR{iEQ_XO|8m z9bwl?TE@F2)zl^`UP{^Fc&53TQ*qH+)6&;U4jqfrKWR>MK2Rlk&`Gkx@}TWrbqlKv zi<{H^k!bFLkQzq z-otxx-&XB>`X_bfr^K0^2k%}j=}=tM8Jj;{Pt0u5jz#XT+f#3;zV)bb7rVbZt^A1D zlb=ubTrG>ufA80Bz^tI~{0NWIrMp79md3{2C5++6PMnX}-p0eW^7Oe6jLjaoChb99 zQ*&SEzAM($JkWJLmsehmp~=#~Qc1W|^7sw&nz*d3y%}2t1v_S+N_|~9t3B$iX{z4t zo#pFUgH9?K_P*bg__X_e{r8)<4}UF7*3*BU z-_~3IQUAaA{Q7V2Zm-`TdX?o&N?zBTyMHgA{&{w`c`^@c{@U;RwEu70Dio-dbHL-t zj^jVOPJ}$$G1*f6x{K!YO0{#ZrY*a(J7#@$NI|E-ffAm}jZ=2L=s*AZvH$k@wLj&FM|i^;op?efdF_kVw$zqRi7$K~m=M{F{uykc-U;CSh{#1u&-ts8L-r{#;Toni8R z7d-EgX2H(85!oyKYx18)$#*nMFJw7};s`^~;>!f$d zC*GC#?%6u)*t-czTwYh|FFd32Nml>d(;hwL-JLHIXF7+o;h-;w0s`~7-?b(){1^p?3D>~icYRv9Ngn$)+_=Vevo<4r6R zZ&@2l3tX04*_Lo(ijgc|@wq$I<)&=xtciN9ugYS7&pLfZ#!BAU!C2P0z0B~f^XwHn zi<|NewT8+jZFpu>ZLnQ*jvG*xq9u#?A;Q`R;&BkwR>*SY4(^yf+Gmm`u&3L+UR>T2walcy)1`q-vyv}mi< z49mO9o1430qVNBDp~M$AN0(|Zo8=0{7{%I96n?Jo65n0qwjk-wnn#Cx}MS1&zt#^~6L zBDMn;S%rfRT|Qj2Pj~I9r(xH3on0Q{mFcXwY#PU$nwk8kjpkl2{QGYC_Wb)@H`Z=@ zU1i)7eRo=}TvqD5r~gFnhp&mcdy%TS!-N;?uEg$ z)O~ATOYFP%>f?ew6Sw@eyi*OoHLP75=C^W*lGZ!Da(^~K%LR*9&Q=t7KE>-mdeNg4 zfvR}-6PNd_%Wh#x@;Yr|ez^Yk_W1eh{p-J-K7U`&K>XL2!l_@MZeG6q|EKwXkH-I- zz5nNjk4)B&xMlty=rt5p4QQSda7^!DkCCM!l%9G$Yb_>yM01VDhA7CbxbSvyQpStyh(Y(?ncIjFM@AB9X@>6=6v2nBU@&N znZ|)iO9JLHy*W2~A=8%TiN&6ad4(UlEKAU`niAZ{eX7yP(6cl1e_m|p49T9hj0s}z zFYChqGQZ%?anC;P&&NEZiZzf(~YUSS49cU z=Q|#~UTap+E3Lc?HYKH(^KQlJW;EpA+asB~`|dpb?Yhq{am@+hQVzVcRdf2yz{U>m zX`ylHO=)gZo~8aQ*>^WD_dCnXncp%kXEb(PT^oOE*76n}t!h7p<#!VwzN^h}TRtV& z>-Da+Yq#BNkMexO(%v3CGiG&9bbPe0YO$BqKLZW_&I@1mm8qB8+wR*LqnBF}z@Vni zqU3DQa{2P&<_-_@_0ATLsu&nf9Jp>5CGXF1IHP0L4(V%Aw{IPj;ODnrzg741l2@g# z6RcbkCUmcEkmhG(SbaB2Tu_;vxB26R32r;AE&3GKhsM=#C{68f@rk+Ec-K{B<*d8S z(+-IK`B>32@zLqiscWUDFaEOX(Zkn@hYmly@Vny&OKAGC&&!|RQ{B+_@UWN9?%MDF zyx;Fsy8ha9>65H8FQ>Xz+0CCTAXC1TC-A+g*tvVVS6#WNEXu^-a$KS-WuK|!+m{a| zQm1e%o3gCgKX~6=HbFaETUoiy`LFUi-t;Y9!hF7;U)}G+$Df}*62{<+IR zlNRs3EthH-=V~Fs$Zy=-j2rbDX9J@wFe`UH-o4;Ki)$r_T)Xx|UL|%iywu>CVbs9V_xK=02J8DCS!Ajb8$LUl+X5>$~w$qVOI6)C*oL z0uTFjlhgPb_V!0bn-puRny=ZreWA+|wj&8#e5b0V6J`oC7x?8eo1`nH$Shv_de_!a z(QRv)5}cQ`FWqW)ezSFnsByOQ_FJEA&T_gW&pe;yra8$yHF8?}wy;H@ zm))+O_R9}vzc1)0&FmKc{%GE|Wo%Y!o*sH2tjPGl#4z|>*0Xs=!WT0had7FZ@|!T{ zby{*ZE63F-6J*rfGaXF>})Qaj99mRe2VZG`B<2%&y#-R}y!mJ~&c&Qd02w z;Y(GflW%Y9O-%6;K7U}{h4yKgo6LlENESKyO;8J&9uoBA`u1hhr+-fT=@c84-ITiG z@rr)U^7h-;mYFEtFu40`nV!pup6jJ&Gx_(Qo$3CdPGNx|Ch{r{(j%@Jdfut z554^}Up=~C^LgX7f6wkl2fSUhmW}b+g@xA*XLT?cZn<{aru*lIDZ6HMUe}%{$Et0# zP|0%li(Bhhw+0kxIcKlEYP;K%X@+pHmZqw~ZqA&w=CX^w)&I@^x2)`^d;NRy)SX<# z>)(ER`Lw(Ju>H^O_&@#iU$@)aU0-pe@vmv=@^VFX|96k_+McziTK1c{nKFDca%pQ(Aw!SaoXka+s#RND-eko3-V{#V z{ajhlsyL|mp~Dlt!!1jcoy4xrnQ7cu{Mz=Ijzx=xVjF+hRk!PWXZhNhnchzH+M#bY zvD~7#==q!#Pc9!WoOh!{q3GOY6W{j5JNo0K1G%o-GAvms!H~?fJdbPH6ya31FDHH$ zH?1?e!Z?X9WUFG?YH6nL@06x}cIjk1aHlxzQHq1wIhRC7`L0#dj2AV}?(T2#d_1XK zoY6Rb+ReTED?Re=r8sykQ4R{b5_>koWT}iN-|jtj!FrmF)oVrX%H-@=nJ8ViH~0Eh z^;25+j%DnX`u*^s$$4XCRewPq&x}RBA}1R)tr?l8nrsd1y!5q7xAXGc{OQ8Y$zCSM zBwo4bEt)dPc=tnzT#xu=pMLzvIPz>u?VpwXiOcrgz4W;A#H5)|jaG6n9ob|q*d8M% ztghvq6TDEypX%8uTX3T)%6dmj5DaZ%s?)cbk&$FWkhyX}Q7l#d=TM ziJeV`Yg|$s_Qi&WcFQX$?o3?b_s+)Ph-SLioYOpZ*VeLSdrr)jc6sv3qk*q6ocW2v zWaH09XD^#SieSZI+m&@m$oBpjV_ijV;j1xXrbQHJRpZd0&`@FA9(8<%A7C5u8xs(c@ z4^?6dZ`hJ|_YHpuf1s(TVd|5)iOXl*xOgyTS>~@do6kGFG(7(~W0TM2mlZV@bC>YD z)qsJGY@dAnuODa2?PpEgsr>otu4}%0N1ZQB6K=g_+SeTa|JV7MAKT^YB$B60;wUVM zU3NI?_4~T-zhAGlwXxl`duET}wr#v$%(jHRG`iyBBJ|X5%QY*Pyw@|9x;j5zoHj3H zX~Ll`-gXCGZe!YWJ zX+_%&JY~G%rX}aWfMu&!5@pQ<*(==JKGVh&S)%6wDM; zx)tSGVkpdP=KZK+{R6S3Q(SZOrFHMv969r98QY>+3%6VoI#{s$!RbE1<|HMhClbq5 zR0>>CAtif!i<_C?dD^Dh+%=E|MjF`@a8ZIgsTgZ5>UojdO{|7$pTrokfVjHL3x zB=*MwhK#Fw!?c6d(o9`HaR~lOR=p=ul5u1Av>(o?8VsxYX0RSzG*2l1wpmsC@A}nQ z40gv(%z0LM{&zHA`;FMu5z{q}1mwgwtkhyq-q}`?{^m^Q#poBy%JjB7N+f;joKww{ zdDz5m)@o^sSyg^ZzxRm6azMoydl-2cVDhnJ5}|N86OXZ?mBd-7KLIN!aqwtMli z^_In+Ezdm8brks@GvNAj`1N7mWQ|>G zv)7j9=FahJkJ`O^^-Nw?wbJC}iB^ruiVj7kEPk4E*Kc3fbfB|XcIq6ND;XMQtE zow*(R#m%to+jX7kn&R9JAMc8Z%vX0)eB#H`IcZf_iSeer7u`&=_da*YV{|4(r_p)AJyCQb(cL~#$UbD6oJ;CC$GtOQ8 zx4-^x>A^p*?Q6^HewuwgRH;|<&FRrwaeb4Flr0@D?-RC;58pMulpHrOnYVYxn z(~I?%Djd32wjyLoD3{!=Vpj777G8&TKM)aCDqvwLy8lsS)vO@1g5B?9nVaVnf7|^- zH+9xBPEA?9<*%zQs};J)^dHZRy0+ENqqOIm=!CnQ`XDJqA~Ht+PhYG3@UVs1FfUmGjDd6hI*GACnQ?)M#KN`l=2P0vqHozU3Xall8k`rx;6qltM9YU(}m)|^ad_sZBs|j`evrh4?l5$*ma7&@GWOT<|4Mpj%t8NB{GCJOQ z^}_P(`d?h3*Q>?6-ne&W6@xwk+6e0uk}|K};wQv(-U{ra;(X4=iS*LJTh zbzZjc&>TJe^Pgk>y*dA{Z}(tBnZjFc+-FdbKBDUJ%Hqfw2gc6HP3_;NPqeT3 zqfuwxCbaXi8PDv0SFg)I6R!VOVIw!U}2QP@K3;VeI8fek(QG3ZIhK}HcMDc5?0=Gs$FTR zc6Wwmt5EiP7nP4ER`xB_+{ozLuIihqr@B0r!+O`WNu6cuBM-<_i;FHaVwXLC;F%5M zqb)wuZ%56UsB9yvKmB&z#YGaY%x<%WoqN;4@?c_|vrEnOi`fy3We&aV$2a?EKIc&2 zZqGd;|K>LvgNS+W&Nv6#;%C(c`Ppxu7R~m)bt@ycLqn-+#;UdxJGxoVd@YzE{Mg7> zb1BF4o?za0C!e!SyynnaCdQEXXif;bs@t>N+Z|Oor~mR7=6i1K6Eu8Y{i64k!xvfU z=@!rD$V?C_^mC9{k(fKXX}+BNv%`-%xQ@+n&f#O=S{GZjUxra5)!6mH>7O+=x_lzr z|41x0?>v9wiZqXUi`d4nFSp9y%oc089aem7?bg&sxgAnI?V0DkeEFil`@GUjIMi}p zn%})yt5*rK|8C^aOq^Go^FC{>Ue~kFLD#mj^#}{hy!pv-vl> z{`>o_ho8sSek`%t7jy35v(V5|MPn=y=BXt$~{~2 zI1U(|x?vmnqOXwsz})~@{ci^DX%}SYWxYBq(kY?v{E=dbbAj=*C6Cr_ow7VNa@QI& zosja-UDijepYfe>aY@*l9h#PU9agU|9zr!Nn- z?@)eo-QdWKsYRDNb?#c=fD2? z*1GKMf1LHg2hsi;qj?q3!wi-P*UW%dQA^ zQE7ek%Wql_o5rh!0eUN%SMFY@{HwNT#qZf|wX0L-b!e>6eaCR(aoikLF11<5QbND4 zU}oNsIqUp$(`l;j$|o>eDye^0G@Zb>^GW5rc=6M08`9(2M8iX`RLgIDTihh@+gJ7M z3;il!CN0_Y^#wS@QkXapoN>XH+iv|0Z~|t#-#5QCmeLg}!xa&pZx) z*!I;d=DpTa#*XXfXKV!Y?f1G{yuiM-6 zr>AoAX)ws}Ex-T&$J~bV_0Ro%ZH)EWn=|FsKFdrzH`9B0*Atg1%YAwnZLit}nD8`K zZZW(f5K;2JrgnPTJ>?)lUYRrTQ#-oUE=^CpxQ@k#b2ra(k=smZVmsFd+~JJKk>O)+ zm?+_TMwThYx#NLG8sk?U&PRMt*n(uEv(v7*aySHbN}MV7E7abzYl`cx=_1k`r82Px z+11}T&z#A4_FIv15%;3!9DFPc2FjQ7bducsQkHqx`Cb;-9dkWOd%;}4_O(~vZu2|S z6QSmo%wQhq_d);vPy64mPM_cR_gA^%U%ni%;(OcvU9bNf|d^tCpuoveX}>SY{?Oa3)TKF9cCO_(7E%;7XPA&1+&stF1mGA zN8L;F>a`Re_L$!c%AvWr%3hkOp=+5I|BV0ld;NOm|DWr>o`074YUjQ^bJ#D2&)>^f z@b~upeRHlqNpQ&XkWB7Y+Z&WIYqzV|_tz7C?OWyh?AxlW`ihDbw{vgjWi8-K%20bC zroMRBQ=6TOceeXZIosjkRF!fp0Vnq*>IVS$kJ>6)4v zn^-;Z1ctV#*OP7>n8+i!0^|3kOOk^ITn6yXJt% zotqtPZ=K`bhwq-QZ}_L8qQXSJ-P!TTHK{;9Rb^$%J%*JWZ2*d1rP?_ZZe$)fXlBeBQOr+2?NZ${90USgx#HqPgt( ziTW#y0cq8rpPl{rNAp$3?NaGvh6qjW=PAh+0q>g{TuQ9wo}142PK~+8D3vAol#&`x z){_Z&+jY~$E?;Z44D{o%&^@Zd8~P|#T>jjd&p-cso?rX(!v}-56+aFio_wJHe$DsG zk8hjzU%s=eAX{^y;+{Qwj+jpgYKjU?U8uTTrPe1nk%`@vDWpN;Kn!j^Cno8h1%N66n)xdwqCI(C$e1eBV1&rg%Hz(YXVmR}8&$rbxx0*$4 zSgms=SZ|e=Sm9@_WoIPkoSE{@x%qBX=M*8~1*?o6Y?3*?LhwH?1Al+}wOdvD_O9?f z?ZWfmzzL2|>;HZJ|LE+?A8)Vkm)=_(uxCa{;p)Hkf7XV*PQUYD=@J!fJLb2T&0~{*&%j?0IB3) z!KsBB-K*~9MX$Yj=1xwD;nkon>pnxDbV&^d^UME_|NpJu`!W8Xdj0jN-+%6Y{BA$D z|M=y?pIwaqznRDX|8dzkqG$4q8P6-@idp0jNgV#b@BaVw9+CIEW>wC+TRTf_`@QPa zXJ)fF0v@H#b9u7sSV&ul>2x9C&K0>cg&E2uH{A>Jnsew1*VeG+@ZFzQr#aT%Kit%4 zxZtDwwMn~WE;9@bIH~L7#Rf{kEIG;Ch#?AKOumm$!A3d#)_G|90KG1um=!^K}+hN-sX0 zFpoL+Kn~xHcFC?xjctlcLo8hXc)s7;vt*Ue%t&WR)l~;fd@WVEDyp+*&)j-(-Sw+J z5mh>S8_RyLwakkaooKf!l1C_U@76gzK}=^>$rfvUp5rxhdGm>G>sJ#*UTF!NUY@XN zht!h4X|wy=t->;+3#MM2cqhr0@!9sA+l*@^RsxNCj4y?MPB&wjB+w*$Y zy4O_}=NLBR=|(9osLh@IN$yNgP+#ny%Xr5*S-As)8+E} zB-aWrb(tyA_T|=-@A37&Yj!fJ1qU}5zV2VnIWsxrLxq*?zRK^r*YBU%E^u*odH#hz ze>kTdW?=|E=kn??zt;P`_R1_y#m+}UK5aSU(eh-O^?~eJ8CTAhY`Mnv_}Z$jBWd2U zL0Swu)~+r8`Z1~H@QbHG|3B&f|KzYe{{PA8DwlsRKfZO_oagcX9<4S^xqWO`sqp(7 zmDi7QD(#xRV`rs_-ulM}7aWwR2 zr6Ouxb9Ad+zV}#vKGkG?^J;+dpO2wB{AtN6G*>n)cI{MHyZ6*dok=c|fBQsKla4)b zziD%RPLP-8m6w5==ldhSPjx$SZetcNCwQTimldsE{FE?Xju(h={EM9AS zt9yqy`y~@!jx`UizXq*w&n@A3H2YWX?Y5gX=eJ(7IPRSE@zk}SiT z7|>qJ=KAj39EOD3QSWDaurHYMNkCX~sf}S^N|kiQ1jFMuyq8wL-V-f+t<*U1(mRKZ zAwQI*rP!A%{&U%{+*s|Q)#M&6#ARuz&J!xd(DOUL&!tl+p(ee9v;Btct@F&?Px#(l zIA#&A&(YPfeJihQY}nO>uPoC)S|nOnDymLXTfn53clct%9OH!nYeSFRNeWCpk})Oe z{(_FXnO1fknepMp`(yGudQPNWs9s-M!n7@L+0UyFo@X$a>7Tw_$asrK*CJIv`tQTb z$L-#H-hSq!%P;@==j7w_R_(i*r^V0T|NQgMn%7CbHxDl0T-GS@lZ`K#C!MX~+SR*L zduBFp+%-1RVhEh4#%kCQ_V(!>A;-HeXY{6b&+JuvK7H%7q*d=_8ZK|r`CRnp!$amD zFG?E}cdYJOA>5nEB;Cd-Kkd?Xwi$=Sm1|?R>CWkWr5u*UA+uQ0xR6Qjchhy3ty+s1 z9lk3n&lY5kNJ@4{**r0*>hp&chMGZp)+R?T3ERIlZ@~lZkfU;d(!*lX`{x7 zMJCfTxA=MQT&LYO-DB<*YrX}N4W69>%Z}I7nYu`2d^FQ7H zZ~ec`=KiPo@7FDt74khZ&nx)pbMOD<{~ww^yJOmW@ydcOKfmp_xmr5>R<$2a)XVfT zGqkm_O>0?~Jd<0gYo_2crrQic#pgU0oaq$E^!F`PUC=Wn;<1HG}Wv6d&Sem~w*e#Qr}wg*X2D zTK}gkdTFLp@V!^JemnAZoDF0YTy*Hr0v6_ltMb<>&s37Scsh6Pv&%+PC(F7C7r8Vn z)0%f{tA4QOl%v%v=e(@2n!A2= zPV&6;4<8>c_n&_I+lPWT$+cBA(fL=8-nq$BCA(Ba^gF9@dRg@Dy{zYFPP}`u=!O1q zj_9vOXIqX)HXl+_3ye7zoTqU6me;)HnLF=p%jI~!a^S(1BtIiFtfZD;o94mSLl zr|aJ;v22Q9Zja$Er6*Tfm)&9AHN)E4K;WeSr-WcafVPX0Wn0L?ZF;vDbKfd2E!my> z{Iu%wB_7jnr@mI|o50Db6WBG?^>uAV`ElEIJRA5O*#lnhZ9X=Owc#Ouo^401Rk>vB znsuf^jPGg}>7Kr{jqBp78SL6xWtWO)26;I~@Lu$AN^+32J=7pj82ehn(7rMEHp8yxx_pI!?y*>W_yh~s9|Gxh( zJpZ0e-S@BgZm&ChrYy^J+BDy8&!1=I`~QDz6Gp0xEAqio?W`^h53 z&Ee_JCpQ0?+RMeqaM@((liwkCZI<$Ecs|>`lniX#20kek`_m`$`1-#ue;%`(%@eNO zDQQ?Zh4H|db@dXz+@(tWO5!y&8Q9AH7uW=CFWr4_uX4$Z=SKeb6h19Hv!=k%IB@>5 zmDkwLIkz_6Ww3ZUW!EC%1^>A7SG-*lDShddng7oUn|0Bv_sm|jJkyQE%TiIjzPZeQ~4a8)w4>hHcD- zN-SBYocUZc`Pa0sGxEvLcFoazYL=2)%AxUL?bo?G3vVBjFsiYW4PWTH)6uXY^xC~% z-MM_H!**L2dx6f`6#w_|GWS}&JpJjXFJ~5&2N$pxDKPO$q|B(P^KS~?9&Kw+oo)U8VsZ7Z zzRJ)7hSO)%w3HZHwq5RG*z?^^zx#LB6F$N4cE2+_j=x-TCVDU1u4}Qzi?k{$Iu0D1 zGt+4KkHEFwre4797bN#=9sC_@rzE9~12;IqC^y|aN$N6>t z-^_qi3}7i2Xp-!aXe!?pR{?Y<4$ znhXu6pE>hxR#4}Ev*%NSij3V^+75^w*}cHAw>;diV9zz(hGo}6`ZS;?>8^}m*;w(S3V_h*f*-uqWpN9URx%W!rr=iT@CT#3@L zm>^BnlxK`J;xA*_g3tbWwUL3Lfx*+oF+`EgC7mzVXXl-~z|w#n`UuOuIaXAF1tNd1g0kbF8bti(D;JlTGQZy zTd4{RCA;tHZGS!c`}?|opXcXq|NOLO-(3Bz*A@y%ZL6GjJm9KeqVCpfTlqcaIg6xc20UxxE3Q!pjbnE@^qO<+YdX zwj_olog9*y>PlTJUwz#r<~L{4s*O@kCKs9f5?9^Zmgc~a!Ev~x=b=@_`SnK=SdK2} z+^{2hy2Md6bEgNnyJe=oeE4#uz|Pf&6Hjpp8j2e$F4!WylB2!yi0@I+5R09^55N2n zysxuwL+@TG+efl1w)Qn9w{VLt%|4}~lojN~^tp20=P5~Dx=K&%&bBTSnr3h4@Z~~< zh<~x?(m4Vbzx#?XPUz^=*d@9&wUcUY59j;wXXBu ze(n0!Lg6fGO6F=!mGey3^xwJ@t59@GDO&fRn}^E@4u;x} z-)+H$_ZPR#{HGwv#<8CN$Rw@ybCWIz%4BqOc=*%`USuq{pU$XO#$L$y*@tJ2#VtM- zCKi{__rA9!4(@k4a*=gL$ehpjp0CVGQ*skvebeE=qPNcZ^C6dp2E|!jzp5fX?FwTu z3uc*b{Qvs@SN73f|Gw?MAMJA0c%Q#bG<6{N}rn2xyE-se-4jmjmKj%rrIXJF4S`_`x zZ}k}khGZW1p8es|FW$D0;d>YB5;W`R$HS?=-~ao%|J?r{^0hzTeJt8NSFS(S&F{n2 z-R9SBoA>O^P5z?k-w~eHwvd6%;e)%&tQ|L&WimDVP`N7Guq>15QI*|vmop+9J4<7g zn+$E{Uuypn_{`Jg+;i_oN^UM|YL}ep%6r8yr{loUruz*yj%#!-p38N9?caqucLQh5 zyKtsT+A6c<#j4q*$_&8*0_t+kF278C(+tjU-ti^sbyW4;s$3V9XH$%n&Rur$IP4Z3 z?KM;L_?3it>C&~YdqdkZy*$s@&pFSWFpK}`o6p6BUsr7b z^V7X&dEXaLxw3In)zsSeEE^cOmGZW3d#tqVcTjE5kB7`pnY8?PST2?vTRFQ(tyz%I z@V@HuJc-ruUN*s!uL>HcOiKIp=kxhzJJ)QFPMhVWx3pm9g@+ z)Qzl)xifC=RbJF(wDfw~gdmfIQn$uw^At?In_1CEL`aQoUxLGzyXzq}nvLs{H*RXuHguQpG_|?q?t5%0^ z6_eQi`c~Yz)k;gJ_#73yC9!p>p3src2}bJAcJPQabS%rvTKiVgXCL?8WQ)fFw?hO> zw-innl2nS@9(rasj{?VsIjam7EJ{rg?LB5+RqtGSuRVGF<7E-^-=9}nuv0iVQO}au z;l9%xr+24jY+L&+Z^4vcLxZF#cDs_+o(Z}3)~c%ip`Elrad_jpjtrK|CQs)i{SdQF zwNROHC&k7_c6)AhmBqim`+q;T`_+HHzWYI^fnb>=!+<7t~q)qinbC>5tp>|32OScl-aR_WzIX z|Me;{dVR6q-yr+het){x&-i&;-fpSKWOXH}w>zV$ny~)%(LKow3nYSGrA9r|RJNGt_D;=^Sz&La-8}t8 zNAJpV9x$4|;obgu|JHAge_&rxb;gIyh%qo{=b28!7@JoUoo|L#t>1q8ZPi|{wP*I6 zIb9&XcDq^D+cP~L*|&q_4~VF6_8t?yC}_9Wrg`Pg?}q1In{f&(3+if>z8Q1OAbAm+ zAV>R>MSQOk-?9~%JIr|=8K%3G!6H#2c*XaP>$Dk`OmXJ?`RP;Av($MZYt0SI1DOxG z%qe~}hcB&EFZ%64_xtwjf>#r#xzudQOWW)HzNupav$#@4rSp*m5yr*l%++iEe7k3# zH$C9#qM{q3XL4p4F^4z`rk=|4n4!@u`Q*&)s+s-ETiz|Gy>j_wMb(UEwrN41a)WDI zS1giNF=kdjbmWZIw*i{K~y$KiVyqmP+?y-Nb*6ppWy{{PVb+r#qm@tCXr6#ua=3z|dT$O$hvtGuI+Kisw`+0!wt zcZTYkFwsP%COw^=Lpl=W-_<$lOY6Tk%l}cxsrh>~{F$24t)t4{YU}sCssDQ2etKEd zwRvGNg-h-|+tP7@$3t4o#e+vf!-><8V^x%G@Sms~+#`kJ~fla_n(IXXMH$5*UY?sxrP_WBR? zKc4^p!G6DXv*ng+>y4+Y-oLkg=1jRoH(cf=8F{??cpuj$!FD}W0UgFWijDzy-CPcI+dtdj z-g-9o|F65dKi@J;e%l+$G=XEgq4vAmwcpm28Yz9cR2{tc?RBH*ySn9U2HXCY#CgnS zw~3v%`Up#dMsMfOImVa8b0_TFX`J9x<-_uB@Ac{0Ldowg&lPN7P~M)n<@VQIhM#lq zms@(7VBXKlTIyT9O*Ow;s9UD?MW z%Brwk#BN*IOuNPJbX8o07#$7>EYgdrowqx@nxQ7*MnvE4%9mXapPl3B_<2%&zJTIH zmvanz@_xT9Tdh7n@~qF9M~|(xYssZ}ooQ%ZR$Rd5VD=Pl>1Xqm_HkgvlxDmZkl?5&b^r`yccPu`YE->%AJE zMM=`Tth&{6Y*YfPuPMca`Y)eqQF|uHGhS}~wexZ}e8m5*mH+#qXy=~azh-w_*mfmm zx6iTn_w0SsRWIKA5Y|-`^vu%tc2cprP^$WZMdB<6OvESt-o5+p(%LU;u0(3=*T~Lf zY^k-ekF^%kRu52$w0L8kvO8}5gn1{$JY6(=91cACs8q=?E9l6|tQ&6^yfk)nvwXGv z?W$9Ty^;YbJ)BG4m;K-Wf42S4{r{f-e^kEj|H(h!wA*%`|NOUTR`H{9yTq5yjtf6)^cG*1V4k@xx4NoIh{fmo-&?zker%8b`|9Dthm+fP zUyGZrwtH>&Dxn(^i#_Y#Sv-I9XmDj`>WBTU6iIRS2(=Sb)53bGHl9pV2V%7%V?Ji%A+}<|z%eSh%tF*GSb-KOM z`kyUNt=(bGnUJ>6YOQ+z>D*bBGnTSzF&)_Ve(A1@34)A08{amxNJ_pCoY1MlBEX>U zd`OFjsW8MvHFUxf_g#X8O71PJH9tN?aC&D)Pu#;aIkeRF>#g!MHACY}mo3YD0whye zR;;@GYAN$~Plu@*t}eTON|f!4$=du?$aMA;fsREj9k(j@+E0|c*Xh=~{4MwPhEGeh zzPAOrgc{1ci@zVQxBbl8@Bd!?yA!3&a-p=R_KYFVguopMVu^aGnbn1U;)VKL1}3sH zZL2=bm~f$by|dY(UkoeQyF0Y{y{5c>=XX5S|O6Pu=DjdiKe`cYK2Od-f~&b)TG8o0%4(wrf+cTa0+_tsf;;x~GFCPq=4&@uSsL zBNgHF2#37i?+nE!o|c*PHgTu@-1pDxvj1|h)>KtJTec`^`^_l}(~dhbJ$cvp;QHx* zB{%1-`<<&k|gHR zGiPhL=b1UBtu9CG($wCW73WQL*`?#HIcuV(pb|r4sHoAgH62zzZX6Q7zbC7HQL>vH z!-T+xPw(syGkCq}jOF?A?C7eK+xPwaWvg74dZebJbpN{5?{9zpw?&6LW!F+yySsS_ z2hVJopwi{>zBcmfw;IjxSHHi%pLwh(s{XTi-LBm0>-YcR5{#U+Y8S6cMNQS8pXckI zZs7Xvu`6zUn%J4dcKyBj>f0F^yrW-h$(kL0wk_B9_uZdysrQ%jZtjVhrpoZ$@*zW^ zgYc)B_v?PYjon`#A#c>*a@mlr!p`0<^!VGt8Bq%HCsg$(Pgu3g+o#tfY^#j$p)*fd zVy30`{VlOF{5;2u;lL!>V_W{eivM4Kt^VWr`Y%6cpIxV$_~yDf|MSw;_4n=PPAe;q zQ_ej1v*vcoj&Icr8+3X*ckKBR_r7CVVSf+Pj28Qv9}0r6mA0Su(6>=HZsp>9oEdv= z`Lew$U+uel$+>qDs-YAbad+PuR+{SY&+3?hTH;ypO#+kDR-hM22Xh!0?Rf5IRzqjS4End&9mTO^PYkX($65_txIm)}AuKb%Pjl z!I6%x;DnU-b6w}npEu*n?EHJSc6N4k73b87f6cfT6+KNgJ;*}yhL`?*sj-{S?NW7{U8yQfBH_@S;A_A6`|3YGRj&K|kToSJ#!!t^fM;ciF!An!b{X=a&0%CY;25kcS=qT6B-`QS#s-4+wR_t!cXmZ0`sS@-Fm=7(2b>A zg`r8Nn0e3lp4=G52Ya+ymMS|`+}gNvlR{HRgzG8yw6sJum-}{cEx#}SmKJzd{N=Ke zryZ}YP4JA}T&gCA(&wKjlvJK!K3`tX|Ngx^*MQl@T~2)c{MYi{?&{sA*Zie$^S+<_ z^`C#A_rLu+f4^YT+qGJ$c5reLx>cLqJ;5Pj zqO$D7WAEZ?KR>Mbrz-eX?)vT0DxYpcH(qw5;#qIQCN=zhStb4R*{MbpI&-^K~-tZ}-zrUY_hoQKowR&0W>MXx>wIeSsFf%f+scvFQNMvqdkldkv zX3yJy@qZuw|5N`<{Qr~R`R5Cy`Iz7QdiCYOHA4fYx}Q(2nXha+vyoF>Tj*Qu{n?*7 zI*zdHy!-6#D<3&C>62SGWP6!^*RTKhZ{2$PpC_dgB0^_v;A%NvYn-;N_DpS$p;zwX zcga>8^Y-1Z*;D;}${pQ?t?5>Z%gze;eK*KHl&ZF&C@#f9SZn%D7DZj=Q=LK^v(s_O_$V$cE;xCaR`sqc>sy|*{k@Xd&wt*Zzg&Iu z%e>SF*WHvZ{TAXooOABDl3dpX{kpHNDp64;ZBJUZwA}4_+a-8VDd>1vM}wrGxVO6O zu^GH8p50ZJk(_FIfq9|MV%GZySX@@*w$9wT?vUtOhOP0hQm;BX^Dutj7|*Vg<5 zuAR2*d_%>=s3wEGKcCFIFr)bPy8C|*mQTZXP7SIGsZ2@n z6>rZp&G|4{zJC9Ay%rXM4|DsSR$jYhR%U$qfP>Wb*pASxI%Y2wPb}Yh(K{*d>Z^Br zJ}&})x7jR8TKs8=-~9SxXFO)!>0GhVW=G%RCqd)?;NVlJnlBVLrB>0_#8n;mHOY|_P3wi;P!pA zZ(X{}k(Ycw_SnZOxms|}*XtMj_U-cd|Kj_5+0`AM?9r0Bk4n9`4@`t_U>VhlZJUbZ=GRQ#5S zA}!iGJo&8e*VB6TfsBex5;VCf3p9z_`hWRd-eO3o4?)5FOHdaZ~d-s{`Yyd|K6Rt zD_vo2FUy^~P02p@-5b~sWah5D`1Y5I*BO~ao|99)e}CS-d-wC>>LDB)Vm9~h3tpTd z%n%jMZESx=AT&zOH&IH^ps{)W^{`d0p{i|5R|-gWT;LQu%^eneUuc!yjx@EOXTGqM zc;7cZXqX&(PKn8hgSZ+Fk%OFDC$|9;&3eVM+Eot}LMH5FZAkM=+H z>F4tKMxP~?oUzEq9_og7mm&-W^%RUFPaF@aN4@Sqceg2#-6X?quPgr+Uj@N*1Zn|eq@ zV4V3^Tc6l`XLIlegf`(s!p$ z1zmAg(fm4Vrl5KG|E+I?666!N?WwB$_2#qxznAv^)t`T^jIGX&$egJvk@}}pZ-rQ! z_<=YfEu+us_J2RVtlKEmlPArPb9-uMT1Crj$&_#BvY-2`l6=}X*)rO>JNf#0ox{T}#6>T&z;M=#J`P)@Lk(K?f-{8#Y)w07%=GVkFi~>Nk_ktW zo(W#tsb90_ziGWXL)u(UmRZZbO7Ge&T*8_w=fL1N$)(d``c%e>{?{JM+rF3EG8qVK z-dgm+i9;~WY{hB2W4Ha||9@z|zqsf7`}^sSHmzf=*}8MR_wBlmjm2~Py!>&v4K zIVt!~Wt42>*efzKbFQ$(nKq+&;SMU>3ukWMy0u|$>)AC6E3I1!)27u*PBTAv?m|mf zaO?q@STQD^6SI4lFIVq=`tPA{&;0U9ZinP%FbYgK*?p&C&I?txK2`;Vq|8gZloX2Q zxhB|75@$^47G#^_ymj{5Q!O+9KeYdMynXlldFP)`e(-gZM%IYyJv5BaGsuTb#!Qlrr{j)Ki@rk+kE}Z2?wUtMO~le=-YJW z%fPy9}BZ8UJnVLVadzh}+chi_}MPd~nV_D*Q&t$QxUg1Y?9FYa8{7Ot3^C+w4= zDDwS>#I`~WwVBgWMFkr|Lc3r3&Hpf|vv{_@yqt1!Uv6x;+Lz__-syOBPqaQW(SGhb zC1a(@0o@%Z4Q6(}x~>wok}=@Z?=v#Hb8pYMd`!YD`P|&u+pfvUNwLhjDKTxPL0duR zgSWHu_wCqSy(1=n=9xx9!a#^mq&NN&Wxp0~8l2AUjw5K~eS47rS z{QvQFcmDS4vuEn=-FokO>dQkdoQqbQb%?06U%6*@$V4EJZ*}y+8HP$B)wOZI_sOpa zJyaAfUu^y$s$4-yd5ufQj@jAyY??}TXO9(l2U*S!fAYzWWukyrll-RpJc5xWD_>-# z=?L#pU`#oi5-G5w^1|0|J`!CM>=}xm_jSc}P26?9bKyTrnZ8X-i=L?$pGz#<7hm`K zPjZLJZ_|?5Gv}9|U$M8oXshNCLB};P%iZDbnxz z_wVf+9d3LJtbclWr>l!nwb=PNuiFwz^7bFQZjjK%=+M{vEhlr{%b9tWZL#Cd0v zmWkRdTl@7M)8(&K(#a|Z_MP8&KX3bvx5@YOv-6+tHf3D$^`5ozoNIaSrf;nMdNus- z&W&GQKK@rzzJJ}r$p@}24bS9RqVrbSabKs}k43pJlf+hag$U`NJhPKujDg>6MbhL4 z8M_;ut7retaC&E^Cogq`fB)yOt=qTNeEM)KfvHGMFyYpbshvlRYx>s3$bb6z&ash4 zXJ*5O?N@Wp{5>{>^UdXNwaN`%4pGydE-qTBb>~cqXxhIom(3f_p4pbGb2_Myb=BQ7 z$MPOt{`SzUrf-wl8O!OZk<)~|Zyys8eByP$%;Eouu+@f-zg(=Amosbj$U7fiet(

-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
SuYaDaxxz8rGvGLK?amZuVEGbb5mLc8 zS5TqY;rT>;4?mmss}jO76%RuvFP_M(w#?(&ORtvW3;0jUrU`j7u!fxBSss|G=epL= zxa|_xNuCfN8@9U*eR(xJT5@vpuGPeUms3a)_SIAjdjCpKQ?;x|;?T`EeLL1UEIJ(9 z%yCjuW={r}7L zXHUQX`%wN*&nx7tNY@ zfw3?6gs(5Zg2}9{&X0?Dmjd|6&OFK&W8I%Ub$H3HDmS^hcYS|{ltW8@lfzLV)_biB zr{9X&Uf||cQ_<6J*u2|#Q|Zn8==Eo>c1&VcmGj#WyVUdku8y6HuHU-$wPxoX^>dfY z_iev+#DQnmFP`ls-7ZW|9$)z4IPr_xvqkF`U$%6+r+4_`n+>x9MR^ym|7kK;?fG<0 zj>AHi1d|pCES_}FbduAq868KK7k&Tn-QxX|t#g{Rq>5V~y4b&b9?Eug{Z_FJ)fR`8 zg+Jw=RXYwNTzc~B%<-@8-{?egok&lL*x zdKLD4-`yoVPNI-A*E*iFcdH zs&9*>UhTbX*wDIR_Kan9Jeq-?FCQ%7vwN!TmT8dU(3=*J>(iNGweXm*UB%P2TQ_j1 z%rN}$?Dn>!wnxq^d$8#H_wSa^H5T_>Ts>29L-(x8CBe3_^P^wSh&WiNuL>(DE3im0S!>LbHmmO}O8*`<26=M4s=-p9GbUOtJW4@$WX@ zKZ%LT4;<{Y9k~xaFo>v4b#--lccaSw%9}^*(|#CDb9OP1iMhSNy+627pq*V(-DvaD zITZpLH}Af_w>`JIq9$+e^?k=Jx4rK*_^QX(@cEtmS@xcKn;$#aH|yn0oFO^mimb5u zxwN)9Z9J`=X>Bblx2YM6oM(u2;psg6wD5KPH}U$f^Z$LWU)u8A^zQs~(~IuSo3CHa zdZ39TJoSf{=|u(ATTgCX+uQx}!|uG;P+7$b=Mw*zp6&i4y7YrY&YoAke@l1sGnmK< z^zFBfd-0)1ct+^{)v`5fCsvv5|LQ%rs^iBp*Yk6Z9}`pFI{jj|;*UKUZ8|k|tEOl; zycCr8pXiWx(DmKk+12OfcpHgdG&>}5_m0D^)`lrFg$m$CoF01>=HR?zl@#Kan!^A@|#tGfIE# z_@Z>@bo~G26Sr9j&*`snvAy(s;p&o5cZMe`Hteble);NQr_`OZ4+W3R)Re!hYkc@i zlAWUqo5a5b4?`Yu9GPj*xZl9{VXM)@&B5N6j_dOi5(-T8k$ysNAI^CkA~tkrX` zrQV)DYul~c>-YWr@FnK(?vG`+AMbVR<#h0ooY`00QhdrX*lS9Ad-$DVB{qjw5wnl< z82(skCz~_Fvd8Z5J$-qA?QKs^B)v?UV zOWDlMwmD{Z4y(WVv2^_<2lh{$&L@mdIUIV-z*Y8OMV*kko+HbtO3vB?i7MjEZ`X!? zoc?D~LA^~xg9NjY)U0JwG!*ChEkFHq(&wg*2PP&<`C3-g{whxJ(wyzMV1aPCocBA4 zRjxNB8JyON9^1Awd14C-gjEKh|HpbCm_(!9C1Zu9anK>Yu;<&;Q@e{eQmK z|C}%Q>1ol<{nqO{^ZWTz&z@dcuH@%myvW983%$M?)E#3f#obxO_OwJ5Qq1aKYP;%KXV1JV%%M%H^&paAD(cce9PL zDvG$nD%s!C56&nkR_yopbQZ z@Cba>=OmL5=k$0_^UE88N<9blWP1w>E}i=P`SZS5Sv{$2F9$}Yv0E91s>eEh)K1-+&t`kjo|uw9kQ`kilPKB z#sqoJpZD7Sr+EEO`TB3#%6i)SE34bqEqmL2{{EjA?eX#N#4hgI&edWRcHL+5p-9D_ z9OptM1i1?pJ2@<@5^Piidl%ZucoZ32*L`8a!@zmDaFyKEhYTK)#||$3b}3Xrf~CkX zHOA`R`|N996_bm;E3Z(_G8HUTDC~~5cd0z?XW3I{xv-<9Kc9Qr-zn*Q+?O=CFWld^ z_y51|`~M#mkC&-Abno{rox+vHeh1%8y>?cobLMra3fFAYh|e<)nV(I$x6!5Z&IOyL zA=yj67%iD4U9rIA(J@sudy{iwhd88|YbVC+{gczKyL-2CYjo|7v%-6Syk~ZBD*Dv$ zt&gcpWKW0gvpEuOYu=)JbveiT_Pyq&w=Kl<3`PYTCAsP%AC7u z)%$mL+&Q}Ej6B{HzcG^%Yw#@n?$oB!+jt_<;qaYnkQi^`{ef8DIHWQ)ozVk3#tRoReXgH5x}4)cjsVXbORY&S-x*xE zc%9+ULE+vCi%(DVCvRimpE#kpR({GNwYXPDy+eF1(hPd*>V>wuAOdJ^qRYyC%$HUVivo zdZbwP)~)BJ6t_j+ZS*{<^iuJ;&ep9hCnt(dYFynlIeO~tGG#ZF@V6`)x2l4fJS2?t zqcqmO-j%=Iuv$`c$2&gFk^7cS2p)e|$Osk$f3+_tXa$UMQKbBpf% zmUH1@n27@=DtF?ZcVqFFf_9dTQ$GFq zBO{%5zuZf|GbAB7tJmjMqh|jzE4_KOw`DmRqOxzV&D$RRBk=VRpEGABZrdg9_5Ad% ztF^1|-mPA{_1f;;*33au5A;2<<1qTMEtEy`DT`X;lc_GtFE7ol&d~8Kx%afKWBTvk z_wLpH`680qGgDRT;c@Bd^RvEJereC$z-72pt~s;c%J@fu1*eKl$-K!QIXgOfepqEl z-MRZ@dqHAanFGU%RSSQ9@^tGuzUWcMzf(M%aXj8Kx?F1N0nAM02?;L0U#(vMtM2cI zf0LuMRW)iPueelS$vNMDe$|JY>G?b+N?d2wtZbNNZB?X@l_qA)V;XGuQq5*Uufc`% zGzWKHwRzeM4>yE2M$T~)4gWr`Sk345HbWs}$z`|J$v(OK^_Ty@H}`k^du#va<@fE! z-`nlppRIQ7^=|X;Ngw{y+|FNneHz#7$RDzdXYLdm^EW3*RI@iqHmiS%-tj+sg7Snj z6|8H&cig)7UG|L9#GB8TTuI;1a#}{`^$z0(f!YIK4t}z^HTUr;%Xj_PtF@N4#2(!> z^RbZRm6uzU&M)gfUC+Ms$SMAp3=BM?Q+BRO^-KHqcDsCDKY#RH+0EtL_iyXT^;NvP zSLIyhbJBIi6eSi;Va9j!=P?M*>F8SN_J?(b^W>CijT0DU*JO&bE_=C0u+q^a}O;jltHjuxV@!-UQd%HcSrZuE($`&p0+WEwE z`B53x37Wvw!P`J>M1M6Zq?E=I#1#tG~<7 zTjf>Kn>X)_jxoC$x6iW^4I0KdGd)9Nw>%Nq*Wn)|b@%cf4u&d^Ib zWBFl@-leF^dBP{&xp=%dt39Fa&yPTtj>(oC9!g9OCOprtL}%}{t}yc0`+D2gTd}&C z>LxldhfeJNwk(XXrQ@A``HLU_X6J9uT^*fU9bJ@Yo@R0(HI}!e#dby4m*vZs@BOwV zu3_@?Gc&I6%n0$7tbRZ7WfsRoK9+{WLo4>qUa>250~edfbau;M*Is?zcXZX{SGQyh zOII_fUGj-r^YIF+%~vtAW3RGSiZHNjR?z%%@1o6{OjnmR>JNhTo;Xj_XAasRk$J>X zU_8VPh);B>|>|mXn273GSM?^@Hb}7kY*(OjxE~t6HtK=cOH9e z>~jBqIsfmfe*NEh)24=(h2MOA>fc^{J^P;@zu()p)5Ewx{ah?d3`gwMU5mOxvlC+! z_I|&1%)z4f-&L2TrIRxsCtBt6y{YbRQ(GX!;2?MTpz}Q*2Cabd%(Si(r-e>9_=uem zY2LPGospM%VMZGhkCi~#gO(YZAA^D>oS4cZ*};)}i>LYc60Kuh3=D1w>$bk@H%**& z?dXz~SM*{Jbg;_IXlRs>3E|yeU2*G(?9T7|JXh~+Y;4$d^4^izRbu8MA&*R1bsRj} z#UIRQR^PHSaS;=fNn7gua);+GmyY^+oeDf4KRxTg>yH}B7u5A^+s;IaDHj^tiSt;$ z{y<5nE7MiEe`0d`F7>(W$PEoOj(AVosZrB5 znXGkq`_TIL>iK^kg{eL-eP610hBZp`W2LBKcd12>M#mM`Tn_74llkK2Yj0J*Pq@}@ z-22?{;?i3x2c}(QIO=ijtccCB{kAbH=52plWu?3I+J|WzUaMz*{k2Gf!-JF0%t@;A zm1Xz8GEdRVuKjlM3PFn1s~HqlC|&YhosoSvcgt$aqvhK-21cLO?A(=ge_O6*X6*IC zZ=G*{->X(niR3+;csL|_b*cH&d!|c+y588BT(X(`;_%#q&vf2NaGb9!HByffGAK}4 zSfa?xWRn*#x6PYDA?Wvl7d*?-X0#sAzFVqLnpm8amSVDes{?1P-8E)sjt=`}w#nQb zjSIVer~GyMx;#zda)kruj^|UCU-;7`bt;tMM)cnwA0O|t-KwRRCsxk$wz{}H@NFzZ z!1De7S7aqFaXS7`V{ym41dk7WXRAJcosz8Z$w{^1>3Y$X>n2Lva&1~Kxx%RTgxYGx zlhzEIn!m2=yxu=w?%&<`e{a6m|9UL{|HJBC|Fx!;++9`n>mz@C)0RKm_y2Rfy}R1t z+lL^(wfriz`L+&;rUl^=GBtd^W-8s7b757MfbG?`YeoepKDf_$aPY@O)1u|o4tz2K zUNfZ6Wgai_ms)=6!wheaHV@+-vE;4?9Gj2#+h%v9y<+}eZ9{SR z*T%46jpfoMx2la9;=Cq?JwC^v7Rd0XWkQhC)CaYzA|t|?TcY{)$h_-#So~?1?BSP@ z1#{F^xq0+?aF{7g`telzNQsPsnysel#EstBywT56(k`h8vU7VLoI9aNYmU=VJHHkV z2?qxy1Af*kg^DvJjSA0k9=JHUxtX!xMWg5y2A?Zuj*5i1+?M}y@cyQI+jE;wf4gjla_hAQk3+>_}QwAyj$_H}} z1|C#aPv0WQ!O^nHNo2w@p9s#G3*Hsg-dpm$Ha~5__Ibr=TCuxUysPFlc>HV6-TU{S z$1Hhkr~jwHJ^q6l_xW-K238gYmg|yFI2GSBZde%T_3nk5p@W#|?bn9frkg{GZY=H4 ztPUzy3%eTi`tHx5Yro&md-_ZMfA;6s{`G&X`Q`0m{M1baudd(sE9!3D=hgZ9e*ciD zNn$Sfd`tC@#U0%<2OeK|FZ6%KLJ7A63G?_3*pJDVCb2kpZ8X@CQf|&~#^>HBRAX%H zDwD`xRzTxR}{Kz zm#8;;Ic<_)AwyCgheLmVzvaSHD|NgDj%2NMkm%Fv^v>s6^yu3=W6xD{J0u&XEIX@W zAYHT~Y9hzk*t=Pit-mL52swJXZVSt1J#zWw5r;?m%=b<{mDsxWmE4`g&2g@07k~J- zHeS%@T2%1YDRb^ROUYWMyJX(V>`7vu7t{SpR8C;xPyd{}#<$btku2jOnd+Lzt=Cuu zWi&*Ve_o~%%<#RjggtOl%B)wChSP(pvR^HD6yxQ!;r-Wt|L(mx*)GKHAR{sJ=iBG= zMVq3`UDmB-T`wr(aOqu%L>OuCv4ryHojsz^r6a2GVQ_R z`X;`Kj}~3l{&1kR_D)CEgAV7nNvmcgJo(SF%U)1WyGxRxEAx|LyaIcPYJW+^yI=JM zU(@|3sf1VQSD1yG7WbbxbBc-U>J2G{Svix!Q(BY`PoF5b)Bp4G^EN#(r#5JQkITE9 zvU&GK)+>{uU3Sb~`6}|+!x`0T%USpx7HSz^y6f{*=l=KKB|dL!pPg92e^kODQSAC! zVNam|J^g6i)0_9DJeg+o#8bTEr_~N=rypmd99<4ow1nQA$S={}!JxcmiqOjDEdlm6 z{}U(ZPh^O2`6YSMhikTPM`uE9%mUGbfB*irfB*mP=Ce<$=i7gKTV%I=`oe_2`upMz z?EltL{@!lZ(bW61reyN9Ff7UI;b@SX_cSB1q{T_+L}MPy0Tu@dk#?V($~t*rZ>?kW zXYQWq%^1I+SUKTENT|`td`s@RA3CIeTKrEmo1L}&$0LRiW|!iEKTNlB+<)l^-kGu0 zMPx>ir2f)XukR)~1r@Yq9}hVoU-)>6^F$F|E|DphW-&N)>2zeoUSD{*R;E;6<;t%2 zze6YS9*GI!nD5-Tra3rMj?-QFj@$mcXj`v}PtqLIXYfBQ*dgon|6=L2sJqIdubtDm zDjsvcePyWs=yrR>pR;$w@8;cNU^r7@xh(qBjg1GN*2*uP;&9K+{)q;=Rnz5`H}4!h z-1po`uw1gPKW=?;rXhzyJ5c%iUIv0!qrJ+_k?dnL;3Lu zw?967vN>qQ6X98l+a@~RHZNEtvye62)bq^Ic~} zrYuQqsI959oGVst&imv9=cU+J^B9$P&e*MgLqM~1_3;1R`Ka#`|2f! z4koI9QkmtXQXP5NyYA?B4>mOw&iOArwwg#--wEBl{PIdpqr_e8MN;+V4k|nIC!Wgj zP~7gPR~zd2-(T?U!}YTxT@+T&WHYhb71*CF%z7eC?7;W->RmH;P1OEb6!o5SP8mn$ zq_1Vt3z-yz@6Ax(nRjZUn4nB)i`?_Oj)vT4?zx;@SFKc&yozbl{kYSMZC9lPsKuQNyz2q{?c)oiiCcL|?HH-?fU?#q{1$jBZtJ;TU20El!e2Y-v^Xu7ZNCo8F!N`PT3UpMx65_t^_=Ib{kf$@}>pJF1s$uDxF^Uo0W;bI#5c6_u4Umvxpox-=(8fBy1i z%eF%uY4eiz*4iI_l-Z(_kbOXgF^UdbNDqQwv7X1x3UXPuJM-3UR$0K*e! za$fJAc{S+ShvyqpBX+LpTiKA)$Tx5C&)nNl*;5o=OR5O$R zI!~ycSh%*yr);q}+0erK;=X3AR2E#q3QG99mZ0DMd$sKJC0v!zvbv+HN%(+ofwr6E+&dgyj zUi0IDM50loEYsB*pHe%WrkPhI1s}{1`k~bEtp02J{ra!J9$s!Qe;6EPy;bz$y5j0x zee>(8ZnF7(aA4JcAR)1A-NU9i?aNq}SwH=8P~i}df(Cz%f|?@N+-Gwb#oMni3SO?S zye%c`q12%BG{4~emNQRTW#%PYJvl5ky_m%$@A92p(LBdkm__CFn;rEXo4UTsM6@qgweVlSu-qfB*un4ogfofngO`^V zmxu^5w=jI4bJTUA(OV{l6K@2qdLlwgFZXZV)2R3VU;Y35U+we%etrMHX+v0qNSw%@ z>-&FAWPP_$;ztKVib68KJs?qyyQzbcGzf=sE+yzgeZ96u!(uzZnwHgD@o zZuj_Uy$lI*mGxCFE7FQ2>KbdD)Lyq8e=Glxea7d{&TI`D^*_@0*Z+O><5t5p|cYl<8X$8YilU5b>2kBQ01PpsEIUhSH z$XvSc_=x=^i6(|VMV~uu;WKAV+}vfj?7q#rmgrbqu=PXcX7?6>bnUr|4q37boVC1K{J$+`(xyi~ zs!880xvu|S@KfMP!Dju2RLjmn+f7a%r(F~j+{u=pwd}FoOv_gX4+q;a`z)z?amDWJ zorAKHiv)@tin@Y2UPew~mH&A3odgSG-h_)Qns&Ue&Mv)?m~?(|;jfR6RLt6$eD5Ck z$QJNxkv6MwvJFGM*u0a9-{0L?#^mr`_(sRgGm@H0ZRebKFf#I0ZHQDdf9R5tEOhY- zW8c(O!mFYcqGT@pD+zv=>~g9@Io>~A%{Z8Q$?U(Je1dm3a&|kg?6&2fY0fDoGQnu} z)LZ-B@6A8cqhpzRde3T`g0nh@W@JkL)nGZ?SeTdmKx*%NZyVdU|Nq&)y&?Di=lbwI z{#48F>wfR6{oFmjcgADu-5)NzUD5Gga$Vu?iDzS;6r0?eQylT>Ut!(7=$2J_r;Ap9 z=ik44rl8FCEsjf0tIw~w%(KXd8!&2JIE9}iSAGRGfP%2ajw;~ z#uhJE5v8sWLCMF*nrHC|E-qJeVQ9W!k+!^2PEGX2-%D4QMGM!})#d%kx?leN#8Lg? zixym;pXi*iWaLuf7A^YxamOJ>>=GY~0AOj7xIUB6@IB7% z(Ff`0)-$(@E?&Wx7B*Yr--apmAH(LnRa>8@3*WKUJ{EWO z1eW4Shb@H!x9UwlEw=r~D#PWwm-9a`Q(|t|bK}x7k39E+j2*HX=H>+x!WcGeWo5`( zdy}!pP(X7=h^hLW?Pu#>oS3h5h+)GQ!(e^E6VJ@fUOH=`tF(dZXNc@!g>RM$hpx)p zx%p_?q*;+BnM%r2_GO=oGvJ*6EWxkgUD~R{A$RkB?h^9gGtn)4Wnw1kaQ%Yho=Hvy zjvDSSxN^618h_zg6}*IPQ=40ag6s?jYc=DY^~R+lPDdQ}<@}x%A1-GrDEQz2#~Qf| z!4(AwjjRj}5x>!}iY*6Porl_?ps@_8qIY%&NbC<IZJ5E(bZr}I+nfU%0mrEW!{dG~t!)uyzk4H?GNV{|Q z48a-htFz?NKF@e|{IB}VVmmv#Wj@yoe{Fd!u{>(?E+dYu^O~Fm4fpR*6j*$4%So1l zISaRns4Q3z!2LZ}+Vzkw*94AIhB;wbu4hcnK3vuzbhG5;iLf`j{6ptI%Mf1J@_NSQ z7n?opO6R|uH?i|wSI4u3&!%R^UbkHSR7~AIBJFCUqdIfB8_*z#_FNa@$U8g}Ws!Ugq!a)n1<;Hc{pMJr0q^4kx4b zK109d!E3)(?Ub2#)ZFQL?Uq{gmOIM@*i)x+Fu7`+Y2@Hh?s3gZOcTwlj3|; zz+TS4SFX%{`M{jj#~!w@&JqiLZlY@UEKtSi#fm3V$x>-kE0g^vdQ1!XY?mkM1xeE4v`eOR*sr%W9z!8+~4B$oX;FKhpde><`>tnEqfWQBhLOj zbN8|%ldyGm+$@l`Dag4|I-PBq47_PJTJ~u0|o8!@~ zQ@f;Pw*KA7Xke{Dg*+uK-O8NS?;G%!3SS+0Dqd@HkO zhsZm*tnD?X|2Juz6F;B#`0*A|)k!8iDd*M%2wj=}zOzF_Z*O@s#}N}%)qa5q4;Kit z*OhH@aq2mn)>muV_vJydrfsj~`-bGqn_jX;r6 z)ZmoL;>%CosTpuI+Fm{9!ROH+DYWWx=+*$vBbjVjx3~4K+gX&J#@{jfp9{l;haGCZ zp>~Pyx{7&BpND*2D14ZO<3-183y%pt4;HTLm=SKkC}TPI;Y$}qrFEgDchp-}Wob$E zX+A9Y{KjIUZl8{V?YHCGHH-Bo3(5r4#%eJ-9#Op9`FuXs}O<@3y^+m95eJzBisePd(*ap8^0V}9-eZs(z|K;YV9sH>&Nd_q*(pv__}tNgK<~t_KWZ8%`hj|F-`B?RKsGQ+|DT z7Wlnz?>qhT*Z2SUc00`QfL%?MS@~>729A>b_V1Ig>Nj$2-_-eTYN}rdS9sF7oWQiU z+l*Vcm^~Mr%i`Fyl9?C?lrDp7Gd9@KR|Xj{>wIY+tV0^&Y# z95`YuH~C&Q=W3~U!aO0R;mn_N@6S9h*my$q^QR9da?e(4=T+x0SzUW1s1(vA;M~%E zma(CDg~L&fJ&w(vJ46-*btPuVOMX9iW=eEYxzz{ecYLSJt~{HgQX})4Whs}^H^W1< zvu4WeKKQ#}a>M7yo%wwG1au?+J!0S4=wW+^Pok7dIOJ=I)y$(a%$ECpZV`JqCo^6t zMgQThD`m#V}1WhME|@7y>m{%FBv<|%Um z1)A=>olwlc;5qM8nwg+&K*Pk&pr6n4E3-?FntH$Nc<_&aEn4n{GnTye*G^JZC zzG9W2;_0`)t@F)WvaX*j-0)BE&b6Ha0>d3Bq@ob{cwjRUge=xBRKi zF^TH@rCle|INNrc+Qxfi-;uTT*LEp#GctI@QShbU(DQr$@7Djw|KIlFddj*gt=x|K zA1{|*&-VxQ#sA*@|D#?0{@(Po@++BZuk3zy;Y`4f35rkess4EpkoQ`^#;e!C#n}Bx zl(^-coyX-@O)1)&SKc&vW|C0)r9A$bVPdPENq7Wy^gq8lXJ)rR%Zj6a)laJ1w_7qC z=wVUe;9_umCor*b{S5P(nu$VpO5gpC%ig-%J7nthWvdi*jpo}t{AZc5(W9Pek@v#P z-#vP(q&#kytu&k3I$3@Jdw6)q^;;8?;=U{7&l_Ip?)FZOi(%2huj?krjt=pw@x z)o0+W!1S~Cqo?_WdKVT}Mn|Rf`xo@y`|tfPS}cx3H_voSboQtHe@@%~d9ym)Y@489 zV{YW{E6b<<&Ak8rCpv;R%QWb3oEODF5hQ!l3$bry9Dz#sWRnnV6nfpJVW5Xl#P2{xt$Z% zRro1y^!!dPyCz#-@;f;WL&wOl_5`)L)1G;Uw;tZ)s;ODrp}dQ)Pv0c}{_C%^=9acS zOnEqImdOrQw`biA9k>-?5y{)KCUXYDaWszu)SE# zIAM{6Ma;AY4$qL1A3BK{a@$kbN4I(Xv_L`mFwq{DuQVqTv7e6@oC`+}R zH;l@bZ~uR`{?Gg3f3M2-|LgvGh2z7{@ACCOzMM7R?{WD(|NZ^k#e0rkd{S+G*GnMj zlJWbB>x5oV4UsR)HzW`41(Z&1=qNyHxqd^W9~uV19Pqz4|=wE{C$564`M2>89N1 zq}+Fhw?1Cp>k;ng!mu#f;@90~pN^X2uC+UvPsAT_?$E9IvQMv9vF`Kf{5bnL+n=i! zC@=h6wQ1$;t@e9+{(tnpAHVy&WZT@Csv0NvuhU#!{DbK;ubfn8EKm28?wu|fKQ5fV z|I|>-S%8V-?DfohF~%QQGr!s0IH~6~qu5$VF|D!hU6xt!v0lb?w~n(hR9vx6OPeya zo0-jMax|NskQXQa^2NveIUW5zf0-6mCBwq8Jy6JL$*n26CXy|h%VNFGK3`RIG3cE} z@oE02Y_s}rcm4Z!=^3+_^3s@hn&sirt&FF1KOWBGPuL4$X3M=_Zl5pHmI|qj%<}8Kd^$={!sB?@KKu2Of>oOus$Z8XuOv z&)V%FE0@Tj;3UA)>glpHE;T)NVOH;f14k5Q4x6d ztnf?%^C6j;48;#ia^kn=21^HTc1;Tn4z=%cVqwtKQ98lEHuZIh_vH068(k+`wLj{t z|Fh-q?)^XC=l!`Izc<#O``YJyH~Z`V{VS>av)ca8y}R{4KKm!yo%L{ezEE7@S&hdZ zzqE7u3PmifD}OJF_DH|PvvS&@DeD|8-rXfAzFBtPmhOJT)}?7m9f>j`th_2L zTMNr0WOI6K)PoskTwby`pv`9e1HE0m=S{-OIjnjnpWalUJJaT2VT$0sJuwBjA6|uI zhnAmT!uWjy!;zTB^RE{tPC6u_&}U+s70aQxEKuq{$7LQ~kz^j{iBSy#oK1<&{#^EbJ4SL& ze>x;gnWVGQEFI6S*HStp6Zz&^;Qj6Q1qC_sxERFjzD;~BY&v!JhdXCxBxKECox#i| zzqswUt6amoX;FU*!`Tb6tU5}$% zm^C$(YopfcE}ePzW&Xd*_uKw_-v8&TdGG7%>;3Ah%J;_1kKg<6)%N{AufBQgU;j_g zgR6MPZSkG4$IreJ>|r~&)A8W>sZTemKW_3^tgS5IF)eKSdbiIPR;)d1Dg4yGgXhNb z^O;+xd~v$7oR6(d$61soDx1x4`OWl5%N@)Q8Tt87X{0*XG$m|i4k%(S`Jn82h*>eO z{m|M{zg2gG&aRoVs@C7~@WoTg4|GfORBesW0MHnI28!Z~LPbsgm9C5h?l>)+o~Iak?!hoN7nDc=j;JCYTZ+EF|1 zPJgg7C(-FpOYf8BO6|`F&untlUD{*#+~E5kw?p}vw{BY9VY}x3+3^s^#U)pQLfNfU z^Pk`2cRM0bV`0v~kRQLd?(4IU3q{!)9M&zL{Hmg|vhL;d@AK!?{rY0koW0ihyV4Bj z?U{DleO-)7JD3jfF}{DBbUyI+#wQIfFI&TfT-Hr}cehaNhf-3p(dH_(cqhS=PM_3H z{;#>(yHQ^Ikwui{k}UxhRpOsre-tsuwnzjnVQ6wS`22Fq8Oyt+5nFD*GYR)uyLI6^ zONB;{f=|=0|5v=f$N9pfMGq4bg0ftEw(>Z4F$gmLKXcrE-NVPT7t~DuB)lVDE+O(+ zvwXJ6WBs6r&wQtfUzJ!zw*0pJ)}L3Rtf3R4XsX3iRDFO$>59)`Md9Vln^t_^?Q!OD z@qveL14FKcgzd1FjgA*Mc}q~>aUcIC<|AKR7epsK+n`)vajtU3@6cR7(RHWvBLCmr z^SdX+OR&qeOF*P+dw%@-<&PQ^YOc1Q{_O24pf@#qyC_4)jYHSlgSpsOPY`&$vc)ni z>-Eca`TO<%FSqZS*Wk2VDOmUFnXHwYv)OD?Y|`8G?|*;3%1hw8MAn|K*KWR(;|^_5 z=KsmYdH(pze`jQLmpyOEa%WO8+TOsRq-W@>*~Df2yZhMxkMsYxC;U8XzCM2b!5_EF zD}k^ z?*1(hcHvPt{JeJltBxDn=XS9im{XiNiR<<59R>mnoD=Uf{*idAe)WLzg~Hd@cCXsy zY|$`5{|(hn?V&SyryC3EU3x(Pz0ew{|wR+f8chW1JG;z#?~(!Ae-$ zkI!Y&_k)aC=S0o8xQi7IT-mbF$~drBhDXDon8R`b>xJATfoF+wD;QLlr?K!nXzbJJ zTq>m4JyTg!cC!aB%U4I0Gjeu2L|K*z2_D`lbS`JPOw-0zscQjWuY6lQ+p>+#@TcOh z4Kk-&e!6HJsaj$j)U*zCXpi}wy;DwCb2I(6yL<4<1pA&ur6jxLBX_&2GHy?b<~x3v zC!xcGM`_BjFPCpc6^6y0TDj$z=)SmchD8hb-`7`NF%|Gz&aRpIF7~d;(&f+Aa+%HE z{5`kk>#w)p?QQqJsZDh|Qv06ePNvc3yOY=DvvFwgO!&a&;GeH=A){#8*x;$om!RVI zLcn`v`>F}W+ax6&@)CK@oYA}zd}Tqw6ob}GpDCvsW=!t9)bQZd)>9vT*#A3yf7jn{ z>;HcW&zI-ErQK-dc%J{$`TxJdU4A^~_m{n5(-oqW=JQcnY3iMyug*Srx!sGksFRm* z)u#(KXLwvyywwGFzSEB_jTDo$+BI=P@&tzPs8uug%qK?W^(*-*UP0o8&W@ zzNhxRU-c{VR@%3REA8j`pUGL(bjZOeX2KDpBfSRBhy7nShy4F=W&Q7|e1{X+mP#qq zu9Hy~SUAP#d5HPK>K_*sT|8BfZBd?Z=KU-4@4N4bB_yTY`#zU}*?@KJ_gar>Kief* zE(DuMT=?zNu;QieNns97j*Z{@&M?@er>8YYSbbhJ&y01Wm`Iz`pMosyn7eGOZ}u@y zX5VWQFZlE9?EBl4iv5&UC~fWXul@DI^LNFrnBA6BHt$*)DX?$P9)amWI|Eafa1>vb z>6H7qXJwiH`6q>nvh$yOS(*1;$^5?2ZeE#V678zGyEv~KDmsCSvlbO8O}+oteoFRr;8RzGw%#pxJ~cr8ON)4&$2C6 zXj|NGcqg>utf}j?v@VH;_PqBqxZ4eYR1-PYAqfQ7aqAV zExAl4<4jW7=0X8iiOC|KLi{^+3$S0FVH@f^{j@8$$d`N1uivlv`|P8y_1dK(Y*iW( z56(o~mSPlp@y_|V#N(>HyP{^CmkT-FzFgfw#p=%EWm3z2c1~BmI;Z&RnKRpULY-W= zUE5_iclxoXB)TkUxV$UP%kgP+_1tJ#uE~qTM4s&kJ$^<+aN#t+GGp$qd-(Y$PcW#s za_w7f{@XS0>OY(Rmo)wNA$)($-wWrwcYgc$ujuE^>-#l|tmgmw5&!?Ty@-&~8rPD( zQz;w|XR%C*%C4Ii5wX0pBaNxuPbq8ZxtpQAH=|a1SZpu6xs7L{nTzw;HvvjfUPqUH z`|IYS#^zz@WW1T7aE6D;9i0mX7vH~dFg~d3BlqwKpIC#N1ml4gE6JTkt=fqKo81i- zI6pBrbT^+{aQa7BbFS3H84KN3Oq|7d>`MUuW{$0bU4lxB7@1DItn_4jTEcubR7K{l zWLi@4D&MLfIcr6Z-8Ol;A@cja=bny1ZhFIJf=)@2#lswdPmDw35#3{a*X~%gf8BQ=doK)zw@wnp5~@SAX5A9Dx;4 zt}T*E=QlVoxm^tCNJu&|JC-?+WAd$8AB)Nlb10@9ko)|Q`>Y=arwzjygN6xr=5lbV z*uJ@`K1-6pOEWXHl)EFqitn1`)y?*y#SARxG8eDf7G|W_lE2tkiD{Chby5V2FpJ{# zq;(TJw*-AEadrxI<=M*U8Ie`5=gjqg@|X7|wKHWCQpSiIY+ zHB#ZQVF3q6^MZmiJNDi3Dl|R7z_H-TE6WVFW_AOn$$P;e3(vomSvaqu>R)6~G%t${KiDRzk`uR)O{;rdiZnYNN8Wkkc zpmI6ZluLl+^kYfE+E=?*&Y!ov|NXxE={=bb1hYzVRyA9kPT95IKeD4iz~IdGi+BI# zZS}SExy8tEN%&F6$?fyp8P2_ZYxnNLfttC|nx{A2nsZY!ML}F<_k8Em9_>5yqGDUP z-D4X6+5i7tulDi&|6jLTSK6BJR@Bwi{r&au`TuY04}bK(U*CUcni6yVrTFVI6Sip` zzaZ$zHNz^|rDA1~-TK$JTAV`D-|t(OeCGAN&pC$k1yndL+bVe_rmcJZXklcQ%9FM` zj812l2x!cfR8SSPIW(b(qtEi@#DYoIFV`4wa4^n^eBF<$>+8P$`YNri zrgLB&NMPJ0HI+;VPC_=Kq@-`RVmZ%YP=O{x5h}RcQ(_dT{>daZgb>x6?@U z_yOj6`;bx|(TS;%)$d!{r)c-j-jbuJWTcq9ZvM5>T8p*CZ`O)(i%&bOFgd{aP*4h! zLej)p;^LDTzAoK%=h?2b6$ciIDLc669qN!cqZIUWh0NVQ^Imu#sjJ9vnxK@RTdEl< zanV^qd4;<}mYG+`G?7(?r&w0JZpw3=b@0Ts3x{S#xGXCTT^qVYgds`mfQgB4R}Ifq zp?6`ajE91>8lqpRDsdR{Ra)JbnRsAMp-5r(B)epdNZ{Nmqoi6>8{gzTUF__=#O0I1L-uL5h;cHa!dGXHUiTe%t3O4_V&)2mJO3sz^f@j z#@Dd+$zg^_tDU_ZPi`?tK8ldOvh+-%s-&Ds-?XwW8)Iov_UVTNA7)8RxF*o8oVcTa zRbJk{PAV~8zguT(WyFiVm7HgOTmNNlPuw(hQSrAe6$%U!uSALS?~M@_?G^~qVzfGb z_v@^_c(GNlg#`D0XPLfy$59g~9N#$&cg)p=Wl zO0^CZ`>j*GlCxhgJNenxZIb*{RqN^0URG8qi7I9!jMsk&}>>~Q${ z=~|Y;21nMupMJVc?)9cAR|GkQ7Z`kdEhcz+QwIm<)4#`c&e)vw5_K@*e%fqk*Y;}5 z!hO3%oj7NSopyTXzCXL}h1ty0hBfS7>x~4j?%MI=zlzv_J2wruxi)rex%O<%D%++` zjRSWUPhpzjelzgJ0bN<6%pb*a($AJ|bqEg?^O2E0kobF6@Y$Z0yUxM>y#I>!{`t7O zUTM+&f8WCM>;HYeyeVkqU87kOc8aM5ZLYk#JT2^aV0GHTJgL^%iIu;9y*>Qe{QqP5 z{m%UUE{)Nr@*6J19GuAB7OSw0gF+TgxoL#3r{pZ+FF3-Jd1$!1=(sf)B9_JdHIQcNml6bnsKbMj?!NcdOa>`FM^9t~iR7JmxoE;Rl^-q~IeiLDJX$%)=PZ@iY;Ef}WAR*PX@|## zDMb>U-kQ5_ew@|P{(Oe^uQ?Sh=Ou#Ap7LC7l)P3{a%vI7>kh|<94A~COv$}H!O4Tc zFteFKf+LwPO)SwT@lS~Rq@dTkPDnJ1+-cG&oi#sp?qz1h=bYvxtEzYO8|v+eUH`lE z{++)85yv%R1O;vOeyj-2cT+SwCfHuh(OI1I`Woj#k!enenzeH{Efo*RICU5p@7#P& zvLu1OW5qP)?M;bJ3STEA^m|NMnW8WLz+6J#(3nkd!Ir9>F`H&oeSUT|Jbr)f>sxE`+St5_FAJTKd8Q>&G*-e288 z?PT54XNw%19u(;EoV>uRe%UZxd`j4cP$w3aT+`<=I~E%jq;eXh={@P#q4W0TQJcn| z8RwqtkzkNKdcvlwV8_g}T1*12I|QBymo2d865L{YFgIh8k9?2ex2;z>n6=W5b!@$M zD9B6FYO{Y?sA3BPTTJxZt+VE2rj)Ey&}dBaIy#94%&f7!8oXNbg_4lslMdI4mAY@0&zLYvcIGNr6bnAQ)xvp)l_5%#VS(N5 zS#CEjOn=*@@Ip{RZpUjKZ`ox}N>{v=;Oe;4_f>Sl?pcz|oC}v4p0&ALaOq1)i{B%` zWz%L__`H2s^J*`*{Mi7b1zDoPAxA?(`3iX5EtKZ^*m~D8X!tA*GxZi-vr5Q7IPKQc zO(rf=S~KLD7kB*ldA>ewwZP%Shj(6^mEGTzxOlbdzgt1gjw*NO^smlS3ymw4FW&R_ z)mHYHdG=zB$EI>H?wQBsHqT49c#T32BWsf7)k|6P+3(M0l{}@P64E8zdFEcUV(4~D zhMh$YtJ^1>Q8d}Gz+kb?v(27@6GO~JbdFD1W2Se>nziVW#+fyrEZH*-9Fkz!F=f}+ zgWWt=_y7HEFL>ObjsrXErb-rDVQp2H5xwSve2zxErSM}AQ<(b-4`PW(=oB209>`Qz6@y`X>UmPwv zN}4aa93~kZQ`i(HwZ=P0=+ca)L)=qlN`KJ*-I;7li3P7N=#m5vyI#YZ=Su=xpT#;6kJzYC6s6&%NwF|3rxG9LDyScdFKkUYxOL z>$Ec~dj6LMpZ(RhV#=iMBtgcsLp%KWJNpEKZ|t$LpDDSet%F6ehoOg~m31*2t8u>| zhtip4dabrLhpsK1b1g`FhTxBquX(=@XWFiPd%3+|-hNMZXv>2wYc7|ZTgh&8PvFMg zc0QKXLCNP@AKYFax4+6}e%4&ULp}?G8Se@o@`};;lK3zzu5fCHLI9upm8H{|pCqd- zeX?<%$f?b99hlyP%vxpibG7T7jQPw8<~hAHpTF@4=+M{^l)<>*>fPy0?@om%obi}jY3TXX&-KvreUWQ7iF~r0;dES3zVgby zhK@pUmsyf+SDTjoK0d$h#`B2jS!Si*E{Y1xaX8S@cus@=^`~E7On1!Xn!sCN%&|H4 zc|ueBR)fX6xmh{aoRHY^A-Sc~plzc2(hUaRlQs7DpB25}_`UX3SV1adIq#QEBAL38 z`DunBvKy`(wGFB5=GBz*+xn@?r*DDX$wQ_+b7sz-l03OS#nRGv_pE?9=eX_G+}!wf zal7U{4HKs=I^JtTL)ZVFC;DQJv386Q8^d*t)`^`Q&*rS;^VgdkChF(u(se1A#bJ>oTmDf*kB)nT6r(3(`mCFg8 zfYo!RJx#OuVyW(^dB*8ym*T^TUOTQ$l0I}mpna2p&hk@>HoxBUg2gFDOa5lP2#?Zo zCWfHOH*+UOJuHo7T=2T&`@Zu_xaD+n^>Og&U{`|B1R!P_sloXf2L zv;2Mdv;2GeKGj8DGrpF3`}ynRpYO3J?b)-3gZ0Rop1>6+6i6ay48p%A25!bU;z5Dg*iFA0Bt{e+LdJ_bHF6_AsY zdlYa~ER@x~JU!7R$2Dl$>4F{8TwFvqZ3vQGYY=K^y!B++ilmpDCGKP?D_KP`xs~)d zD7PytbHBdVK&MF|B#N~m>{WRD-n-xb{{8*k<<6^3TeiL5y*qtZ!;bTJHe^fi2y#jH zo_=5d_jIalx5v^K&lr#O=$Jp;$;guQW7n1S`+jA`-d_HEdGPAw7jv{&KRX0oRbuDe zEb1Bm;?^mN#n&eNOwE^*XUMp1R+_wI{@z;WhV#?a8D@y_KcCtWr#Jmhg|TrGr%$fv z4*8npT8Bgwavt9PEXs8Lwbios8-*7h*Hcny?RaL;A&|s1V}TM=qJX;6uOBa$+ccc{ zV^#I)$v1-+?(>%yYv+EvaIyL+>yp%~)}f3dnhJ|V7(?8hJ6ajH&W}#koRoGwMBcH% zpD}K_n@bjRM{_cx(dJZ9Q>Olf$>$u7bqdapbd(&=?Hvx3w3a>wgkbB^D*6?Cs= zD_6_WgEN&U`KTLjUQ>19?-mn5MWN*uI_nOLsw;&F+FbK9UnMFScF>~AccrU~pZCno z)1~i4xeAX)<+7^R@u{0O^Y(knbF-1E*xpXHZB}9uG~pp^IGaz!pCoKwUf@dEXp}r zWHfW8o!K%2<4L`o43~Xp$T}SmU-sJUypr3#Ud!#%Wz5XZab#b=yEE+j%%`r28BKzP zS*4p9gEOU!Tv%qEw+!lDcA2%&@{F3W`ueeS-#Uhe+h--c(7?Th`kqUcb_Vpl`&i^V(gpKoPiXq$gM ze647~mFuTxzg0B7Q+%#8R?<^)_MDW1UPtzvv#b;}cG;eq+4_27%f@YMCo|7F9CR*U zYm)%OZfmCbOL?va8F}@GHm|r6b@IrppH2b;?LWIg!vh!C?w%d%Z9aMa?DQ_atIib*x99J#tK3yn^Pf{@dTMOeMZ?@UZ_QRYzCq0F!ad!GXNtWryI$Av|j&BT==+`xKveM;We(220JndZB zWRWBm0~R(BxAl`Q&Nw2&ayij(J0M$cTPI{imho+6pc^cIMW!lQF>yJAg7bJohvw6WsC(buh^P)E8~}_mu8yr;~1~ZNSUKDXL=GJG*%pt;bk%j_sS7iyOm>h zZFk3^6h@aNd^~HCU(IPUI2o1Avg3X7?QJXLyncQ)&;PbEICs}koBdHz-ic~#D{>cZ z;CN?MKKJgO_0vDU40YDy=TB?nP-=bpfs;$sk6Th%mLalOXRC`#q}QyHs`sHwid-!X zr{6JAT4bvMf^UVbR^~9qW~4S{dgo;t5R(n7e$Nz(nO) zzu#opY4W*$-xb1;@6Bj)>%W0}Pr&W%Yo{=+`7Lo-Z4yI*qlN91&pTHzANciFzfO$p z_BKO>ChkkRt(;8;c}LuovJNR7+FHAl=#@zMCWLoOX zs(^V6msPC<3`4wDcQE>}uNQZ?`h0_aseJj-og%Yp(+>pA{Fu$3`#fBv;I93gbL;(k zkJZ(F{_tXP|3>?|-(n42TMXG;57qEB+qxVxSaNvg;j6#mxOtVItm*d*&8-q#xmJ=# zX;tXA+V^|IH6Mm~U9_9GT~DbvOM9o_^D}b_FP}1bUh@6xp0ZHQR9(}rv3&tibBn`L zWV39JZ_`(uCE`+75g{Ysk+rAEIo(xBX>A(M;ZljVuetNHcjo>5dpXrO=GNin3s2^A z{!ci-<+#)%^WtunLWv#BMR8s%jmafW&AlFD0eAB~+^$;U31v*m;uoigQE|NGi=l^tByoerImG@4(L`9AkP zyMeFt)@xe!>II>(GyRrdzF@h0*#q0wr{c0lF5Sv9WAtCTL0d>nXGgE)u4R)#U9Nq8 zox4lfuD`djX-(jUYQM=<5pT%9eXqBF?PxNoD6!1)0ZcN=kZS6YBrSTwWY;$m@IU{kQe<KSGoG& z+1j~Z7@0g5cxStYp4}>1xb^LE``TY0K0f?d`sVY?zx_=S@^W$e>%Klas~sx3vyJ?^>r-Oiws@ARPf}>8oBmb z+f5rz@ZS{N7uV&xhlNk+*rvA|YWEmzNOYYT-v7LSwI?-mrj8mzfreX0sIGuc&AaMV z>R&G;9lFJ}Txp9}_Sz|vp8KseWk0Uj$Y50`lBl!PV8LR>s)anW_eB4EyMiwwhL>SN z;tOY&6b|`9K}9E)tNUzspX26JQaG4dYPq(*zu!tt>DhXnHf~n( z<=U#IbZNIz!Rl9j{`%pex;>{~mQ>Z`9<2Xee!sTn|2&Jv+%3V0dHS0#JFS?+8c=oeq8O& zXZ|5$twqZ#2Zk@s?G7%#_Z91wtFb&TXm=3FOlD(YV%cA}4s z8(0JQ*6W8WW$AdYH4E)%ogr>yJl9(NLA(Tmz?(a9p_v_whR^TC%Eo%FwlkZ(`|L@E zkQqv~W`Y-OuG~$Knm9dZI=i5o#`13!eH^9gRv+frnR+lJv+->W5@%9aW0e+aY^?QE z<$|H3*nn!>n_hzh#2F&H2BtzqhaU`{Uo= z-`Car{iVI)&ho86`uexO{`xaJ{_m+}LQD%^zgcF{?)atW*v^%5E5xd9mb1B8C>>eK zwN`yy>I;_R4*s>FKYT==&R87u=grw$!hIr=M4-tAw? zt?$8h@s?C#wr;$@b_v0i0^B=SRVi30E{P0kIqvb0^NrQ|+6h-~U3+U)^x8$sebkG)z#slUNd(3xlLhScFbuG8*frlOG@5P!MKS*ms!#$ zo_{GAG|_0ijergFh29PW8AXOU7D`SLOLRJ3i0%D8lf~Apxz3%T-{eAzwrRPsG^*nc@Yw1nXMf1;Zs5<#{H^a&khfb^CJ$r0UwWE%T z3WtnOpIuJg3D!dbtekgvWi1xv>mLpYefn@=zhBTH&YgYx90M{#eT}oWPAM`yExVtA zp@G5E#W5sBmvyIfvVtRvQP>VgW`)gFJT`nL(yWS(YUh@(=requaC)|Y?u2e>1=-EN zbM3b7+BKWERQ<`N=KWsrK|bEY+6*Q=f$ZkSFO;ecKeWh&G@)F)-SHc|L;rf{dwJ61iwA}`}^s`jT`>H^soQ*RllBfp1XRGK-c@~ z`NiQr>f%?UA|pE1Pw(uo)O0=a`S9V#v#%!_em!-@d!_U9r$05XTr#&ld#!ZlvW2I% zw#~ks(YikQo~3!?0au2EbA=b|t{kvA$aO3xV$!Lrr3DjPGDIhKC@ehDBkc-2dEhf#y~3zim!U~co%c8jk4de<&Lee1kw zTeqsUOzCWEsl0nnQ9w+}Yt^iCtSVkouL|QCS1e`_SeTh&A;MshX;mIFwf}UP+?6m# ztp*m21hbIu47#o7zfQe!RB_s6W`Ws~A?i=2c;315UpJDI|9N=A-x{ms3YBM8_ANEj z3l&qFq_lQz2E+V$+uzq!e1G@&xZ$yty30Q&ZEpV-U-$W&e*NFI4?b3KEj;hjp;)Z5 zbjpW?#!Az^%6Ly%wp8!@dHv-U`lmN-JGzIxN^{Pvm`IBW%OnMaC+u|mapPO9c)^EVx?4Vuhv7nGTjFLA&(xjhSV~FNqbGUf&|g zex{+o{ZY;$-;Sl%PCu1wE~@F@GHHz=PlLdPGsh(O8hH#(-gWm;;`*>`tw@`JgWsv- z9YTyuXIZ|s7)H!<`ME>>pcTW36uDw8$-6!+VuG*Qx)>|`%STCDB2=hQJ+%l>+^tk1_oCZ?8Us@789s~alS3^uDxD)MnO+I*;}_GZkn+wJZh z1f(CH!$yP(ZRv zLC{p5w5d%ojQ#uO$++_UYP$8|fTZ<~$u5eVEDo$DngR}jS&j`ZO%9ETa+l`{95zf# zXe&>AFo#{?-g09hVbN)p=6vibnchaXm)*J)wc6F?ScvT1N%OhhJ>%eUVK7!Y^Ze%? zAr@n0Z#@Qu6Q=!^hYA>1v~JHW{+?2@__f^0ipcuso@Eq}4WC!rA`` z+e62iPYz06YEx$UoxPA&V*V`2N@J1I`>f|rA3ooIV0yp&FT3es>woX4`Ss)3+1dQ| zwYT(t$Nzh`ogvuekeYYMqJ`GN#(`-XvsTUIz5RLasl>-GrdzJN;-#6iit&V?%*L;R zN|&NqmRA|X*DpE2#HYEr|H9p1Z5{@Wb8}9aCU+YCy2EDN?$2kC_CX^kt7z}CoxD?A zTevn_YfKba@?rks^;5MIlQVU$?_F;4?2_p7Mv>|oi_f{&m5OszS{0T_E!!)%z+j#C z)8skQl5Ok^E#FOZd@ggitT|BjedS}@s;BQPz8;YgWH`sH;InMj{^_fVYS{j|AGs6j zqE$FmDrHTMp%(X=(5&L}bVetwNn(}K?p4*hm=7uoWMyxjEqJ!$%vEmltnMvbDO~zK_OQteRNO=SE<^q=`;#KEfCOYiU;hwSk;4Lq?nCZ{E zt#y`{EikiDNcvWobK9d+qv>6nMX~#bj!D~Q&6t(veXvX6jN;a-TI;4=UufMjfxY3g zgqqCtyWWo6PRCO#KQv^nv#OT=z3=v{!a!ac@6cu(@ z?~LZ@WUxCkjl;=>(eml;qayb;))ukE{@mc`!t!DQQ*(jzv&)k{&UY?n;O4 zGAKOPEqhUX#;5mkukX!wB2J7d{R{~$hk`sj7@KdN7366s@!9z>=X&W~_jI?3v)*}# zb+y4ymTv=cD@9g}4FL!T@-Cy^)VrR^+%We(zzb=2j{N?-q zH|4nt177^T7G?OlW8&^kI~|7Umv_#acJKGLT&JZ|XDupyeb0rV;C{JU)U84e9qWXf3sc9vu9qp z?Pl&N`IEhy`FD><3da$~V+tWho~KM>U@%f~a`Ktk%KqWtH-p)EZZl>IPPt{2pd|R^ zz!H^dXYTWD?PSz=(HAgdZlvP%4=)%5nmU#V7%LgCzhGh;pvohy`)o4f#Lk@^Gb>-b z5KidJ*8Wo?4!P z{GD<+Aq@#iiypUZ2%mbrJzSuTLseqI3`xQ1mt-O%6m0G=H3?0R=u%K_@t>-8S%x8N zE`!Qe8H0#orCHmxx2@%7U^HMe6k6O8t@6m8ag6{&#L~1w{ta!nXB@sN;mOhKlqYlnoUsH170 z&YA8M%{nF+vFdz`-;ULH&oe0%b>H%Owf^G?-48LMPBWf8F=yV{@j#7x=R+~C3Kxsb zI;&?tln_49u^=S8bZcYci+5(sIkDF*&oWGlx^w23%;xk}mrtMmTD0@hOU0MUQhb*= zczmWS>7z&) z>#)>46U!_qG=JjP39n9UTz~vX$EhW0FP<&)aNjEYfWsq3K(a~3Nz~xjMujukEgX#; zwimc%|5m4lhXymN&pn(ecRnKe`qpc$jY=iE`jnJ>m1@rDwQ{i<%+yqxJ==Q0;)25r z(vv%beqH?SXR<@=N?wHi^h_}ooAzg#(+)7qwmz^ho59>kKPXqiph?OxvQlVuTYw{v662QI+dH-x9^a;E{)YXEwOmZ<(Cgj-^u1?y}x^Rd2|-nGAZNf z+ait_HcbgyBGT~XgM6XqgfGI*{2doEV>UZ+xhcAJSgbu1((SP#vF(In*7*+>-bTDV zjVg*IR`wQdH$+-aF^SyXwzDbw#f3yZA2Gf>9q)Is-!?8!jTK;3-ZXjZ#ELTin6JD0 z-)F7eTIbqm$r?Xxse!O#Lt~|v|ACm~Qx{LLR5h#;l-eO@V94d3>~ZY$jBDTfICOM* zlzKigM7#>=cu}*2Wkcn^*HSA^yjynpxWvxNu(>WK#~dD=Vai_XnJbxZt#rp)XiCsz z`^HNp?*t8Zv9^`|e=Pt1&vN_!8RuH3FI~IbHK}L)zN$ZO^Y^)aOPn07nrK$8DlKd% zpn7J4x1txxO1R#2&YavG-uSch&n+*Rzri zjwMP74+NamD_+b9)%cpoa9~RdJDY+dS0ht{q;A0PIn%DTNNX_8+9@Kj%xr20OJF;b zA@5*OY+MQ(PEk33B*&q{#}dzkcw*)@#vIWE-Ll zL?-S?mAXBLO_{yH<8s2Q85}GQC(Yj$E3hqoG3QK7#{?%^r8A-JU!81@uST3A<5)FYi)mrdvu0`S6hwp-3SJ?@k{8{sE z?{00E4GVfbTXwF}E_HNadi{Iq%!4&I8b53GY>Q3T=zg;}-sJhQFCRWM=vXb=x$WA4 zGdX^Dx4(9su*7p#0=J~I1&{gM3um86FwZ>E#%kuWNNBqOTlxIy6Xg?h-=^*s3QSZ~ zx?5g8-#Pb{(vMdkP1b$;@=y3>kZaeJgo=M(zQq4KUH|j1{{Kbs`ma}Cike$|#*%r( zmdzbsEDxK^@X}5zyK8Rn{Ls|ZNoVZ5vjr7nJa_ne%Y|$UW^qsn;^t&;T0SM&uZhEW zi-6{{4MFZ7YVYgo`!5wy+HU#lbm(t~NoNe_W~Ci-nmhA~V@8|%k(Y_C9M|{p&;RSy zW{_Z4E)&Rc+`!>FgXyelbK~3Wb5|^jxfgO&#z!!Q>7pa2?whMWuilz=(_M69$K@zN zHDSYW4q+BP^==M!A@(O%_NZ%l=kD^f`BBqm^v0CU_yQcSRH>R?S z{|@*2+t++geztmhtDM(@mp=wA!dWWA?HOsk`gAnzyd&VX@;$QW8ya zZjIXA=5S-5vGEbXC(?lef`^V|^Sg4bD&5uG_FE@LFz5Z@yVFZmCuPegGi=V$*Jp`| zP;!u+5O6g*d#}506JP7e1839?e1bXILi8M7JV~tmesSrv+0QpH1ehPt@i?2-_Dg75 zDPvD?t-)+FwdrmGX=QQ#f*W(IT|%NhaLf&J;{1L{2sGqG`%u`hUa?eOW-Tjxv5Gn+~8xYl{_S?odA z#q)d)PI24 zOv010Eza;Qo;FpWG35(m;_cIXa%;mTT{hcxi$n7Fw)R^e=Wi>Rc=IsNx?7JgpAOHD zi;s_ExE{3eySCD-gCRTO;=LXguRUXvcHO2)eF-De+YME|#nTscP0udpXL$BlK*hmo z?eo)5KYe+))K*7D_rjHO%{l)%4V+zA%iDvlWxwBC{czdghUpF$@?uzWs}v@=Owih~ zT}RWq`t7^BY78q`C+^6W4;E#8^zhHuY|H)sKU!Z^tg*WO>)FGHzvcgZnZN$Fe*I_f zza}eBy!`U{UbXps=Bn`5#xlw=ZH$*cAHDPU?d|JVqL?l0#a5mbWYJU#yF7imc#d{o z=Q4eLf8mf@mtJYgIF$=5+9jgn67nE{J1m7i_w>3war5TStn}I265FopefU7$Gsd83 zBb8f+ISsXb7}T9z-*l*;Eqmhi-*zTb&IzxsJTPZh|IObLWwk-GYYiNyI^BqknP+Fa zf05G6;~^}9XH6PhQX;4M&)>zNn_W6(mq|igq~;t(&MgWojRog;SQt7wSv$Sd4jC11 zU{tc=WNJG+*SKa~rp>vzjjLu!CoY(6;;FRdtfZlWQreDlL0-B-2@H7?9c@>pD3m9B z+hA+tdpM^oI3USP%_ZePt&Q}pBgfy@{d)QL`1H)UB|4|8qOx3dgnA!;dil8G<5pc3 zn>M53vpm91{WrGV`~H_Tj-lbesZ2q^sPpdJ8r((1LRCwaRYY`OcRJK9%#tb`%B8M8H?oF_;YJ8kDbGR%CZ4RmGix4ldhvJ` zmSo)Tn9(`Gpfg>DgD2$1q~LP_OI>C#`fe|tJVivX(JE;vhe%_}t%faIm{OO7tn6hf z^38qcT-r0yYvS};p*3@Bl{LYDxbRT6VcN6Z0Oy|45Bx(wWp!G^qy?+4 z1#K(5YZu#YqM0V}(D=^pgf#ct+csvKJWP#TwV7kuCXr_eOP3n(@``gw|NVLR_dcC; z+hW(dNSg|7M$e!levLttM^=NpsYDm-lc2?Qq()$=$DG_LpCO-ao(Y+h@lwU-j$1zx&(&|F8Uh`?K5|I;Loy z)zPa}S9*Jxfq$8K(lgbXA1`CGdA&sir=4f1UUFj_FLQ~HS2VMt_fsVSlf}#LGz5BS z&fJqE5cz}2!sX5Nx7&Ekl|EMP$UCEO?8!S&DY1RzU`2Z zP&T6|W6vgjk5yTkshO#<;oQMjUw@tTBkY-w!JWjuKPeXO?3qhE*w=6vKHkW9?LtGy zvnNmP-j%(3hD*-t;g=MZ#ceZw-w?3i5VV~CphG~#TFH$6?cDn=rkdv)qTg(|^|7&A zrtU!0>YsaDg&#{;9XsJ|YdL)-lP_Y3=`6#7(T32nw0dD!Qk^kWu*_#7H#NQ^8a;*osN>$x%|41o5T6L zcP*RE!+huO%a@#|Utiwz?c&O5_W!@Udwjfq?X`{#hMfyK43ZmCJZyJ4vM{qHGAwBP zEn!x)xO%dwhn(}z8`q;cPaE_eaN4PB^K0W{b;mP@4ekF_7bI|f$jNFkOuF$go5^7V z&s*=wTd(Zn75w$;?c>##Hx(4lZffb6xsK1_^{!pEPwT#HyQdsl|NY%vdHcNQUrpTV z|9_m@VZl@^II)YV;?8%*NAv9apR2_jHWsr>4WIF4T{VmO$Co8mM`X(kn1%FbNGqhV zF|nL*eetv?((BNhLnfZP+^c@eeo5j;Yl{hTJ`iIvKg+u66hrvtk`Sdu{`xv=4U$Uc zGQ3;W^JR5>{qOz%Zr5*au(7vyj!dzv{r&a2{l5=?>woN?|L@1&{@uqO|6-F_|9ji{ z*a=llC-NA51!p{ZbIa&;N66m^dl+KA-{sLv$ZVZ?HK)oa^m~N%woOm;r%pa>^L$rX zs4w5#$Pmq~VQy!t0(D)*=6Uo4b+PQuj$%DKx1*zR<|Y61G=UkB<`WGSJ(7E-=l=e( z{{OF-V~bzSN$M+#l3y}yXL<7m7YivxZXJlzVqe{zg=| zup}z`&8fX5@W~?KS->bjHgKf8_1^s_r|9AcU zzaQuCtNMEN@nKF!)&h}~B|%F!WtDT^IVSUN=Horrp2^wovH5%b|F`Y6J=fRY-&b8# zXQ=vUQqxzJ{hxEYXWzWflgV=a#n}}5D^bj87UweGC2ntA)@~}K#CA$xpR&72`1@Dx zV*b}S+9kh=B{VUfc_SdEw^mH)yGNdnoW@V3)V*OXMJE>z2!SKbOyc*8Y9^>sRvear=J%TRvr? zVR2&QQY(oC-5i%0y^Lp<*~qywOtTJPFlG)uRn5Z5ASWmH>A9J)sqn;2TMXO274A5g z|MR^|$=VG}{eVSkX-rH+yk1wBox-_rD z;_;3~1t$LQ@mHd5uQZ7AH5YaW58iywdY15m!&O?|yWdq`pXqup=;zh!UAJ#CD6AC? zmlb5}>--Sye?x6o_G(lA9vd$O-i0=II1E0QDKyM*%$;&b@CC!F_AAGESPh$)miboS z%Jr$g%6Cu8n0u;L(8?f|{d;<9Zrg4V(c8M~_qE>^FYd*iVMyD5_WtJ^rX~zG7B9_6 z+;w+;xRMjU+oymuJ0?zHH@Rs^5xbU3r%PXy_;BpTE^XG$`*z#wy{tK_r7VBMsir=9 zlH`OSZHA)P`SJ7rUETk0bqy`bONRH#75jads$| z^A;@+F^9{%-19un9E#=seB(FgqriwB?WFgg51;Pdq9wKRc{fL*e`fKyV%EQpFRK=E z@4US)M(=f%8sF6xiHCb{Fn(a{nv%ahOi}5|!tCe+Hq1h2KQr?%oqA)S&YW8!oRn8JW9qfrn|g0`oG3 zEz0)qUK|m$P?+%I|M6?{x1QW2d%f|&ro{a39cOF~%Q`TycoYb^FfFm@5L&7gT6#U) z|8D&j`Cm)rf3bgAbM3>*5Y>rF4ox%OSGL|E>`^>L{+p3T3eDnF?ZksccB_$ue zyWN(0`r52lJsm+_pO;>n)oo*$!NNB4_-WUa{b8aLqgH=4ySiyfqlLAZ`MNWVXS@O# z)|W;k98z?R=$oDL#$b9`)9%H`ZUwrkM_pfD{ZYL2P?f*5%~?|=iF-9KSBp2ylw8Xd<}|;gs&G!;eAb8E+xuCPo<(MQiI`hh?~i(Z`6tf- zpYLJS=N~mktGrnkd1mRph5YNKr;7Y2&}NzIb!5W#6K`%r?Y5MAUdiT}y|%;f^DXIy zJi~j=uaQROwG!DT6uh%x4)aV%JNdRz3cO$68E}eT2ZT4xk$*S zmp;Fi@bDUQVsf~iNW6E$Om# z$`qF#Il+e)FHcWj!)Gaa(X(4K@X#t3kGok)E2k=J%nc7@h!B?G@Q|6%>A?H4ezn)j z&e>aP83Z3GhXyEgYieaqboUXNEvjT(z5UDE+vOKJBnwmv%XN>QQTlyq%cS)5vi;|NnXY=Qk1ie|!pkm??C}ul?}D2S?`D{(Jg-e*Mo!KfUL@Zhu-4ylm$b=asG| zhg2LK7hbKNedu}BjbCzko2L9s)w~>@fA3>S#pVZ}mxrdVYYkZ4V>R`Jj?#oSvyOu0 zK}`}$R{j^yW-BK3voTIy(tb0dr)i^z;KgfOvbTN=Z!mMG|ca^A+-lXv=sGCr8d zdatcK`I}he=I605y#y_vD)voM?B^`>TexL|$dv^O&o~O%GdTszH;A0Q?5$nm!Q80A z>3>j!Z`y~J-N6!#ma>K0Xa0N;w$bC&okdZcv%F?@pL0rDb$41>*6CZ)>cB<<=gb$ldJ+vaVLnTRO+dMBm7mo7+&}h|-DYlUGzWv^t)7^5mhP+?L8I z#@r7SxZDg{H7;0Ge_x|&!{n&q(7>T`#LMNG?)<|gzZtLH`Pi`~o=rJXysxBAw&Y1k zXrIS!2X@}wfz}j#|AgD?i2VKY z;`OE*4{z`P*!Xz^jWedscqpfL=FGV(OcTxLW?G3|jF=a;(mblKxJY1n>B)I>?=;7> z%&JV()HZwl0YQfkGOCpqC385dx=i4Sayeb9 zCARECfwILyVJpTBev4PAt0ggUt1aExwY^x8KO*Yz@(>w;$qHw-tl(&!IeThm*PLYb zpA$U2t0P|RRTgadqPRfA=M*Df(@TRg{xng)6dw9B90{V9{_AYo&(ako(Fkwk^1 zORBk)Jmt|~#*neKjlFegmfZ9|=MswE%lL?FE>ag- zXtF?>=fJ{$2``yE6+LN!VgHc8#d~xrZN^-H_JP;ry4CsP*1Qf~ewVkntxnDO;CJTkYz}p?_-xbm%6^9l0TWN$>g9Tn zz_zP|-|gw6#lfF1u5c4jSmGq(P-nqa!q{KvrPS%N=7`JP$W~^}ZqI`+^_-7*H1hFS z_IXU6mFJ?ld=8uEl$Y%lRx)4W{~hJGtNQup?(Xum6{~_?v`*1_x_o}kC;9)U>lt5u z{?#^D`OwYdPG&2<>jio2_&&9wP5Ol9a#q=KM&#D(o2A+TGB=wPWXGkq)lN&V+RD_ZKDjYbK~z z9nwnhFyEHY(fafjgCQs9&gv}=0@=TU`7au(9gd zqT}r@EiFpR+f|OJGPJW#nOUk-zFp4XaKRjQWB+&0js@`@I?uPXC}ZL?ze7AqE!Q-C zHf=t$G)d{y!CMDb8(eAcnDO~DpYIe7B@e~KB-aT->{5mTr+7AVq^kNdGl)G4@@y05 z{Pb?w2F8O9I&=0&GI?@Y?_kyn+GWndy3tZqY2%{JA9k(V|Lcvhlg6VxCgEl+Lh@`2 zrVGq8HeP5L+7NMbuf1-1fT*3w+mBz$?9R2{o?RNtJvC^VlIY*U0ERocu@g(TiZvv% z6)7Z1I2t=VT**Aamc>(Z+C%??hn^(bHYo5d6cQ9@)2htyWEOh)tg>|XSDv>nN0ctg z1{8dXw>+R2nzfbj=Qjt7#3>1eChjvC1fLb{*;`v>Wv`oUZoU2LEauf4nojxG{j?OU z{l0ki=iBRN+3()DJNI>!jZEE-!}4}6KVRza`x5678+t~k=+Cs7o+jd3X9&7H5Ks+$ zm(6lxrp}VxoBhPz-R-EnR5{6cgY|Q!P7CLN*Js*Z+;s>Gaap8c?3v0SA|mk0NBFgh z<%(cIS(n7X{9eUk+lJ`uUh`c?7*29tiB;}sn=+w8AY3r?y@=34i)%^V7Qs4un`ZCa z8h3gPi(*+ts$ur(%PaPs+|25+DD+#J(w1*xiEHKr-+YpsbD_qd`GiK!sb3u{W*a-Z zM9i5X*_rrsU;PtNmGPk7jIG;*Zeo4MOz>JH`*M>gYhE&1!8_x}w4|LW=H$J6K6 zU%b04NPUs>%bm`84gNLND9anx{IOU0NR`jtS*9Ffk$~@@H@&4S*0GCBR?o<1|?n&7=b8{sB zY>$b7?T#u3-@Kc=VK;Z-=Bb@)+FO_Y?wgdUsvr;-5XQtk)${t+l?+aI&sG~u=KHJD z*O7Tn!L9Z!i;@NZ>7q*li{CYHxw1G0Pf-%y#39dD96kNgwRgO0=302Yd=}MlV^vq0 zar(nN4)Jpd?n*{GcU31EISFsOR^iIvqsa4cMU>cQ1NP49`-cmglpG%jI19JB7cahc z%f{Eq(NRCjQl(B~HpOEaXgD%_p9Vv>NPMOBGz@%wq*$6dGTYnRU7-kR8R zL+Ql9>AP8`tmF#b!KOH&t|ia!i1{M5L>GlD-LPwJj+QYKFU@Idnkv@4?@Qd*31xwy zU6Wf5oq5$0`TR-ld8OW~cljqzQ|eb&e3Yr>a;571-r$830=UFp*;W-hiag}qSeW!k zAkEA?^<}~r1q(*TWHemTcOfw5FT0m zxxk>cW4CBiJ9|g>q*8?$-cr(EuI-MF|GjHnur5#eB;zmd?W%w8xK+OYt7B!&)jj98 z%Ue8Y-u=n`-_Q5=3Vwe)A7|<_cVmnHp^Rmc_p6E%ZNm(f1)UOp)*x{xu|RZdhKq=Z z$ULoCg1)M150^GaPF}E3=(COfX(t`YZhcEYP9w=)kM8XV=Ga6IZT zaFgkn!EwqvpI3VhSGSSv(oF(^g4~NsEoJ++Uz^Qz?{}1l*s&%K{|V;pzKp?wp)8f} zv)|iViYf#>+tT&cW34;avRaSXuP+`*wXI&Q@3v`g?W#zw`aaAD^%PscF1SX;sY*OP`>Bx6jxAyk7tR z^6}3zGn+O=Cu_B7EYe&oCg?Qh%f7t8Y{SQWevS{Frl{?jqaHU`si$*w2H&YmciJ8G zrfpfFa(ddS*Snsr<2mHv(!$_>H!UW_ob8IG*a2ZR2Aei>g+G&ScXmv^{a*0Tz3Z7% zmZ>SNEiF%0TIVCMK#-MDg}JJWquhJeDZ5Ph>^nU3Sjz{UtlLq{KM1 z2ZV>l#J0HfA7kd+lo;}0t^V5e1Y`6nR$dDRTFlGQ)wBt|}ai_b22jS5)w9ZDm+)0(GwRucmJ1jXD*wZ4ZiDOv*z`#!mDi?uK#1)TJSDEj?c(4w`E<8 zedKo)UuFBbhczt@3GOu5agbT&o!*2!ZMUb)=IVC;%<)NJ!_t4jc9T^E7JlLKkA3tY zD!cotpvf^U7e`Oaz|hc}FLP}p1u{R_xUFb7b?aC7`um!XcFW(Nb1wF2=B}NQa{lJ# z?ec#f@UQ)I`2OD?mtWNCUER$+{hoDW*4!Dn_0K)E&ATIVTek9qR{j+Ht-NujuB)+k zS@7Ch)#l}rk7wF2z3dnHeL4N%L%|kj+byZB?afv1@Aep-Td}P-cCC`1pF)pUiOu%3 zGQ0WtdGD5N*woY58)-j(`cm_qR{1}DIv8{$eRCcsS2rYCOUo&6oDdL+EVT%{Fd>sg zy-B#hds${mgo;42N6FGno=Z+BZ7MQUiJ!onBRPG8aPx#+NlUM_1nd&pp0<7SzpL^8 zUfzwd`~Bte)~|;X=RGf)u{`0|P4)f%;{W}6Kku5^+3nX3-<`QdS;$YxK)}ai-fCeB zod|~HDI84Pl`IE4Iy;TdTNXuia2!}}mA>5m`ll!J9!I!}w@vSb zBjsA`m89;(J}9Th&^Q)>Do@oCzkOSTHO+Pji(KTtlT_BC<#lsD%- zKlEn174X>hOS8sn>kIRQCOO|!ZZoQ>i*;do%wAp2H-TN_)~0!W>ZwXvQdiBTlsV^I zh>pL#BdI#<-ejfR+d>Tnp0|z)3r1hd-nP8=VDbK6ljon7EppB6*xai!6*yO}}Ta^f_Pm;! znVmg7e(Tg;CZ#}if4daXXF(?Q6Dnh?&9$RMo+;K?&7GE7TT!8q^uBq)Or2jQj8E5I z5J)xOQ5|Q>*eEgS&ejPXPyQQqbj~e&`Cd%5(>hxLJ-6FRI zW|&oJ7zc*r*O1N;8-L`ctOob>z>>)!YBS?yvh+5cmA{ z?t6Q4gp11@73sBQ7gg5k-?ysM&plM#Xpnrn+C%CZLi#fZOu2;qyx{coX@?(Xr1h>7zge??$*ka)9Ep5o z0^!#>GS(Je+!Q!r?aZ9;lZWoB~z$iyDe4HD!xd7`aq#XHPWg zie6YM+H>sigqJqfPW>Ma7uJ3IlNagt{d@lzomG-nGiR883VMEH*PJ&eoLxFA*oA_> z)>e11T~;iP_!sYKr={4jWW}Z()#b`vi6(KO-7uk`Txc1O!aE_AMF%h5<*}W$ zy6D^eclXxlo_0zNOA}qT^yjX1*OX(WZ6$w&2Q=Kic3$XXLM_9yeQQ^zi0^ivwtAl4 zrU2ipC!#V}j3=o5a9HAH(0;k%Nu*oQ?^2~jd1ofh?^E2kFk<7`Vvi|74_h36Kk|25Ds%cxVviZ0 z=dbRa)tauMcX)58mgp_COv-V}duZhDld*QGgAiwR`RjE(DmxD@=96FdG3fWaoWAN(ras%W z_fW#G+zEH9_nvD@esh24S*FRaOEtG0s}?hRk~L8!ZobMYMycRU9?A6?hjnwgo=3fY ztJ~1t?jCvW`PT1KELcw1dMfTYnshbMbN2KJjJiw?q29Wo-Mi;E&aeGBRpj4`cV&^& zBD>7A{eJoUdo%y9p31*}>iezCSvI;~S9(@rafZn&et+e>GkMceGriCIn{RrrF^A*H znJHJp^4S(#i&~}k+RD$QW1E=4wyjxdWpCy(PEy;jZngEbYdnrWIU>9GjCC#@Q*U6& z34h&kBG>w*iT4~oZ_62NjEA% zu|cK4amv)|7upq)PMv+8(2?u@WtGDXt;G{dC%xRJJEOv&Gi9FSoz5KVIfrH@>HT_M z|IgjL{nP3AKYJErJv|(f$5-s~=hbEXf0yI`|G49Q;z`-Hyx^7ZVq1f}Y;_heZw_uM zSoNbpXSUXk%WbiF9WG%jz44SR9|HOq?_B(@g(c(g6%yOTkcNx z2;SD1D(rghGwY&|#UfKPSBbT!`B$b!Wt(i;m3Kbif`L*|6P8Dt#Is7vijw}|xqptm?k0aUd>5QBtwi(k_PjO+6 zJ+e(}k6S=k)QNpIe3zFvZRI%7e%Oa)*}pAoo?m2}Snz40lBeUU%=YTyyRpzO z#$@6PuD-tO@wP&qbLWeG|7oz+_S*5!Z{K#?|34_dc>4T5k0$^A9JyRMSockP=ClKc zcQLefE?c?k&9umA>M<2-CK(h9zRgn-Iq15@Pj#uQ zs4lp%+&ucsnLBFJl)M^)BKLfmmp);4WLJ(*yQ#GFtZPfejHBnBtFoG#Hmjv@4*zMU zM=w^-G`%Hoa&@!Ip&SRLk`tow5grYRwqI7QyHh3qo#US2>9t)f?GsA8pXsaiHQL(s zHXn_Q?Gs{7kgO7_-a64(^yv)I`cKX8_wU&I`{?=lWoy<{nO~ossdT~L?&lx-KiB1L zCFRqfZm@|rFo%dwZ4EOPb!~cnIA*Dm!L-Ue2%}*3T)<2zjx_Ghbe~bA6XO!+jQsZ`xrY{KqxtSCkvX^PVi0 znRx8_j)cyOwY|^YX$B;vEozONdgGvu^Mf@VZk@Gpyedlb-JQ2BzuKtq?1cZe#%aDsO&5Mz1=4s7Fb-#T<<#L<~FsfXTI;)FMnQEcaL7o9@Ct| zGXgCW?UO8Lq;Rn*7r*$|q4s6__pQli_>x`Lva?^X(aFot=RC(~oZe`#`a<00DIrUQ zmwc>{+nZ;rv}blDJIj%YK8*bd6AYTdEc!01ufM;q>hG`ke;42TZ@uHg(zpEgb^Z9d zFU#w{>^|}>{@*+G7PU8dEgeB2Gc5Of4=po#=n(0ib?@|!Dfd<_Qc6jS{I!GKro*FB zdWLJp+H1MCy2l@1ylc#8puTO+?!t*jgEdpHS&4?S%@B6&mw072OPYON*`j-QpKkL> zo%cL)`N|bp(cz)rUYtqUw=3NIboDg;g_Y-~1yu!Y->GW2ae{@-pHC6-+v|Rde^*ai z?c(-bVZmB&soK|W>-POCRNE*s$>qm`wxSIVG5p1UKimIaJUgiF@8)=cOcwuOO_yBT z`k$}P-v9H~-#;v1RqOQ(-g7Z~+~=xqR!dbXmshJyP!jZw(&7;Ed->FgL)D`_j$OsV zT(0PMkCKv&r_L&_#%sJz_ocX9SzjyFa&ji0Bh)%MK{-5mQNn`vQ}z{Op9pG`Q0B3^SvZexsA zZO9brEj7)5|5Qu*`h1n$LcJT(jPx!HgD=JM5xY!c2sdDFn6@<1p^v4=_ToY&&R zse(Cm6VCV)B#NY-O*Pn+bK%5_sLczd{e_yor<;e%3a;Fq(OI9ez{`e*Z83{~vzV>a$Oj&KE7PwhT0uICduq-5HayYhv>pXM!``M>FRHlls!|jt}88y3F**C zEn}Ine38?`Y{BF;T85!~@q53v@O`#iv(+FtFfj3)rdaod&_@@$BTkE6JutI%N5Dgjp^c=_F##KsU0Tq9<{mtjn@{>pI&CA zzx?y7cl)-9U#>Bqn!dfg(VbJ!d7G0B$BRG@mWj_&7dTk#TwjpBQtuVd%>SRm`Tsxv z|GfU+mq5NW`E$S4eEE8J{l3B@`+rWh|MT$szT(A(QzJvCKQ$5EHS=pk_jbLYDI#X8 z@?AZ47|G}!3g*q6rFuh)v-ho3$0jk)f*o70-99C#6ysGHb$g+PQ85Y$GPRNFBx4vXTvGELd&&<**lw|amSUDYulp( ze?GMTyS%)-Jbqs->%ol|a<67;r9YTHzwY1B*VnW2&lG&$YToa0_W{dR{S{WlpNbit z$jv&T%71!BgzD|`-yDax_bvs|zzvd}5v!G)aYB;rOS;=O@fn+p#ih zfBdn&O{)z)iv_!>Z<_jC>3F7{&*g^&J9h8hy?d|ixzBa$H($M>>9c90lcC9>7T1WS z*Je4d?pSc+$dpjVTWV_#-M+u?TS@BIxgBRGoVq3F7wRjXA7tqAdEaxh_*lrXZkI9Zx9z)RSJ<}Y zoi+ScTorr%oq5Mk+aopZew(8Sud&kUjy1d2#o3SNsIT>z>fxpN`J=#;;E(gp z_pf@VJneMyv~bV;cI$81&5f;<4tmwpI!SQ)o#nCaWx|mg-~3_ADr}WmvfxR~>P-h0 zcgQ_I9J16Vy`f)nN2Q(CsZF0JegEuw;}=g(zv|+SfDh^Mdw%Wz zd;9+e8@nwHQ#)Pe-`l&__Qm_Z2fy3>J7+FwyXNjaIev2xSh~bfPSwm{*Js82X4`k7 zil;(X8CATEwyxaS-|^(SLiYcA)@M$#x!%4uZSpp&OtT}O6NOH4b{)2vE9^Zn%6qQc ziL>|joqq1x(ly27G?#R;r}1O$jpuvff0r(qGlxlni+ReYbJd&sb8WT?N3Gx3{W{IY z|4i!^_bCjCo63c=zH@xvc0PAjDfjwSg5Tbh{d@cV-?g_B_x<=2`o3t*)5GGbdvx9Q z*M0rbeSV+SR*{en4&g+-tS!mDKjkVsW?Qc1kvjad#%^0VD^pBp=cSfY5iXT2qMCPj zT|!*enljvd`|>5H&#FD!R)n5Z-LrdZ(5aADueOz~2+(wG+mfGkPGy>v$@lcBa_{ba zQ_l5q6nJLnS^s`XqeJr7Dch1cg_54v9?ju47j!jwap{Dy_p3dZA3Rs}oNsW>D{J@U z%}?ISaR|At6ZTnJaOl*Bm9Lilv*3$x;dsfaz!$EytaoOJD(e*{=l3>EEY8d8PN&p0 z)=rX|;H=DH78hBTW3xp~*Q|A=ttMBYvUyojY8h91IhS(r`><0XlTGZIcq1PrPuLsr z@nY=7e=YIHAH81xUNFEVq@&e0=XUKD19l0%b<(`wSDD?KX<{P4JWVmr$!E{WwQ&qr zPj7U3bLHO~<-2n^4^Dr7W_!r;Ll2$9H=1rfH{-e0*3_xF|2npw=^HB=PP7O6LSn$+en=c<4U;k@$|Gtm2kKg|#wzKxa}80YVjvpsYs zI{Ny(&{huxl?8dzPw*v1cu#vM_w=)%VH~Bg#LJD5j}lg{2#2&UEeU5V@9N zWv!!M7nZu12~NMc%>A9o5w-oZ*77e`QJL7Yul>{HX-cj)c1^Cd6-nE4uhU~{WI&UP z$NCG=vTL3v@L%A#85zxRsW~b3ZmdT$dyegzb2Gl1wjPwZs}y@7;l#!O7ZF{CbI(8j zGn||{L19Mp>@9ch$}Mj)e4y*!$cXB#qv;L@N_46@K zwLlR^fY;k&%aY*7Co6Cw`-TT(5>l5zvb#Lx)E~o zv&-`Ukz`l-HcK=#IQD$E&yKmv#R{C)U5w*1WoPa0_Su&zf9v!s zAI6%!7u0tyWpwlUa>3;6yX71~Q!A~WeW;wZN-I3PynSb+L{jAK8SRa)mI#!U$4{5( z5?a_{P_5f~Hn!PfQ_8#BQV;6(3B7KJ54tjMotshjg+ozYtIpjzd}pW0vGaDnzy5!G z-*(Rr`F}}O|32M4e0cNi^!0JIg%x$*qwoLQAoj{F?c;|J9}cq1-;?QI9{+FEcfHg# z*AFtyiRNuwTg9e0-%5;SVYR`ZSrcs2_RPL}NFi82G_*-NBWJ@FHCC5~uZv>T?PT(H zt^LmL&%jwck>O}jSq>Krv7zwuZX)dYm3W7rJ$}k(?mDsH|}&VZ`W$ixDYXQQEh`-PTDhG zizA`JY18fR3O0UBdyp(KWBo+G{Kv~4Tw2cja={!^i}>SHUSHwnN-bJc`oUPkjIU^! zfY?HocTDD&Lsl*EaBZ8aYtbeU%$auWkdIB6h;BNizQCGSKaDXA*%c8ouAUvfYf!im~PvDZU4Leyn8R^WX8w38Fjy7uJj11e81QH_1@J6f-N(;PE1|)r*6%+`>El7 zR=h?O-^U%d0Z?oVTWnU(U-_TP$zy=2LC<&RWxQ?(Q)^Hhzmffwn8l zU!}dXTlpob^Ta{6q;>xvZr&dM_x}IO{~zlA`*Hg`AOG8hD{t;Dzid+b`!T=$&riOA zZZ{6^E`RU0cJ})ow#HzbrQ` zlzwzj@4gdvQRUS&yLUGTeLg9quzJ#k)#g@@`iz6$@9^7Jt#xnW*}Z(Smp}W?E~=jR zM?X||)gh5`d7IzYj?2p~`}OMcd5Zw`xeYHaHm>*cum62{KI_y>t1VYQ@8U9CJ!R3x zQn}yO_gw>v?#u3&5_@LO>1nCSsh-d89rsb|{J&L1-f3!b=Da(rxR!f83vg%Q6i-~_ z5%TWtW9NyRst$Q6UtL&zWyMz5dzQr?M@Gi2tKbt<0{uoM)Cve3!qP(S5j$FDC|j_bQyW}REK zS6zce@{2peDS@PgGrDB>jBT8rJiA$A?EiY#yveJ5Ip-W(=2E07^TBS*?)7`}Oc}SS zzS{eE`jriLIQTC9_A0u$WcQl2f;<<`&i?vf%Fd-}_tq@$JaTLH_h*)ed)MmU5!?Lz zwD)bV;AKjBySBLrpXuRXUV78Qqwj^S`huwyM?JDG-u2eB|NnFS|J)n<|80JM{(PF? zloi`-?%dt`;{5#jug~7Tjk#wt=ic4@FU2Cm^6t4f-_l;}B3hc5#OEm9_~Dh??8x9F zAAT?P{gPw9p_=zq-WkRZg|p_#=C>M(Hadq|G#$#inqF@Ho!>wE{fVZj9-0^bZ+za* z`|oOLu9)5V;_3zNW!%mj#7~g*UwM%kIuFjt3y!Zb*yZ2ta zu-)s8SrLEDe`{8Ob9wEH_WwNFzxvek`L&1Eq}quXc$?J!dG+tBzTMxhCzeZC)>@vp zYn>XVyU6EZ%N+A}hDp}$@3(4NXRKJ&nmT_@irdWBTjsC!N!a>oiIdCrPwnn?~?a6OX?vp5v z;xb_K*_gW}j+ayaRr2kySIgAOG)48RIe$-D)uGZVleqcR#%q>0&p*+evFmQ$mwSSI z<~fOm{qYmI>vWZ7t*ZLDzNtiPx0cGpOUgRVK2LvLyqP#bXJyFDhhBT!|A(aRxYu}s z%Xvx0T)xk}8KoNZ{2j?i?PA+vRrZJu_Ya>^mkhtmw13mIFGnU8mu#3Akge;I zvUdGzuLCyMAD>-QBq^t|wqa&x^8L7b%XO;t_RMR2vfhvT^|o1GH~7xW|DPtvc6r{q z6V1sri>|$S`0?exhnbnvdUoCWefaO?U0-gT`g6z3^OI}Rt(oP!7e_`2%=qK=TSfn+ ziLZYAzB$h<7vK73FE%kTLqca~*m{;C&U9V3SLfsZ{no$#FaF=(`+I6n?^);HzrW+& z{e8c(!fFeveRl3&{&{Eo#F%gDDRV4#o%yqT<-L0{T;07o-q+cUJEeb5^p}_}x%5Tq zRw%*!{M zuBEi}tEuz>p5vYCmR;jDZjYTKTdpN&Cjb0(*HpIF%H?zT#lQTWxzDL9L;KRqjzd*5 z`SyREo3Hrh&zC7B$%%8Hq}a-x|912>yV%@k7VBTAZT864oe`;e`_FWz5Qe9@hKurAmU*Ecvoo%UWvBJvTPio_^(3)F!@@QFqNJaTcmCU%Rzu<7_LX z_;~_vG$*uIpEMI%pqMh>YU0ujvzL5&vcvS;nVIiXou?f-e1_|0%;^~K&`8^?wKsP- zL}YiG{dr_=boj>0=NB!X&E3Y4u9tXkai>T7Q4Qq@?zT#o{+b^Ccy;rv%G?66j;3xS z4es}Hwe2!z+XN!&(z3JP+qxZE-F;ZOx2f^Y^;=&~f1BHLgW;d8xY6{!d5aggEIlNf zeEcg{6TkYRMVr<=zPMs*>{Dr~SeQa|ir*7#jO7=1BxYzxuI^zE9i+i8l zwp;M-7SrNech6OmST02fG*}BwJI$44%qP%U#+UHw+N~QlA7@recJ6o9P z#ocqKe?EM;@aBbMf?QlLJ}PV#x%g&DUVi@aofSteUk;x4I`=0-0iRR+ZsntQv*NEQ zc$h2axyk(cIX(a1{{J8C|DCq~dHBPxlK%mH=YQSm`|Q|aqo8zmcTP9=PtTb<1!jHi zQ8e*YNeG(H#P5G+8sp?8m6H=IR!kIT$$b9gy7ji^xTk4eD<3>@J$7f^_H8=ZYp3LB z7>jE@^H}b5uJkzL$)@P|-Up7?c74B^W7-n5HR5)q;JMP(3`MR1uWeLk8+bR|scVUI z3yRn7+qUhQ(ocn;Z~mLY^%9C)1bUR44zUU(OH{Y~PWl{D_kH{R*sJpL@@G?59pCvQ zwl?KL`1^Tv-+tb{-aUVJWl6=7s#_fscOK^#RyEMP!kf5w`gy-@fs>ZEiWojg2n8B= zybL(~)@G_qVGxI7tI#!u18dl#W<~aIGYRFb&}8kHeK>@BvhMxAKaQHm-P`iClhxeZ zeD6+nm)9B77OVd^&Alg7lpd5eA*lFtlbEEM($nH8Gc{u*W<6*~w*R>?b&b!P%AK3p z@1Hv6D{9W}krr~LyFr;F$mD=RkyA0VFt@<8G#|gGiKW6%ttS2mP-nZa#zm-zO1=c>=vo+7--Z6zNzY=Hmjgj2f6r+-3xmh~+by?rl3$A?aXH6YlO&r$F zt`yklF7M5DbDoeecZc%1CF{f!5%R`ZvuCB5IYd4`ht5@TWU z_i;NnEH;mhvj4ts+rNsq_tQV0Tw^#{qVa{=LP10lVu@zutZAp6}%^z5RCon%~#FK78_lh+pyzML$h9 zN7HDHRQ}h?FTZ^F@L@@v%C`j*l%~8356{R|tKFV^VUdvF-pW9~`8}U2TX!~1DX{Tr zHxggF_1f~!Geehj96X?S(bHxM+w4j^r;4qi52s0NjC}HV>-Ulv5$UsiwChc5ieCPUthV@Uc=N8P; zW%*`hetny#Sd(9GOzYzcOEq^7BqXm((?1Kudfw2zkgs9u#Af6I1#h_-MwF% z4mV8Cx-z$EGEZKbOVn$JO=Wi@rqG)QRL%=?EPIhB;dau*f?rX><5r!r zm!|jY&oRQ8GoJ69@~EZsWu<0R_7Y}UuZgj{GJY=orR8S&d0)Y`KK*{X_phec<@$c- z65egn^nUA38J8=E_pVJ#TdvNq*8XW=>@-yl!*5&k{Hv1~uCKV0Dp2amw%6_9`mHNB z3SM@Lxbe@=xoE0u%cZ*X>sNMYWK;=0UFi4f^{#!@R>BXDy7?dJJyb3S`}X>9fT_SrXO z&sMQiykN)*q%S5}d*lESKQ*w^4`JR6Ku>IeE?`r~HsmIrTO?dnB?3bQD0w1?*IWPY< zJ^o&~^IR>~>XN{uXZOP8**0&n=Y9P0IYaNy6wTHOofO6y9cSD>-Ci+a&8oZA@zR}7 z`5F~wv}y*;N{Tu4CaL39$h&uW*V5L%f4@~k?B9c@cg^koJ=mkyH$%%_ckeW2Co^R+ zhUCl{+e9xrHoiz|nzQ4}KCaIpuYdlTvg@ajm_gy)^7rZgvQOxkUD7o!za3jv`M>I( z{(YAhhd!TqXLe6eurpqY1?QfB{`r-8`@tWXwPzoPgf6_LbM4-NoHuio+&9j2 z4@vEgXId2F6!*)B>qKv`hR@v`4)Oe+Z@d#5XIH;Z_IY&h%(J^^Y%;tybSUq2xU-id z+TwxjpPD$qkR2ta@@8c*9J#rk7Ny^HKJiX$6b|=&E0DMNquor)uVpF1e=^pJ1}b#S zXvxpxV>tJG^X}!Fxr{gWd{9?ta#XJN6?hhv9UmrWIbYnMwc5a4KIedn&+MOH_ob*cytaAb zT$=S;=cMQzucfQ!-+J;vK=aut3GSq|Tlv2CE?aYCsfeh~^J^~K)idJetLvUY*9s=C`*1zj_&uC%@g z&zFn3A^-1L`9_;L*PcEzsI0E4`~0VW|L@o}ewWFtb;H#4F2`)ewiR%zKU_2E zQ~mWH=g+OV@xMA&y6@W6o{qKb4DU}DPP6}Y*uLUl&cCnI=kLDB{`uik6VZjg_wA{w z`1Ry;x&PfctuuoUmaM6byI8$FJgwo3j9lV0>v+whhqW9vmdf-WU-|4sngClz2lJ^* zQJb&vmj!T|1a|4 zZ{G~8qE^ z$qy%`A{{cPPF;WGHjC-xfJx3m1^SCzw}b>>D>ikZo?^UB%N zm+mOeSyUUn)W+wBz=sp1XSDUFFBGXeS|B{J=EUDD(?s{!87r@?(z;%hCg|6w@;Oi8 z#xd)L9gMfn-m-g;+9KiiaUR!&eRBWrxxM?!{J(!fME=^F*EruW{FOUc{C<0Ngu?&b zWzoM^eESssMCH$~)bIO>{Vko}eQ;VYA+2%!_wD!W6AEj-$@<*8Ch`6IHum zrXA%xz01zVW{tP>)Zmyc3BgZ0Z6r3nyKAw55N7)&-?M+M@LD2|MwApzSsSJZ2x0!zTN)S5BIX`oG`n; zW%=iyTjtEZU74F7xBvI6)#mf-zljR^UY_!0?VfwqD|OZvM7?>tS?T-Q&$p|5L+57M z%Jn;IFESN7`FH|*(E(|J^6%5!TGnhVdo0Ic{Q9oh-Lf6l=`kl*qxlwxQ^5x3-wrh>KYaPICzsbk z^2=kLiO=sbk8D+1$n57jE2D-7z`t+{1ff0F|!q5Nth*04B z+!lk+SK2qe+omhrJNcE$A;G|q*3*@5eX^20Q&m>6xwyQ}%@kuvFS*?lcE;k)89~`- z?niwrD{Y;E*t*>GogU6xb=t;?t^8I_zn=3f+lNb+Ub$hg{iDgZytKQ`4kiUx)@n%Z zKE&fG+3+G#)B0fh?-$25RoU~@u3dKXv4ZjW&pJnLK70Q8!kTv8pH9ZfLM|=B(@hrg z?6p!z`>lBE`oirl5-)dcyPY3!q&jwSzVzC8JpT{=cM9@he04WOlle#MtfP;=cZqCR z`uNY~%DLX6nxD^{ty)&Ez!lXG8%BkCxSwsxVrQA3b3dG#HI~be~Dj836<@$YU z*Sa^)-YFiQU2rF9d&0|R8|JjM#ZA8={$4KK`}TEAT4wIwSFs$yNA8~e!+!twvT2{< z|G%4_KYi+x9rL6OLUvyJEId7H@m$fhNAHGucPYMcu_&J(oN}RNmFk_AmCGL{?-05n zSbK-BSUVBpcnnh`b?)q#awSh8tTFw?jgtPt*#0LuS&?F)bD!^wj=#S3 z+AEHV{dPC1&G%}Te)te!z~Cr7t21}OtGvDU@7v##Id%6fFQeQQ*@q=obCv%r7B64B zR;#3{B4^&Zh0ZJ$_TCJaS{V+y$UG6#Q&CC^XT2DDzGP?5(nfpLy%Uu6G{?y0&;x6=+Q zB~0Lt)IGJ}{oSCNEz#@GbT&tG@hO^V-Yhtz_QKyGqKY3{2Zab8C@v`&PT9=frFA}${y!Y|^ zwrv!9o;&AdivqT z9zr}vZp3&zUa~DxeOD!mi^|Ds2Nr0*XT5x4WlPSc;M2Dm4tRPj37GKJuTgVKll{*( zUpL?W_3&Xq)o(tTo_|8-mmmJH-v86k`2Sb?KfnHM5fIEi8@#hA`u(a^!KK_9bLXF1 zTbi>=?Oo%Bl;f*51oxTEow?H5*xz+eW$4NE%nB!ptmL1~o_*Un`fk&`eRYddm0oic z^QUSQUON47PVF|X!tB+VC%HF%Pp#adQ~ShBLk@10vhJEq6VmHW4*D{bO?ZFoM)OH<6@^qgI+7rduWRuf(Oy)<^_)npAe zR~PvZ*Li%UvrcvIPR>p>wXxIf@9%#<&+q=PW2bN5e*CsDPk(;>uWz2A2VI&9q_vHA z*Rgr-lK-Xl@WT4*zkW*u|5lY`DSYzp{E9;zEh;~oH?GZ#o4;c3o?lVQ{3=dqi&XdL z?r*N{P+WBR=bxX?=ik3~r^BN?Ikt1j?CohmC9&7@(i8>dk3|G+?cbCYjgF4+tX@p-hJpO7S1mh^l)i3zp}u__C}c(cLmi}ok_C2bLhm@ z4SXvriag5ueH-gf$i%+P@899md^#m<)uBU?r*Z|a>~?>5D?$Hq z_XWNyOho5@W8S?u`fz`uf7I`n2Y&a~*`4|=E?fO^`(I`5%`uskJ)Pmk3}9E8&8yJz?9K)y}&LI3`ecd^$Z{uWgKcHjSb z?z3ps0czI5%{=SD|%BpL)76aS4{>MZ4+Gl@$`LD$4>%B;?#FfW?{rC{#^|18pj|cY-%Lqla$WMRw zN3Phx~c^cf|6;>KjY^J0-%E`hLgW*f(Fl-HdH*!JX7%-A&)rI0U1nslNYu>sF$j)ifTp z*SGH0Y8_wxG>5}o@AI-CUd@f(rx*knm?QH~>c2X?;zg5sO|DK0? zr%1jPeCF%O_)0`>L4tWe@%y=#}bik^81*^j>7S^XV1SkGi|PtcYKmQ*XR+=TY7*+QmsNdHbI4 ze0XrS)AJp_>nsDV2Rt{G)&BO@@p}v#*Lg{wz-b*PM3)>+Tvc^t>CBs#D}A?}`P!r% z`}S|#S7*1`cTU(%>*!$NFaBBWE;Yka{^8p){^^cY^JNUc zQX0!QeD}yd#UQLb@qI48f!NeyK_h#=wWc$2VlC%1F8y8_J3Y-ie@a_^3X6zfTf@@J zyNcegy}#zvWtJ1Wf45HicQ7XY&YeX^FCX6h`Q^u_=0D5#eb!a}`C9(}n?EJHPA#~1 z?OJtsXiRC&@2^_|_Z+i1pQh$?=2Fd#p6rK_@89M1-#z5l@13oBj)6INB3uT>5H zc#fls*ej^o3p>SoZAqge($JaC&$A?Ik%UG1-3XIdnkGB<{|Fj zii+*$w3lm}TnLXl$A9+hq20F=XK}u}#xd7?OZ$T-jA6{})&~<5U9v5<89pw2v@}@9 zJ3=FK@66pZKg3$^pW82dHF>Ay2NT=9JNd)2SX-}OyPUlK`!hkKFZD%X2f0$0-Q1llE=SU~eVew2TPdiV>&v}{ z8GN5N=`<=#+gDbg7&Z6Eox0c7^82f+t88rVoZWuc*VJa#?zOK^g>WXwPqgJws@Y+? zM*WlAk#~33n)3UH8np)pG`q9d+KTG#wHDlN!@-!=ckl3_#6xqQN<>r}v$39VxVuK$ z^3bhUtCD{nt#UcgWM#V9VE${x6&W1mC50t?n~i-oEu50qSswNJ#k|F7{2QJyyZbOi zOV2J5k+C%G<5;jW@Z#0IO^jlS6o z7~|$Ruf$7R=VsSNX7he7f1Q-Jwp4TJR>o^8+`GzON0)89Cp&RNnqSk*ox59ia_nYp zsC6yW4~S6p<}#U`_vKQJ#`ejP6<$*_=E~N*3zKmytB>0E^HZ3`IooAS>RSvLR+oQY zP!Ke6}MJhk$B9|zB>Ai49$&WY$;l+lR^ zvVHXNquhgto5~fdRx_lC77kX17bcN-eEkVE$0>;vFT2LoXDh z_yP_mW&NHwmw#XQ;u{yH%R_TlambWOYL2zJvhUv6K7ZdT#yqnpv?sb>q%4dSpN-epCy|Z<{C9Y&>lg-(qwt@SEFn69}>x`W%*c#62T|9ip zsoK-_mxD{j&g3v{AibDL7}d$S)q)9 z6Fft810UTvzqUH5K}BlsZfkCJ-Ps;(EJxE8Y*|xldL+sI=U4Ib{~pY~|8MPkx%ci4ve zEd_$}maUwUl$+~4KVD|$&l*b<1f0?m0#^+;#%l3v#H-gxYxfF8iRfkvn zZL2Kt}5@?L&c?ZVy5s#(p9N`D#tWYzf{ zS>v2sv_Yy&-Y(JKvOV_tDI4zpje!F17w_>s(l*hX_rbQ46U(|HyQV1Voz^PAZocyn8Kg^=?)k$IB&e{%(!7pK^B6?c_{d zrB%~-jk^$~}>r=)lr^$JX?Wj6~G^O2u^- zE*4(qe0C%<(_m-CJNX@vYV%`EPX9V^Xi|}t%09y%D}zi@?Kc@N(%vg(%zR4X-mIO@ z2fp|7zuv{A@v|v>UyjLwJaKcwELHF2*Iu3N)W{DmikTddcumH7!iz&JTuCclt&2<6yni|<+jGVO zE#qSA;2EZy1^i?ePBe0CX=G6;jV{e;oF23)%Vo~Bt%9>3-+kTwx&H6(|1W>v|G9U1 zyMO%m^R;n-OMcF;c|Y4;>;1ibwbgd|G0XD`GN40_TFE;{>A+t4^lOQbxXf?ygM0WIsg2b8LL;lx^?dP*FEdX?B-X$-}^J^`QzS0 zJF^10#Wh_X-qkSpX8hH5Vw;Rxnj6#G&D{6G4}be8!nRWIMA9DX842;zH=UZeKzQ1| zD_73x{0jg7=luS^50Bgbec7L2GIb}Hrl9xFSO30z_P<|K8MSJK*CiE8ZEi=s3u&z! zaw(008~K=Z_GPTO{Op|b8HVKv){Rvb$A2sjaTj>R5_T@ze*ODpnOz)ur>geh$EwZ-SmkC*c=*MEP$`?86wQNqlxKD&~)a|>LZ zqtSO_2kWdo8|)I+*%)w0G2Hc=db|6=jjRo}Q&PUjhbC^{`$j5aZ7he=9?d+zr9y%4 zW4rxQgXA7x5Z9ll&wqMz`u5e8*{3(@oV~;QeA_b7Gd8kDsb%I%7p)Ron=ibe_LiXa zTs5}atD=wpJNE6AxT8zkh7BQ$7ioXqp_BVbKh62)>aRx+2lbfkmtYht`+oW5mUXKS zCLB3%cC+=a4J{|va9`PL+`Y+)J@C;>nGf5f@>kh0uoZ^`7TUHw7djDH&akBeyisPflmRkxKnN?iB z@7q7Sa=XZ5nb)P)**NQFa&j7SdtKaj>hC9yi;9bG=dX#%TKoFvJdW0*$Lyk36s>%e zw%A^6*0WWso?Wc2ucv#T%zkUAyEB^Zbk6%7Zv(2`&XW1LwcmLOmr3dzv)K^=rZA)7yqqFC; z6kBZS3UA)Kj9f|&S)a*8{V3S8Gc|Tvas2uA!wb8j;;(<6EdT2A<(~^eUGnns|w`vo1$T#dY+xbS=P_V!5_CX(>^3V6ISvlk~{p1w*`d33vtL$t{d7m&<-YW1OZ-zV{{8S_2mjCVCr8dO z=AB(7rWSYo>lU5B8J<UxjL z+YQ&|UaWqfz1*qq#ah$tYi4Z^n)Ur=(UA{ACn~4Lr>rz;5MnZMSUbm{U9sc8;e+Pa zd*02iIr>kq#ZrCa-soFAt5>nOeqSA+clXwb`UFMAvWjn0^4JWPmfo$K``yREh2tFC zT(gPR3l~am-l_lNU&7}8-G59)PUqOLt&E#`YZpiMHPxI;N-ZDeF}*RryyZ%;adK#< zL8?SoQD~;Xu1CvPXzl*K?b|yS-iF=2p{vE-w`Hches^v6)_2U;)Hd-RJMlI%O?Bd( zj)>{^P9EMJ?Xhs%yy|#v+uuK*=U;z%{Qsxw`~T*OS1IpUslTs6VcOHh{PT*hE|0I@ zHYe%;&*FpkSzdA+*tO2GNL=wy#IzW5{^pe{PJDS(v0%d;-ni-8wy14gvortDM)a+N8|YQ4>nf=dRys?!IGp?twd|ca4K%w6-ky^md=CsoJMR zpJa8j>PU`cW-d*=sTB+l&oR9|*7N^e{QuMWwSPX||Nr;?_f*}|Ahr1E>W2;oAi`#F_BMz?UYo?a*37#9z@4o%~`BOus zQ*4WwH(aaO{PfrHlH1?2%D$&)vITdUyy>aq77tT;^qRN%NVJm7Lg^pf-zy!JE>++E zxbz8AZn?pojbGUM&&So4u4xyG@Ka~*oK|}=aEZa=k0nvl??sjKvwVNI_qSZzI_udr zh1uJ#?#W5p)t%p6Atcbj`|y3Wx5C!oU8+@W9qM+xQ%#nCxYO;?GDE0g-clhIB?YI2 z&*bFf;y#54_fA!KcJ0AmWNEd$@uB%%l_P*J~}gYE<31uRHbG`jrH}VGX*UV?Ue|dI%VB0o^2gb z?=}4s?{-$|w^e9&O;0_!jOEO=sB^nE&rk5X&8e}PL$&DXJ6Dqg7apaS=-2ml&DQ;L zB5VHL{XgsfAO7`h`~Kh6_bbX={`a1W*XwPy+hKhpzvfGKeEr8SA3RcTm0Pb{|NPiI zAyGB++j4tWrD`n|GHlO^jC%k2RFBcsty|NYqV6m<4PNPb*|?^GWsB_dKR#X&*T%I3K1sA&bH?1w-&U)|PC;fkK zw(t8YZU6gZ|McaTmk94!o6i3E_V3T@E1LS+D(T&}1ELKp*1fi>D8Bw8a?kf?!dpIl zPyJEG^tI!S^O^^*eWPQ1@2%Tk^)uE*tnTlzY1gXqp9SchzQnS%eQVnC(vJ?CGxjn^ zT-S5jyEAvcQsDG$CDF4#AN`hc{^kv-zMij^8z!xO`$yu^*T#+qij4x!XJ;85m^m|l za{sko`?$PW1lc&}Uk=Jz6VPaB_28befZvns>`bfLgx-#8w<=?e7O*M`*xGz$`}Z+2 zbe*8{ym|V~83IeT?~5v*aN@@jE4}Kv|1;k7o^A^KRu!X_R5f2+YW5nnrI{Z0^iE88 z@Z#D^)$pwK5*v7LZ?5UwacxD1*s(Q74xYIir5k)mjqm-#<)3f1EzH@LC3NOVc>k$& zymQX;TechDyDcg7kk|ayBL+d&U*~?;dEQp@bdh<`{pPON%Bed!P0l>Gx+~Z3qtWG6 zGACqe?~gfitWGR(ZCxRp|9ER^WDrYTPhRJNknFR0vl|LbHh(c<^67Y1dF}J$o!JHL z$~`+fCN(e};@dWD?;1wAYuCj5e4+&Tb>i~t|86*DUt?AM|K8_c4>zZGpZZ(H^yl&O z^Vf=_{a&9u^ZV4oooTmg=IprR5k5`n%{C8?;$mh^(Tj(!RA)cTjohWW@6h+}{O{sC zrgA>$P-J80<6EIM>+3!LvR@D0?VPM$8oX1Z;a>H%i`CLaUk~hU-}1!%t=wm8#p`GG zd@YT=>tpT}{Obf){ECB*JM^9(`m_GutNH)W<^O%AU;pd--x@2w&8xx=Ub+?j{q*{H zdz&?4iyYJ?=2TUGejP)0@^@s}9=#JoDD8fC!^a>t9d#o+3E6%Pyou5Y!somah?!>9Dw!l+xFJHG$>QgTmivX{X3jMZ-+312V#oINw) zO#Zcf`nhp;GQKzU7W}nZyVWPeCMWt^xzY`j_D+oiB^PCuXnuCy|1CBZj~}r!Z_q92 z;!xy0d*T=C47L848A^*{eLnoUd7C3pL5V}zTw=nm;zxJaE&dQ8!ZF3Sr^W35yVZGz zi@fz&=Na#h|Eqs=@5c9xPnh3GH)q7$xOabV_4O!ktN-s3CbyS|Pd|NJHFQSOZRb0V zt6ukR-fOZ)Z)v%me_Pw@P0r?T=fw#8;`a!njU%5sc~lc%6l?=N+BU3 z73-dUdsa|gY;5!3lX#4{fakyS^*8Rz6$h>Q){>j3n;Hf}G6WSD6-S@0QAjjcS$=qCRQw*fH#J(%KO}tS=z6hteeSF@F-NO5cLU~k zlG>A8rcRRL_fMaYoPWOgq_0Bk&hKIk9)|G@LLS-ALx1i6dD;Ho;rqX?t*`rax>WI5 z^3#Osn3}rs`hSo9eYqeLwQZ~S`!k6Lbv-hCMI=I+s#`WZS$dKquTO5qHr*q0goFhp z?zYdds@zcEpWQ7||4m@ov=^&fcU)g(X_ffw^Q&iX&;I=z;^pRfTfxXUdYZA|%u*RX zopnvSr=QpSvHJS^h%WZ^_o5WeWa+-ScZhqV^0cybR*~;#>TjO&eyw1^o}IDRmVXiA ze|}H-P_NBftL>K`1>HEKV^z_yjs4)kKE>3dI&0USd9(9@kH<=F_pDEgE^S-MqBgO3 z4TGv@P2{-~oQo!GeV5ldm3i*tugUftT9+TL6SC;=JmGuoUWe+LyK4^n@`&2zyv@z| zo|lv|li$pSSLKOZ`1`$wa*V}g1^n}$O{(5@nVFOP7d3^Z?2Rg7aYz;}^;xK4x}|w`^tBX* z8B>*tRhG(jEk7w6aH2}{Z=&FuzhD7_e_nrXVZ?K-sBSxfwFO1$=hahk+POJf#r2kQd=~QN7U#{M z8wP`9svjQ?zr4D7{WSMG`U_Y2FMnz< zb>^arO2?%w52r5L%A~2U?{&!TOMf(vmqq8Ns=N+u)*Y*5o$h^E{_W3sO~vfj89#ZY zYeWj>dq3Txl%$i(T3PY?y8WMr+wK1RSl)f%#6$*$1_n7~f}Gy|K(uIk7s*S}CDV`=OiEn#BBjDkUC1`}c=g1?}&W*j9D+ z;l^)eEBz}Srg>X;d(7QZzU5j}_Abv8eO;oC;_St9=092{GVl4$EZeoOFDuV@c2cN! zU2D$Q#}6y2em`*J?_YjfdHq`9Li2tHbNy1UGc6q*%6pG6n!GNZeX}pr=V1nO^Q{Pn zZN`7Mf3qx&GJ87b!2`z(`TGLX=I!cxD7VSKoWV={g_8KzK?5T6Akq zM`uWCZ0`5161;W)D&AjLX5Syhz35I|iINhxz5Zc81+!qqWRQD>*IcJJ2>HdPz?W*^#+$5 zyObxiDqg$Sl@Yix_2AT?e{l-@t@C#*nb7e;L9K!%-qGi==i)z+rxr*G?#t|`pSx_5 z%IQLfZF(#TVr!$9F-u%Hlo-|$vg+%{ol)5(ORcW6U+-AA)Bl~`&3{pc`YWeqRGgV% zsmSdkZ|^hx>Dz}+g_()Q#*Q~QHQyc-yjIMkeps_?Pp;6TtusADmO9Vu4^>|p6)~r` z;hk5Gj5o`g>s1qii`C1|Z@;X3CdaZoFgGtEs_VE$;IrTF+*Drg5>hNZ`nD&L~{Clsw&fRX8?Ft8rU%Vg9 z-=$6Mulsq^_L`c~l0%N#*RD<9ux-nQd*_#L{<&$}9nFs&-0m_5Y|dwIPA@mVCaqon z<r~@ zoP+?WAk&qC9iQ^@A5;efNo`TNlCZXQyZ!I<`cJdZzq9-MWb(6hQCzzhxzCN?Ygzm2 z*TY0Z&CE})T4EmM#R&5+w2kXoZr8iNeYYUz3T5su6PItd(%r+q+gh+QS8l_8kpuVi zn=Bcky*Px<=*(#9^z7_#{gw6L%9%R-;`%O;#Z!)B(i&;zms022EoYeWGa08J^bOr0xw@^CVQW}Xb35xTsnwA@ zp6gle1Z``)(P$vhx|H#@0h9bDX%|kt6{}`H_e*m7RZp4$bDj^BAdzYy88 zZgu%*x!1SOOcY_eS5~}a#rCwcos!y0QS2P=av8NlW_He*WWDl8?q+XaPXnQQ+Y+sU zZ!!kmzjTS=tcAycJ_XKxk>9~59(;>o6s(NjA;!frM-l_V=h&xn_seb{Atw@n7HH7venOIRxDx^ZZZlqzS(j2XY=lxD#niw_1n5v2W;uMC)X$U`f<^!&%RQkPrAD|6cqL^VMvYj+>oaxa^|Ro#oNDK4$s5; zm)HOJbheRS-rsc9$&DVmJoO(xwx9j7`g*=j$5I3DRi#1~i{dI*G4>VQ_OXp#eC_#- zi1f^iBRq*#9W31UPdU|UU;MCrqaBYkFycXxXcQypSQ~Jic{@}*D z-vf<9mq=Cr-cIK>Kv$BsJe6!PSO1Om4o{qw$ zjGf;Vzs^m%zq_-m^?6!v?xU}jr?+V}oXhoeFsTfIa!;MA_Q z+1YX~0?)N2xAO9_weH*$aX_Kdtn z9I-sjaLLE|cHiFBuHH0xW8K2T?=~;m^HcDgkI0N?QHL@%#$3BSDN2j&)6w7M(JZI= zk1C}3T4@y~g-zMyW2xDva9d0$Q+Sf{qK#pDmri>Vqu{gQT3F9leU-Z>nWQI}#%Yz; zGEUJlH$R?!e5F^;?6OOyT`x{8J5~AEWyw}E`7h@CeokCvV`DS-`bO{diZ2V_%B9V{ z5WHaRcRSx;u@gH*OXl!r_dhiWGcD(?&Y2Y8G)MH>jIAx%6N<~2&)qxRyQ9i-Pu^*N z`FnP2Kbz;Lule3CUatRa_wvoPpAV}bHl}K<@)>A-!CaPXjyizQLgnMlLy!1(pBpOr_C!)KDT+N&fBdY zR`7oQBV@dJYiV=X_7A&mZnz+7&)?u~Z!VseURW;r^P&8|2j+G)KfiqqUw7&*pVPK2 zZhy9Xn_v6w>FV}st@c`Wj)qq01BZS`o&Ax|GxH9I-@iBi{3miXoSWXRKefek)>?*^ zis{SG$qNR2c+siDB))f_LdUDT#Qn1(yN@|HCw@7~EF@BR`0}oTi>0||XKNjd5h!^7 z{P^tIhhJ`SdELI#@B3;ao2rW|x3_k18HeQO<;Cr~kz%vziN zv!=C^x1YhwYO%nH;?Kqs6LowXEkd&7%x;Tp%~|~VZMLuBpZ1eyKIxyF^sT`7^=Yo+ z2t6r&!3R(5m?plRc=@XCZO7%S*>n2&qWf;XJ<;nK9jy5NwEX8HF`o(-&WOSg$ z?_9K&)}`OVCsv;+fA}fw^5l}Z%6{?gvm!la-(p>!EZUvZqu?S?c>b<#fLX5Lw{Xtz zm8$oTUQJCe?=H}*D&G_MX}w%{%3S^>PT$)sDkn|-@NbRu`_uKm*4cA`#^UDbE)~e1 zb?E)dyKgx&-_B)TecNts-rA*SoLPDTJZ7?~ADU3gvPH&gWqEsK)>nb(=pz%{L#Fv9 zf4sf-`@U6j{O!&&OI#^-F=_O z=d5wCJ?hwt%t_V`%`?;HrP(#4Y*$lG7dCfKiuZS$Z)0=r_{yr!+rR7E+1&AS_52{D z^y%5dJ>L`-MP>8Pe`a;ww92?xDNt1~@9&gQ9l@`835QJo$Rh<;6)ss!wE}gm~|pz|C3x>3(FSa(c^Qw=+-9hAZss zms~J?+a15)z<+=5|J>VeSMjr=K7Z}?m03@}ePGaff9C(Y>+|i_c%O+<_#OQFtId|V z^IPIGxl12z*{^!rKr&6Xsndt4$caDx7KiPgxnlbxIC__g-g;%4<7T7m)w=OTk>GT- z(@*DQ?@jG{-0NrDDrnR3WY4XiPg?m;RQRYIdHetP)%mraMJX@mKDg@=a`nueWabGc zTDMNn5$j%)R`~Mg=jVmb&U`Vsx%o@cuH9WbnNF|>ba0d#2a8VJedcheasLwgC!H(; z@1CWt(klPC@BVcwrCS$LXZp2QS)p)B;h{Yn`Q_u= z?6+5be_Q^M$O?m4l;He zy*sh9!%QupSzvu#Wd`<#-? zZr6VNf0jdg|KYq!yQmTy~+-wo2%Po8$$(7|Q*6BrBwmo-=`v3;w1eec1&dXp!Q*PIZk=$`4q!_qw`!Oibl-kJ*@g1T>2?mW4C{JFhV zrpFFz;rEr&zr9c9do=W|$aB&Ex@dpG%TMZ3g09@|(u^H9+ItOkzQrGzEAX*S*Yao3 zb3p;Y_SCRL^7rgpE9WlHv+`-2;1P2CMcO>0v!5;>U;e)S$IF*LlP2<-&Wu#$`5byZ zb*8#z=&7x54WlE&gc>|FSsIS4&&Xc*n|9->)p>CD%RYKfZq7gw_5H?M_ywe_BOp81H^Rp>ej;`72IKTuan(twPJ!`j7hmpT0i+xawa@-qve71MbgKTpRoUwR!!n8P>*5 zU5kU~D}7fooOB_3Wu2o-!am*O9I}c2*|&vF4&OD<$e-3>a7H6_zx#`wzvpVz>^~^s zc$jg{fnaVQ7S36VZ>;~c`-bJ|tLLNN{CU2PH8yg#kEz7@FYPQ6k=6R*E zblbvJv(CFH)>+6!Wxw8~G~NF1%l4XdWuLnS90!CtD(3&0kz2_loRFwkQ+e*5-`TsF zo-CF@{CcRTQ+@lKt#5V|hiu({(({jj*U!55a~ZNU_nMcU zc3AP}S&gx@@B)Q3*Y2jqUbpLech0e6j;yHQPS=y$m=7Jgy7&7=o$ih=YYK{X&+GlK zcXa<_Mg;+l*D5Rw7YdAor|h}+qfBzYz%M)H4u&Ik1v_MWf3M_stk!JW?)YY3SDVUA zg$rk6%J{#YS-zQX|I{}g4$s^g-YrYBeleKbVGzt# zQBffgd(eSn-R*A=UtTtnzhJ}ADcPK@8p@==vuZ2LmG}RC{PVwGZxb&)J?hlG5YLBa z&LoI6-*{Yd=e14u1eG9xxdN@6%R|y7?gUClUKL2av3%7mR|k&57EvX}2SIItmzHR@ zO?!M`LD2bF?Tgd%<+*vR?d6{waIzC^5OCXkF-lnF*(BTcR|eAW1pK}lBv;u?l*!0l zymi{&7ZS<0tM+YNmARHb;oG8Zx&husc3m!p>p9)UtEczBLDp4FwIYIj8M@(cSsRZeCgWhc#O-zT0yBi;vO_ z$vaQx{jN{=cT@O5>dgJ_3UB_Wy09=a?&s;g{AK1div*`7@9tIa&t+Qhm5)K?jT}ov z_SvU94VItU7CGC6ul?{^lXHtW1>(MGlyAPeu3Obg&R=k2u5RA5JAY;GMX!_=Q%k?& z`oL^edB?Joe)s;LE_r)T`~K{sQ<%T#@$R}RHtAx+cPnLgUIvDi9m<>v0(k+acXSqH zxO1&mVqko(ch~UchSnEtPQ@oK>@@uHr)I58lgQE>=lqS5zZpO5Y?>_Tr8&R2`d!@V z>5U)ca@j09gF<=Po94{Bd;i|=813J}vRxgn%gl5*6b*WQG95U|Pyw&i`W3iV@#m-NEefjg}%c-n7FPY05JY8DO`gH8{$Uc1H@CpI$ zOAiYKnod96DI!;EV!Azc-y1efje_g8J~x6MM0+OWui`Y2IWzNSo`JU8iW^I(mAw_# z-+NTvE_PqVjA?1cKNl7)*pVIU=dtPQGQVZ-m^daJUXeMAL3jG%9q~>#cF#KeBGyY( zt9YSdRhCGbmI61YW6o`}z_Yeq?_*!FJTN+=&A%vf#;)r|*JF|$-n@G*rE=h~m8ct6 zubbeU2XkqhpFDbpA8-p+W6W1)=0DNXrA!no}Tp+%a?p?O9N&E zH7KwiH?uMn7fcg7kh!Y);+b`cTMZ1qKTK%oYZSDUPE2}sfnkx%w4bL}bg}#lSRz!d z`#FTi$#&lP*&bUzys!K5Kt+CD*wKR5M*G%RhJ*bYiRbN)DIrlHv`G>{C5QiS+}tCd3~e8-O6H1OBEG24Qr{6 z$-9_$Jdd9}lixT*@Z2F`!OOQ79ByRNsQK|%M@2D>PdY0qTX5o5o&4sTJE2$c(5mDaRy$gy69k&A#TAvlE-ag_mN+APn!|R3gI*16 z>k64oo*GYyEO%n&c(=e%B<)7|l0!KQJ2?8@HS}>D>|o*hVN|pHz!nh;MSa{TWNaqa>MXS__wa1F zbJIeE)3Av{p)Gp4jLZRR`57NlOKR+OH-&d~JUDT&(y{4-fv}RRdHaWw2~$rQ)ifke zjJna)!8GfJ%jwrE!mk=0yReLLoBC%C=Cb2?wZBVZZ+m<-+Q=@*XS`_!i%!dmt{qk8 zE?h@6T31h4_3c^94%53;) zxywjNoNc8l&moEVRr2<6{~kTJ-~5)#qWePPDuJzXOZUFAUn~AWFz{?+$Nq}H9xkt| zS{YUf?{vNq^K;MISZ0TF=Wi!>3f?GXagaZ3yTgGYLP4tj|HGg0{~w)Z)G~M}u}8G?Xrns^^RprY#DMbn08w}N`Dq%{K4AO z`mu#Y>%@x5W{OIRQg0UNtXryQZ2UVw*8ENvyQ7|Tr;>yL_e_xwPxh$Z-&;NB`(LRO zQ$GJ**xfN%L{aUKh~JF8dmj9DfA;XEomPjtulHr@80(PJvW|mb6hYzki%oWr!wYQmRZL%9|10YUel|y z*Su+6^Sx=kqRVj(hC?cGKXSi0Y22;So9Uu*?4H<;qY}DSPdBpQFGlZ0^Or`AvApoAcj)tG(ZL zAVl$;zypTj@;^L0<<{@t)$rq7721w=d{8J= z&{U0;brERTzQESat0OF;$8g%=I}t_k9AY!Mu16V{m%iT1K50tIl59bzBlota&eE?s zdou2au4K7<_wt~Nf{Qn)nCh(8UOUxwUGkrA{`dc1)!&~Y`#00}4EKVv@5kF&wHLA* zyIpu#5>zhzcvBufKR=`Ov-brSerK<}o!Jp3{akxWX4!$l*=t=HDj0&_*Jix8>QdRj zem9m&Snx~Y%CwEMXK3DS>^RY3DebDna_qw1PUTzATLnc7j-60oY!^7NZ{49E^8fDM z*Js`L|J`jT*L*|e^V7e+y!`9#@zYzK7i2KBc5@i;d>5bcHdd$5`1BFq;@bTRpJttX zs&!!Q?Us|CJ+s%p-6q6SKHchUsaKE_*RQ;!;|7;mBm@OMQ{ z>(WwD+&z2o-&?Z|txFf57&}q;hUt|H2kZZQc)6#nKeq4Fv$L}=t33Jk@v%E=02i-! zx$5K*g^{3YTF#z|y$eQCx}S{DZES6h*$fwA(-5{Wivf!Q?-_O|0oSpMtP2)r>i?Oz%kWTA`N#d62(}0!FIkw-6EtOgFYgO6Hb%GnWL-soKn^5G`%L=l_8-LET>&<(&RJz5Vm-%Nw^=->>`q_x=ALjU9J8 z7fcb7`|D?Ndh&uRuimwl2fWa`e9lbs&9>&=*gG>rZ3L$V8Es70e;652#NoX)OxkOH zV#EwiW&OBQ2O^Y|9GdTj7m8aft9$zGPW{EDJ9aPK$e?;)#?fEr!b~>*i7uBjRZ2@c z{d~=Z(%4Q7`I?jQNoQ7SAL+g9jG*=H=IcxUg(gf}$@a{?aPi;k;olh^pD%eQ=-9DUM?uTY z-t3R8de6PNLUYCEE%!nV-*L!@n71tY>HBupv*&xCe>HJsXA)j?@cHB_7Kid8hVtsh zNB8!f@##`X%3jMc&u{t43(JeWqrT2vQoVY9+&rG|$7}6G9~A$x5~vWE>|B)et<^>D zk_f|}bnSQ5#XHT}%L`1L`zk-5@#_9o*LPRR;~jUgo7Pn2f|gDZmoEnz(q`YX+i0#R zwQBj#3)S1#%0HLvWO}IiDgsvaFb2eOA zxvAlJ;>?S03u3OV{hGK+&*^2j34@Ey5tl2xiXJW#8fKc_Idkn^Xs^sZ$(p})e_yZP zXR-14H7(A`EjM4czpS}g6|H}}Kx_Uy{pqK#HeR?gJ!5bEk1rw9+cRfvTWk5CQua=* zw5@QOlEdtkM?0=nt=?L}vgu0Yt7v26!g9a+YPDqyEO)QZbL8;JR63<}g^|f2Ib0-T zM;kNO#2LIi+Gqa1wEy!vd9D4QkNqAoEoxk{Ys+iuW&P{x_kA2Sxc29|2cbhad7VCb#@516RB}m~To1x4^&g`|T^A2A<)X>>v!u;mg zjG)CoB^TPQeY;)EZu_YQ!Pd%o&)?eIv7I_Aa{Dgpg%JmK^e+6B!Igabmw|dur)A9d zh_?&netmg)c=@j{5&Nt}em*^azpugm-%Ck`h@@E(6R&!IF zb<-OQC#6%Y{@+;o3}R%RVuim+a2zg~F`M1-&8}s$V~#z1y0&0}8PB$__m~9(-@H5a z*V(+6k&Qt!L0m;BaoXq7*Rk0TgSwp0-=8b^R!zm*@$KK8HT#z^mIWUA7k+74#>6z?UCBqio(EJyRfs@qI9oqYrR<8{=c%jtU6a;<#_e; z!g|&6xgVeR9$H&le=&QCK_%mDiTp znJixP?r8QW0n?m$Vw$S=yHX>cRlZA(*>3uVwRWQ9#-FFM*ShRk9{wt?^Q6|l&m3p= zzEgbWllYn-4rKYFiq2aOvg8FJCY?+yDKr{QZ@K%)d8m<>h{JSnu_XW5Eer@(+G)x>{-U z+2MfB=jvZ&H)W#ajeJ}8Ua|aT$;5iNbHdUNj-pxnPoBAOxA6GkA9v$98WNsOsM>Um zEoqzEls1kp5zBtP|Nrp*J}bF>KmWexXzpyCV^Ed-T>bas@9*3BH5PCNe{r~P+UK<9 zpOUcSoy`%O1#jMMobUVpeeePqwKmtDgoo0Mhh_>kPfc2%$b9gUkBgxGj6ZwMwPk6l zDqNPdVKUlmQ+;F4{b`x|;@#|P|42Ak*6?zeayNT&72Vw?_fV%a=k@Z_TltfpH9VYA z!Fa7^5qs>q+h2Fd@UL(AnDyG`da6^oh0K$yj}KpqGXMSKr6rHcj@iF_b=`S5a?Tw7 z=C7vwZpO_k!oBn5$*|$T!>P4HCo4dLwdO4QwJC`_1on_&Y zQ@nQWlPZ?XQ7-5C9o)&;$r5t{-Omh#Q;IWX`j1Ds{+{4;#%5}eSMKeAOPRA;5`xtj68kuI_9ji5G@oPn?=w%r zo{2S|aNKs{G}Do}yvmG{DHYhBbX=iW0bkFMV54Z36J*D$~RP%WupX{}#jofc+lBs&X^i7Sa zl0ckIz~PyaHb+B3T$J{l37U|YeZB1E;Vu0;Ll(Zwd~GxJ|DX8!&)@66UO)cy8oNDkdBOLeg^!d{ybf9JVo~FMv&?~qZR@q% zFSl!DKbI&>>Ctg@SijY){bSQ-9;dP{#&vmoj2tf*&#cs1eB<>Rg%Tcz4uge1e((Q1 zyZ!K&-S_`YHegOrHM#tG*EY8LA1{|bbJSS1IN{}DZI)uswhyN@9&gj$egB@vMDK>` zwOjHvJA|s|F8UEGFKM3T%fez{%*_?l*V)yf5b5`g*}~gzpK$3n)riXv-@V@Ubj8Lg zNr@?WY0gJ+YnIdF5Hmpm~}g#St;~Hdi&Kwi`^Nx7k?<4;hJK?yhv~d-|rjS z>i_&T?{9y3eEH|C4<8@CefnoiXT0p|mjPE5ugudwo4eq+imUB}MH?)dI#>7SaC{S* z$H_ZWb9=Oskw@_%VZl|LTh0iZ>}6sU;8U#O|9E42+0Qx#hral`vUi)`O>^MjIU#>U z=SYi(`TP01-zGB5{QK6UbyLh8YfCq!M>R+I&wTFo_H515JNJqG=Du(Li`hh)n0ICC zX%(l&M@;&`v1)g^uLMJzv~cXPS=awA&6H1LOx`kQ#+|vsJFd^(;UCpe$S==ZzWcPT zpkeot+osKeg*I}lJ7%}e7MZmoL{V^>-IZpJOMb2@EV~`Vf-U|$XXTxEVBXTL-xs^6 z#2h}B`?UL=rOL@(uN{)cYJU=X^3p6tzVW#3;7!TnK3Hq~^xZ3kroII8Ev0g+e>xZI zds*9Qidb=P0dTPLW`FO$Zm!XFU89uk`5)D^XQrf6f-(VQuV+7BD$*=YslBfz;u;GrQ_^W3(-?mgMT;$UZ z-ySlVCzqkXLWb{ik(J!8mzA-+iI0R{9X>9R8hbmzL1|{z&12j61oyd5-;x-ef7r=( z*3Wih*ZL@yS+75Dt^Ip7eE+Yb)8+5ghkxJ|)(hS{ug`_)`P)x5H@{lW`8I#YebF-^ z&n@R){5faSLW{GPO*Ve2IxNG=B$2gaSMeSe>-SUFf7l!MdGFcp*6B?hJW3n+lX)04 zmx|0gc5UWPkDKoPGiN%5oZ|hrbhkk3%)>l04+P5{o~gIJzsHB!{= zs;T_l2FwR`A8GaEi0N2891l*V^ozpiRpVq}^-{d1M%+P`lO-nFl-sd%CE z*FTupx~vPDlu*f2mMzx=%-f8#s-|F@E_f-FV_Vec-xyQ@Zi(9TeDzM18ZRQ{R-u7d_QZI%X zTF1KIbf(n6F9neS)T4c`~P|UufzBM{8=sh;OIGq510Mz>wbPczwh78 z>206<_y1hBa+*lC5B==aH69*S8ky>s^NbgBCP<^O+mUthoP_hIuF zF-ruTSK~Iy*qeeVs2+LgQ0Kh_ zc7tyhPA%H|{sx1Po7;rUncj1gI4)~-tVn#f_xp2grOP2tK9(#9Fi|aW(R*^{(i#2N zTgyXVKX_K#lA+I^TP&!x^vk~7WPe1c})>q3k4WF zUnj2n;a};}@?m00L-_HdSCu|&&`C2pBcl}Z-ci2sg8O?z8K>GE z+su-xf2|1cYmo3sOuN>;b>SpaQ?DPY4>K5!cu365vetQ5rd!gGCna^J>F%Bf*S7eW z_)dQHV%gLW0jDJlre_+5u)aO0@=Wbvgi3XUd>_NB2Ms*?&YYF=83k@Fu8T;~>Jjt_*~v8FW|+#tNBgcQ zZT+&ZRZ=j|%B^A5*{ywv$14qG8WmlZb%=N{zF7H*t0iP^pP|?Eogpa)Iow{&nq$Y) zVj#gN^MEhUq35i71Ji;I6K*HQ0yh;Nmx9oMaw7-stJ#-Oxa4!&k5Fy*Ddq1sr_ zt~0^$Tc;FR$ZSx`fBpLM$MyAJua@7BjJ{r)5!v_p+ug&NmS>g<^m%Ed&Qeb++7djZ*<49*N+uHATasm(G$vUu*C)t5A4oN6rkuIaB`JmX}D+jk&;fH?z=NvT=&-~Aar|gQ^{;=?-TXwJ4#fQg}^7~E)RmMs){s?ec(>k|{ zkNLh}TJ$XQIU5HIr_u5JI9dgP^+j;oPhf@~1!J1ne;&)pK6-h2zmFTQ&ka$px zCxpq>wq3DN;P_)Em&IQ*XKlN7JYe0?t+V;IPt@wzp*tz=nYYqB<-P+uefDPWnC+gp z@O9oXxFJ|BK56yxs9yogd~{iza;BHA zRkEq%IdWs$z3THb*$mq+WF*VJdZ)j}T`|8*I_v(;nT!pw$Aelfy*FW-RW09q?WMu? zNC>$6k(G(j>rO?N+d5MFjT- z2JtBhntKGXx_1J~A|VXfZGxnir%0;K97zdZ!8!ucprHbh!F`&Dt}&9ltz0(JIR!Xfi=( zj?$u#sKc@Xo()Gj9aUPkY6^CW7+)8heww2~Zmr@Y8EJ+^6)vo-y+`i`e^?MG_K3|Tn|I7F0mw){a&Pe0atsT4A9Hf&i4!l_Q zjL(fF6QmqmK3j&7bu*y{F)TQo1(+-j%@0WVS3>9IPsa`tf+GNL}8<;K|Y6-PP+GWJTtg6 z|J-$M^U&Qf>n}g45jNU>adPgS-?4YUH9T&JN)sq+=B zV0utZG3$G|S8)nK?2*~73FVg`Dz_TjDHUWKqu_~RDzc^%_3vnb_7QLA^S|7qUc-aPZ^u|>}_G#U;m-xc1PFwr3Q zSz4F`^I=9=9fcYfsoyj1D&C#a(j#ZWWX^jayRHAG^1Hsudh@ji zTI}-9WI1z&?U;kuD&>yDZM)Paztq!qO6pkWvZM5No7?-Y8^4#`;y)cz%$cINREOck zvgDZMnXLO*@{U}dz5RIUjV-HgXU^-IkRbKjT|lVC`9jE>s+&=(mp0%!ZKw>$C$WgwB6Oqkqzh=dd@t#qH?_u9F zt62^fBHbD9?rzz7FUtC==840Gi@zGlw07*u%I;-4Q!;B=>bk^#FE6Wa{V8#!os~zZ zr{h|b=F^jRwyJOZRuj2?t#krgV%+-c*TPL^^<6HRWB2z<%k$#A5AV*eF?5rb6-;&F ze!rJp(W=L6-u!t28?%`kR_Zm}aJ%m&pZ3b2a79$ciH$-xBo1HQ^|Ha>x|Q!Cjt6PU zU#_`@FX%{Ena22HS(E_J*2dz7Z-N|L7EBiw$4mJJxAYl4s?uHBBBd;N>2ulH%h|Kk za0&%qfw5?UnPErOE10ZTC*6KZv-j=GP#`tXiFSU0bQ|T4?nSqv3es!rJO`xAcV!Z_@7VUd5$UVGvkv=g)9G^nZW-ufMzEmV52pX;u0) zDBXb}jW?KQYr$=s0K;9Hz4O-bCEdGYAGbd7ox`%c#ChI)=5O;FUSF&G_2pu_QIMES zYSal`rs(kU>l`fK9E#K1LwZ-ZpX+Mnu$iOGJ|nH|L>i;YyN}D|kAHsJzQ5x4kEeHE7l9eCJMKDdm7W`+5wk(Th=ny` z6@$6#REfrkFZD#vr>Lw{aGao)@?B*bgUcq}%R1NETfb`^IkRHLGKQ4JsrHZFo%s6r za`1au8>`GEcQ`vE%`RIvm}jSlmby5*^#m|GsVynt`N+b0u!hHXp{vW8&u`xKym4gP zw^*_BPT~x{{?gYyDlDA6r;Pr+{LP=bYlW7B|K)F+WIA?iO}iDHeP&Oo>cO?9?8@t+ z-|tk~oi{gf$CN}#y=57@+>h)Ko$11I(em`4ExbFcSr~O~Y&h_V) zvhzP4e%i?AvZWWJF!x4fB~i5}l|o9BoZhVyV<^}gXK!0;z$^Ih(2FUTF74)ixi98z zZja%a?-^?s#(BBU+_kFRHul`>YyJG~)pKJytS1Qs?kZpuxKMtYQ|RqSf9q2gkE(9E znJ1k{`I#;M!qTO8&IX1fbC+^3#Bhry-gwAzbZ$OV`);mxbq$%X-yAt`=tKhp&%>5I z-sOf$Nj$u#m)fq~yPH?hURFI+^q|gp*~H*Q3|hNyi3;9ro--+?pJ%d1uWVD1RcPVy z8~4xO*~R~Qm9=({L8#E9HJwf;Pu!m}JyI#Blj*=(V^%S?LpSC+Kasgu+ahhPB<94F zp!LAR<@nPHvO)|jEi;9uH(kD_EA#xP1IwWfj@Cfi#hcnz<)yu|^PRlAUt;ge&zIXz ze|3J}yVGl~E5`;kMji&)(;xPI%d0j}=PAFtt;3+hg85#~!j2Uijb{l;DlE%1>h||P z&*8{;X3;+7++FTBPFxccWWK{t{eIoqjLQJOBHh zAO4{m@bzi&b>(=l8HqZa-{`~)69y>fa?6jbC-OCG|(P4suF6ZJ_ynA){%EACeu25%} zX}6+`x7fAqT=v}V{KQy|Lktge1O%OYTwWZ!R$TaAmUqzZ*$ zUUuz?*>1?j(#S3-u;BN;{PZPO_A~e$PF9O3$jQkuDCjTn{vLFLslR>kDk&G$Ivd*= zk`r5+zXk5-^6=<*A11=LVyn;Po{lCpr4vS1u9vbl9B)rfw7fFOT!`UJTCG^=gs5$1 z%QCapy1T4p)w*FI7|7jfYbSnpwNDUt%B|hAc1LHFORtOgP!RD-@ zn3wYbyMIBsbCnjXW>1=qu+r2YHDd_5gCto?-6xg0>8K+NwC!n%j&rJED($#+gb0+QhbK+ZsoJq%t zKb!^S;pGg;F3TNMip_o3-9PuMKPJL?1#64+B9VkguCIJArr2ILelYDqheGe%-Y&6+ zr_S_P+sWBFeRd0!WoB5GImzkaid6}IjD0T{S~8w>q&%B;D#~edOJl?HR|jMpST@K# zTgxBH%i#NRDZeRq`I8SUY_61rx)?(Mtan?(hAQ_^QBo_qVgM zOV7x+tyHXf@FHeKlz;8#0$=qB?gE~>Qr*t$^$V0Q-o)lO|5lu?1v5_r4aJM>onkvt41K#|zElle3xWuPv zoYAloIkaQK%(c#E*_xXfly^xpPFd-4#6iRQxr)88fq_!em*Yzrj!Eo0u5@M6;ip|; zt*$Cp&h!ds8oTci)oc09>*aFhqs4T=11`aOXAJmx6jTyQ)XP^mEj_ch{MO~gI->7d zF04sj-noMR@;=2yTN`7%H5VRsR#Zve&3);Dq6_Q9wZAVM4xGVpASWlOd8X^`{<3!> z*0qd9AI>sl)QJe02UrYK1_fiIX^;QhxNA3OzQMLb zmm5<>zRY4=Qjls8{N?M}g&tp$q6`)!T@e!OaanQ3;i6#71c_OupEn%-!{xmERKU*y zE56UqPv1Bb(IDBjbHP)i1TE2nD?9ogF7P;zxb;El46C`zf*Ynst-i-`M(zcpT&tx) zY7*l-UUff}soce9pHCFHzEY;&Jvjb)Xzr9CuZMq+@hu1vocNBr;7*~`!xJtO(^XvJ z3?4MjdE7c%mq9>@MX}?!gq-7{nF+K0hb3Q-^Wf}h=~(4Awe$6@=-0nqJ#>+{$#^A7 zov~D2dHcq9_c$-OrRzOg?83pt)Mbz|N2qys>+DtC(ro+I^<4JZd1R85VA>SFN(Kha z%O*>6H-Dd3u`jki+gg8pnBiuJ0(L>m`R8NZU1Xklw%qAyXKdUHTwJz4bmPhFI1yR$YlHF9<4OXO zjAb``R~W6(+Woe1s-vxqvV8uvRTJIUTP{#$Y=3spKeKY7%WiSCq#oPTn|D`M-15?> zT&*E-qV-N~hPZ>6;iT*%@9pdU%-;V~oIU>h-aSk+u4dl&a`n3X?`O+D+yDJlK7UCF zmzL;S{mBvWXMedUO^y+qKX3he`@aUBwSPYt%YW1p!MqGn{RZCbLnIa>3M?{>3w zug#o->?co3pPBVdQdICx?iUA#hdUC}`#y^Z-rmOhHdn(#^RmsA?Nu|Ey)3Z!vR3e7 zOqNh4Q%EN27Yt>?$tee_%G3xfY^QRw9 zdE5HRFxXh>QIE;4t!7FWl>37BKGpDL2#8p8#YyMPi^kGA8;6-<{J&OQOVIxnvztwH z*-oEOj*hIgCt5l-FKFw&(xK5RxnXPJ-aLkLU*8tZ`Mh-5bS}ZUE0mfWcP39={yICf z)jKW3zjkcPT`{lEc!ePw+m?&F-!=Tc`YYynS#0E~jaTCT{t5Sue!aSV zZ{6M{KipTvTzmUr$Ctw_5lTs~-xgTBxmepU{q)jEzD;}yYsK_>Zmiv4u(@(dP}bJn zuj~)0E%Tgqx2MW{{W3i}1^t~`K{E`QqL`N)wow!joaOyi=}=9x1B3IseY_mao_7yz z-4MVaXezt)T7dKC%SQbDueW6$Qf5rnV6-}NYnz~7s=u_Gf2vE5;e{_7d?s($-EWw+ zcFUC0+w5#@YyW<|p7JnDE-E{BulL+tD`&;8zkYA4*U9Cd4OkgwW+|@QdD&3EDXr0G zZ||HYIllHF*2nXVeUyT-Pr5Z~PYKG|v!L~AZ%5VT(%iIdGS4r+oM7U{{O9H0&zXXc zmim4A_I7pZy4ts#kE3pH`?hWCz1h|V30JgYmfzc+=XFRR<(UMF!`WY7HW(D&HWFOA zW$kX}AoY1?n>22BELbC5*%+P3&X99VNATes&xO@q3J$L>x?axql}PAZcj@8xnJpYM z`&A}9=bP3rb7Dn#OqkxohzyBJxj&Li6xGgNRp48?V|BaGLd$}cjA>^zySx<-O8k9X z^>_cj>Hi<@|8M?3?O^-LdCzys9cN+iKQHc5G-ZC>k0Ti?nYY)uwp}i)v^W{${QGJ6 zd%3zN#e3{n8=~r~{{Q=`zkk}(1ALb+Z{y9?IZ!;;+wpRd1;=FLUv7b`trZkLd=rna z{rXk^|Eu!)UmyDS|K=61`}=B#%=72PEoT)=^0FDNE41ZOeVK^p;G*5r=*Sry}+PTE&$!rsZy5+adCDZ#tXVjQDm|=Xv2%S8h1-S;j-L zQIIFDQcyNTi%Fm1^h*ohV8y=0Nk0{+G$rO0C;vxeg2|&vK2+ z+4YtkJ~K~L<9bhwia|c}BrV3axMy5S9P2W+cI}=Qst|Bpm$_Kx(6Itzk%M{D^L!>q z&#ley=6N=Gijv6ffd1T!`l>XC9lIi*c-%U3ftNY#?F%)J(?;7Dp1-a=<&v%1kED`kwl)vK;Ic1$nTPMx)E<@bA0uUj%)4tBI&v^w7*?i_P{ zTD7an^uvi?N|iO=J*bOYe`e{0yalQv>^W~M&u%O_fZPu4t2lRJ; zDz((m{<`eMX@2>2K1*_gU$0%&HS&C$S8u;}R_2O;;Cx2KX;ITM)fO4N z@w43cCVXj{=^>_K`?%G??-T7#rks~%D*gQ0>XYcEj-MwQd5pdJ>+4_c6&Ebuzl~S0 zb4^5L)PI-iH`bv_?giZ3GnT1_bT;gGWp>AL#g)v;ii)p){`5HBs$%$IkZV<=z$AF) z^b@gU1;%arWc!a>`J4a$Hsz46%N4sK%av&kEBZLTXIrp%9-hc?aCOHjzspPvCl2np zZnfxy&x6pciyva2?or<^xp~G-v#M2h14GXmI~=;&ut7+=MZw|{hgqpYjQ;rpDQV0K zdR`Bi*cjyJ-TMF5{{Q~}@7C9=#;m{o_D@ahG-bJuFKZq;b28qyVlv!lFvaWgna?)6 z9j9*6Iek7$vTM0{{Ga;&*Z+r3YgPDmz5m4b|9EP1y&#(Jve9&jTJDHvw-}qA zJpNf}cP4-TpGSZF?f>roclaXtjKUuqDSUo%V+Ygtz5zEVn`9#T+7BJo*_K-^cm0~0l2%V*RqT16R*!SM$32)juSQK@DZw)1vrT1o z?TVGr75PzlV%H4aUfX^Xy|Yj5%!9_ypC|YCF`nKelV-j-S@x0LwyQI}G=(3Y*)r!D zf9?*~6<^Et#jM*Nz}GJwma%T9@Uftl8QJB$TefCzshqcD!#ZbY`*qU|*W|8Tc(8Um zPu-lf@^lUs|JS?~TdvLOU3sPH_mhK(?uO|X@7}txEq4Z6gFzF!pK!9slf#8BM=qxZ z-w!HwbWz&k6Flj0na>*0($k$WyZ+aFpWL>4xkuuUs^<$BUj+3xCOuviZ|CyhntS_K z3s!61yN3#6qj$x9pBnq)+O`rCCzjwshuc#s`BhqXv?jlg+1%LiwoF0r$>X~I`My1c z*BcKUfBf=oje{6>xHD_s!})rvkFL{EocKF1Y6j=xfYuhxoyyN1pOax)m|{J{eACKr z_p)P)CHWjR6E&v%kV_7E=CtMQjxeSp5xcprul>2N;6>=zlTWekaisQp83-0;*e%%be|Mz9>emnVey`n4oyb^5Y^sW6` zzCLcc)%I&yQ+G|NT;;cLZ>cFqMsD=7pI2XRzyJ5c;r#ml?dJXKXZ_q3+wW>;TWcXV z?fK~)G28P5B^I!qF{|R&C{23iQo2fQ-u1g%eKM387X4hoEXaH@;@Dv$<4~r;^HS05E`samzX+<&r2;v6s2t;^TW{q1n% z`}AIqUj^>$7k(6oG6}wv^Q?Ut8-4y23*(MMcGox@%9b}=yZk`v_5~SjPx(@z?u_Kk z8`JMrHr$`ku{DI%MNHkHU$EkBTL+iP4CV!?TN<+FSw7p5ciZOtvxP2hs`X24k1$?{ z*;t;&CS=Z?!m6TR5M@2XGU4H=WF9pav0c6ik*EAtG4hnCG%2unry2@R75K{W>D?{c zTOA7@81Q|*bM>$QL)$EC9%GZk1}lqCt>TqAJo7@$ai(W`dr!8ttqd1Gz zQ;7%2!#5k77q%ppd zklQAF!E%Yxg!cl9C)EwO)b6n9o|rJ9Ria}>*NlF%sLf|DI&0WYV|4I2@aIm|LoSD) zr{(IBbOrvz1nCd=Ps?(7{^l(3^8QNooCookA`z1Op@l9m)@} zi+wXJyI950=+UPX{6@OudE;blBf*KvZQq-=$86oOWwlY;6)v4*?p59`jnZsqKL2UA z|MM{a|F`_-&-?xT{lEMw+#7fObyd~xR~2*Q`==h>vt8*)vh?exUk{hu{4;(3kFGo2 zv#vx*zO;}(cYR%+E62rb(a*kIzTUmO^XBVuJiWRabZ_gQ+xKg&&$sXY^Lqc!yW8)_ z%|HLS#%Qyh+uUbq8_j|pnQ|@`B|p1Z;NN3ZaN$gcTYlnHZN{{Ds>aM!JkCcln=bCQ zj#+R|_4!2L3(YP!4yUpS3wUT-q9mzWcm>m>^5=77KbaYvh%dn|& zcBQd7Gvk?t#ac5`a?1M)QqJiZIt4vA%^-KrcXprLv167N>>K)D3o9+zer;xS%hreQ zr|vjl8k zxK>%1eRrlp!Oh^kckbq#{%EKq)HTIYbUwp{iuRAIs#Sg*J8(v0)h+mJahw5Yh@7E|F}spIq1mbtJ^c{Q!1Z0D_CMkVKL3WGx(SkhQRI#lXb_1<&4 zd}CG?!$yXQGp}3aN3Xy9=BDE;t_7729&K9^HfT1TmW^*a;!)zAV_c--xp3`axg;N# zE$d=k0<>nZM@(Qju;EAI-Ur*-WoN$4lVFgEnGt!M<5|ayEYon6tiP|yH(!-gd*8Rf zqCiF0l$8tzUdt`lIKr_)E}@~r*5;7bL*H08#|wAev_0?etMCLAa5%X#xcfX` zka903AuVfS$7(!2&(C;EWRE3`@_bY8pVkkkBiQ(^v(-AbVqhpxp~2T zmz2$>tm~ADBCo%h6V!C#jG|e{Y2Lb2^SU{yTV$k~^0Si<%-t))CAsvtz+o$wY~FVd zQ#Twg44vBL;_~W+?zb1Z4r*N7+t>YGwbEloX(_{u>5Es*mQ0z%+OV7N^V+rd!Zjxc zl(Q>2t2xAMKmE`k{Gt8c|37|I+?sLad+uu|rZe7iXZ$RxU6;CYYs;me>@};ONd9|y z`S|kB-#&a`5IKH0NFx5KC6nfZpGB@S@9q2j^Z(D?`Tt*T-(O#9bEcH(v;Mt3H>a^) zVOZq1Dc3+JtUFKZNNWqj{Q3GjgS@+H`K~kZbjYv;3K%pSrQJ>F__6%?^PpuHdmA^; z+fnAq%rmd->!#;HHv)oFpPgD%t8wqK-a9u&reeWe+}fL7t)Hy$=$4z? z?sxX84qunfzN6`xc(Qw6_~Pv+Gj=^RQOfW?-g5h_t7oR~VJ@ZgRjDnCMGTBt>k7WN zOBuJ#yeZYwk!*5A;Mx4zk9{n=%ME+@I(G2>2bGd~=hF#pCmfq|9Mlsbv|9XAX&EVcmaqCi7rA?EU5oA1S zR<-oZ(udc6`+46@8qc~f9;yfcf2yvuI`UU z%W0eam7iZ7ZudW3^d@(2%r&mMs^1)Kxkqkae*ClQ=ZBX+A6N2D+TOq3U+L2JYf<%8 zR^_Jkefs+vN^K*esmbEm&)Q{df~Iu}Q&YRkgo9UxV>fd?8aB^q+VcWVg^Wb-Z#Vh4+?_ajF z=Hs`wCcZ*G@42_lithTl^u2qx)aT5`+}W#) zQ|8UOr@l%salWc*YUT2Wktb4=W~}P!Xq+4$DDs7ytShiZ^Zo=uXEk33Ulk_6JG==gQl@C0w+1s~oYEazKOogia2e)m{b8}o@ zYabsj8^%|x8vZsr|MSNKs=uyz&0Kfe^bB`HX*UO(NhnjwtZQFqnNRwe_L^GL~zWt@eoBF)$CUg11r8TmX-;E3y)No&?uH&{2ceeOLyfAKe~ z^Zb0##vL1T@8+nx#pq3cU1i8WD?QCDxb%Fh{gdZT2VIJFUt28;x?Sdau;Y+1aHBcNnL0DIeG&YPhnfDlspeeer?c`X`>u zlYGF(pyPHxP;l-0byAZUle3_?DhYCyo}Dcer3vcv!0);qx4^2b-LQd&fv%_(3Cl= z^Tp$*o)e~}TFyUj)Okg!WA#k0iJ4_r4ci2+S28su1}<@Pv6b1-cWoAPzye9O^U4g% zXN6z4RPJGlJizI~;%c&Usq-Am@c0LPd)L3_I^)8_@MJ<48}}JM1%2sjj0~FZg#>qB z+|ON{a5L)k<*1g7Nym>LPl~-A){(oVGO}it;e+4r_x-;b|9A8D_44=s|Hzz`{^5?i z!?LK_A74V2WyYR&eU@MM<6zsbj~dVY&!2u7v8<}*^M||T_vdcQW!t@D9qX&Yhfd3n zAOG%uo!|b?hxPwHtv?rQU;C@%)#Z;BCHYPNzMW-m*<1Jf)!)f8zZp7s94fgT#-K7O zL|L(TMJwYh&emo}<6WV~<$q0QFu#B5+>-iZ*KV%OlLeDne!Xp+$0vJI#i@tEC-3|@ z>!)ipzUTXuNXNcEy`|=X)rEPpR5n#Rw5T{w(R;r9Ud7B)63OdTl?5vF46atq@)s;@ z4EV~R5%VMD!ePs-nM#YcZZpp~IP_@2okM{w{1meb{uzikZtg z8JZ_O*ee}y`OxGk-CGteJJHn;ot>@I4ecXQ4N!R1&+>?>Fv#H$`V?Jr` z?o%?EX=>Xm=Y?+l{w~Bx*OsMYhWiZXlI&f5k3(Ju&3?r%Z)dBgddTecR-X*Joa5}V z=e4~BABFSxpLoR964)x#v&-zRI#2m6MyGW(zyEy{R64iS&FkOU%|2?2IIQRLt-rU2 z&;9$1=aNEKgO1#-{qjeGyIbVd`gmIz|LLcTvvYe6#w6v-9yXcL(OC7~NB>pmo>;T3 zUgx$NFErrhV6d0r3rgQp`}9p}kD|uxxX)8h=Xu_BzLQn)`tPQ=l_G+P4F>bGOplo? zNd3CXY|{O$=Cedxmb`r3wPNeB7nPS}_}I-Ca|mkcF#m~rq~Nsns_nIG?SN;MdLj4r z*`=znGbn!c5lYC9+q>+;>1}ygAHSHq)Ch81WgtHD-JAlYDw{JW1uJdOehzy7ui$+^ z*46^)Q%y=q@2;Ea3mbp7InQt*>hOsLX#(p`{#APz$v>~WNmr3vxhKgFSL$Zzo&HLhwC5AjwP<*-V)ck zYJzoX?h~z`dwVN?KU^;V@9O=3(i~M)RZPpQeD9Xn>2z#dx7xMl<5BTcyQ~A3+vm^P zVDNeMar4g)3yn{wZM(LKqyE;q$1j!7A77XI<=gvz-|{Cum%ms4Lw@_|>+$uj7dRf6 z`14;*TC2ZRM%_|Rl&kf!4tvsthfbyz9Rdf8FHVW{n)tAp&0T_1@V?SDjbPiwQ;fFU zw4JJ?bVr>>&*?iu!)_-1Nfj{%YVw~l6fs`)4W2*U*)r6XfBB?{)m7cmMRz3mE&Z7q z4v8dGF1>R2;ik0?5v~%3?0-*$EUV-x{=T+%?e;AjTYi_y2CwY6@q5~5#%G5@n%h>T zWt@Ciup>sV`u=q77qKguf>$mr(sXV4b>i$!Ax<`#XOcE-?-#_T@f8QV9`DfDvwyN+ z-E8RB=U!@j_~3Ti)W89W&hT z-AIVz;^#3}KCm<7;oaqD59TUyz4zrPh!hhveeWkNVQEQuvoa|vYlY}Ekx11pW=1YUmGB17(DQ~nqD!vF zoZ(oHi{W?s815bLzrqEoKJJ z7UmiY`=^bz8wDO5{`lgJ)t;3?o7;1*i`vduDl|2SXVLe4d9&M?AMDZT;uH=MbaZ!% zS~hd%j@J^4w;I~o2^`$^^>u0V48uh&vfBH9-b`LA#1)%(`hg)e*%$qu5Bs`lIuf=Qvdi>E4Y6jto}Y&rKcOOEr* z$cHwaD!E(wU&m-W6ihYr3H^Oz{`(i1EoRb(k6Cf4GJmzR@UPlxb(vvxdAd}Vg&NN{%T*Ix_s&j+i{l=pvjIAs1j`@OX>b^o4y zOq{l$;;k0LVq4$5Gjix4?S3pzbp`#=rvWn zfF)&)q0%HPqwBNSmBZ80Qa&^?95~RyA?TuT@|B|g(wu1JJMGyIyj+4cH3TG$MCTu! z#us`u>e-wh#>QONqn9k23!O1sG|h<9T%!ND z*M!IqzyF9HNL^+5*7Wn~uT^{VraEM3trt_8wd`ey)!gf=*~_*jE<1ZimxI|+<<;X) zmD@}Og}YX5TJXx@NYE>l?YEYR;XNFhhoMwfYuT=hg__i_j`nHub zuO_Rexm#@S{_U{r>CukveADVoF8#NNcZX-eY0Z4;TM zUb|J5=fQXJY~Y4-Np3gk5Fcv!)w21(UJuV1Kdr94m{@lyaYF02-PezY z^SA5A&DDFZo-}Vq-s^?~3qgw(68YXkFvhLtb<#8)uQR4fq^x*J< z{Fr6UA?mD85);!{6%TO*RxIaWX<-PLUEJNVyO(d`pZ?6*E|<2<+4}sN8N&);K}K;E zr7$r?2Vu3eLf(`QP79BPD)G$ReQQ^)!wo5m8@tvm6)Dg;6qF&+lbd7kENJ1%U6xF& zB1tE=WOjEv2y&T`-{>vZytb|{Xi&xpzW9ypRGv7NO-Yi=peZ}&C*^&IZ z+bQY%p>9rU%(s7W$zAhxiQ8Mp@IipBKgxSX|3!KAcXyTA+II07s!mwn-|qX%{XUno z&+-?~ueX2t_Vwd${=3h*I^N4Yzuy1<&vJkH|3CiqKdjaKTv+F@(e4~?nih9r%=WL9 zcYp1&m5V=Vaxx?O`{fW!{P?x&9jsrskCw^7|3W% z{I2)F)xaWTDmT-Pg!Bgvdt9TR>=16_-@GuxhxLy83}v1)br;QruT}}}*u0uyVyFaz z!{L~-xyx_KwOC0saSJLXN<36(;Z)%0Y;ZWZP(WyMyFcsm$A(JRu1zc2BgSW{R`vbW z#Ez=>@%!rzl;_*q*;`%;$la#?z0XkQ`;)6K?;0!xubo=O$k3BvwrBN9rT5&q;pY$Q zr`|fWTS72!kr9Io$KBYgE-i&lmz(V5S94V^xU%S7%#*V(dBqtE-aD@dxRCm+GTTAp z$O19J?0mt4L7#dO7wx=qXo_*;J?rVGfi&iWkv(|=j~|-&8Y>^)bjE+WcmCN@i6gsWpPj1z`FVb!ZJ4G`hs(3- zXG=Q*92lEgEN#HhrvxkKl)(uu>O7dQF4rZAu8ma^?|aoP0FPcgV*=`-2p2o>>u!Ni5ZE9Wej z8DqV2VdYF0m!khOov&!I9uN+eVmM)P>_FnyX?$@z7!*YrQ$o-3izu%$P)Zl!JS2DF zZne7Ob%qnw@=QJ*tV)aL2|q6n?_1V!pv{kIGly~c_x8u0wtbgFvTf!1oo{c;wW^uP z#b9@0Pwwou6GZivro1X$IjLiN^sUtHx^20(a`F4uHnv2}*!S<# zbN}WuD*Qu$i`UhgE8KYCP$7R|M~T$L4}(H?8hPppC6X&h|~Z7=6QVW&v)8OHmST1F5q9a=j;?=r>Wg5cpSS=h+41s zw$@xJX;<~m++gXw>A@LkGlg$gFJDl2T5_F_%aX;eE+TC1j!JD)G-kNPH9k{zw-war z5j)m;;EA2pj?Bq;4=wp@<&gJ8ATZ;;6_51p>zto5wa@IytqtG9^fX39P?GVi>76s5tPV;ao65Lo zc0c1f!>S(w?aq#qjIQcGtDLuN(&59Uu`^Gb_!gI~6RdvO5aFdLBifxSDGu?9Q};bRvkF5a4WR3lhZ1G!l8Dj#EQS~1PyOhzTRi@ zdHLt5jM^K2-?;U)|9*XWRn2LY_i_6xOQR%mEzh`Y*k-7pdV21TD^WX7ecaK}CwF&d zp=MF!_0vy3{n&8DD*OHGTXi3wolRxkUsts+X8t-edAa?1mqp41qWILqZ*TK$+~afF z$aaoj$g|^@I~VA(Tk5LEx*xHS;d7b9{oY4^%9JD#vqveb`{@=Gof6K$qtGv3|swN&p2K^{_a}K7vZ)9KhF23pX<~d zNbI>-J$+NOHfOouZQaYI^EV1L&dhzjFK_yxA`9J^?o~!_a;D|p_R`F(U2(Z^hi~ah zg{*Dw*CkK)OqtHLVMmmv>RADokkt($DL1E|)UZytwuS3jwgzfrK292XVTHMn!H)f^zuWT ziq=xe21D&TTUcF>T(I3>oX4rFH0SRRoAVpl?PnN>Hz>|apE_j{ziHY!kt4!_9KsAQ z95oVp3Nz2QH#0amc$WXWR`vcxDjP$&#*yHuT^thc$bIIi__gNSxn_TpL&kX^?CcXfH>9NyU zC*8lC9d`4#Uyuve#+y70Hq$#cEPTQEqUXx4UFo;N=Jp?7qSw8lQIXm0ruoJ+5d|j( zftFU6JjGzP-{*Q}>Q1SgK8J}#(Ma6U$uVu4N1gHANlS0PG&p~GNt&;(;vJ<;VFwm; zwAc$Lb;%s&IrL8YlkJJ?dxf3|n}7bsL4IyTP4ybQT|MOhUlZF zdF^^DmvWb`YLa;U&BsMTxZB{Z&TYA}y>X=_x37ht?uoIlscL$3{PIb`s3Yy4D-T%+ z*!W$3eL2VH{=Xl8f39+nX*vAsqrvTf5DTxTQ39v>o$hgZiTRCL0nU1O&v?J-zYLQuxiT4E?4>*T5BPDNVIcF zm(Z@=0@VjHCuy-V#FRTfd*Rm9ar6*l#(7zug7T}isj*%UMG_o55*ej#HO|m(c0M(q zYZr_B3|AMgi_zloE^ZC0E*Rs0=!P8GX`$@e?UQT-ZIVucSW@s8K!A!b$9R{J&?i|JkiObibaTBR3Rhl;bjxS zsl7RG*ZNH5n09$mbqGU*&8bamF4@o1?_VFk(^>aO2S>2x8S{pNzK`=;nHeUXQ9RUR z&2m=3>tSV5xyR|mZ+%=lbh>*dRy63SGbZ$yoi{G_RB^UQQSoM-5On2mAhT2Q6{jP` zJWr#t=7yA3s4FEFF*s(f(owqXA}Cug*Ow@4>MFg+*-kq zv}5(n7fMb7f2$a`KD>73qF|dO3rGLP-)7&8ZJIRZZm~WwB`=L{xB3OfJ2%66SRQ1& za`8E#a_EWkI{pQB6%uthy#a%T65l@_+rSH`AV2OnOt(x7$4}wA7qY;K>`W-P_dGu1l%m(m`V?S7e^yE{%}cHZ&d(bH<`>;IR{Ex1wk^Wm1-zqj+#rriFr{(jx< z<;6L(=hXdtyM6g*e(|mS_W!;-{LKIU^7qS^&7NPsetZ7^Pw%JKSM8j0?*6{|`E}YC z17_aY7xVo7|39Z+mi+qj@}=jKpE~->FQ*s|J3|`J(#p*P3*|u3#DV(Qn9e01T_^jko z=i>ZXbGjEPJg+^SxY~Ws>`AZI{I1KJv9H7R%5H_`NUN^nl@Y^rjZ+GcLoKN zW^?@tP7CRp!Exts&Wb$U+$c%Coi>x-wg0{--Z1lQ<@tW54MuaXZ9h=W(1d^vs zUoNloxN^VCxfP2(?3uqyH@h+Zy!7f4^hc~afQ&U$__w#M;s!y*zeqX;{|M}a;$NR0nuD`#p_UE_l)e);MCKg;L;|2qGl<+}L&fA6o~ zmjC~4{J+=r|K<|l;z@vjKJhH)~qRCZ|s$9I?X)ui`U&HZLX>-W9M!R>UFa`)VgV=(%dOIyO#)@ z-R!a8&~uZg2F}dCT<^>cs%no@U2xdQ@SAV2mAP~2Ysu8-|86+UU*hGN_Fh*#Of&Fk zd*fs!e#MHbi@z0xif;5-E->*Xmxa#G+5QVf4&>hU?Bq~sl-lK*RyFhFhQLOrz`*Z3 ziC^w2y|&FgV{GIe&HnV{_DCgoYLP;n%OatZlTMsEv-nWT-jWlS6lZ&d3f%||6VaY{GiG~`s?jaK?K#;m zp6~3KDzH>5bc*P{TQ}KmHA{)!EMX|{SFQFC4NPOoP_o*qV66O%D=qUEQ&wo{-?W8c zlV&aGTJ*iAaNS*pG}Gm_t0R1xT4v0B6S-}#?W?7`de>eLS(+unC%f%?)nmQ$+?tL5 z_RSM?R8~;`QCC}A{d?X+Irft};vMS8_MNmXyLCok$K=bO9v|HY78H@CfgKHu)0`qlmaemQ=Z zKA!vUX+{12U%&nyPTf4e?$6J!+hn(Im#_cxL(KW#x2K<%^FPo1apwQu>HmxW{(D+A zb^Gh7>+iF@Ufyq?v%L6jUb6Mu>9IcAu03jhJ{~_Gnt%Ai;nS%HRwSuyj>!vu%9jCU+C3u9AwgeLTDPmRe7SN3(-CFA-+E zD9gVg=iTwbxAWLM?yZb=t+Hs+__nJwRhrZIwa(&44q~&XrfDe!>RdeY^jrVSzY*PA zr=Co+h<@TQPp>*{lfkhMJZfiko^8y37PQDM=)~ohA)&4%KmFNHeyRNN<#yqo{d2c{ zUM8z-v0Hn2l=_MluSAu0eV>)Lg004F@BL+cg$#8vZZlh7s)k+$>d#_2y7 zzx>(e)r!iBz2|QKeA+Ix*hgs2!>5t5POHl*>wfcXPh{SrDum&HhP5QuCg<>-La>}F=$FiVgtkW#7P|j zA!#0wAJ?6ozG#U^+71uTlL{J3B=#_8ce4mCxiLY=Wm;BgdgRUT;;g*(%2qdWqUEmF zS^IQ+y;I_>6Z-c=YUmAt#ilp5{#~KPka6uo$2PGtdCwHb6-o!YUPLRpy8YjG;>F3e zC65f=XE4lX<+{?gN&NlH-*#D6w=9nZ<<2hOZl7x_$B-tY>ll3XOPf~PD~3rr6W1&= z(OVbab)sj@q>dB&+P7@Bkxtt_(I(%zp(n#JJhXRCa?dmIDH*Bz8{SIfGVd?(TF~L~ zR4~rHDkNpjwpzLN--&6RXL!8tevfUj+M=yKDfi@(kOcx41-|Cq-dFec)6>)X+h@P~ z{qN`U`H8zP+XPOzTvPTKS1-T&p{VX3&t8S~^ZjhF_*rlajQ{`bt<9U4pZkBu-Ed2D zie`K8dAq*7{rtKrOXYe0A0PL>y>8K7A@L&jNNpD*;Usqz-lRpI3pcP#wDp?f`zVCl zo%Oe6UA=7p+Xq|Q)e*0XHLpGsR66VJV%VT}Nkz%H!NtYFur_U32LH;Z&x8dy0w?)| z1)e%k+W7j~@z(*4(>ghqb)4aG$dD4ewygf{^mk5fX|p1Z8=gAJpb>h+X^~Q6h!Izs z+EbMh0aITyL(%?oCud5QD>Gf1=yl|k)vjN0lI=V9T%P&#-GVL;#|;do%_**3T^(1% zZuPO>RVq@OBq^!(#>jU|<&CLt44x_lMJPpu3M}ooxonlbznaqa@fnKb2MA zJhzG~(5P+edV%ZLw+p|PpXYykPu;FR1(IQxtX6#6CZr*{V20B73WI>anX7_C8PD8a zdw6Nc)?im-?TMeuW~>qWY_V)+>+j4HMe7)yT}o%lxW4?7yM5PQ{);m%a~!wnQ=Fvg zWD@4Hb<-VZ*3fA|p-V6A{1lZMrK`wlV76jbiH%?Q5ym?|HB=_cGD=TekQTN))@$j) znDSzOWmg>utxMmoFH}CAxvh8IN^S?2t$Y&YtJQ8Qn4CV`zGy|(uDRdN=#-mECqKWn zrBNp(RmnWlgU>kR<gv^7boKhF5Qn@7!#~QSSIk5sa~7B z|GhfH^FlGf?==p=C)gft+}s+xW&6SGq*N9^V`l{&4T&gQ@nEjB;J~{lnr3KfPOCbT zq>|8gU`I$%+7?Mpu5`Y;ze*P`kuUx*X{OUmJHsM-e!-Pe-jhz3U-^A}Isa|5Jlju7 zGG9L(cD=WL_S^bjU;2MCfJlonAG4`t-}& zxGnB7H*90RvuVfoD^9WRIJ4I7&YHWa&v@c_eg7#{9S$vSm#4~J2^CK9UHe`^%5e3h zGm#-(rWdEDq~6}JS@44`Q=s9bDM6uAQkYISF zZps>0u?tLb3*`8fSkpGWymzBul56gZ8_sLbSj$wLWz1=Oc&qA+R!QW73qe{u-vqq% zBv0BzoxG*8gO5=7*=CGV*4G|7?T9Blg#wd1yW2w8@CyB>f z-fvV{v@3V^pMRehJguDAU3ewxs!Pf;&dn=CG&vGkJeN9L@@DWCIkh9`(PW>{G!@Uq z-*O~RygPq_DJm*-g;&UP_A?*;%{jZv=IrIn-+wNj|NZ?vuSmvBR%yE^zK6mt^Oii9 z>*(mXZFpB!Yx?QbgQ~OtZ0wzTbWUAM`qd4+DZ(pwH@)3GQ&iRXruxK;YP;XsZ`5Zj zkqT)KSGwRC7iF+9)-rwFhN6z}uQwJ7ENKi{D|JNqzf`MrzFbJKqWnL7$^GV?+jJKx zzVn##&njGL+wxUmevW*hTpqfM92Rn1d3j>rYL&geZtj|YE^y|eRc=hW4;5PIxsdhQT0n5tG>TT&An# zq3O3YPF`^piofnGd}cGFY1CBywdLE_iAW}=EZaF*XU6eH6O)kT(>0E2aHOWL32l@T z6ud4if8xci$^HpyuRonSl;+Jlx4=KN>E?RhJ4?R)Z1%TDW#>gSp--;Nm>5+=brb^zsaAyy|F?%p?OkO=>A6@ zMwtShlafoquHWcUnxV>aF=N#={j@LJ?B1&hHF_|%bj^sk|EN~jt!_aDh! zW%3c+y}IYx&a&?XXDyG<%@U7%=O}yW&h~Ap)plQwD4g}FetX~WL+D8_`=$jB3pw3a zbyVtjP0}eS6A7B(*_F0*!Ky{^1uI?Fo@ufZSX~@z6udLyl*YI2564&i4^$Lob`{}b zI(huUrkMf_JLGL&=l_kE^4&JPW8T~_)g5(xTMuMUz4_Pw`jdKtYg>?eN#K z?Eb3;pJ%f8EnlnDdePLyouTyR!c{EK=7`=5f6HrN+*N4E?GifiOwr`1&Mi}SUXne~ zv|^ImsfU{dC(o43b?@5qQ)Hrp=LA+SMb$2m6gLaa#Kmv_-3(avF8HpnhhV^hRloO% zZ_?T#{^#T>uKqce={hzcVbKN04A%VEwzhgfo~N&C%KhaWUYpcTzBKfy?7y|*s|t6( ziW6ts!W2;I=ss?;_zBNr-UO*HEQAI)bR2n zYE_nVr%EKN1a@k$EOkGibSbhq@MWO!#@jodzAdquS!7rr63K01c1+;1mYdmrt7~F2 zRn|7onNU+(`}Ri3Cf-#-f;=-fhLl8J{c7FNK9%!A;wIj-XAj@1UaNjTHA`kodxUps z&r_9vtAd|joUT51>dZM+jVE^vTUMP=*%WZ`jQ%FCZ$Xdpiq2IZzt*vC+P(>woVf)L za@}~F2iJmLKPP=k9ZTq3yDnpCh=Yg=&a296KvM z_15PD*RCHtA=TrtD)gA`r*$(lI2}YT9ZwhkDH!~t;J=p9gsnBJ9F&Wy1Nat1tWw&v z#bvSh+Kc}0lK)5MzF+ZM&`~3xQR~Ex%e!`E^c2|f2{3pp_}Mc{F2H4~NZ;&c$)j36 z-&MP!gBw{}YgYWTzMxhtv@%R-aV^hUx6tzQ{ihfhxK%t|97860oSFT7>gKX6AA7Y4 zYk$4kt{G`7>%o71(Y41<9@hT4V!E-3HPJ2P+hwJ@jvoEI7a!cH+`UkTvm+=}Q$#9I zG-YMZ6a~+~kbBc>-b~*+>1^wouBg^`pA2@U@83FAQgTgQVodhjRn& z-QVxwYQ?QN!6ofUENAiD>#L+T^%`#b{IbOAXzhIAiN)t~s!hc1%XnOF>=i30pYmmI z<<0h>8+TT((Yj^PDY5nBzKJy}4(@s&JM-ijI}P*fsA=lLlFnN$1T!{vJ*#;0{rByR z_}j@!D!1xZEaH;d?;5)|%YMhTsnvU8*K7Odxi&dO8%|?U5xiSzern2AzboHtKE-vJ zyn35`9DIRKFs}k=lkC(Td`@~yWVfhjXtuY+U`%F`90kVfwl8yTv)Q|!psFK z$D1Bc7l`>RA=o)%k?+i<9Rho}1*aLEz5AW=V&p)8F*4=)WT2qQ^k#8Y) zsMEG6{=lPCp1pBfWvFz$`g|2zf&duCM@)q;W35Oxb%C=R;`NI zsMVaZV|Cv}k-TZ0E-H~}N;l*-UprRQz2UgeOuZ#9Htk7p_21YWXx(zo*0}%ha{rD; zMfchcgshkC5?!!lWBzK5O{wC0j|(2%`Hvf-s|^%zTGR5&;35)dG~U)kh)Od zAK5JZRUxv;Z&n|a$=Lb)@482)Bsyo#Y<<6i{u_&3#o0= zynFERzvaQE8zf#YUUkG`+tyR@zviq775k#Sq-Yh{|!|pQ>St$PF`?g z!nYgBwKKz>%quI{En9ppOs~|t&i&W-)-9hr<$5baD?`Ifyw)G(7qCpL?`QfLx|y3N zhttI~q4CVYRSSRo`x_c+W_`Tyg;|K1;L9*8qw8x{x$3qqi%(UWr7C~Y@%!R;cOJZ) z`7-oNW%l$P9gLTH+I?1jYSX@PVcx@UKQDdSv9b5>p1XP5`_`#mKUF20t1F<<&GB71 z>Du0-CKqd$>^{Gv?CHiw%W?}kbFHqqYe|P zDoCbW-_Q8wjH6S8CifhkXtU^MQ_#%MWoO0ShFsXf34Q1 zk@&PgBw%e{u7Ui4i__j{g&cXa*kzhOgw~{_O_;TH(|s<5$rqSBPKrKPS74P% zew%RZT1i`S&-BxqzjXv#ZOfjTe^+1q(p~NMN6*#Hdpqy%sxPxtSBsb$rXLe9m7b!W zJTtN~RZZmRk|?=a!DU(3MR@$98=KD@Dco^<>%%Es`<}bVD=P>Fbv=-uGVyOm*8*8R zmESq659?BI|Bbrkap_voR`~^n+kX6ad%kn#u@W_H<<_|tersMF{5RjNrC0Q}lG5|b zl0hMX(T(#@oLV6{xz%M|uA|k;+uJrym5y2D$>5>*GIDi}I0Hx5bFOAp&7EqN3^`wJ zJ@_v6weH)-^gZR7U%gmf*DqXh*P^hb!u;yCz5I?W>P3p-p(PPah1ORhT3`RZ{j_Lj z+d+e#pm1XUA|}L$`pS# zhDHw8#Krm7^9p}42?)+lOe^-=|1EvCY}nN~lNLUk8`km4u}#5P=H)R)L)o;MYqieh z$lDn&zGI!cc9X^eFXen|yMB#TGkV=~9UooX`RLrv*G&xqv-+IU%r2*eJ`t__a*91a zKmYwaEhSZ^K3}QD{EBHaazj(wI-Vu4di{E=7X4vwiE6-;iw`g6D!X%67#Xj3Uu3mn zud}D{m)q4LuOwV|o_$fkCp4TzueDWeE-o^~Ia=E+v`;V#Tv_1I9`fw% z4mtkkQZv`g3BDD2ch-t$i$a-r*T+oQ9Xcs>(t}&~wmx7j_WS+s@9&>QFY9-`-@US` zO7MOD^~W<-?RuMh`x~!y=)L>*`zxyxgu*xi`R}&xa1ZtSzHp9Q^pgI+o^2=1%x+&% zOAi%sni^>3w`<~AUzy)+Lf3EYOifD_Uwg*I$!FWyYd5dDdfR^LpRx7W9EYjL854d5 zswOcXdUKgoPShcUOK@Jt3Z*kCsT;oJ9oWpZYA$DRXztq9pm$fJ_n*wkao*oM{q)lN z$Ijf@ww5#LA&MNG2t@5WEr`LE+Id1sN^4!)_r#o1e%P2py$ulp%zwPWXJ+rhO9h_XJS8tpq z7P(~Ys%ystu3mp#og#aA?UMfMA3Ti$r*?D*gmSI2w_B;S>6fsNN`Txmn*;y*qucZT4*Ka$0=N zYSYpgUco2B zUthiBy*uSvy2EYD4Fxl1+UG3Q%e@Gr52g~7(g=}|MaygAsFi>tT|XB$n5d;k9Z$A8y0TLpx#(YCM~BpPgZ{YHQb*dGES7T+`j!pMG7drgGFKeLz?`nkHtG6IWL_wc#7Y&_%UTD95v#`&GD zGO=yy{l#_1A~rNYJ_K+uP=iZ0|NsG z0}mqu12Y2y10z!d0|O%?l+6bc0JA}I^BCE{Y(@qq1_nkUD4UUifl(C7W@cbulwx27 znZv-qu!K<>&R)Z)0aXLiYX~xdL5_ieVGE-Xm@UGLg_VJsf}y3AskxPj z0RsaA1Iq*k26hl_U}R*t0P4U0{}~t-Fv09(Vpzb8V1qOS_d0-zfKz;Q8#{Wk=5uVP@@c}{pZk|4_ppa+a@$kva%V!c` zV93oYDGG9TQizC*QeZ#Fz{J4Gz{X(6z>t_+T;Ln*76LNU)74p_ILO@z#D-!<28M6f z7#Kmk%eo!`3JM?=30N?>ps0j_fiZxAfx$Q>wK$o9fw6^wfgz{7qyQxTfq{WRBqV~^A!dL29=!B+)M@r<{u!vxv7c83=AwhAn}saQ5S#UKOU{9&*wmJ zoH2@>L5Lx=BgFC-F)&zsWnf^QfDlWX$-uC^m4QLx7D7yA0Rsbn3j@Qp^GN;?1ErUY zl9B=|ef{$Ca=pydWIa%T!Yv}G0Im`@1Ia!;kbUs5Q*cR5ODxSPQ2?dt1~#~^TCgn{|(83q<%ZU*LBl)JL?OAfs0<pl&3`Pv* z4Au+|46Y1b4E_uu43P|R49N@`47m(N4CM?p42=wJ4BZS97^X7JVwlgcgkdGaI)+UQ z+ZpyS9Ar4gaGK!)!&Qdc3=bHdGQ4K^!0?UXHzOk>J0mZnFry@+JfkY3Hlq=v1*09K zE29r%5Mv}`0%JO39%CtE4P!H7H{&G6S&R!AS2Av7+{t*5@g(C##+!@}8DBDfV*JI# z%*4Ya!X(S2#-z_=!Q{x~%@o2E%aqPk$W+DD!qmq!gJ}`d8m4Vb2boSYU1fT}^qT1_ zGXpa>vlz1?vo5m*voo_la};wLa}jeLa~Jb8=0(iwn0GNBXTHq*fcXvcPZl;7VHO1z zT^4H=50+4tWR^mfI+h-mSu879wy_*xxy`&Q$aBy?Ta~N^BaYS+CaWrvE<5N8C&|;y(LXU-+g_VV!g_DJwg_j5) z6@DthCZaClDUv19EwWbRoX7`JAyE_2FwqLp*`j+zABZuFsfl@t<%so*Z4$dC_Dft= z+)+GLyit|H_*+R`DO9ORX}!{2WiDk4 zIcsK~4Ms??~gR=K6hscNB`p*mglxaxN`b+vG{cD3zlFVrQ}J=Lq!SF7LA z;MK6#DAZV_aYd6&(?T;_bFStEEha5ftqiT%TIaQyw9T|LwdZPI)M3@J)XCFXq;p-D zN7qreOn0^JLp?D)AH62M?RxL@RrI6uC+Z*9|7T!okYljK;I^TVp_gHk;V#2ZMp{P6 zMzf8s81ovt8P^-{F#c$wZIWs-&*X-wu&J+UyXhg*zh>rUMP}>FUYe_zCz#JMziuI7 z5n$0{al(?-(#f*ka*yRtD>JJSt4&t#t#z%ltyfvUuu->3wOL~G*jC9l(RQKj13Lw~ z1iJ-x59}4~6YUq-KXOoZNO4%^@Z3?;G0Sm{<2xqai8EC*!2za^m*J^T#K~Z%$xM2v1m*@F&qfaZ%#eB+sNd zNgtA(lcy)YPH{+?lJYXuHg!_!^EBJENogGzs#`Dn3nM-(>Zfi=Ep3Ltod2r zv;DJ|XaCO$&sm?#k(-daGfyZlJMT!oTz*CV#RBbuwt@$RR)tdwKNNWvEh}a$jxF9% zB2rRNa;8+Hw6*kMnO)iJvLEGP<(n%6EAlGNRBBarRz9zCt6EykQk`6Vs7A4-vF2f| zW9`B^#=4}sL-oq_E%i?u+#6Ojay4c*o^3K{n$+~AIlOsSi)>3n%i~t}*41r%ZAER@ z+pXK@cd&G1cAV=p?wr~AuPdeNRJTF*wC+DW$vvlf4SQ$w{_jicJJ)a4KW_rtguDsY zCfZM2F-dSz)uhLheJ5|5qA;av%9p9}Q%_DanKpkq*Ywiq4`%qx*fCRO=7gDlW@XH} zI@@XX`Z+RlI_G?!n=<#(JcoJf=F86Sp8sn>=7Jjw-4||Kq_$|3z zh7F|~UTsX+cx{vSrbC-8Hm}>Fx@FE*;jLZU7`Ih!`>;J@`-2^kJ1*|@+IeV~^{%bE z^>#1cqqt}GUeUe%`?&VC?EAmJYX8>*1qa?7Oh5SeQ2e3Wha(POITCo}+)?kNCyu!t zJAB;n`2G{NC-$7QI=Smwz$&a_DQo*L&Y`zy1E+^+WQ< zik}ugkN*n)_564FAND^p{%Zc+_0Rj?o&UN2|Nno>zyt0n#XI{lF#UhWzz60rq%?q9 z8~<-Jv@q~+aBy&P@NjbS2y%0A3ySjd@bHUD2n&k}3rh&{fB}>OvoQ!pUT$t)K3)Mn zJ^>LvK0Xl;!6yRIA_$>)pcIB)u)zNV41ydC3m6tKGb%AK2{JMZGXB5Iz{0@w{|KW! z0|O%~BN#xMGz`oPj7%)7Z0sDIT-=Q4l8g*Y%rHp-21XV}W+oOUR(3WHR%RY!Mg}H9 zW)?*uLsrMYL}8`EMk6+ni3>M6DXWME9ZWJdDQddt%&s~q_`}1G$;GB(i#9c@xl9g8 zaXqxS#7z8B=%dt6n_CWFE`9vjO?}Fe)-6+yTzT?k>DHrHpMDKXD{Gs!Y}>JG&%T+v zho_gfPhYJakLD5i%#W66kP)V58sBxkQ$OQ+3E`Bgp zNqSh+G|4$wG`ZO1qbeK70qkm~hc3BH4p}Uo;u>mJlKKel1l%rAM>ygCEe0NDu$_Vo z_6!TJe2RZzw8m!F-SVC57WQXUR$4F4|J+mlt=8+Baox7B-<;!qMMb~0UR87AS1|ia z)vxW6%`2YWFxl{Accxs*@9*^yib0ZBPD`i1_`FI$_HbUt#e@GDX5E=){_k$!5e7!3 zo1H2z7botIuDbI&&$|Cl?ELSSmd9UPw?60HrUe(&Z^pLwy}J{Zym8wk4l5>~7bOOB zE>Dcr-?qs$b+)^ebM&6w-^<_Mnj82=aDBP&{fJ!$g?_(mK6ta>kJR)(&t84CkPm7v zYmQyQ|9Rf#T5kDZpWn?gtU5ZOHcHAyGWf{P z-*cWAU$0std6HrNlU<78>!nV;%}qIR+e+n^4)6K`rjoL`E%j9**4kkQU;mA8xgVUq z?>74bkrl7^M!yYVPuXJn%`8gl?}qvb54VP&UY>k5SMlHE{{2U+Z2Jnny~_%Hv~y?0 zC7zvUK7Z8vvoa(*<#q0z_iJji#m$vv`&M6G`{B|vp%=>2bH1&*zs+iL&&}L4mkG~f z*ne&nTl7^X=*{dqd@pPKdrX<`gmWh;v46~RZflz>l6UGq!H=PNN|7l zS0hjJ3okDG&J(aX;4IO)fv;uqyIuxf`JXCbkM{7oRJEKxz@HVR9m==;s#M)H%lGz6 zFUUO(TfTW+sgU!mKS?(AK79Lc2Uy&WOF!gQ_0fIfHNQ)o*6z(R(Tk$AL+@Uvhi%Kf`{&;M`eiH38vBm59o`hX zc>cm{mb&!!_CMoa{YXeQ|F+-1;Jcr*r+TE->)pRjcy-uuigEwHaNyTL-B*=8?0=L` z{Ioo6+MDg_Qa#60{fMk-RBW-?y+%lQh%nHK+Q6R)l3wkH(ZxTn?cKV) zc+R!|4Cb5v>GHb!3NL*1Fd%YsX2D&>e)eg9<(K|mam}*nJ44BZ{|p?5m+b2qMi zQL^)Fjn&<=(j7L_{?7iszf$0D;^DdHUd;GZDYw|Z&&cYV+uU2_rt?1jvtFt$*<-f(jBEcMCcR#zn)VP@~PI|_Jm|D$$8`%FdDD~W5v)A&-&b@q#+}dK! zs-M0Pw0y zzwEqY*EjLZiTNjbuNZyYuxp;T-N88D+)a`@8XTm|_E+ou(wzCVU4n_uz~F$9&xLzM*91?d|#BrhMGDspNAa|I3g^ z5{AX|r2kHQdF8h7v)YuGYl~&>P3@nR(40}?`1kI$3(u=3e2p#mr**m7?M7ks+6>jT zEXNc7Gt5=_yd_NP;m$LSGX2@J-rnCe<8R85!o~|P1z8#su2tNXc;&qQaa7`7skP^Y zY#&TsE`RaT*RnrOTi#vTv}^U+sau4i${0-5Jnn9ly3-c=rfeGH3V{Uo=OWd4j9(_U z9kadNJ?jty{`|XC+sJORxi77Ew$wK>y5MHr)g$$N}s%5++1I3_<9DzTU(*UHyqRd{+gtM=xe(U~@L?!8+P?|)^?UOU0&{{M>yUb|SYNy+>&Kl4AslG$Plb!Khb zGJpB!pjV&HZOmRUdDn^0SM3bV#BBEz%B5bm(%A1}{8eXuXioMFhX$1f&Ik5)f2f^W zv^;qi!~OW>i3+dmryR2D>Rtcs*0+O-2WmRXzpspW^e1~3Pi_XkjHhlvzWmiHPA{|P zCgK({4_VGNJ+S|tp8t2(>PdIo3$D!Q<7kar^gKM^!2#uC?xjl@&nB#CxO2?Mujv8% zrTlmI|1-=Fy2eVh7S!`601I_ec4P;-|{JQ#gkb#J_61MkwS4xhih+MLBY^{!0E^Pkp@;Vn^H4nR#s2n|0H+7kAHMd@E$G6#H`P)wY=ze15hH z-7J>>dGLeXhpx$0I{aIgH15v3r!sN(i~kIB)&_^!sxCa+?doS_Z=UhzdACbcji$Pz_XJ-i8^|s7^^UYVafP2r?+=$=NtLMBuQhV+{gDDgH z&9@zQ^mt@yWsf@TY1=f%t7XPL$B#m9_umTmdNX3S!BT%`*8#bjw*f2P+J-)v$=YKV@9yb(m~n4BgNeiMX#O+(zdzX@WE${eLs^ct$SAGsSbUS|8-OFF%9#qeK`?qaT=^C9q^VUu>oFVsY`ZZ6*U6W)_ z1|57cH*?>_QXYkj9~)t9>v7kh7(_>;}zz}kP8_g2nV=4}g; zcWr(1cwA1`e;d?)XouSDUwbuL zc9R*~?it0i&Ia?Zz8ezq&9tXtLpzh)jSGTj{xfJw?VEn3aMwJ$;2E~|VLKHMOmMsQ zpCS5lkm+A@Zze2Javf<)N=T;Uh zj`zA>GK0m;uKP|$~ao{EUj3*;0?p&Hovmiuh}9vUOdqfAO44wq*>ig>P@h{8iumSBdqn zu-Say9}mBWeTsW6yDiPRee3+M9)<4NzTQ<{awj*gv7cu;FJ;QtNk;m`m3fx)epRRc z>-#eI(yujnUuJJRaGfb9+4K5l=RFrAFIt@}KEK3l?`+OHmv3jcZ`r9WduZ#MDEoBY zV++5YG>v+1k@hI2e$~Pmhu+jIH~3iW!2ERgzm-|Wi@tWf6ych6H};*GNruPe`McR-EwQ=l{MFMQ@3srUda$9v$Q>@G;ZEK{#((@ za~;CwU#QxDE4E_g(l(QO_s-tFv6EeV;a9I+`*^=!x#(;Zx%X^E*(A5OYriWeZGW*g zKX3j2%LjiQINrBABlZ1}wdX4~ox1mI+V|}L4DMg9a@S{tR_{AGe{!nnwhbJ+4(gTN zJ$v@Zp_ckQjc>u&S<3&9)j#$MfHuCK-LZTq#l8#2Gk zYi&=R4-?yVCj6G!&ZTEBWFOAW`Ol#JrQ2Qk^2&veY>lP6-8*jb{dvEv_k{_^+ONeM z_h-j?%T=`ntGv1N+9tXD(l3wg7q3QKIKS;lq>9k<*pvw=|Nb+43Z0X1t%@g8LAvBW zgN@UU?@SNkEQIA8*WVFPRWNy1mlk00Zr7WfZ{FF}y2aW%>JM$&Ue~X?|E2v~r&f!b zTTAt3>73Yg>TYe)wO!YD7xK58o~fSubJg=8)9K=tyL0ta&vsnA_v~%?yQQ)J8FE(3 zD}HO9+x6l3#tRn{_`lt^Kl!cdI;PPvW%AdH)$coV&I1 zdUe)fr@FQWHmA#$b1NvkJI%QB{rdV_x&`YTQm5ITRhd_7ZvQ#JXj9eX?%2}v(k_C* zYZ98uUfq4|z4XShq`LvXayN+0W$8`dwSLCO+=*6}yZ4l+>h0EE_EzowtVoZt*mqLi zzSjg8S$?~H@hP4@(RbqXO2u=G`?gNhVJ_cZ{%Nv(RAy=D^vDo~i$9MYH(gospCN3S z>04nHi%IpCx4+MSYpOIMeDSx*Z*ud_oNRi=X&z}%yMOP`bFRuOWS(Dmv!>}<1IyjS zD_hrHzPC2HD%t3M$s2~|b89QVwUlk0pRZRa)gO3Y>)RF;|8^IKCr|p8_uX=htmG-} z*DbrfM#gQw|J}c-^-HhL{O!$icVg|@JYPQD+V#~jH#T2MZl3e(KZC`seUth;Qny|B zQ1Vrd#pOSP#r=0tKi;pKGwbFn)1x;{=Py0%xpi^g{bm0dmVdd|Xr;Eb{{NN3uN9ZC zyW!oOvL~Ctk?-Tb&7nRy=lxb?v6=0DtMWa2#;g6GwC;x=I(zE%c6-j<^S$R>Kiynf z{_a0R>M4o)p;kAFV_wbp#3Fx*f3fu(5vLul^BU*%zTkVaxvKHhVyoQ_Usv*#w)C2B zd+)#f{npIKR*l`!-v>Dpe|gQUczX@&Bfi~>Z(aU*>ZyF_ zueA-ei=JJQx^ys@VdK(GG1v3$=I_7LxazLMyz|zxr@Y>{y83J3o}HJ^Of$XwFZYZ|flW|32$|`8T1=Me`PRb~{U*F4D=CdB2pW`qu4;yO+Q0Yf|3j zmh1Di;PvgvQxw1Nm=w)?_r>=7=l_1K?!H*8eg43{W9_@;e(eos7hd-F-c9+rKVNRM zUbpzBd5CFB+THH8kyCbDk0_iLz1gjLUvIeei(mB*#iZv%2fxU`hcs`xeEBj^tW{vV&$LBtn(V$RMz1Bv4)!&dKT&I0b&-(OG zi2vQ&Klkr>tH0`xu)1(DIpN*pExoC0d6VP+GlcXW`yRG+vp?@Sz9rkIy}3Tmyn6qJ z=Xn9yt%bpVw?%y0!FQnDF@0<1quSWlKb~zWy!od8+?Ch1w?vBT)_i?4Tef`BG27e8 zac_V5U8t)HvtA)&WvaouTIoPtkMRB7r}uZw)+^3uTzltjpR{`+``sU_eSU5K+H!l( zm9v$Zm49seg8nnimMz#{km_3~;P=UW;=f#Fjqh9x--;qj^GXGz)K07M{Hyrpba~kU zGcSn6|$m#qXFA>NwPWUko9Xp$f9lHk ztl1NFwBqSHVf$tL8(x`PnMd@dT?{Or@jNcoanZ&XO_#2Vy-J^vWuCDrQ^x1c^f~=e z2XtyC=u`;XJ=?hZ{MJ>>!a6s;&CdIFurar#^WUu-dB?5?ODSG@RL9bxnfdKbLz3IE zXlP+jqW+3Tn0coLPe zkKg|%{7*D`K~Hh0n{0ONHjCF=c+cIsQayL?HqRNlHf}A#f99X_RaLO?ds%*U+ACZ4 z-PtJu&-*^!J@ro9chj0b(&oFIZ>O!(Q!u~(#s6#A)^m&I?0!>|ad6AGC+~dwuFYH8 z@u_a|Y~DxAv+AaQo7Ff``NgcFx($i5tJZF7iZ&P9$0;NB@1^{njq`geau;9QxI5;Z zPC3I{`8m7nws^eqOIO{=r&eJkwJA4KuV%*g9Xt9S$o`K0yX3`OCXSphzt^8GbvteL zeCzJ$?C$RTHQAd^&5Am|HRS%{@*ipShZIT^=j7i^-S5Qh>w2}Lg714y`QH22TwiSE zuG+^@yl~Uow^FhZf5nyu99#Ehd%%TLpJz)uDCh3ty>k9pO8w%${~4ZLy5Rltn|8s& z@cs!JCw5mKj@;0>@tI@&l5c-a)5VuX=V`D1nmgYsTEFs%F&YHr#pT1euzWZ}`*Y11Wmp630iufizLF-iB?m1~v*WZ4Zf2yl~{FZZ% z(6!$jAMVP#tVn8`GfjY{XPH3q#^iuU7k;YE|9|b+uVeggbtW3zFX*>SQRI$hT3+qk z!O*WRd(i%xbn?pkMlO@>nXbBSXDYmF)gO4DDJIcfa6y`S(5=$eD-jpJY&ZC)8CrPf z@);(3)|mT?yZQBm|1)?R=WMRu*r!*rbpfB}MJd-^{97NriWYkA9d+^K+!e>JJ=%BS z@P5_jU)D>#Wto5R`hNzl)zMS##3#$xT~pj1ao08B)vH(k8Cw2+4c+Ou@Q8(Hx%#t! zCAW*Z*!CPQyJT2A(s<>?>|{~^2D}dP0w$?9l99nhF>f4j#u#})<_8t%KFX6MlnH{S2zCFshQt`;@ zuh0Hnym32ryX~5SrSI%+A7tG(cm9h{^`-wA9!^l&Gl@%mf0=M~r||d1X=y1N&fVl` zxMZmm_4nf6-fr9Ld#6g>GuzqHzI}&XV#LXfM{k>6U3lQf@syR~tF!sOPfyw~X&rjz=8shr+*-|7)c?Q! zeAm$*zOQb|=jXPF@6K3Pak;=c=KeY7wEU|Q8Ksgz7QJ`=Gu-a5E!p}vT%}A*{)lOs z)c1&E2I3YoGGr#r6fBgQJJa*Z+ui%)@~^tf_AWO(SJ`mkuIC%}z0s@n7bM+HvtN<1 za?Z6l?(Xx}_nz1F=l?QW6PYJbtfl-`b;ED>XH508*Z-_P6?)S(PvG*p1wX!P6x>PH@Grgnvc4^%H=9wOsoOQ=@AS&NwI!W$jCz81{;qoTfp6!l{|q4q>t9*2%=4U^ z@}J?@l5>l{R{iOz+PE=)-Sa!guK8c&yZiI`)nE2EBi5~5Uhw-*rTfb-*WSFbFOiRI zviY7dv19Sd-$AM?ZtlEVw5zl>Ty^J`$l_Zs@-E-9U6i77Zs}JS{yIMQIsAWpR)4*8 z|JF{IUgw`bS*Lw7T|0T*1OA6QC5{AKd3%>FYA)M*&+e=HR)!XAnv>HdFZjyht>D8y zpZ_zw68UU3|7uM8hWl=AVK;TFWp`)V&w6`4>A7}xLg9k>J7t?TM>hx>x#yXkVq^L~ z{nkQVGrj|_=7%J_J$O=n@4tP!ns=?gD!BYckqh75{U1sv9XR`L9^We6YV8d|H*cJ^ z@igk^cvp9|u+sX>tGnDQQcSG6x#wKEHv8eyqx)TVAKmwT_dof6cSG-9t6RVE+twd5 zbvE|h`tXhSz{d^mPDEOq&A!EvIro^9DU1J3naJOc+aBMD{B~@<*P7;q8`qY&o`CAZ~fVxFSq`J-=#a=bCL_DneM8OzE)MWd6(`=(S zdtdh5e}fw?t=LF_2#x8h`rlZ}G&5 zmwT@m_9r*w{mf8YczJEr+rLlU=C8bNxVHFLZ{vZ2Yi4$zzf1|7v4C&NnS{J!+dlng zn9b1fpCL;-LPhr0_vS^f*{^!fT>n=3+(gBm{|qNIw!d1s^iuJ=l8UVxe+ut7bdTvl z;Ka~~ZyP^+w=NLA6=ila{=N0B#ihCBpUX_&luUcg#whQ(F4o}J$|Oee zE&k77n)m0;pUKa8JAZEFQ@m@VzWMalzB`HsPW11!_HOkPUUHKC&(%x2UrpZmJI`&G z|0cV)tCC+WJ#ymAzLINh^J0{bi5`fHkG;5Z=Hs&wFW>Q=Ib8gN?aujK_2s`#FR%}} z$0PHmw}!)ZkI64BU@7~@dHbtx6Z`FTeaf9b7O4xc?G;H>@?5uG z@7RQ!6Bo>wuM#<{ac=G9$3Isul@EFK{B5PZfP?&3t@zL35?OEdOnS$5-K_pq(BuBn z0`ci-4Krn%=1H!88=T#~OnTuHzw2A)f4S~cxB7y@Zf%Y11^R;j8GC%+31E0 z!?XFnU&SQGs6KbjNlZR=-~XQNx}45;A9vsW(;|Lx_tlGQvpW&oA5W{=-qXp zN9H}*P=7yo`>UnWr6)IJ-`L#$8Dz1<>CK&?U++c69{Ibn_r~r1=cmhlm05qEz58ZT zRng(%6^X2qb8M{r*?Rvo%zRrNcKy$VBOAneW}9BHn8^Gy*~Rga&Z6Tx7uX(Mx^A-Z zyMA`_7j+X&PMI!pNxl02rhTt3S>iSMP{0nQ)WqVKx$EN zVs1fBDuacN0tgAcwNqzcU=W3?a?a1mFA4@NGGs6_H8nOhu4c$%U|>*$D{x6HNe#)& z1vwRC>F^ERn@2$F_TY+LD>Bpk)6$AlOBjrdjiNM<*fKCM$fHPyr4|)u=I1e(Sm-F2 z8bGj#W%3nH@LE>5QqSDP^i-eJy!4U`22&%mg-6{$&JsqI56?^~$zU)uF*twh6(<7& zg92O{y#6^iwWKIB*)y*swWy#dwImU=nvub1QBS8f0|SEuLTzS6YEFbpW^QU8*kz`M zM%(mu+`&{6iK0eZF>)0H1A`dc97srm!aM+EB7^aAQOTtY3=G-`75Ql;<%vb94B*8E z3SdWKF4BI!^0+kv1A`1)TS-P~Zc<)iW{wqT4Q^3tG1!@AKT`|B85kHeu&DD2aCHa! zJ2@jUFFn;QzbH4cgu&RtsQ2%KPzDAD17h_+W5CeB($tKjdj`lpM06tD6_i?>pHm77 z-_X3w5(Xnxhq%iqF^^Chfl$j}ZlrH`t6c)L8XB9ak!VUw-goY0U|?YIboFyt=akUI Hz`y_id-n$^ literal 0 HcmV?d00001 diff --git a/demo/main.lua b/demo/main.lua index 602b89b..1964701 100644 --- a/demo/main.lua +++ b/demo/main.lua @@ -40,6 +40,12 @@ function init() couch.Node.GetRoot().children:Append(ball1) ball1.transform:Translate(0.0, 3.0, 0.0) + + cube = couch.Mesh.FromFile("cube.glb") + cube:SetupMesh(); + cube.material.tex = couch.Texture.FromFile("container.png") + cube.material.usesTex = true + couch.Node.GetRoot().children:Append(cube) end function update(delta) diff --git a/shaders/CMakeLists.txt b/shaders/CMakeLists.txt index fedf513..9946efe 100644 --- a/shaders/CMakeLists.txt +++ b/shaders/CMakeLists.txt @@ -1,14 +1,16 @@ project(Couch) -file(GLOB shaders *.vert *.frag) -list( - TRANSFORM shaders - APPEND .h - OUTPUT_VARIABLE shader_header_files) +macro(add_shader shaderfile) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${shaderfile}.h + COMMAND perl "${CMAKE_CURRENT_SOURCE_DIR}/makeheaders.pl" ${CMAKE_CURRENT_SOURCE_DIR}/${shaderfile} ${CMAKE_CURRENT_BINARY_DIR}/${shaderfile}.h + DEPENDS ${shaderfile} makeheaders.pl + ) +endmacro() + +add_shader(flat.vert) +add_shader(flat.frag) add_custom_target(shader_headers - BYPRODUCTS ${shader_header_files} - COMMAND perl "${CMAKE_CURRENT_SOURCE_DIR}/makeheaders.pl" - DEPENDS ${shaders} ${CMAKE_CURRENT_SOURCE_DIR}/makeheaders.pl) - -add_dependencies(couch shader_headers) + DEPENDS flat.vert.h flat.frag.h + COMMENT "Generated shaders headers.") diff --git a/shaders/makeheaders.pl b/shaders/makeheaders.pl index c6d5a6b..b206951 100644 --- a/shaders/makeheaders.pl +++ b/shaders/makeheaders.pl @@ -1,33 +1,27 @@ use strict; use warnings; +use File::Basename; -sub convert { - my $filename = $1; - - my $headerguard = "$filename.h"; - $headerguard =~ tr/a-z./A-Z_/; - - my $constname = "$filename"; - $constname =~ tr/./_/; +my ($filename, $outfilename) = @ARGV; - open my $fin, '<', $filename; - open my $fout, '>', "$filename.h"; - - print $fout "#ifndef $headerguard \n#define $headerguard\nconst char * $constname = \n"; +my $headerguard = basename $outfilename; +$headerguard =~ tr/a-z./A-Z_/; - while(my $line = <$fin>) { - $line =~ s/\n/\\n/; - print $fout "\"$line\"\n"; - } +my $constname = basename "$filename"; +$constname =~ tr/./_/; - print $fout ";\n#endif // $headerguard\n"; - close $fin; - close $fout; +open(my $fin, '<', $filename) or die("Couldn't find $filename"); +open my $fout, '>', $outfilename; + +print $fout "#ifndef $headerguard \n#define $headerguard\nconst char * $constname = \n"; + +while(my $line = <$fin>) { + $line =~ s/\n/\\n/; + print $fout "\"$line\"\n"; } -opendir DIR, "."; -while (readdir DIR) { - if (/(.*\.(frag|vert))/) { - convert; - } -} +print $fout ";\n#endif // $headerguard\n"; +close $fin; +close $fout; +print "Generated $outfilename\n"; + diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 8315bf5..e4d83fb 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -1,7 +1,15 @@ project(Couch) + +## STB (image) target_include_directories(couch PUBLIC stb) - target_include_directories(couchlua PUBLIC stb) + +## ASSIMP +set(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT FALSE CACHE BOOL "Turn off all assimp importers") +set(ASSIMP_BUILD_GLTF_IMPORTER ON CACHE BOOL "Turn on gltf importer") +add_subdirectory(assimp) +target_link_libraries(couch assimp::assimp) +target_link_libraries(couchlua assimp::assimp) diff --git a/thirdparty/assimp/.editorconfig b/thirdparty/assimp/.editorconfig new file mode 100644 index 0000000..d95a7e3 --- /dev/null +++ b/thirdparty/assimp/.editorconfig @@ -0,0 +1,22 @@ +# See for details + +root = true + +[CMakeLists.txt,*.cmake{,.in}] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_size = 2 +indent_style = space + +[*.h.in] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_size = 4 +indent_style = space + +[*.txt] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/thirdparty/assimp/AssimpBuildTreeSettings.cmake.in b/thirdparty/assimp/AssimpBuildTreeSettings.cmake.in new file mode 100644 index 0000000..342da1b --- /dev/null +++ b/thirdparty/assimp/AssimpBuildTreeSettings.cmake.in @@ -0,0 +1,3 @@ +set(ASSIMP_INCLUDE_DIRS + "@PROJECT_SOURCE_DIR@" + "@PROJECT_BINARY_DIR@") diff --git a/thirdparty/assimp/AssimpConfigVersion.cmake.in b/thirdparty/assimp/AssimpConfigVersion.cmake.in new file mode 100644 index 0000000..701fa61 --- /dev/null +++ b/thirdparty/assimp/AssimpConfigVersion.cmake.in @@ -0,0 +1,11 @@ +set(ASSIMP_PACKAGE_VERSION "@ASSIMP_SOVERSION@") + +# Check whether the requested PACKAGE_FIND_VERSION is compatible +if("${ASSIMP_PACKAGE_VERSION}" VERSION_LESS "${ASSIMP_PACKAGE_FIND_VERSION}") + set(ASSIMP_PACKAGE_VERSION_COMPATIBLE FALSE) +else() + set(ASSIMP_PACKAGE_VERSION_COMPATIBLE TRUE) + if ("${ASSIMP_PACKAGE_VERSION}" VERSION_EQUAL "${ASSIMP_PACKAGE_FIND_VERSION}") + set(ASSIMP_PACKAGE_VERSION_EXACT TRUE) + endif() +endif() diff --git a/thirdparty/assimp/BUILDBINARIES_EXAMPLE.bat b/thirdparty/assimp/BUILDBINARIES_EXAMPLE.bat new file mode 100644 index 0000000..2dd13a5 --- /dev/null +++ b/thirdparty/assimp/BUILDBINARIES_EXAMPLE.bat @@ -0,0 +1,26 @@ +:: This is an example file to generate binaries using Windows Operating System +:: This script is configured to be executed from the source directory + +:: Compiled binaries will be placed in BINARIES_DIR\code\CONFIG + +:: NOTE +:: The build process will generate a config.h file that is placed in BINARIES_DIR\include +:: This file must be merged with SOURCE_DIR\include +:: You should write yourself a script that copies the files where you want them. +:: Also see: https://github.com/assimp/assimp/pull/2646 + +SET SOURCE_DIR=. + +:: For generators see "cmake --help" +SET GENERATOR=Visual Studio 15 2017 + +SET BINARIES_DIR="./BINARIES/Win32" +cmake CMakeLists.txt -G "%GENERATOR%" -S %SOURCE_DIR% -B %BINARIES_DIR% +cmake --build %BINARIES_DIR% --config release + +SET BINARIES_DIR="./BINARIES/x64" +cmake CMakeLists.txt -G "%GENERATOR% Win64" -S %SOURCE_DIR% -B %BINARIES_DIR% +cmake --build %BINARIES_DIR% --config debug +cmake --build %BINARIES_DIR% --config release + +PAUSE diff --git a/thirdparty/assimp/Build.md b/thirdparty/assimp/Build.md new file mode 100644 index 0000000..7c90860 --- /dev/null +++ b/thirdparty/assimp/Build.md @@ -0,0 +1,73 @@ +# Build Instructions +## Install CMake +Asset-Importer-Lib can be build for a lot of different platforms. We are using cmake to generate the build environment for these via cmake. So you have to make sure that you have a working cmake-installation on your system. You can download it at https://cmake.org/ or for linux install it via +```bash +sudo apt-get install cmake +``` + +## Get the source +Make sure you have a working git-installation. Open a command prompt and clone the Asset-Importer-Lib via: +```bash +git clone https://github.com/assimp/assimp.git +``` + +## Build instructions for Windows with Visual-Studio + +First you have to install Visual-Studio on your windows-system. You can get the Community-Version for free here: https://visualstudio.microsoft.com/de/downloads/ +To generate the build environment for your IDE open a command prompt, navigate to your repo and type: +```bash +cmake CMakeLists.txt +``` +This will generate the project files for the visual studio. All dependencies used to build Asset-IMporter-Lib shall be part of the repo. If you want to use you own zlib.installation this is possible as well. Check the options for it. + +## Build instructions for Windows with UWP +See + +## Build instructions for Linux / Unix +Open a terminal and got to your repository. You can generate the makefiles and build the library via: + +```bash +cmake CMakeLists.txt +make -j4 +``` +The option -j descripes the number of parallel processes for the build. In this case make will try to use 4 cores for the build. + +If you want to use a IDE for linux you can try QTCreator for instance. + +## Build instructions for MinGW + Older versions of MinGW's compiler (e.g. 5.1.0) do not support the -mbig_obj flag +required to compile some of assimp's files, especially for debug builds. +Version 7.3.0 of g++-mingw-w64 & gcc-mingw-w64 appears to work. + +Please see [CMake Cross Compiling](https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling) for general information on CMake Toolchains. + +Some users have had success building assimp using MinGW on Linux using [polly](https://github.com/ruslo/polly/). + +The following toolchain, which is not maintained by assimp, seems to work on Linux: [linux-mingw-w64-gnuxx11.cmake](https://github.com/ruslo/polly/blob/master/linux-mingw-w64-gnuxx11.cmake) + +The following toolchain may or may not be helpful for building assimp using MinGW on Windows (untested): + [mingw-cxx17.cmake](https://github.com/ruslo/polly/blob/master/mingw-cxx17.cmake) + +Besides the toolchain, compilation should be the same as for Linux / Unix. + +## CMake build options +The cmake-build-environment provides options to configure the build. The following options can be used: +- **BUILD_SHARED_LIBS ( default ON )**: Generation of shared libs ( dll for windows, so for Linux ). Set this to OFF to get a static lib. +- **BUILD_FRAMEWORK ( default OFF, MacOnly)**: Build package as Mac OS X Framework bundle +- **ASSIMP_DOUBLE_PRECISION( default OFF )**: All data will be stored as double values. +- **ASSIMP_OPT_BUILD_PACKAGES ( default OFF)**: Set to ON to generate CPack configuration files and packaging targets +- **ASSIMP_ANDROID_JNIIOSYSTEM ( default OFF )**: Android JNI IOSystem support is active +- **ASSIMP_NO_EXPORT ( default OFF )**: Disable Assimp's export functionality +- **ASSIMP_BUILD_ZLIB ( default OFF )**: Build your own zlib +- **ASSIMP_BUILD_ASSIMP_TOOLS ( default ON )**: If the supplementary tools for Assimp are built in addition to the library. +- **ASSIMP_BUILD_SAMPLES ( default OFF )**: If the official samples are built as well (needs Glut). +- **ASSIMP_BUILD_TESTS ( default ON )**: If the test suite for Assimp is built in addition to the library. +- **ASSIMP_COVERALLS ( default OFF )**: Enable this to measure test coverage. +- **ASSIMP_WERROR( default OFF )**: Treat warnings as errors. +- **ASSIMP_ASAN ( default OFF )**: Enable AddressSanitizer. +- **ASSIMP_UBSAN ( default OFF )**: Enable Undefined Behavior sanitizer. +- **SYSTEM_IRRXML ( default OFF )**: Use system installed Irrlicht/IrrXML library. +- **BUILD_DOCS ( default OFF )**: Build documentation using Doxygen. +- **INJECT_DEBUG_POSTFIX( default ON )**: Inject debug postfix in .a/.so lib names +- **IGNORE_GIT_HASH ( default OFF )**: Don't call git to get the hash. +- **ASSIMP_INSTALL_PDB ( default ON )**: Install MSVC debug files. diff --git a/thirdparty/assimp/CHANGES b/thirdparty/assimp/CHANGES new file mode 100644 index 0000000..c0c73b9 --- /dev/null +++ b/thirdparty/assimp/CHANGES @@ -0,0 +1,607 @@ +---------------------------------------------------------------------- +CHANGELOG +---------------------------------------------------------------------- +4.1.0 (2017-12): +- FEATURES: + - Export 3MF ( experimental ) + - Import / Export glTF 2 + - Introduce new zib-lib to eb able to export zip-archives +- FIXES/HOUSEKEEPING: + - Added missing include to stdlib.h and remove load library call + - Fix install for builds with MSVC compiler and NMake. + - Update list of supported file formats. + - Add TriLib to the official list of supported ports. + - Re-enabling PACK_STRUCT for MDL files. + - Use std.::unique_ptr + - Update D3MFExporter.h + - Update MD3Loader.cpp, using index + - Fix all warnings on MSVC14 + - Copy assimp dll to unit folder on windows + - Update jvm port supported formats + - Add support for building Mac OS X Framework bundles + - Check for nullptr dereferencing before copying scene data + - Update ValidateDataStructure.h, typo + - Enable data structure validation in cases where it doesn't cause failures + - Remove some dead assignments + - fast_atof: Silence some uninitialized variable warnings + - Check for area test if the face is a triangle. + - Set mNumUVComponents to 0 when deleting texture coordinate sets + - Only scale the root node because this will rescale all children nodes as well. + - Issue 1514: Fix frame pointer arithmetic + - Prevent failing stringstream to crash the export process + - powf -> pow + - add Defines.h to include folder for install. + - Android: + - Fix android build + - Fix assimp for cross compile for android + - Use define for D_FILE_OFFSET_BITS only for not-android systems. + - FBX: + - Fix handling with embedded textures + - FBX 7500 Binary reading + - Remove dead assignment + - Fix export of deleted meshes; Add LazyDict::Remove method + - Log an error instead of letting the fbx-importer crash. ( issue 213 ) + - Replace bad pointer casting with memcpy + - Remove useless const qualifier from return value + - Add explicit instantiation of log_prefix so other FBX source files can see it + - add missing inversion of postrotation matrix for fbx. + - FIReader: Silence uninitialized variable warning + - Update version check in FBX reader to check for version >= 7500 + - Use actual min/max of anim keys when start/stop time is missing +- GLTF1: + - Fix output of glTF 1 version string + - Fix delete / delete[] mismatch in glTFAsset + - Don’t ignore rgba(1,1,1,1) color properties + - glTF2 primitives fixes + - Don’t ignore rgba(1,1,1,1) color properties + - Fix delete / delete[] mismatch in glTFAsset + - Remove KHR_binary_glTF code + - glTF nodes can only hold one mesh. this simply assigns to and check’s a Node’s Mesh + - version in glb header is stored as uint32_t +- GLTF2: + - node name conflict fix + - Fix transform matrices multiplication order + - Preserve node names when importing + - Add support for tangents in import + - Fix typo on gltf2 camera parameters + - Moved byteStride from accessor to bufferView + - Implemented reading binary glTF2 (glb) files + - Fix signed/unsigned warning + - Add postprocess step for scaling + - Fix shininess to roughness conversion + - Prefer “BLEND” over “MASK” as an alphaMode default + - Approximate specularity / glossiness in metallicRoughness materials + - Diffuse color and diffuse texture import and export improvements + - Addressed some mismatched news/deletes caused by the new glTF2 sources. + - Fix delete / delete[] mismatches in glTF2 importer + - use correct name of exporter to gltf2 + - Fix possible infinite loop when exporting to gltf2 + - Fix glTF2::Asset::FindUniqueID() when the input string is >= 256 chars + - Fix glTF2 alphaMode storage and reading + - Fix glTF 2.0 multi-primitive support + - Load gltf .bin files from correct directory + - Add support for importing both glTF and glTF2 files + - ampler improvements; Add new LazyDict method + - Changes to GLTF2 materials + - Remove Light, Technique references + - Start removing materials common, and adding pbrSpecularGlossiness + - Use !ObjectEmpty() vs. MemberCount() > 0 + - Working read, import, export, and write of gltf2 (pbr) material + - Check in gltf2 models to test directory + - Remove un-needed test models + - Start managing and importing gltf2 pbr materials + - Update glTF2 Asset to use indexes + - Duplicate gltfImporter as gltf2Importer; Include glTF2 importer in CMake List + - glTF2: Fix animation export + - use opacity for diffuse alpha + alphaMode +- STL: + - Restore import of multi mesh binary STLs +- Blender: + - Silence warning about uninitialized member +- MDLImporter: + - Don't take address of packed struct member +- assimp_cmd: + - Fix strict-aliasing warnings +- Open3DGC: + - Fix strict-aliasing warnings + - Add assertions to silence static analyzer warnings + - Remove redundant const qualifiers from return types + - Fix some uninitialized variable warnings + - Remove OPEN3DGC and compression references +- unzip: + - Remove dead assignment + - Bail on bad compression method + - Fix possibly uninitialized variables +- clipper: + - Add assertion to silence a static analyzer warning +- OpenDDLExport: + - Reduce scope of a variable + - Remove dead variable + - Remove dead assignment + - Fix another potential memory leak +- X3DImporter: + - Add assertions to silence static analyzer warnings + - Add missing unittest + - Workaround for buggy Android NDK (issue #1361) +- TerragenLoader: + - Remove unused variable +- SIBImporter: + - Add assertions to silence static analyzer warnings +- IFC: + - Remove dead code + - Add explicit instantiation of log_prefix so IFCMaterial.cpp can see it +- PLY: + - Remove dead assignment and reduce scope of a variable + - fix vertex attribute lookup. +- OpenGEX: + - Add assertion to silence a static analyzer warning + - Fix for TextureFile with number in file name + - Return early when element is TextureFile +- NFF: + - Add assertions to silence static analyzer warnings + - Split up some complicated assignments +- Raw: Fix misleading indentation warning + - Reduce scope of a variable +- LWO + - Reduce scope of a variable +- IRRLoader: + - Fix confusing boolean casting +- AssbinExporter: + - Add assertion to silence a static analyzer warning +- ASE: + - Add assertion to silence a static analyzer warning +- AMFImporter: + - Add assertion to silence a static analyzer warning + - Add a block +- OptimizeGraph: + - Fix possible null pointer dereference + - RemoveRedundantMaterials: + - Add assertion to silence a static analyzer warning +- ImproveCacheLocality: + - Add assertion to silence a static analyzer warning +- RemoveRedundantMaterials: + - Set pointer to nullptr after deleting it +- Travis: + - Disable unit tests in scan-build config + - Move slower builds earlier to improve parallelization + - Add static analysis to build + - Remove unused branch rule for travis. + - Add Clang UBSan build configuration + - Treat warnings as errors, without typos this time +- Unittests: + - Add VS-based source groups for the unittests. +- Collada: + - export tag + - Update ColladaExporter.cpp + - Silence uninitialized variable warning + - Add support for line strip primitives +- Obj Wavefront: + - check in exporting against out-of-bounds-access . + - Issue 1351: use correct name for obj-meshname export for groups. + - fix mem-lead: face will be not released in case of an error. + - Anatoscope obj exporter nomtl + - Raise exception when obj file contains invalid face indices + - Added alternative displacement texture token in OBJ MTL material. + - Obj: rename attribute from exporter. + - Fix OBJ discarding all material names if the material library is missing +- Step: + - use correct lookup for utf32 +- MD2: + - Fix MD2 frames containing garbage +- STL + - add missing const. + - Fix memory-alignment bug. + - Fix issue 104: deal with more solids in one STL file. +- CMake + - Fix issue 213: use correct include folder for assimp +- Doxygen + - Fix issue 1513: put irrXML onto exclucde list for doxygen run +- PyAssimp: + - Search for libassimp.so in LD_LIBRARY_PATH if available. + - Fix operator precedence issue in header check + - Split setup.py into multiple lines + - Detect if Anaconda and fixed 3d_viewer for Python 3 + - created a python3 version of the 3dviewer and fixed the / = float in py3 +- Blender: + - Fix invalid access to mesh array when the array is empty. + - Fix short overflow. + - Silence warning about inline function which is declared but not defined +- JAssimp + - Changed license header for IHMC contributions from Apache 2.0 to BSD + - Add Node metadata to the Jassmip Java API + - Added supported for custom IO Systems in Java. Implemented ClassLoader IO System + - Added a link to pure jvm assimp port +- Clang sanitizer: + - Undefined Behavior sanitizer + - Fixed a divide by zero error in IFCBoolean that was latent, but nevertheless a bug +- B3DImporter: + - Replace bad pointer casting with memcpy +- AppVeyor: + - Cleanup and Addition of VS 2017 and running Tests + - Fixed File Size reported as 0 in tests that use temporary files + - x86 isn't a valid VS platform. Win32 it is, then. + - Replaced the worker image name, which doesn't work as generator name, with a manually created generator name. + - Cleaned up appveyor setup, added VS 2017 to the build matrix and attempted to add running of tests. + - Treat warnings as errors on Appveyor + - Disable warning 4351 on MSVC 2013 +- OpenGEXImporter: + - Copy materials to scene + - Store RefInfo in unique_ptr so they get automatically cleaned up + - Fix IOStream leak + - Store ChildInfo in unique_ptr so they get automatically cleaned up + - improve logging to be able to detect error-prone situations. +- AMFImporter: + - Fix memory leak +- UnrealLoader: + - Fix IOStream leak +- Upgrade RapidJSON to get rid of a clang warning +- zlib: + - Update zlib contribution + - Removed unnecessary files from zlib contribution + - Replaced unsigned long for the crc table to z_crc_t, to match what is returned by get-crc_table +- MakeVerboseFormat: + - Fix delete / delete[] mismatches in MakeVerboseFormat +- MaterialSystem: + - Fix out-of-bounds read in MaterialSystem unit test +- SIB: + - Added support for SIB models from Silo 2.5 +- AssbinExporter: + - Fix strict aliasing violation + - Add Write specialization for aiColor3D +- DefaultLogger: + - Whitespace cleanup to fix GCC misleading indentation warning +- MDP: + - Fix encoding issues. + - PreTransformVertices: + - fix name lost in mesh and nodes when load with flag +- C4D: + - Fixes for C4D importer +- Unzip: + - Latest greatest. + +4.0.1 (2017-07-28) + - FIXES/HOUSEKEEPING: + - fix version test. + - Not compiling when using ASSIMP_DOUBLE_PRECISION + - Added support for python3 + - Check if cmake is installed with brew + - Low performance in OptimizeMeshesProcess::ProcessNode with huge numbers of meshes + - Elapsed seconds not shown correctly + - StreamReader: fix out-of-range exception + - PPdPmdParser: fix compilation for clang + + +4.0.0 (2017-07-18) + +FEATURES: + - Double precision support provided ( available via cmake option ) + - QT-Widget based assimp-viewer ( works for windows, linux, osx ) + - Open3DGC codec supported by glFT-importer + - glTF: Read and write transparency values + - Add Triangulate post-processing step to glTF exporters + - Update rapidjson to v1.0.2 + - Added method to append new metadata to structure + - Unittests: intoduce a prototype model differ + - X3D support + - AMF support + - Lugdunum3D support + - Obj-Importer: obj-homogeneous_coords support + - Obj-Importer: new streaming handling + - Added support for 64 bit version header introduced in FbxSdk2016 + - Travis: enable coverall support. + - PyAssimp: New version of the pyASSIMP 3D viewer, with much improved 3D controls + - Morph animation support for collada + - Added support for parameters Ni and Tf in OBJ/MTL file format + - aiScene: add method to add children + - Added new option to IFC importer to control tessellation angle + removed unused IFC option + - aiMetaData: introduce aiMetaData::Dealloc + - Samples: add a DX11 example + - travis ci: test on OXS ( XCode 6.3 ) as well + - travis ci: enable sudo support. + - openddlparser: integrate release v0.4.0 + - aiMetaData: Added support for metadata in assbin format + +FIXES/HOUSEKEEPING: + - Introduce usage of #pragma statement + - Put cmake-scripts into their own folder + - Fix install pathes ( issue 938 ) + - Fix object_compare in blender importer( issue 946 ) + - Fix OSX compilation error + - Fix unzip path when no other version was found ( issue 967 ) + - Set _FILE_OFFSET_BITS=64 for 32-bit linux ( issue 975 ) + - Fix constructor for radjson on OSX + - Use Assimp namespace to fix build for big-endian architectures + - Add -fPIC to C Flags for 64bit linux Shared Object builds + - MDLLoader: fix resource leak. + - MakeVerboseFormat: fix invalid delete statement + - IFC: fix possible use after free access bug + - ComputeUVMappingprocess: add missing initialization for scalar value + - Fix invalid release of mat + mesh + - IrrImporter: Fix release functions + - Split mesh before exporting gltf ( issue 995 ) + - 3MFImporter: add source group for visual studio + - IFC: Switch generated file to 2 files to fix issue related to and ( issue 1084 ) + - OBJParser: set material index when changing current material + - OBJ: check for null mesh before updating material index + - add vertex color export support ( issue 809 ) + - Fix memory leak in Collada importer ( issue 1169 ) + - add stp to the list of supported extensions for step-files ( issue 1183 ) + - fix clang build ( Issue-1169 ) + - fix for FreeBSD + - Import FindPkgMacros to main CMake Configuration + - Extended support for tessellation parameter to more IFC shapes + - defensice handling of utf-8 decode issues ( issue 1211 ) + - Fixed compiler error on clang 4.0 running on OSX + - use test extension for exported test files ( issue 1228 ) + - Set UVW index material properties for OBJ files + - Fixed no member named 'atop' in global namespace issue for Android NDK compilation + - Apply mechanism to decide use for IrrXML external or internal + - Fix static init ordering bug in OpenGEX importer + - GLTF exporter: ensure animation accessors have same count + - GLTF exporter: convert animation time from ticks to seconds + - Add support for reading texture coordinates from PLY meshes with properties named 'texture_u' and 'texture_v' + - Added TokensForSearch in BlenderLoader to allow CanRead return true for in-memory files. + - fix wrong delete ( issue 1266 ) + - OpenGEX: fix invalid handling with color4 token ( issue 1262 ) + - LWOLoader: fix link in loader description + - Fix error when custom CMAKE_C_FLAGS is specified + - Fast-atof: log overflow errors + - Obj-Importer: do not break when detecting an overflow ( issue 1244 ) + - Obj-Importer: fix parsing of multible line data definitions + - Fixed bug where IFC models with multiple IFCSite only loaded 1 site instead of the complete model + - PLYImporter: - optimize memory and speed on ply importer / change parser to use a file stream - manage texture path in ply + import - manage texture coords on faces in ply import - correction on point cloud faces generation + - Utf8: integrate new lib ( issue 1158 ) + - fixed CMAKE_MODULE_PATH overwriting previous values + - OpenGEX: Fixed bug in material color processing ( issue 1271 ) + - SceneCombiner: move header for scenecombiner to public folder. + - GLTF exporter: ensure buffer view byte offsets are correctly aligned + - X3D importer: Added EXPORT and IMPORT to the list of ignored XML tags + - X3D Exporter: fixed missing attributes + - X3D importer: Fixed import of normals for the single index / normal per vertex case + - X3D importer: Fixed handling of inlined files + - X3D importer: fixed whitespace handling (issue 1202) + - X3D importer: Fixed iterator on MSVC 2015 + - X3D importer: Fixed problems with auto, override and regex on older compilers + - X3D importer: Fixed missing header file + - X3D importer: Fixed path handling + - X3D importer: Implemented support for binary X3D files + - fix build without 3DS ( issue 1319 ) + - pyassimp: Fixed indices for IndexedTriangleFanSet, IndexedTriangleSet and IndexedTriangleStripSet + - Fixes parameters to pyassimp.load + - Obj-Importe: Fixed texture bug due simultaneously using 'usemtl' and 'usemap' attributes + - check if all exporters are disabled ( issue 1320 ) + - Remove std functions deprecated by C++11. + - X-Importer: make it deal with lines + - use correct path for compilers ( issue 1335 ) + - Collada: add workaround to deal with polygon with holes + - update python readme + - Use unique node names when loading Collada files + - Fixed many FBX bugs + +API COMPATIBILITY: + - Changed ABI-compatibility to v3.3.1, please rebuild your precompiled libraries ( see issue 1182 ) + - VS2010 outdated + +3.3.1 (2016-07-08) + +FIXES/HOUSEKEEPING: + - Setup of default precision for 17 exporters + - Fix xcode project files + - Fix BlenderTesselator: offsetof operator + - Invalid version in cmake file + - Update pstdint.h to latest greatest + + +3.3.0 (2016-07-05) + +FEATURES: + - C++11 support enabled + - New regression-test-UI + - Experimental glTF-importer support + - OpenGEX: add support for cameras and lights + - C4D: update to latest Melange-SDK + - Add a gitter channel + - Coverity check enabled + - Switch to <...> include brackets for public headers + - Enable export by pyAssimp + - CI: check windows build + - Add functionality to perform a singlepost-processing step + - many more, just check the history + +FIXES/HOUSEKEEPING: + - Fix of many resource leaks in unittests and main lib + - Fix iOS-buildfor X64 + - Choosing zlib manually for cmake + - many more, just check the history + + +3.2.1 (2016-010-10) + +FEATURES: + - Updated glTF exporter to meet 1.0 specification. + +FIXES/HOUSEKEEPING: + - Fixed glTF Validator errors for exported glTF format. + +ISSUES: + - Hard coded sampler setting for + - magFilter + - minFilter + - void* in ExportData for accessor max and min. + + +3.2.0 (2015-11-03) + +FEATURES: + - OpenDDL-Parser is part of contrib-source. + - Experimental OpenGEX-support + - CI-check for linux and windows + - Coverity check added + - New regression testsuite. + +FIXES/HOUSEKEEPING: + - Hundreds of bugfixes in all parts of the library + - Unified line endings + + +API COMPATIBILITY: + - Removed precompiled header to increase build speed for linux + + +3.1.1 (2014-06-15) + +FEATURES: + - Support for FBX 2013 and newer, binary and ASCII (this is partly + work from Google Summer of Code 2012) + - Support for OGRE binary mesh and skeleton format + - Updated BLEND support for newer Blender versions + - Support for arbitrary meta data, used to hold FBX and DAE metadata + - OBJ Export now produces smaller files + - Meshes can now have names, this is supported by the major importers + - Improved IFC geometry generation + - M3 support has been removed + +FIXES/HOUSEKEEPING: + - Hundreds of bugfixes in all parts of the library + - CMake is now the primary build system + +API COMPATIBILITY: + - 3.1.1 is not binary compatible to 3.0 due to aiNode::mMetaData + and aiMesh::mName + - Export interface has been cleaned up and unified + - Other than that no relevant changes + + +3.0 (2012-07-07) + +FEATURES: + - new export interface similar to the import API. + - Supported export formats: Collada, OBJ, PLY and STL + - added new import formats: XGL/ZGL, M3 (experimental) + - new postprocessing steps: Debone + - vastly improved IFC (Industry Foundation Classes) support + - introduced API to query importer meta information (such as supported + format versions, full name, maintainer info). + - reworked Ogre XML import + - C-API now supports per-import properties + +FIXES/HOUSEKEEPING: + + - hundreds of bugfixes in all parts of the library + - unified naming and cleanup of public headers + - improved CMake build system + - templatized math library + - reduce dependency on boost.thread, only remaining spot + is synchronization for the C logging API + +API COMPATIBILITY: + - renamed headers, export interface, C API properties and meta data + prevent compatibility with code written for 2.0, but in + most cases these can be easily resolved + - Note: 3.0 is not binary compatible with 2.0 + + +2.0 (2010-11-21) + +FEATURES: + - Add support for static Blender (*.blend) scenes + - Add support for Q3BSP scenes + - Add a windows-based OpenGL sample featuring texturing & basic materials + - Add an experimental progress feedback interface. + - Vastly improved performance (up to 500%, depending on mesh size and + spatial structure) in some expensive postprocessing steps + - AssimpView now uses a reworked layout which leaves more space + to the scene hierarchy window + + - Add C# bindings ('Assimp.NET') + - Keep BSD-licensed and otherwise free test files in separate + folders (./test/models and ./test/models-nonbsd). + +FIXES: + - Many Collada bugfixes, improve fault tolerance + - Fix possible crashes in the Obj loader + - Improve the Ogre XML loader + - OpenGL-sample now works with MinGW + - Fix Importer::FindLoader failing on uppercase file extensions + - Fix flawed path handling when locating external files + - Limit the maximum number of vertices, faces, face indices and + weights that Assimp is able to handle. This is to avoid + crashes due to overflowing counters. + + - Updated XCode project files + - Further CMAKE build improvements + + +API CHANGES: + - Add data structures for vertex-based animations (These are not + currently used, however ...) + - Some Assimp::Importer methods are const now. + + +1.1 (2010-04-17) +This is the list of relevant changes from the 1.0 (r412) release to 1.1 (r700). + +FEATURES: + - Vastly improved Collada support + - Add MS3D (Milkshape 3D) support + - Add support for Ogre XML static meshes + - Add experimental COB (TrueSpace) support + - Automatic test suite to quickly locate regressions + - D bindings (`dAssimp`) + - Python 2.n bindings (`PyAssimp`) + - Add basic support for Unicode input files (utf8, utf16 and utf32) + - Add further utilities to the `assimp` tool (xml/binary dumps, quick file stats) + - Switch to a CMAKE-based build system including an install target for unix'es + - Automatic evaluation of subdivision surfaces for some formats. + - Add `Importer::ReadFileFromMemory` and the corresponding C-API `aiReadFileFromMemory` + - Expose further math utilities via the C-API (i.e. `aiMultiplyMatrix4`) + + - Move noboost files away from the public include directory + - Many, many bugfixes and improvements in existing loaders and postprocessing steps + - Documentation improved and clarified in many places. + - Add a sample on using Assimp in conjunction with OpenGL + + - Distribution/packaging: comfortable SDK installer for Windows + - Distribution/packaging: improved release packages for other architectures + +CRITICAL FIXES: + - Resolve problems with clashing heap managers, STL ABIs and runtime libraries (win32) + - Fix automatic detection of file type if no file extension is given + - Improved exception safety and robustness, prevent leaking of exceptions through the C interface + - Fix possible heap corruption due to material properties pulled in incorrectly + - Avoid leaking in certain error scenarios + - Fix 64 bit compatibility problems in some loaders (i.e. MDL) + +BREAKING API CHANGES: + - None - + +MINOR API BEHAVIOUR CHANGES: + - Change quaternion orientation to suit to the more common convention (-w). + - aiString is utf8 now. Not yet consistent, however. diff --git a/thirdparty/assimp/CMakeLists.txt b/thirdparty/assimp/CMakeLists.txt new file mode 100644 index 0000000..a58676d --- /dev/null +++ b/thirdparty/assimp/CMakeLists.txt @@ -0,0 +1,680 @@ +# Open Asset Import Library (assimp) +# ---------------------------------------------------------------------- +# Copyright (c) 2006-2019, assimp team +# +# All rights reserved. +# +# Redistribution and use of this software in source and binary forms, +# with or without modification, are permitted provided that the +# following conditions are met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the +# following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the +# following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# * Neither the name of the assimp team, nor the names of its +# contributors may be used to endorse or promote products +# derived from this software without specific prior +# written permission of the assimp team. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#---------------------------------------------------------------------- +SET(CMAKE_POLICY_DEFAULT_CMP0074 NEW) + +CMAKE_MINIMUM_REQUIRED( VERSION 3.0 ) + +# Toggles the use of the hunter package manager +option(HUNTER_ENABLED "Enable Hunter package manager support" OFF) + +include("cmake/HunterGate.cmake") +HunterGate( + URL "https://github.com/ruslo/hunter/archive/v0.23.176.tar.gz" + SHA1 "2e9ae973d028660b735ac4c6142725ca36a0048a" +) + +IF(HUNTER_ENABLED) + add_definitions(-DASSIMP_USE_HUNTER) +ENDIF(HUNTER_ENABLED) + +PROJECT( Assimp VERSION 5.0.0 ) + +# All supported options ############################################### + +OPTION( BUILD_SHARED_LIBS + "Build package with shared libraries." + ON +) + +OPTION( BUILD_FRAMEWORK + "Build package as Mac OS X Framework bundle." + OFF +) +OPTION( ASSIMP_DOUBLE_PRECISION + "Set to ON to enable double precision processing" + OFF +) +OPTION( ASSIMP_OPT_BUILD_PACKAGES + "Set to ON to generate CPack configuration files and packaging targets" + OFF +) +OPTION( ASSIMP_ANDROID_JNIIOSYSTEM + "Android JNI IOSystem support is active" + OFF +) +OPTION( ASSIMP_NO_EXPORT + "Disable Assimp's export functionality." + ON +) +OPTION( ASSIMP_BUILD_ZLIB + "Build your own zlib" + ON +) +OPTION( ASSIMP_BUILD_ASSIMP_TOOLS + "If the supplementary tools for Assimp are built in addition to the library." + OFF +) +OPTION ( ASSIMP_BUILD_SAMPLES + "If the official samples are built as well (needs Glut)." + OFF +) +OPTION ( ASSIMP_BUILD_TESTS + "If the test suite for Assimp is built in addition to the library." + OFF +) +OPTION ( ASSIMP_COVERALLS + "Enable this to measure test coverage." + OFF +) +OPTION ( ASSIMP_WERROR + "Treat warnings as errors." + OFF +) +OPTION ( ASSIMP_ASAN + "Enable AddressSanitizer." + OFF +) +OPTION ( ASSIMP_UBSAN + "Enable Undefined Behavior sanitizer." + OFF +) +OPTION ( SYSTEM_IRRXML + "Use system installed Irrlicht/IrrXML library." + OFF +) +OPTION ( BUILD_DOCS + "Build documentation using Doxygen." + OFF +) +OPTION( INJECT_DEBUG_POSTFIX + "Inject debug postfix in .a/.so/.dll lib names" + ON +) + +OPTION ( IGNORE_GIT_HASH + "Don't call git to get the hash." + OFF +) + +IF (IOS AND NOT HUNTER_ENABLED) + IF (NOT CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE "Release") + ENDIF (NOT CMAKE_BUILD_TYPE) + ADD_DEFINITIONS(-DENABLE_BITCODE) +ENDIF (IOS AND NOT HUNTER_ENABLED) + +# Use subset of Windows.h +if (WIN32) + ADD_DEFINITIONS( -DWIN32_LEAN_AND_MEAN ) +endif() + +IF(MSVC) + OPTION( ASSIMP_INSTALL_PDB + "Install MSVC debug files." + ON + ) + IF(NOT (MSVC_VERSION LESS 1900)) + # Multibyte character set is deprecated since at least MSVC2015 (possibly earlier) + ADD_DEFINITIONS( -DUNICODE -D_UNICODE ) + ENDIF() +ENDIF(MSVC) + +IF (BUILD_FRAMEWORK) + SET (BUILD_SHARED_LIBS ON) + MESSAGE(STATUS "Framework bundle building enabled") +ENDIF(BUILD_FRAMEWORK) + +IF(NOT BUILD_SHARED_LIBS) + MESSAGE(STATUS "Shared libraries disabled") + SET(LINK_SEARCH_START_STATIC TRUE) +ELSE() + MESSAGE(STATUS "Shared libraries enabled") +ENDIF(NOT BUILD_SHARED_LIBS) + +# Define here the needed parameters +SET (ASSIMP_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +SET (ASSIMP_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +SET (ASSIMP_VERSION_PATCH ${PROJECT_VERSION_PATCH}) +SET (ASSIMP_VERSION ${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}.${ASSIMP_VERSION_PATCH}) +SET (ASSIMP_SOVERSION 5) + +SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" ) +if(NOT HUNTER_ENABLED) + # Enable C++11 support globally + set_property( GLOBAL PROPERTY CXX_STANDARD 11 ) +endif() + +IF(NOT IGNORE_GIT_HASH) + # Get the current working branch + EXECUTE_PROCESS( + COMMAND git rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + + # Get the latest abbreviated commit hash of the working branch + EXECUTE_PROCESS( + COMMAND git rev-parse --short=8 HEAD + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) +ENDIF() + +IF(NOT GIT_COMMIT_HASH) + SET(GIT_COMMIT_HASH 0) +ENDIF(NOT GIT_COMMIT_HASH) + +IF(ASSIMP_DOUBLE_PRECISION) + ADD_DEFINITIONS(-DASSIMP_DOUBLE_PRECISION) +ENDIF(ASSIMP_DOUBLE_PRECISION) + +CONFIGURE_FILE( + ${CMAKE_CURRENT_LIST_DIR}/revision.h.in + ${CMAKE_CURRENT_BINARY_DIR}/revision.h +) + +CONFIGURE_FILE( + ${CMAKE_CURRENT_LIST_DIR}/include/assimp/config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/include/assimp/config.h +) + +INCLUDE_DIRECTORIES( BEFORE + ./ + code/ + include + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/include +) + +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules" ) +SET(LIBASSIMP_COMPONENT "libassimp${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}.${ASSIMP_VERSION_PATCH}" ) +SET(LIBASSIMP-DEV_COMPONENT "libassimp${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}.${ASSIMP_VERSION_PATCH}-dev" ) +SET(CPACK_COMPONENTS_ALL assimp-bin ${LIBASSIMP_COMPONENT} ${LIBASSIMP-DEV_COMPONENT} assimp-dev) +SET(ASSIMP_LIBRARY_SUFFIX "" CACHE STRING "Suffix to append to library names") + +IF( UNIX ) + # Use GNUInstallDirs for Unix predefined directories + INCLUDE(GNUInstallDirs) +ENDIF( UNIX ) + +# Grouped compiler settings +IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT CMAKE_COMPILER_IS_MINGW) + IF(NOT HUNTER_ENABLED) + SET(CMAKE_CXX_FLAGS "-fPIC -std=c++0x ${CMAKE_CXX_FLAGS}") + SET(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}") + ENDIF() + # hide all not-exported symbols + SET(CMAKE_CXX_FLAGS "-g -fvisibility=hidden -fno-strict-aliasing -Wall ${CMAKE_CXX_FLAGS}") + SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}") + SET(LIBSTDC++_LIBRARIES -lstdc++) +ELSEIF(MSVC) + # enable multi-core compilation with MSVC + ADD_COMPILE_OPTIONS(/MP) + ADD_COMPILE_OPTIONS( /bigobj ) + # disable "elements of array '' will be default initialized" warning on MSVC2013 + IF(MSVC12) + ADD_COMPILE_OPTIONS(/wd4351) + ENDIF() + SET(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Ob2 /DEBUG:FULL /Zi") +ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" ) + IF(NOT HUNTER_ENABLED) + SET(CMAKE_CXX_FLAGS "-fPIC -std=c++11 ${CMAKE_CXX_FLAGS}") + SET(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}") + ENDIF() + SET(CMAKE_CXX_FLAGS "-g -fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long ${CMAKE_CXX_FLAGS}" ) + SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}") +ELSEIF( CMAKE_COMPILER_IS_MINGW ) + IF (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0) + message(FATAL_ERROR "MinGW is too old to be supported. Please update MinGW and try again.") + ELSEIF(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.3) + message(WARNING "MinGW is old, if you experience errors, update MinGW.") + ENDIF() + IF(NOT HUNTER_ENABLED) + SET(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}") + SET(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}") + ENDIF() + SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long -Wa,-mbig-obj -O3 ${CMAKE_CXX_FLAGS}") + SET(CMAKE_C_FLAGS "-fno-strict-aliasing ${CMAKE_C_FLAGS}") + ADD_DEFINITIONS( -U__STRICT_ANSI__ ) +ENDIF() + +IF ( IOS AND NOT HUNTER_ENABLED) + IF (CMAKE_BUILD_TYPE STREQUAL "Debug") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -Og") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -Og") + ELSE() + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -O3") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3") + # Experimental for pdb generation + ENDIF() +ENDIF( IOS AND NOT HUNTER_ENABLED) + +IF (ASSIMP_COVERALLS) + MESSAGE(STATUS "Coveralls enabled") + INCLUDE(Coveralls) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") +ENDIF() + +IF (ASSIMP_WERROR) + MESSAGE(STATUS "Treating warnings as errors") + IF (MSVC) + ADD_COMPILE_OPTIONS(/WX) + ELSE() + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") + ENDIF() +ENDIF() + +IF (ASSIMP_ASAN) + MESSAGE(STATUS "AddressSanitizer enabled") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address") +ENDIF() + +IF (ASSIMP_UBSAN) + MESSAGE(STATUS "Undefined Behavior sanitizer enabled") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined -fno-sanitize-recover=all") +ENDIF() + +INCLUDE (FindPkgMacros) +INCLUDE (PrecompiledHeader) + +# If this is an in-source build (CMAKE_SOURCE_DIR == CMAKE_BINARY_DIR), +# write the library/executable files to the respective directories in the +# source tree. During an out-of-source build, however, do not litter this +# directory, since that is probably what the user wanted to avoid. +IF ( CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR ) + SET( CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_HOME_DIRECTORY}/lib" ) + SET( CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_HOME_DIRECTORY}/lib" ) + SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_HOME_DIRECTORY}/bin" ) +ENDIF ( CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR ) + +# Cache these to allow the user to override them manually. +SET( ASSIMP_LIB_INSTALL_DIR "lib" CACHE STRING + "Path the built library files are installed to." ) +SET( ASSIMP_INCLUDE_INSTALL_DIR "include" CACHE STRING + "Path the header files are installed to." ) +SET( ASSIMP_BIN_INSTALL_DIR "bin" CACHE STRING + "Path the tool executables are installed to." ) + +get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG) + +IF (INJECT_DEBUG_POSTFIX AND (is_multi_config OR CMAKE_BUILD_TYPE STREQUAL "Debug")) + SET(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "Debug Postfix for lib, samples and tools") +ELSE() + SET(CMAKE_DEBUG_POSTFIX "" CACHE STRING "Debug Postfix for lib, samples and tools") +ENDIF() + +# Only generate this target if no higher-level project already has +IF (NOT TARGET uninstall) + # add make uninstall capability + CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/cmake-modules/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) + ADD_CUSTOM_TARGET(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") +ENDIF() + +IF(HUNTER_ENABLED) + set(CONFIG_INSTALL_DIR "lib/cmake/${PROJECT_NAME}") + set(INCLUDE_INSTALL_DIR "include") + + set(GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") + + # Configuration + set(VERSION_CONFIG "${GENERATED_DIR}/${PROJECT_NAME}ConfigVersion.cmake") + set(PROJECT_CONFIG "${GENERATED_DIR}/${PROJECT_NAME}Config.cmake") + set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets") + set(NAMESPACE "${PROJECT_NAME}::") + + # Include module with fuction 'write_basic_package_version_file' + include(CMakePackageConfigHelpers) + + # Note: PROJECT_VERSION is used as a VERSION + write_basic_package_version_file("${VERSION_CONFIG}" COMPATIBILITY SameMajorVersion) + + # Use variables: + # * TARGETS_EXPORT_NAME + # * PROJECT_NAME + configure_package_config_file( + "cmake/assimp-hunter-config.cmake.in" + "${PROJECT_CONFIG}" + INSTALL_DESTINATION "${CONFIG_INSTALL_DIR}" + ) + + install( + FILES "${PROJECT_CONFIG}" "${VERSION_CONFIG}" + DESTINATION "${CONFIG_INSTALL_DIR}" + ) + + install( + EXPORT "${TARGETS_EXPORT_NAME}" + NAMESPACE "${NAMESPACE}" + DESTINATION "${CONFIG_INSTALL_DIR}" + ) +ELSE(HUNTER_ENABLED) + # cmake configuration files + CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimp-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake" @ONLY IMMEDIATE) + CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimpTargets.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets.cmake" @ONLY IMMEDIATE) + IF (is_multi_config) + CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimpTargets-debug.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets-debug.cmake" @ONLY IMMEDIATE) + CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimpTargets-release.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets-release.cmake" @ONLY IMMEDIATE) + SET(PACKAGE_TARGETS_FILE "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets-debug.cmake" "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets-release.cmake") + ELSEIF (CMAKE_BUILD_TYPE STREQUAL Debug) + CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimpTargets-debug.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets-debug.cmake" @ONLY IMMEDIATE) + SET(PACKAGE_TARGETS_FILE "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets-debug.cmake") + ELSE() + CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimpTargets-release.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets-release.cmake" @ONLY IMMEDIATE) + SET(PACKAGE_TARGETS_FILE "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets-release.cmake") + ENDIF() + CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimp-config-version.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimp-config-version.cmake" @ONLY IMMEDIATE) + #we should generated these scripts after CMake VERSION 3.0.2 using export(EXPORT ...) and write_basic_package_version_file(...) + INSTALL(FILES + "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/assimp-config-version.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets.cmake" + ${PACKAGE_TARGETS_FILE} + DESTINATION "${ASSIMP_LIB_INSTALL_DIR}/cmake/assimp-${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}" COMPONENT ${LIBASSIMP-DEV_COMPONENT}) +ENDIF(HUNTER_ENABLED) + +FIND_PACKAGE( DirectX ) + +IF( BUILD_DOCS ) + ADD_SUBDIRECTORY(doc) +ENDIF( BUILD_DOCS ) + +# Look for system installed irrXML +IF ( SYSTEM_IRRXML ) + FIND_PACKAGE( IrrXML REQUIRED ) +ENDIF( SYSTEM_IRRXML ) + +# Search for external dependencies, and build them from source if not found +# Search for zlib +IF(HUNTER_ENABLED) + hunter_add_package(ZLIB) + find_package(ZLIB CONFIG REQUIRED) + + add_definitions(-DASSIMP_BUILD_NO_OWN_ZLIB) + set(ZLIB_FOUND TRUE) + set(ZLIB_LIBRARIES ZLIB::zlib) + set(ASSIMP_BUILD_MINIZIP TRUE) +ELSE(HUNTER_ENABLED) + IF ( NOT ASSIMP_BUILD_ZLIB ) + FIND_PACKAGE(ZLIB) + ENDIF( NOT ASSIMP_BUILD_ZLIB ) + + IF( NOT ZLIB_FOUND ) + MESSAGE(STATUS "compiling zlib from sources") + INCLUDE(CheckIncludeFile) + INCLUDE(CheckTypeSize) + INCLUDE(CheckFunctionExists) + + # Explicitly turn off ASM686 and AMD64 cmake options. + # The AMD64 option causes a build failure on MSVC and the ASM builds seem to have problems: + # https://github.com/madler/zlib/issues/41#issuecomment-125848075 + # Also prevents these options from "polluting" the cmake options if assimp is being + # included as a submodule. + set( ASM686 FALSE CACHE INTERNAL "Override ZLIB flag to turn off assembly" FORCE ) + set( AMD64 FALSE CACHE INTERNAL "Override ZLIB flag to turn off assembly" FORCE ) + + # compile from sources + ADD_SUBDIRECTORY(contrib/zlib) + SET(ZLIB_FOUND 1) + SET(ZLIB_LIBRARIES zlibstatic) + SET(ZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/contrib/zlib ${CMAKE_CURRENT_BINARY_DIR}/contrib/zlib) + # need to ensure we don't link with system zlib or minizip as well. + SET(ASSIMP_BUILD_MINIZIP 1) + ELSE(NOT ZLIB_FOUND) + ADD_DEFINITIONS(-DASSIMP_BUILD_NO_OWN_ZLIB) + SET(ZLIB_LIBRARIES_LINKED -lz) + ENDIF(NOT ZLIB_FOUND) + INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIR}) +ENDIF(HUNTER_ENABLED) + +IF( NOT IOS ) + IF( NOT ASSIMP_BUILD_MINIZIP ) + use_pkgconfig(UNZIP minizip) + ENDIF( NOT ASSIMP_BUILD_MINIZIP ) +ELSE ( NOT IOS ) + IF( NOT BUILD_SHARED_LIBS ) + IF( NOT ASSIMP_BUILD_MINIZIP ) + use_pkgconfig(UNZIP minizip) + ENDIF( NOT ASSIMP_BUILD_MINIZIP ) + ENDIF ( NOT BUILD_SHARED_LIBS ) +ENDIF ( NOT IOS ) + +IF ( ASSIMP_NO_EXPORT ) + ADD_DEFINITIONS( -DASSIMP_BUILD_NO_EXPORT) + MESSAGE( STATUS "Build an import-only version of Assimp." ) +ENDIF( ASSIMP_NO_EXPORT ) + +SET ( ASSIMP_BUILD_ARCHITECTURE "" CACHE STRING + "describe the current architecture." +) +IF ( ASSIMP_BUILD_ARCHITECTURE STREQUAL "") +ELSE ( ASSIMP_BUILD_ARCHITECTURE STREQUAL "") + ADD_DEFINITIONS ( -D'ASSIMP_BUILD_ARCHITECTURE="${ASSIMP_BUILD_ARCHITECTURE}"' ) +ENDIF ( ASSIMP_BUILD_ARCHITECTURE STREQUAL "") + +# ${CMAKE_GENERATOR} +SET ( ASSIMP_BUILD_COMPILER "" CACHE STRING + "describe the current compiler." +) +IF ( ASSIMP_BUILD_COMPILER STREQUAL "") +ELSE ( ASSIMP_BUILD_COMPILER STREQUAL "") + ADD_DEFINITIONS ( -D'ASSIMP_BUILD_COMPILER="${ASSIMP_BUILD_COMPILER}"' ) +ENDIF ( ASSIMP_BUILD_COMPILER STREQUAL "") + +MARK_AS_ADVANCED ( ASSIMP_BUILD_ARCHITECTURE ASSIMP_BUILD_COMPILER ) + +SET ( ASSIMP_BUILD_NONFREE_C4D_IMPORTER OFF CACHE BOOL + "Build the C4D importer, which relies on the non-free Melange SDK." +) + +IF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) + IF ( MSVC ) + SET(C4D_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/contrib/Melange/includes") + + # pick the correct prebuilt library + IF(MSVC15) + SET(C4D_LIB_POSTFIX "_2017") + ELSEIF(MSVC14) + SET(C4D_LIB_POSTFIX "_2015") + ELSEIF(MSVC12) + SET(C4D_LIB_POSTFIX "_2013") + ELSEIF(MSVC11) + SET(C4D_LIB_POSTFIX "_2012") + ELSEIF(MSVC10) + SET(C4D_LIB_POSTFIX "_2010") + ELSE() + MESSAGE( FATAL_ERROR + "C4D is currently only supported with MSVC 10, 11, 12, 14" + ) + ENDIF() + + SET(C4D_LIB_BASE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/contrib/Melange/libraries/win") + + SET(C4D_DEBUG_LIBRARIES + "${C4D_LIB_BASE_PATH}/melangelib${C4D_LIB_POSTFIX}/melangelib_debug.lib" + "${C4D_LIB_BASE_PATH}/jpeglib${C4D_LIB_POSTFIX}/jpeglib_debug.lib" + ) + SET(C4D_RELEASE_LIBRARIES + "${C4D_LIB_BASE_PATH}/melangelib${C4D_LIB_POSTFIX}/melangelib_release.lib" + "${C4D_LIB_BASE_PATH}/jpeglib${C4D_LIB_POSTFIX}/jpeglib_release.lib" + ) + + # winsock and winmm are necessary dependencies of melange (this is undocumented, but true.) + SET(C4D_EXTRA_LIBRARIES WSock32.lib Winmm.lib) + ELSE () + MESSAGE( FATAL_ERROR + "C4D is currently only available on Windows with melange SDK installed in contrib/Melange" + ) + ENDIF ( MSVC ) +ELSE (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) + ADD_DEFINITIONS( -DASSIMP_BUILD_NO_C4D_IMPORTER ) +ENDIF (ASSIMP_BUILD_NONFREE_C4D_IMPORTER) + +IF(NOT HUNTER_ENABLED) + ADD_SUBDIRECTORY(contrib) +ENDIF(NOT HUNTER_ENABLED) + +ADD_SUBDIRECTORY( code/ ) +IF ( ASSIMP_BUILD_ASSIMP_TOOLS ) + # The viewer for windows only + IF ( WIN32 AND DirectX_D3DX9_LIBRARY ) + OPTION ( ASSIMP_BUILD_ASSIMP_VIEW "If the Assimp view tool is built. (requires DirectX)" ${DirectX_FOUND} ) + IF ( ASSIMP_BUILD_ASSIMP_VIEW ) + ADD_SUBDIRECTORY( tools/assimp_view/ ) + ENDIF ( ASSIMP_BUILD_ASSIMP_VIEW ) + ENDIF ( WIN32 AND DirectX_D3DX9_LIBRARY ) + # Te command line tool + ADD_SUBDIRECTORY( tools/assimp_cmd/ ) +ENDIF ( ASSIMP_BUILD_ASSIMP_TOOLS ) + +IF ( ASSIMP_BUILD_SAMPLES) + IF ( WIN32 ) + ADD_SUBDIRECTORY( samples/SimpleTexturedOpenGL/ ) + ADD_SUBDIRECTORY( samples/SimpleTexturedDirectx11 ) + ENDIF ( WIN32 ) + ADD_SUBDIRECTORY( samples/SimpleOpenGL/ ) +ENDIF ( ASSIMP_BUILD_SAMPLES ) + +IF ( ASSIMP_BUILD_TESTS ) + ADD_SUBDIRECTORY( test/ ) +ENDIF ( ASSIMP_BUILD_TESTS ) + +# Generate a pkg-config .pc for the Assimp library. +CONFIGURE_FILE( "${PROJECT_SOURCE_DIR}/assimp.pc.in" "${PROJECT_BINARY_DIR}/assimp.pc" @ONLY ) +INSTALL( FILES "${PROJECT_BINARY_DIR}/assimp.pc" DESTINATION ${ASSIMP_LIB_INSTALL_DIR}/pkgconfig/ COMPONENT ${LIBASSIMP-DEV_COMPONENT}) + +IF(CMAKE_CPACK_COMMAND AND UNIX AND ASSIMP_OPT_BUILD_PACKAGES) + # Packing information + SET(CPACK_PACKAGE_NAME "assimp{ASSIMP_VERSION_MAJOR}.{ASSIMP_VERSION_MINOR}") + SET(CPACK_PACKAGE_CONTACT "" CACHE STRING "Package maintainer and PGP signer.") + SET(CPACK_PACKAGE_VENDOR "https://github.com/assimp") + SET(CPACK_PACKAGE_DISPLAY_NAME "Assimp ${ASSIMP_VERSION}") + SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY " - Open Asset Import Library ${ASSIMP_VERSION}") + SET(CPACK_PACKAGE_VERSION "${ASSIMP_VERSION}.${ASSIMP_PACKAGE_VERSION}" ) + SET(CPACK_PACKAGE_VERSION_MAJOR "${ASSIMP_VERSION_MAJOR}") + SET(CPACK_PACKAGE_VERSION_MINOR "${ASSIMP_VERSION_MINOR}") + SET(CPACK_PACKAGE_VERSION_PATCH "${ASSIMP_VERSION_PATCH}") + SET(CPACK_PACKAGE_INSTALL_DIRECTORY "assimp${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}") + SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") + + STRING(TOUPPER ${LIBASSIMP_COMPONENT} "LIBASSIMP_COMPONENT_UPPER") + STRING(TOUPPER ${LIBASSIMP-DEV_COMPONENT} "LIBASSIMP-DEV_COMPONENT_UPPER") + + SET(CPACK_COMPONENT_ASSIMP-BIN_DISPLAY_NAME "tools") + SET(CPACK_COMPONENT_ASSIMP-BIN_DEPENDS "${LIBASSIMP_COMPONENT}" ) + SET(CPACK_COMPONENT_${LIBASSIMP_COMPONENT_UPPER}_DISPLAY_NAME "libraries") + SET(CPACK_COMPONENT_${LIBASSIMP-DEV_COMPONENT_UPPER}_DISPLAY_NAME "common headers and installs") + SET(CPACK_COMPONENT_${LIBASSIMP-DEV_COMPONENT_UPPER}_DEPENDS $ "{LIBASSIMP_COMPONENT}" ) + SET(CPACK_COMPONENT_ASSIMP-DEV_DISPLAY_NAME "${CPACK_COMPONENT_${LIBASSIMP-DEV_COMPONENT}_DISPLAY_NAME}" ) + SET(CPACK_COMPONENT_ASSIMP-DEV_DEPENDS "${LIBASSIMP-DEV_COMPONENT}" ) + SET(CPACK_DEBIAN_BUILD_DEPENDS debhelper cmake zlib1g-dev pkg-config) + + # debian + SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") + SET(CPACK_DEBIAN_CMAKE_OPTIONS "-DBUILD_ASSIMP_SAMPLES:BOOL=${ASSIMP_BUILD_SAMPLES}") + SET(CPACK_DEBIAN_PACKAGE_SECTION "libs" ) + SET(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_COMPONENTS_ALL}") + SET(CPACK_DEBIAN_PACKAGE_SUGGESTS) + set(cPACK_DEBIAN_PACKAGE_NAME "assimp") + SET(CPACK_DEBIAN_PACKAGE_REMOVE_SOURCE_FILES contrib/gtest contrib/zlib workspaces test doc obj samples packaging) + SET(CPACK_DEBIAN_PACKAGE_SOURCE_COPY svn export --force) + SET(CPACK_DEBIAN_CHANGELOG) + execute_process(COMMAND lsb_release -is + OUTPUT_VARIABLE _lsb_distribution OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _lsb_release_failed) + SET(CPACK_DEBIAN_DISTRIBUTION_NAME ${_lsb_distribution} CACHE STRING "Name of the distrubiton") + STRING(TOLOWER ${CPACK_DEBIAN_DISTRIBUTION_NAME} CPACK_DEBIAN_DISTRIBUTION_NAME) + IF( ${CPACK_DEBIAN_DISTRIBUTION_NAME} STREQUAL "ubuntu" ) + SET(CPACK_DEBIAN_DISTRIBUTION_RELEASES lucid maverick natty oneiric precise CACHE STRING "Release code-names of the distrubiton release") + ENDIF() + SET(DPUT_HOST "" CACHE STRING "PPA repository to upload the debian sources") + INCLUDE(CPack) + INCLUDE(DebSourcePPA) +ENDIF() + +if(WIN32) + if (CMAKE_SIZEOF_VOID_P EQUAL 8) + SET(BIN_DIR "${PROJECT_SOURCE_DIR}/bin64/") + SET(LIB_DIR "${PROJECT_SOURCE_DIR}/lib64/") + else() + SET(BIN_DIR "${PROJECT_SOURCE_DIR}/bin32/") + SET(LIB_DIR "${PROJECT_SOURCE_DIR}/lib32/") + ENDIF() + + IF(MSVC_TOOLSET_VERSION) + set(MSVC_PREFIX "vc${MSVC_TOOLSET_VERSION}") + ELSE() + IF(MSVC12) + SET(ASSIMP_MSVC_VERSION "vc120") + ELSEIF(MSVC14) + SET(ASSIMP_MSVC_VERSION "vc140") + ELSEIF(MSVC15) + SET(ASSIMP_MSVC_VERSION "vc141") + ENDIF(MSVC12) + ENDIF() + + IF(MSVC12 OR MSVC14 OR MSVC15 ) + ADD_CUSTOM_TARGET(UpdateAssimpLibsDebugSymbolsAndDLLs COMMENT "Copying Assimp Libraries ..." VERBATIM) + IF(CMAKE_GENERATOR MATCHES "^Visual Studio") + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.dll ${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.dll VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.exp ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.exp VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.lib ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.lib VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.dll ${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.dll VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.exp ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.exp VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.lib ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.lib VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM) + ELSE() + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mt.dll ${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.dll VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mt.exp ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.exp VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mt.lib ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mt.lib VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.dll ${BIN_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.dll VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.exp ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.exp VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.ilk VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.lib ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.lib VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM) + ADD_CUSTOM_COMMAND(TARGET UpdateAssimpLibsDebugSymbolsAndDLLs COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/code/assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb ${LIB_DIR}assimp-${ASSIMP_MSVC_VERSION}-mtd.pdb VERBATIM) + ENDIF() + ENDIF(MSVC12 OR MSVC14 OR MSVC15 ) +ENDIF (WIN32) diff --git a/thirdparty/assimp/CONTRIBUTING.md b/thirdparty/assimp/CONTRIBUTING.md new file mode 100644 index 0000000..82ef07a --- /dev/null +++ b/thirdparty/assimp/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# How to contribute + +If you want to contribute, follow these steps: + +- First, create your own clone of assimp. +- When you want to fix a bug or add a new feature, create a branch on your own fork following [these instructions](https://help.github.com/articles/creating-a-pull-request-from-a-fork/). +- Push it to your fork of the repository and open a pull request. +- A pull request will start our continuous integration service, which checks if the build works for Linux and Windows. + It will check for memory leaks, compiler warnings and memory alignment issues. If any of these tests fail, fix it and the tests will be restarted automatically. + - At the end, we will perform a code review and merge your branch to the master branch. diff --git a/thirdparty/assimp/CREDITS b/thirdparty/assimp/CREDITS new file mode 100644 index 0000000..26e21d2 --- /dev/null +++ b/thirdparty/assimp/CREDITS @@ -0,0 +1,183 @@ +=============================================================== +Open Asset Import Library (Assimp) +Developers and Contributors +=============================================================== + +The following is a non-exhaustive list of all constributors over the years. +If you think your name should be listed here, drop us a line and we'll add you. + +- Alexander Gessler, +3DS-, BLEND-, ASE-, DXF-, HMP-, MDL-, MD2-, MD3-, MD5-, MDC-, NFF-, PLY-, STL-, RAW-, OFF-, MS3D-, Q3D- and LWO-Loader, Assimp-Viewer, assimp-cmd, -noboost, Website (Design). + +- Thomas Schulze, +X-, Collada-, BVH-Loader, Postprocessing framework. Data structure & Interface design, documentation. + +- Kim Kulling, +Obj-, Q3BSD-, OpenGEX-Loader, Logging system, CMake-build-environment, Linux-build, Website ( Admin ), Coverity ( Admin ), Glitter ( Admin ). + +- R.Schmidt, +Linux build, eclipse support. + +- Matthias Gubisch, +Assimp.net +Visual Studio 9 support, bugfixes. + +- Mark Sibly +B3D-Loader, Assimp testing + +- Jonathan Klein +Ogre Loader, VC2010 fixes and CMake fixes. + +- Sebastian Hempel, +PyAssimp (first version) +Compile-Bugfixes for mingw, add environment for static library support in make. + +- Jonathan Pokrass +Supplied a bugfix concerning the scaling in the md3 loader. + +- Andrew Galante, +Submitted patches to make Assimp compile with GCC-4, a makefile and the xcode3 workspace. + +- Andreas Nagel +First Assimp testing & verification under Windows Vista 64 Bit. + +- Marius Schr�der +Allowed us to use many of his models for screenshots and testing. + +- Christian Schubert +Supplied various XFiles for testing purposes. + +- Tizian Wieland +Searched the web for hundreds of test models for internal use + +- John Connors +Supplied patches for linux and SCons. + +- T. R. +The GUY who performed some of the CSM mocaps. + +- Andy Maloney +Contributed fixes for the documentation and the doxygen markup + +- Zhao Lei +Contributed several bugfixes fixing memory leaks and improving float parsing + +- sueastside +Updated PyAssimp to the latest Assimp data structures and provided a script to keep the Python binding up-to-date. + +- Tobias Rittig +Collada testing with Cinema 4D + +- Brad Grantham +Improvements in OpenGL-Sample. + +- Robert Ramirez +Add group loading feature to Obj-Loader. + +- Chris Maiwald +Many bugreports, improving Assimp's portability, regular testing & feedback. + +- Stepan Hrbek +Bugreport and fix for a obj-materialloader crash. + +- David Nadlinger +D bindings, CMake install support. + +- Dario Accornero +Contributed several patches regarding Mac OS/XCode targets, bug reports. + +- Martin Walser (Samhayne) +Contributed the 'SimpleTexturedOpenGl' sample. + +- Matthias Fauconneau +Contributed a fix for the Q3-BSP loader. + +- Jørgen P. Tjernø +Contributed updated and improved xcode workspaces + +- drparallax +Contributed the /samples/SimpleAssimpViewX sample + +- Carsten Fuchs +Contributed a fix for the Normalize method in aiQuaternion. + +- dbburgess +Contributes a Android-specific build issue: log the hardware architecture for ARM. + +- alfiereinre7 +Contributes a obj-fileparser fix: missing tokens in the obj-token list. + +- Roman Kharitonov +Contributes a fix for the configure script environment. + +- Ed Diana +Contributed AssimpDelphi (/port/AssimpDelphi). + +- rdb +Contributes a bundle of fixes and improvements for the bsp-importer. + +- Mick P +For contributing the De-bone postprocessing step and filing various bug reports. + +- Rosen Diankov +Contributed patches to build assimp debian packages using cmake. + +- Mark Page +Contributed a patch to fix the VertexTriangleAdjacency postprocessing step. + +- IOhannes +Contributed the Debian build fixes ( architecture macro ). + +- gellule +Several LWO and LWS fixes (pivoting). + +- Marcel Metz +GCC/Linux fixes for the SimpleOpenGL sample. + +- Brian Miller +Bugfix for a compiler fix for iOS on arm. + +- Séverin Lemaignan +Rewrite of PyAssimp, distutils and Python3 support + +- albert-wang +Bugfixes for the collada parser + +- Ya ping Jin +Bugfixes for uv-tanget calculation. + +- Jonne Nauha +Ogre Binary format support + +- Filip Wasil, Tieto Poland Sp. z o.o. +Android JNI asset extraction support + +- Richard Steffen +Contributed ExportProperties interface +Contributed X File exporter +Contributed Step (stp) exporter + +- Thomas Iorns (mesilliac) +Initial FBX Export support + +For a more detailed list just check: https://github.com/assimp/assimp/network/members + + +======== +Patreons +======== + +Huge thanks to our Patreons! + +- migenius +- Marcus +- Cort +- elect +- Steffen + + +=================== +Commercial Sponsors +=================== + +- MyDidimo (mydidimo.com): Sponsored development of FBX Export support diff --git a/thirdparty/assimp/CodeConventions.md b/thirdparty/assimp/CodeConventions.md new file mode 100644 index 0000000..e80e04e --- /dev/null +++ b/thirdparty/assimp/CodeConventions.md @@ -0,0 +1,12 @@ + +Open Asset Import Library Coding Conventions +== + +If you want to participate as a developer in the **Open Asset Import Library** please read and respect the following coding conventions. This will ensure consistency throughout the codebase and help all the Open Asset Import Library users. + +Spacing +== + +* Use UNIX-style line endings (LF) +* Remove any trailing whitespace +* Expand tabs to 4 spaces diff --git a/thirdparty/assimp/INSTALL b/thirdparty/assimp/INSTALL new file mode 100644 index 0000000..350a5f1 --- /dev/null +++ b/thirdparty/assimp/INSTALL @@ -0,0 +1,50 @@ + +======================================================================== +Open Asset Import Library (assimp) INSTALL +======================================================================== + +------------------------------ +Getting the documentation +------------------------------ + +A regularly-updated copy is available at +http://assimp.sourceforge.net/lib_html/index.html + +A CHM file is included in the SVN repos: ./doc/AssimpDoc_Html/AssimpDoc.chm. +To build the doxygen documentation on your own, follow these steps: + +a) download & install latest doxygen +b) make sure doxygen is in the executable search path +c) navigate to ./doc +d) and run 'doxygen' + +Open the generated HTML (AssimpDoc_Html/index.html) in the browser of your choice. +Windows only: To generate the CHM doc, install 'Microsoft HTML Workshop' +and configure the path to it in the DOXYFILE first. + +------------------------------ +Building Assimp +------------------------------ + +More detailed build instructions can be found in the documentation, +this section is just for the inpatient among you. + +CMake is the preferred build system for Assimp. The minimum required version +is 2.6. If you don't have it yet, downloads for CMake can be found on +http://www.cmake.org/. + +For Unix: + +1. mkdir build && cd build +2. cmake .. -G 'Unix Makefiles' +3. make -j4 + +For Windows: +1. Open a command prompt +2. mkdir build +3. cd build +4. cmake .. +5. cmake --build . + +For iOS: +Just check the following project, which deploys a compiler toolchain for different iOS-versions: https://github.com/assimp/assimp/tree/master/port/iOS diff --git a/thirdparty/assimp/LICENSE b/thirdparty/assimp/LICENSE new file mode 100644 index 0000000..262606a --- /dev/null +++ b/thirdparty/assimp/LICENSE @@ -0,0 +1,78 @@ +Open Asset Import Library (assimp) + +Copyright (c) 2006-2016, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +****************************************************************************** + +AN EXCEPTION applies to all files in the ./test/models-nonbsd folder. +These are 3d models for testing purposes, from various free sources +on the internet. They are - unless otherwise stated - copyright of +their respective creators, which may impose additional requirements +on the use of their work. For any of these models, see +.source.txt for more legal information. Contact us if you +are a copyright holder and believe that we credited you inproperly or +if you don't want your files to appear in the repository. + + +****************************************************************************** + +Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors +http://code.google.com/p/poly2tri/ + +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of Poly2Tri nor the names of its contributors may be + used to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/thirdparty/assimp/README b/thirdparty/assimp/README new file mode 100644 index 0000000..24619ab --- /dev/null +++ b/thirdparty/assimp/README @@ -0,0 +1 @@ +See Readme.md diff --git a/thirdparty/assimp/Readme.md b/thirdparty/assimp/Readme.md new file mode 100644 index 0000000..52a195a --- /dev/null +++ b/thirdparty/assimp/Readme.md @@ -0,0 +1,183 @@ +Open Asset Import Library (assimp) +================================== +A library to import and export various 3d-model-formats including scene-post-processing to generate missing render data. +### Current project status ### +[![Linux Build Status](https://travis-ci.org/assimp/assimp.svg)](https://travis-ci.org/assimp/assimp) +[![Windows Build Status](https://ci.appveyor.com/api/projects/status/tmo433wax6u6cjp4?svg=true)](https://ci.appveyor.com/project/kimkulling/assimp) + + Coverity Scan Build Status + +[![Coverage Status](https://coveralls.io/repos/github/assimp/assimp/badge.svg?branch=master)](https://coveralls.io/github/assimp/assimp?branch=master) +[![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Average time to resolve an issue") +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/5be56faac64f46fc941ac890fb4febef)](https://www.codacy.com/app/kimkulling/assimp?utm_source=github.com&utm_medium=referral&utm_content=assimp/assimp&utm_campaign=Badge_Grade) +[![Total alerts](https://img.shields.io/lgtm/alerts/g/assimp/assimp.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/assimp/assimp/alerts/) +
+ +APIs are provided for C and C++. There are various bindings to other languages (C#, Java, Python, Delphi, D). Assimp also runs on Android and iOS. + +[Check the latest doc](https://assimp-docs.readthedocs.io/en/latest/). + +Additionally, assimp features various __mesh post processing tools__: normals and tangent space generation, triangulation, vertex cache locality optimization, removal of degenerate primitives and duplicate vertices, sorting by primitive type, merging of redundant materials and many more. + +This is the development repo containing the latest features and bugfixes. For productive use though, we recommend one of the stable releases available from [Github Assimp Releases](https://github.com/assimp/assimp/releases). + +Monthly donations via Patreon: +
[![Patreon](https://cloud.githubusercontent.com/assets/8225057/5990484/70413560-a9ab-11e4-8942-1a63607c0b00.png)](http://www.patreon.com/assimp) + +
+ +One-off donations via PayPal: +
[![PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4JRJVPXC4QJM4) + +
+ +Please check our Wiki as well: https://github.com/assimp/assimp/wiki + +If you want to check our Model-Database, use the following repo: https://github.com/assimp/assimp-mdb + +#### Supported file formats #### + +__Importers__: + +- 3D +- [3DS](https://en.wikipedia.org/wiki/.3ds) +- [3MF](https://en.wikipedia.org/wiki/3D_Manufacturing_Format) +- AC +- [AC3D](https://en.wikipedia.org/wiki/AC3D) +- ACC +- AMJ +- ASE +- ASK +- B3D +- [BLEND](https://en.wikipedia.org/wiki/.blend_(file_format)) +- [BVH](https://en.wikipedia.org/wiki/Biovision_Hierarchy) +- CMS +- COB +- [DAE/Collada](https://en.wikipedia.org/wiki/COLLADA) +- [DXF](https://en.wikipedia.org/wiki/AutoCAD_DXF) +- ENFF +- [FBX](https://en.wikipedia.org/wiki/FBX) +- [glTF 1.0](https://en.wikipedia.org/wiki/GlTF#glTF_1.0) + GLB +- [glTF 2.0](https://en.wikipedia.org/wiki/GlTF#glTF_2.0) +- HMB +- IFC-STEP +- IRR / IRRMESH +- [LWO](https://en.wikipedia.org/wiki/LightWave_3D) +- LWS +- LXO +- MD2 +- MD3 +- MD5 +- MDC +- MDL +- MESH / MESH.XML +- MOT +- MS3D +- NDO +- NFF +- [OBJ](https://en.wikipedia.org/wiki/Wavefront_.obj_file) +- [OFF](https://en.wikipedia.org/wiki/OFF_(file_format)) +- [OGEX](https://en.wikipedia.org/wiki/Open_Game_Engine_Exchange) +- [PLY](https://en.wikipedia.org/wiki/PLY_(file_format)) +- PMX +- PRJ +- Q3O +- Q3S +- RAW +- SCN +- SIB +- SMD +- [STP](https://en.wikipedia.org/wiki/ISO_10303-21) +- [STL](https://en.wikipedia.org/wiki/STL_(file_format)) +- TER +- UC +- VTA +- X +- [X3D](https://en.wikipedia.org/wiki/X3D) +- XGL +- ZGL + +Additionally, some formats are supported by dependency on non-free code or external SDKs (not built by default): + +- [C4D](https://en.wikipedia.org/wiki/Cinema_4D) (https://github.com/assimp/assimp/wiki/Cinema4D-&-Melange) IMporting geometry + node hierarchy are currently supported + +__Exporters__: + +- DAE (Collada) +- STL +- OBJ +- PLY +- X +- 3DS +- JSON (for WebGl, via https://github.com/acgessler/assimp2json) +- ASSBIN +- STEP +- glTF 1.0 (partial) +- glTF 2.0 (partial) +- 3MF ( experimental ) +- FBX ( experimental ) + +### Building ### +Take a look into the https://github.com/assimp/assimp/blob/master/Build.md file. Our build system is CMake, if you used CMake before there is a good chance you know what to do. + +### Ports ### +* [Android](port/AndroidJNI/README.md) +* [Python](port/PyAssimp/README.md) +* [.NET](https://github.com/kebby/assimp-net) +* [Pascal](port/AssimpPascal/Readme.md) +* [Javascript (Alpha)](https://github.com/makc/assimp2json) +* [Unity 3d Plugin](https://www.assetstore.unity3d.com/en/#!/content/91777) +* [JVM](https://github.com/kotlin-graphics/assimp) Full jvm port (current [status](https://github.com/kotlin-graphics/assimp/wiki/Status)) +* [HAXE-Port](https://github.com/longde123/assimp-haxe) The Assimp-HAXE-port. + +### Other tools ### +[open3mod](https://github.com/acgessler/open3mod) is a powerful 3D model viewer based on Assimp's import and export abilities. + +#### Repository structure #### +Open Asset Import Library is implemented in C++. The directory structure looks like: + + /code Source code + /contrib Third-party libraries + /doc Documentation (doxysource and pre-compiled docs) + /include Public header C and C++ header files + /scripts Scripts used to generate the loading code for some formats + /port Ports to other languages and scripts to maintain those. + /test Unit- and regression tests, test suite of models + /tools Tools (old assimp viewer, command line `assimp`) + /samples A small number of samples to illustrate possible + use cases for Assimp + +The source code is organized in the following way: + + code/Common The base implementation for importers and the infrastructure + code/PostProcessing The post-processing steps + code/ Implementation for import and export for the format + +### Where to get help ### +For more information, visit [our website](http://assimp.org/). Or check out the `./doc`- folder, which contains the official documentation in HTML format. +(CHMs for Windows are included in some release packages and should be located right here in the root folder). + +If the docs don't solve your problem, ask on [StackOverflow](http://stackoverflow.com/questions/tagged/assimp?sort=newest). If you think you found a bug, please open an issue on Github. + +For development discussions, there is also a (very low-volume) mailing list, _assimp-discussions_ + [(subscribe here)]( https://lists.sourceforge.net/lists/listinfo/assimp-discussions) + +Open Asset Import Library is a library to load various 3d file formats into a shared, in-memory format. It supports more than __40 file formats__ for import and a growing selection of file formats for export. + +And we also have a Gitter-channel:Gitter [![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+ +### Contributing ### +Contributions to assimp are highly appreciated. The easiest way to get involved is to submit +a pull request with your changes against the main repository's `master` branch. + +### License ### +Our license is based on the modified, __3-clause BSD__-License. + +An _informal_ summary is: do whatever you want, but include Assimp's license text with your product - +and don't sue us if our code doesn't work. Note that, unlike LGPLed code, you may link statically to Assimp. +For the legal details, see the `LICENSE` file. + +### Why this name ### +Sorry, we're germans :-), no english native speakers ... diff --git a/thirdparty/assimp/assimp-config-version.cmake.in b/thirdparty/assimp/assimp-config-version.cmake.in new file mode 100644 index 0000000..923a387 --- /dev/null +++ b/thirdparty/assimp/assimp-config-version.cmake.in @@ -0,0 +1,48 @@ +# Open Asset Import Library (assimp) +# ---------------------------------------------------------------------- +# Copyright (c) 2006-2017, assimp team +# All rights reserved. +# +# Redistribution and use of this software in source and binary forms, +# with or without modification, are permitted provided that the +# following conditions are met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the +# following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the +# following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# * Neither the name of the assimp team, nor the names of its +# contributors may be used to endorse or promote products +# derived from this software without specific prior +# written permission of the assimp team. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#---------------------------------------------------------------------- +set( PACKAGE_VERSION "@ASSIMP_VERSION@" ) +if( "${PACKAGE_FIND_VERSION}" VERSION_EQUAL "@ASSIMP_VERSION@") + set(PACKAGE_VERSION_EXACT 1) +endif() +if( "${PACKAGE_FIND_VERSION_MAJOR}.${PACKAGE_FIND_VERSION_MINOR}" EQUAL "@ASSIMP_VERSION@" ) + set(PACKAGE_VERSION_COMPATIBLE 1) +elseif( "${PACKAGE_FIND_VERSION_MAJOR}" EQUAL "@ASSIMP_VERSION_MAJOR@" ) + # for now backward compatible if minor version is less + if( ${PACKAGE_FIND_VERSION_MINOR} LESS @ASSIMP_VERSION_MINOR@ ) + set(PACKAGE_VERSION_COMPATIBLE 1) + endif() +endif() +set( ASSIMP_STATIC_LIB "@ASSIMP_BUILD_STATIC_LIB@") diff --git a/thirdparty/assimp/assimp-config.cmake.in b/thirdparty/assimp/assimp-config.cmake.in new file mode 100644 index 0000000..0ab9fd0 --- /dev/null +++ b/thirdparty/assimp/assimp-config.cmake.in @@ -0,0 +1 @@ +include(${CMAKE_CURRENT_LIST_DIR}/assimpTargets.cmake) diff --git a/thirdparty/assimp/assimp.pc.in b/thirdparty/assimp/assimp.pc.in new file mode 100644 index 0000000..c659e19 --- /dev/null +++ b/thirdparty/assimp/assimp.pc.in @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@/ +libdir=@CMAKE_INSTALL_PREFIX@/@ASSIMP_LIB_INSTALL_DIR@ +includedir=@CMAKE_INSTALL_PREFIX@/../include/@ASSIMP_INCLUDE_INSTALL_DIR@ + +Name: @CMAKE_PROJECT_NAME@ +Description: Import various well-known 3D model formats in an uniform manner. +Version: @PROJECT_VERSION@ +Libs: -L${libdir} -lassimp@ASSIMP_LIBRARY_SUFFIX@ +Libs.private: @LIBSTDC++_LIBRARIES@ @ZLIB_LIBRARIES_LINKED@ +Cflags: -I${includedir} diff --git a/thirdparty/assimp/assimpTargets-debug.cmake.in b/thirdparty/assimp/assimpTargets-debug.cmake.in new file mode 100644 index 0000000..1ebe2a6 --- /dev/null +++ b/thirdparty/assimp/assimpTargets-debug.cmake.in @@ -0,0 +1,114 @@ +#---------------------------------------------------------------- +# Generated CMake target import file for configuration "Debug". +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +set(ASSIMP_BUILD_SHARED_LIBS @BUILD_SHARED_LIBS@) + +if(MSVC) + if(MSVC_TOOLSET_VERSION) + set(MSVC_PREFIX "vc${MSVC_TOOLSET_VERSION}") + else() + if( MSVC70 OR MSVC71 ) + set(MSVC_PREFIX "vc70") + elseif( MSVC80 ) + set(MSVC_PREFIX "vc80") + elseif( MSVC90 ) + set(MSVC_PREFIX "vc90") + elseif( MSVC10 ) + set(MSVC_PREFIX "vc100") + elseif( MSVC11 ) + set(MSVC_PREFIX "vc110") + elseif( MSVC12 ) + set(MSVC_PREFIX "vc120") + elseif( MSVC_VERSION LESS 1910) + set(MSVC_PREFIX "vc140") + elseif( MSVC_VERSION LESS 1920) + set(MSVC_PREFIX "vc141") + elseif( MSVC_VERSION LESS 1930) + set(MSVC_PREFIX "vc142") + else() + set(MSVC_PREFIX "vc150") + endif() + endif() + set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" ) + + if(ASSIMP_BUILD_SHARED_LIBS) + set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@") + set(importLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_IMPORT_LIBRARY_SUFFIX@") + + # Import target "assimp::assimp" for configuration "Debug" + set_property(TARGET assimp::assimp APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties(assimp::assimp PROPERTIES + IMPORTED_IMPLIB_DEBUG "${_IMPORT_PREFIX}/lib/${importLibraryName}" + IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/bin/${sharedLibraryName}" + ) + list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp ) + list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "${_IMPORT_PREFIX}/lib/${importLibraryName}") + list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "${_IMPORT_PREFIX}/bin/${sharedLibraryName}" ) + else() + set(staticLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_STATIC_LIBRARY_SUFFIX@") + + # Import target "assimp::assimp" for configuration "Debug" + set_property(TARGET assimp::assimp APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties(assimp::assimp PROPERTIES + IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/${staticLibraryName}" + ) + list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp ) + list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "${_IMPORT_PREFIX}/lib/${staticLibraryName}") + endif() + +else() + set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" ) + if(ASSIMP_BUILD_SHARED_LIBS) + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") + set_target_properties(assimp::assimp PROPERTIES + IMPORTED_SONAME_DEBUG "${sharedLibraryName}" + IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/${sharedLibraryName}" + ) + list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp ) + list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "${_IMPORT_PREFIX}/lib/${sharedLibraryName}" ) + else() + set(staticLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_STATIC_LIBRARY_SUFFIX@") + set_target_properties(assimp::assimp PROPERTIES + IMPORTED_LOCATION_DEBUG "${_IMPORT_PREFIX}/lib/${staticLibraryName}" + ) + list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp ) + list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "${_IMPORT_PREFIX}/lib/${staticLibraryName}" ) + endif() +endif() + + + + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) + +get_filename_component(ASSIMP_ROOT_DIR "@CMAKE_INSTALL_PREFIX@" REALPATH) +set( ASSIMP_CXX_FLAGS ) # dynamically linked library +set( ASSIMP_LINK_FLAGS "" ) +set( ASSIMP_LIBRARY_DIRS "${ASSIMP_ROOT_DIR}/@ASSIMP_LIB_INSTALL_DIR@") +set( ASSIMP_INCLUDE_DIRS "${ASSIMP_ROOT_DIR}/@ASSIMP_INCLUDE_INSTALL_DIR@") +if(ASSIMP_BUILD_SHARED_LIBS) + set( ASSIMP_LIBRARIES ${sharedLibraryName}) +else() + set( ASSIMP_LIBRARIES ${staticLibraryName}) +endif() + +# for compatibility with pkg-config +set(ASSIMP_CFLAGS_OTHER "${ASSIMP_CXX_FLAGS}") +set(ASSIMP_LDFLAGS_OTHER "${ASSIMP_LINK_FLAGS}") + +MARK_AS_ADVANCED( + ASSIMP_ROOT_DIR + ASSIMP_CXX_FLAGS + ASSIMP_LINK_FLAGS + ASSIMP_INCLUDE_DIRS + ASSIMP_LIBRARIES + ASSIMP_CFLAGS_OTHER + ASSIMP_LDFLAGS_OTHER + ASSIMP_LIBRARY_SUFFIX + ASSIMP_BUILD_SHARED_LIBS +) diff --git a/thirdparty/assimp/assimpTargets-release.cmake.in b/thirdparty/assimp/assimpTargets-release.cmake.in new file mode 100644 index 0000000..b09b881 --- /dev/null +++ b/thirdparty/assimp/assimpTargets-release.cmake.in @@ -0,0 +1,112 @@ +#---------------------------------------------------------------- +# Generated CMake target import file for configuration "Release". +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +set(ASSIMP_BUILD_SHARED_LIBS @BUILD_SHARED_LIBS@) + +if(MSVC) + if(MSVC_TOOLSET_VERSION) + set(MSVC_PREFIX "vc${MSVC_TOOLSET_VERSION}") + else() + if( MSVC70 OR MSVC71 ) + set(MSVC_PREFIX "vc70") + elseif( MSVC80 ) + set(MSVC_PREFIX "vc80") + elseif( MSVC90 ) + set(MSVC_PREFIX "vc90") + elseif( MSVC10 ) + set(MSVC_PREFIX "vc100") + elseif( MSVC11 ) + set(MSVC_PREFIX "vc110") + elseif( MSVC12 ) + set(MSVC_PREFIX "vc120") + elseif( MSVC_VERSION LESS 1910) + set(MSVC_PREFIX "vc140") + elseif( MSVC_VERSION LESS 1920) + set(MSVC_PREFIX "vc141") + elseif( MSVC_VERSION LESS 1930) + set(MSVC_PREFIX "vc142") + else() + set(MSVC_PREFIX "vc150") + endif() + endif() + set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" ) + + if(ASSIMP_BUILD_SHARED_LIBS) + set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@") + set(importLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_IMPORT_LIBRARY_SUFFIX@") + + # Import target "assimp::assimp" for configuration "Release" + set_property(TARGET assimp::assimp APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties(assimp::assimp PROPERTIES + IMPORTED_IMPLIB_RELEASE "${_IMPORT_PREFIX}/lib/${importLibraryName}" + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/bin/${sharedLibraryName}" + ) + list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp ) + list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "${_IMPORT_PREFIX}/lib/${importLibraryName}") + list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "${_IMPORT_PREFIX}/bin/${sharedLibraryName}" ) + else() + set(staticLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_STATIC_LIBRARY_SUFFIX@") + + # Import target "assimp::assimp" for configuration "Release" + set_property(TARGET assimp::assimp APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties(assimp::assimp PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/${staticLibraryName}" + ) + list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp ) + list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "${_IMPORT_PREFIX}/lib/${staticLibraryName}") + endif() + +else() + set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" ) + if(ASSIMP_BUILD_SHARED_LIBS) + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") + set_target_properties(assimp::assimp PROPERTIES + IMPORTED_SONAME_RELEASE "${sharedLibraryName}" + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/${sharedLibraryName}" + ) + list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp ) + list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "${_IMPORT_PREFIX}/lib/${sharedLibraryName}" ) + else() + set(staticLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_STATIC_LIBRARY_SUFFIX@") + set_target_properties(assimp::assimp PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/${staticLibraryName}" + ) + list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp ) + list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "${_IMPORT_PREFIX}/lib/${staticLibraryName}" ) + endif() +endif() + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) + +get_filename_component(ASSIMP_ROOT_DIR "@CMAKE_INSTALL_PREFIX@" REALPATH) + +set( ASSIMP_CXX_FLAGS ) # dynamically linked library +set( ASSIMP_LINK_FLAGS "" ) +set( ASSIMP_LIBRARY_DIRS "${ASSIMP_ROOT_DIR}/@ASSIMP_LIB_INSTALL_DIR@") +set( ASSIMP_INCLUDE_DIRS "${ASSIMP_ROOT_DIR}/@ASSIMP_INCLUDE_INSTALL_DIR@") +if(ASSIMP_BUILD_SHARED_LIBS) + set( ASSIMP_LIBRARIES ${sharedLibraryName}) +else() + set( ASSIMP_LIBRARIES ${staticLibraryName}) +endif() + +# for compatibility with pkg-config +set(ASSIMP_CFLAGS_OTHER "${ASSIMP_CXX_FLAGS}") +set(ASSIMP_LDFLAGS_OTHER "${ASSIMP_LINK_FLAGS}") + +MARK_AS_ADVANCED( + ASSIMP_ROOT_DIR + ASSIMP_CXX_FLAGS + ASSIMP_LINK_FLAGS + ASSIMP_INCLUDE_DIRS + ASSIMP_LIBRARIES + ASSIMP_CFLAGS_OTHER + ASSIMP_LDFLAGS_OTHER + ASSIMP_LIBRARY_SUFFIX + ASSIMP_BUILD_SHARED_LIBS +) diff --git a/thirdparty/assimp/assimpTargets.cmake.in b/thirdparty/assimp/assimpTargets.cmake.in new file mode 100644 index 0000000..ab1a8d2 --- /dev/null +++ b/thirdparty/assimp/assimpTargets.cmake.in @@ -0,0 +1,105 @@ +# Generated by CMake + +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.5) + message(FATAL_ERROR "CMake >= 2.6.0 required") +endif() +cmake_policy(PUSH) +cmake_policy(VERSION 2.6) +#---------------------------------------------------------------- +# Generated CMake target import file. +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Protect against multiple inclusion, which would fail when already imported targets are added once more. +set(_targetsDefined) +set(_targetsNotDefined) +set(_expectedTargets) +foreach(_expectedTarget assimp::assimp) + list(APPEND _expectedTargets ${_expectedTarget}) + if(NOT TARGET ${_expectedTarget}) + list(APPEND _targetsNotDefined ${_expectedTarget}) + endif() + if(TARGET ${_expectedTarget}) + list(APPEND _targetsDefined ${_expectedTarget}) + endif() +endforeach() +if("${_targetsDefined}" STREQUAL "${_expectedTargets}") + unset(_targetsDefined) + unset(_targetsNotDefined) + unset(_expectedTargets) + set(CMAKE_IMPORT_FILE_VERSION) + cmake_policy(POP) + return() +endif() +if(NOT "${_targetsDefined}" STREQUAL "") + message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n") +endif() +unset(_targetsDefined) +unset(_targetsNotDefined) +unset(_expectedTargets) + + +# Compute the installation prefix relative to this file. +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +if(_IMPORT_PREFIX STREQUAL "/") + set(_IMPORT_PREFIX "") +endif() + +# Create imported target assimp::assimp +if(@BUILD_SHARED_LIBS@) + add_library(assimp::assimp SHARED IMPORTED) +else() + add_library(assimp::assimp STATIC IMPORTED) +endif() + +set_target_properties(assimp::assimp PROPERTIES + COMPATIBLE_INTERFACE_STRING "assimp_MAJOR_VERSION" + INTERFACE_assimp_MAJOR_VERSION "1" + INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include;${_IMPORT_PREFIX}/include" + #INTERFACE_LINK_LIBRARIES "TxtUtils::TxtUtils;MealyMachine::MealyMachine" +) + +if(CMAKE_VERSION VERSION_LESS 2.8.12) + message(FATAL_ERROR "This file relies on consumers using CMake 2.8.12 or greater.") +endif() + +# Load information for each installed configuration. +get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +file(GLOB CONFIG_FILES "${_DIR}/assimpTargets-*.cmake") +foreach(f ${CONFIG_FILES}) + include(${f}) +endforeach() + +# Cleanup temporary variables. +set(_IMPORT_PREFIX) + +# Loop over all imported files and verify that they actually exist +foreach(target ${_IMPORT_CHECK_TARGETS} ) + foreach(file ${_IMPORT_CHECK_FILES_FOR_${target}} ) + if(NOT EXISTS "${file}" ) + message(FATAL_ERROR "The imported target \"${target}\" references the file + \"${file}\" +but this file does not exist. Possible reasons include: +* The file was deleted, renamed, or moved to another location. +* An install or uninstall procedure did not complete successfully. +* The installation package was faulty and contained + \"${CMAKE_CURRENT_LIST_FILE}\" +but not all the files it references. +") + endif() + endforeach() + unset(_IMPORT_CHECK_FILES_FOR_${target}) +endforeach() +unset(_IMPORT_CHECK_TARGETS) + +# This file does not depend on other imported targets which have +# been exported from the same project but in a separate export set. + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) +cmake_policy(POP) diff --git a/thirdparty/assimp/cmake-modules/Coveralls.cmake b/thirdparty/assimp/cmake-modules/Coveralls.cmake new file mode 100644 index 0000000..5343f15 --- /dev/null +++ b/thirdparty/assimp/cmake-modules/Coveralls.cmake @@ -0,0 +1,126 @@ +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Copyright (C) 2014 Joakim Söderberg +# + +set(_CMAKE_SCRIPT_PATH ${CMAKE_CURRENT_LIST_DIR}) # must be outside coveralls_setup() to get correct path + +# +# Param _COVERAGE_SRCS A list of source files that coverage should be collected for. +# Param _COVERALLS_UPLOAD Upload the result to coveralls? +# + +function(coveralls_setup _COVERAGE_SRCS _COVERALLS_UPLOAD) + + if (ARGC GREATER 2) + set(_CMAKE_SCRIPT_PATH ${ARGN}) + message(STATUS "Coveralls: Using alternate CMake script dir: ${_CMAKE_SCRIPT_PATH}") + endif() + + if (NOT EXISTS "${_CMAKE_SCRIPT_PATH}/CoverallsClear.cmake") + message(FATAL_ERROR "Coveralls: Missing ${_CMAKE_SCRIPT_PATH}/CoverallsClear.cmake") + endif() + + if (NOT EXISTS "${_CMAKE_SCRIPT_PATH}/CoverallsGenerateGcov.cmake") + message(FATAL_ERROR "Coveralls: Missing ${_CMAKE_SCRIPT_PATH}/CoverallsGenerateGcov.cmake") + endif() + + # When passing a CMake list to an external process, the list + # will be converted from the format "1;2;3" to "1 2 3". + # This means the script we're calling won't see it as a list + # of sources, but rather just one long path. We remedy this + # by replacing ";" with "*" and then reversing that in the script + # that we're calling. + # http://cmake.3232098.n2.nabble.com/Passing-a-CMake-list-quot-as-is-quot-to-a-custom-target-td6505681.html + set(COVERAGE_SRCS_TMP ${_COVERAGE_SRCS}) + set(COVERAGE_SRCS "") + foreach (COVERAGE_SRC ${COVERAGE_SRCS_TMP}) + set(COVERAGE_SRCS "${COVERAGE_SRCS}*${COVERAGE_SRC}") + endforeach() + + #message("Coverage sources: ${COVERAGE_SRCS}") + set(COVERALLS_FILE ${PROJECT_BINARY_DIR}/coveralls.json) + + add_custom_target(coveralls_generate + + # Zero the coverage counters. + COMMAND ${CMAKE_COMMAND} -DPROJECT_BINARY_DIR="${PROJECT_BINARY_DIR}" -P "${_CMAKE_SCRIPT_PATH}/CoverallsClear.cmake" + + # Run regress tests. + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + + # Generate Gcov and translate it into coveralls JSON. + # We do this by executing an external CMake script. + # (We don't want this to run at CMake generation time, but after compilation and everything has run). + COMMAND ${CMAKE_COMMAND} + -DCOVERAGE_SRCS="${COVERAGE_SRCS}" # TODO: This is passed like: "a b c", not "a;b;c" + -DCOVERALLS_OUTPUT_FILE="${COVERALLS_FILE}" + -DCOV_PATH="${PROJECT_BINARY_DIR}" + -DPROJECT_ROOT="${PROJECT_SOURCE_DIR}" + -P "${_CMAKE_SCRIPT_PATH}/CoverallsGenerateGcov.cmake" + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Generating coveralls output..." + ) + + if (_COVERALLS_UPLOAD) + message("COVERALLS UPLOAD: ON") + + find_program(CURL_EXECUTABLE curl) + + if (NOT CURL_EXECUTABLE) + message(FATAL_ERROR "Coveralls: curl not found! Aborting") + endif() + + add_custom_target(coveralls_upload + # Upload the JSON to coveralls. + COMMAND ${CURL_EXECUTABLE} + -S -F json_file=@${COVERALLS_FILE} + https://coveralls.io/api/v1/jobs + + DEPENDS coveralls_generate + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Uploading coveralls output...") + + add_custom_target(coveralls DEPENDS coveralls_upload) + else() + message("COVERALLS UPLOAD: OFF") + add_custom_target(coveralls DEPENDS coveralls_generate) + endif() + +endfunction() + +macro(coveralls_turn_on_coverage) + if(NOT (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) + AND (NOT "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")) + message(FATAL_ERROR "Coveralls: Compiler ${CMAKE_C_COMPILER_ID} is not GNU gcc! Aborting... You can set this on the command line using CC=/usr/bin/gcc CXX=/usr/bin/g++ cmake ..") + endif() + + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message(FATAL_ERROR "Coveralls: Code coverage results with an optimised (non-Debug) build may be misleading! Add -DCMAKE_BUILD_TYPE=Debug") + endif() + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") +endmacro() + diff --git a/thirdparty/assimp/cmake-modules/CoverallsClear.cmake b/thirdparty/assimp/cmake-modules/CoverallsClear.cmake new file mode 100644 index 0000000..a9ad487 --- /dev/null +++ b/thirdparty/assimp/cmake-modules/CoverallsClear.cmake @@ -0,0 +1,31 @@ +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Copyright (C) 2014 Joakim Söderberg +# + +# do not follow symlinks in file(GLOB_RECURSE ...) +cmake_policy(SET CMP0009 NEW) + +file(GLOB_RECURSE GCDA_FILES "${PROJECT_BINARY_DIR}/*.gcda") +if(NOT GCDA_FILES STREQUAL "") + file(REMOVE ${GCDA_FILES}) +endif() \ No newline at end of file diff --git a/thirdparty/assimp/cmake-modules/CoverallsGenerateGcov.cmake b/thirdparty/assimp/cmake-modules/CoverallsGenerateGcov.cmake new file mode 100644 index 0000000..1047371 --- /dev/null +++ b/thirdparty/assimp/cmake-modules/CoverallsGenerateGcov.cmake @@ -0,0 +1,482 @@ +# +# The MIT License (MIT) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Copyright (C) 2014 Joakim Söderberg +# +# This is intended to be run by a custom target in a CMake project like this. +# 0. Compile program with coverage support. +# 1. Clear coverage data. (Recursively delete *.gcda in build dir) +# 2. Run the unit tests. +# 3. Run this script specifying which source files the coverage should be performed on. +# +# This script will then use gcov to generate .gcov files in the directory specified +# via the COV_PATH var. This should probably be the same as your cmake build dir. +# +# It then parses the .gcov files to convert them into the Coveralls JSON format: +# https://coveralls.io/docs/api +# +# Example for running as standalone CMake script from the command line: +# (Note it is important the -P is at the end...) +# $ cmake -DCOV_PATH=$(pwd) +# -DCOVERAGE_SRCS="catcierge_rfid.c;catcierge_timer.c" +# -P ../cmake/CoverallsGcovUpload.cmake +# +CMAKE_MINIMUM_REQUIRED(VERSION 2.8) + + +# +# Make sure we have the needed arguments. +# +if (NOT COVERALLS_OUTPUT_FILE) + message(FATAL_ERROR "Coveralls: No coveralls output file specified. Please set COVERALLS_OUTPUT_FILE") +endif() + +if (NOT COV_PATH) + message(FATAL_ERROR "Coveralls: Missing coverage directory path where gcov files will be generated. Please set COV_PATH") +endif() + +if (NOT COVERAGE_SRCS) + message(FATAL_ERROR "Coveralls: Missing the list of source files that we should get the coverage data for COVERAGE_SRCS") +endif() + +if (NOT PROJECT_ROOT) + message(FATAL_ERROR "Coveralls: Missing PROJECT_ROOT.") +endif() + +# Since it's not possible to pass a CMake list properly in the +# "1;2;3" format to an external process, we have replaced the +# ";" with "*", so reverse that here so we get it back into the +# CMake list format. +string(REGEX REPLACE "\\*" ";" COVERAGE_SRCS ${COVERAGE_SRCS}) + +if (NOT DEFINED ENV{GCOV}) + find_program(GCOV_EXECUTABLE gcov) +else() + find_program(GCOV_EXECUTABLE $ENV{GCOV}) +endif() + +# convert all paths in COVERAGE_SRCS to absolute paths +set(COVERAGE_SRCS_TMP "") +foreach (COVERAGE_SRC ${COVERAGE_SRCS}) + if (NOT "${COVERAGE_SRC}" MATCHES "^/") + set(COVERAGE_SRC ${PROJECT_ROOT}/${COVERAGE_SRC}) + endif() + list(APPEND COVERAGE_SRCS_TMP ${COVERAGE_SRC}) +endforeach() +set(COVERAGE_SRCS ${COVERAGE_SRCS_TMP}) +unset(COVERAGE_SRCS_TMP) + +if (NOT GCOV_EXECUTABLE) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() + +find_package(Git) + +set(JSON_REPO_TEMPLATE + "{ + \"head\": { + \"id\": \"\@GIT_COMMIT_HASH\@\", + \"author_name\": \"\@GIT_AUTHOR_NAME\@\", + \"author_email\": \"\@GIT_AUTHOR_EMAIL\@\", + \"committer_name\": \"\@GIT_COMMITTER_NAME\@\", + \"committer_email\": \"\@GIT_COMMITTER_EMAIL\@\", + \"message\": \"\@GIT_COMMIT_MESSAGE\@\" + }, + \"branch\": \"@GIT_BRANCH@\", + \"remotes\": [] + }" +) + +# TODO: Fill in git remote data +if (GIT_FOUND) + # Branch. + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + macro (git_log_format FORMAT_CHARS VAR_NAME) + execute_process( + COMMAND ${GIT_EXECUTABLE} log -1 --pretty=format:%${FORMAT_CHARS} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE ${VAR_NAME} + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endmacro() + + git_log_format(an GIT_AUTHOR_NAME) + git_log_format(ae GIT_AUTHOR_EMAIL) + git_log_format(cn GIT_COMMITTER_NAME) + git_log_format(ce GIT_COMMITTER_EMAIL) + git_log_format(B GIT_COMMIT_MESSAGE) + git_log_format(H GIT_COMMIT_HASH) + + if(GIT_COMMIT_MESSAGE) + string(REPLACE "\n" "\\n" GIT_COMMIT_MESSAGE ${GIT_COMMIT_MESSAGE}) + endif() + + message("Git exe: ${GIT_EXECUTABLE}") + message("Git branch: ${GIT_BRANCH}") + message("Git author: ${GIT_AUTHOR_NAME}") + message("Git e-mail: ${GIT_AUTHOR_EMAIL}") + message("Git commiter name: ${GIT_COMMITTER_NAME}") + message("Git commiter e-mail: ${GIT_COMMITTER_EMAIL}") + message("Git commit hash: ${GIT_COMMIT_HASH}") + message("Git commit message: ${GIT_COMMIT_MESSAGE}") + + string(CONFIGURE ${JSON_REPO_TEMPLATE} JSON_REPO_DATA) +else() + set(JSON_REPO_DATA "{}") +endif() + +############################# Macros ######################################### + +# +# This macro converts from the full path format gcov outputs: +# +# /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov +# +# to the original source file path the .gcov is for: +# +# /path/to/project/root/subdir/the_file.c +# +macro(get_source_path_from_gcov_filename _SRC_FILENAME _GCOV_FILENAME) + + # /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov + # -> + # #path#to#project#root#subdir#the_file.c.gcov + get_filename_component(_GCOV_FILENAME_WEXT ${_GCOV_FILENAME} NAME) + + # #path#to#project#root#subdir#the_file.c.gcov -> /path/to/project/root/subdir/the_file.c + string(REGEX REPLACE "\\.gcov$" "" SRC_FILENAME_TMP ${_GCOV_FILENAME_WEXT}) + string(REGEX REPLACE "\#" "/" SRC_FILENAME_TMP ${SRC_FILENAME_TMP}) + set(${_SRC_FILENAME} "${SRC_FILENAME_TMP}") +endmacro() + +############################################################################## + +# Get the coverage data. +file(GLOB_RECURSE GCDA_FILES "${COV_PATH}/*.gcda") +message("GCDA files:") + +# Get a list of all the object directories needed by gcov +# (The directories the .gcda files and .o files are found in) +# and run gcov on those. +foreach(GCDA ${GCDA_FILES}) + message("Process: ${GCDA}") + message("------------------------------------------------------------------------------") + get_filename_component(GCDA_DIR ${GCDA} PATH) + + # + # The -p below refers to "Preserve path components", + # This means that the generated gcov filename of a source file will + # keep the original files entire filepath, but / is replaced with #. + # Example: + # + # /path/to/project/root/build/CMakeFiles/the_file.dir/subdir/the_file.c.gcda + # ------------------------------------------------------------------------------ + # File '/path/to/project/root/subdir/the_file.c' + # Lines executed:68.34% of 199 + # /path/to/project/root/subdir/the_file.c:creating '#path#to#project#root#subdir#the_file.c.gcov' + # + # If -p is not specified then the file is named only "the_file.c.gcov" + # + execute_process( + COMMAND ${GCOV_EXECUTABLE} -p -o ${GCDA_DIR} ${GCDA} + WORKING_DIRECTORY ${COV_PATH} + ) +endforeach() + +# TODO: Make these be absolute path +file(GLOB ALL_GCOV_FILES ${COV_PATH}/*.gcov) + +# Get only the filenames to use for filtering. +#set(COVERAGE_SRCS_NAMES "") +#foreach (COVSRC ${COVERAGE_SRCS}) +# get_filename_component(COVSRC_NAME ${COVSRC} NAME) +# message("${COVSRC} -> ${COVSRC_NAME}") +# list(APPEND COVERAGE_SRCS_NAMES "${COVSRC_NAME}") +#endforeach() + +# +# Filter out all but the gcov files we want. +# +# We do this by comparing the list of COVERAGE_SRCS filepaths that the +# user wants the coverage data for with the paths of the generated .gcov files, +# so that we only keep the relevant gcov files. +# +# Example: +# COVERAGE_SRCS = +# /path/to/project/root/subdir/the_file.c +# +# ALL_GCOV_FILES = +# /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov +# /path/to/project/root/build/#path#to#project#root#subdir#other_file.c.gcov +# +# Result should be: +# GCOV_FILES = +# /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov +# +set(GCOV_FILES "") +#message("Look in coverage sources: ${COVERAGE_SRCS}") +message("\nFilter out unwanted GCOV files:") +message("===============================") + +set(COVERAGE_SRCS_REMAINING ${COVERAGE_SRCS}) + +foreach (GCOV_FILE ${ALL_GCOV_FILES}) + + # + # /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov + # -> + # /path/to/project/root/subdir/the_file.c + get_source_path_from_gcov_filename(GCOV_SRC_PATH ${GCOV_FILE}) + file(RELATIVE_PATH GCOV_SRC_REL_PATH "${PROJECT_ROOT}" "${GCOV_SRC_PATH}") + + # Is this in the list of source files? + # TODO: We want to match against relative path filenames from the source file root... + list(FIND COVERAGE_SRCS ${GCOV_SRC_PATH} WAS_FOUND) + + if (NOT WAS_FOUND EQUAL -1) + message("YES: ${GCOV_FILE}") + list(APPEND GCOV_FILES ${GCOV_FILE}) + + # We remove it from the list, so we don't bother searching for it again. + # Also files left in COVERAGE_SRCS_REMAINING after this loop ends should + # have coverage data generated from them (no lines are covered). + list(REMOVE_ITEM COVERAGE_SRCS_REMAINING ${GCOV_SRC_PATH}) + else() + message("NO: ${GCOV_FILE}") + endif() +endforeach() + +# TODO: Enable setting these +set(JSON_SERVICE_NAME "travis-ci") +set(JSON_SERVICE_JOB_ID $ENV{TRAVIS_JOB_ID}) +set(JSON_REPO_TOKEN $ENV{COVERALLS_REPO_TOKEN}) + +set(JSON_TEMPLATE +"{ + \"repo_token\": \"\@JSON_REPO_TOKEN\@\", + \"service_name\": \"\@JSON_SERVICE_NAME\@\", + \"service_job_id\": \"\@JSON_SERVICE_JOB_ID\@\", + \"source_files\": \@JSON_GCOV_FILES\@, + \"git\": \@JSON_REPO_DATA\@ +}" +) + +set(SRC_FILE_TEMPLATE +"{ + \"name\": \"\@GCOV_SRC_REL_PATH\@\", + \"source_digest\": \"\@GCOV_CONTENTS_MD5\@\", + \"coverage\": \@GCOV_FILE_COVERAGE\@ + }" +) + +message("\nGenerate JSON for files:") +message("=========================") + +set(JSON_GCOV_FILES "[") + +# Read the GCOV files line by line and get the coverage data. +foreach (GCOV_FILE ${GCOV_FILES}) + + get_source_path_from_gcov_filename(GCOV_SRC_PATH ${GCOV_FILE}) + file(RELATIVE_PATH GCOV_SRC_REL_PATH "${PROJECT_ROOT}" "${GCOV_SRC_PATH}") + + # The new coveralls API doesn't need the entire source (Yay!) + # However, still keeping that part for now. Will cleanup in the future. + file(MD5 "${GCOV_SRC_PATH}" GCOV_CONTENTS_MD5) + message("MD5: ${GCOV_SRC_PATH} = ${GCOV_CONTENTS_MD5}") + + # Loads the gcov file as a list of lines. + # (We first open the file and replace all occurrences of [] with _ + # because CMake will fail to parse a line containing unmatched brackets... + # also the \ to escaped \n in macros screws up things.) + # https://public.kitware.com/Bug/view.php?id=15369 + file(READ ${GCOV_FILE} GCOV_CONTENTS) + string(REPLACE "[" "_" GCOV_CONTENTS "${GCOV_CONTENTS}") + string(REPLACE "]" "_" GCOV_CONTENTS "${GCOV_CONTENTS}") + string(REPLACE "\\" "_" GCOV_CONTENTS "${GCOV_CONTENTS}") + + # Remove file contents to avoid encoding issues (cmake 2.8 has no ENCODING option) + string(REGEX REPLACE "([^:]*):([^:]*):([^\n]*)\n" "\\1:\\2: \n" GCOV_CONTENTS "${GCOV_CONTENTS}") + file(WRITE ${GCOV_FILE}_tmp "${GCOV_CONTENTS}") + + file(STRINGS ${GCOV_FILE}_tmp GCOV_LINES) + list(LENGTH GCOV_LINES LINE_COUNT) + + # Instead of trying to parse the source from the + # gcov file, simply read the file contents from the source file. + # (Parsing it from the gcov is hard because C-code uses ; in many places + # which also happens to be the same as the CMake list delimiter). + file(READ ${GCOV_SRC_PATH} GCOV_FILE_SOURCE) + + string(REPLACE "\\" "\\\\" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + string(REGEX REPLACE "\"" "\\\\\"" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + string(REPLACE "\t" "\\\\t" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + string(REPLACE "\r" "\\\\r" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + string(REPLACE "\n" "\\\\n" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + # According to http://json.org/ these should be escaped as well. + # Don't know how to do that in CMake however... + #string(REPLACE "\b" "\\\\b" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + #string(REPLACE "\f" "\\\\f" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + #string(REGEX REPLACE "\u([a-fA-F0-9]{4})" "\\\\u\\1" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}") + + # We want a json array of coverage data as a single string + # start building them from the contents of the .gcov + set(GCOV_FILE_COVERAGE "[") + + set(GCOV_LINE_COUNT 1) # Line number for the .gcov. + set(DO_SKIP 0) + foreach (GCOV_LINE ${GCOV_LINES}) + #message("${GCOV_LINE}") + # Example of what we're parsing: + # Hitcount |Line | Source + # " 8: 26: if (!allowed || (strlen(allowed) == 0))" + string(REGEX REPLACE + "^([^:]*):([^:]*):(.*)$" + "\\1;\\2;\\3" + RES + "${GCOV_LINE}") + + # Check if we should exclude lines using the Lcov syntax. + string(REGEX MATCH "LCOV_EXCL_START" START_SKIP "${GCOV_LINE}") + string(REGEX MATCH "LCOV_EXCL_END" END_SKIP "${GCOV_LINE}") + string(REGEX MATCH "LCOV_EXCL_LINE" LINE_SKIP "${GCOV_LINE}") + + set(RESET_SKIP 0) + if (LINE_SKIP AND NOT DO_SKIP) + set(DO_SKIP 1) + set(RESET_SKIP 1) + endif() + + if (START_SKIP) + set(DO_SKIP 1) + message("${GCOV_LINE_COUNT}: Start skip") + endif() + + if (END_SKIP) + set(DO_SKIP 0) + endif() + + list(LENGTH RES RES_COUNT) + + if (RES_COUNT GREATER 2) + list(GET RES 0 HITCOUNT) + list(GET RES 1 LINE) + list(GET RES 2 SOURCE) + + string(STRIP ${HITCOUNT} HITCOUNT) + string(STRIP ${LINE} LINE) + + # Lines with 0 line numbers are metadata and can be ignored. + if (NOT ${LINE} EQUAL 0) + + if (DO_SKIP) + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}null, ") + else() + # Translate the hitcount into valid JSON values. + if (${HITCOUNT} STREQUAL "#####" OR ${HITCOUNT} STREQUAL "=====") + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}0, ") + elseif (${HITCOUNT} STREQUAL "-") + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}null, ") + else() + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}${HITCOUNT}, ") + endif() + endif() + endif() + else() + message(WARNING "Failed to properly parse line (RES_COUNT = ${RES_COUNT}) ${GCOV_FILE}:${GCOV_LINE_COUNT}\n-->${GCOV_LINE}") + endif() + + if (RESET_SKIP) + set(DO_SKIP 0) + endif() + math(EXPR GCOV_LINE_COUNT "${GCOV_LINE_COUNT}+1") + endforeach() + + message("${GCOV_LINE_COUNT} of ${LINE_COUNT} lines read!") + + # Advanced way of removing the trailing comma in the JSON array. + # "[1, 2, 3, " -> "[1, 2, 3" + string(REGEX REPLACE ",[ ]*$" "" GCOV_FILE_COVERAGE ${GCOV_FILE_COVERAGE}) + + # Append the trailing ] to complete the JSON array. + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}]") + + # Generate the final JSON for this file. + message("Generate JSON for file: ${GCOV_SRC_REL_PATH}...") + string(CONFIGURE ${SRC_FILE_TEMPLATE} FILE_JSON) + + set(JSON_GCOV_FILES "${JSON_GCOV_FILES}${FILE_JSON}, ") +endforeach() + +# Loop through all files we couldn't find any coverage for +# as well, and generate JSON for those as well with 0% coverage. +foreach(NOT_COVERED_SRC ${COVERAGE_SRCS_REMAINING}) + + # Set variables for json replacement + set(GCOV_SRC_PATH ${NOT_COVERED_SRC}) + file(MD5 "${GCOV_SRC_PATH}" GCOV_CONTENTS_MD5) + file(RELATIVE_PATH GCOV_SRC_REL_PATH "${PROJECT_ROOT}" "${GCOV_SRC_PATH}") + + # Loads the source file as a list of lines. + file(STRINGS ${NOT_COVERED_SRC} SRC_LINES) + + set(GCOV_FILE_COVERAGE "[") + set(GCOV_FILE_SOURCE "") + + foreach (SOURCE ${SRC_LINES}) + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}null, ") + + string(REPLACE "\\" "\\\\" SOURCE "${SOURCE}") + string(REGEX REPLACE "\"" "\\\\\"" SOURCE "${SOURCE}") + string(REPLACE "\t" "\\\\t" SOURCE "${SOURCE}") + string(REPLACE "\r" "\\\\r" SOURCE "${SOURCE}") + set(GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}${SOURCE}\\n") + endforeach() + + # Remove trailing comma, and complete JSON array with ] + string(REGEX REPLACE ",[ ]*$" "" GCOV_FILE_COVERAGE ${GCOV_FILE_COVERAGE}) + set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}]") + + # Generate the final JSON for this file. + message("Generate JSON for non-gcov file: ${NOT_COVERED_SRC}...") + string(CONFIGURE ${SRC_FILE_TEMPLATE} FILE_JSON) + set(JSON_GCOV_FILES "${JSON_GCOV_FILES}${FILE_JSON}, ") +endforeach() + +# Get rid of trailing comma. +string(REGEX REPLACE ",[ ]*$" "" JSON_GCOV_FILES ${JSON_GCOV_FILES}) +set(JSON_GCOV_FILES "${JSON_GCOV_FILES}]") + +# Generate the final complete JSON! +message("Generate final JSON...") +string(CONFIGURE ${JSON_TEMPLATE} JSON) + +file(WRITE "${COVERALLS_OUTPUT_FILE}" "${JSON}") +message("###########################################################################") +message("Generated coveralls JSON containing coverage data:") +message("${COVERALLS_OUTPUT_FILE}") +message("###########################################################################") diff --git a/thirdparty/assimp/cmake-modules/DebSourcePPA.cmake b/thirdparty/assimp/cmake-modules/DebSourcePPA.cmake new file mode 100644 index 0000000..d8a786f --- /dev/null +++ b/thirdparty/assimp/cmake-modules/DebSourcePPA.cmake @@ -0,0 +1,347 @@ +## Debian Source Package Generator +# +# Copyright (c) 2010 Daniel Pfeifer +# Many modifications by Rosen Diankov +# +# Creates source debian files and manages library dependencies +# +# Features: +# +# - Automatically generates symbols and run-time dependencies from the build dependencies +# - Custom copy of source directory via CPACK_DEBIAN_PACKAGE_SOURCE_COPY +# - Simultaneous output of multiple debian source packages for each distribution +# - Can specificy distribution-specific dependencies by suffixing DEPENDS with _${DISTRO_NAME}, for example: CPACK_DEBIAN_PACKAGE_DEPENDS_LUCID, CPACK_COMPONENT_MYCOMP0_DEPENDS_LUCID +# +# Usage: +# +# set(CPACK_DEBIAN_BUILD_DEPENDS debhelper cmake) +# set(CPACK_DEBIAN_PACKAGE_PRIORITY optional) +# set(CPACK_DEBIAN_PACKAGE_SECTION devel) +# set(CPACK_DEBIAN_CMAKE_OPTIONS "-DMYOPTION=myvalue") +# set(CPACK_DEBIAN_PACKAGE_DEPENDS mycomp0 mycomp1 some_ubuntu_package) +# set(CPACK_DEBIAN_PACKAGE_DEPENDS_UBUNTU_LUCID mycomp0 mycomp1 lucid_specific_package) +# set(CPACK_DEBIAN_PACKAGE_NAME mypackage) +# set(CPACK_DEBIAN_PACKAGE_REMOVE_SOURCE_FILES unnecessary_file unnecessary_dir/file0) +# set(CPACK_DEBIAN_PACKAGE_SOURCE_COPY svn export --force) # if using subversion +# set(CPACK_DEBIAN_DISTRIBUTION_NAME ubuntu) +# set(CPACK_DEBIAN_DISTRIBUTION_RELEASES karmic lucid maverick natty) +# set(CPACK_DEBIAN_CHANGELOG " * Extra change log lines") +# set(CPACK_DEBIAN_PACKAGE_SUGGESTS "ipython") +# set(CPACK_COMPONENT_X_RECOMMENDS "recommended-package") +## + +find_program(DEBUILD_EXECUTABLE debuild) +find_program(DPUT_EXECUTABLE dput) + +if(NOT DEBUILD_EXECUTABLE OR NOT DPUT_EXECUTABLE) + return() +endif(NOT DEBUILD_EXECUTABLE OR NOT DPUT_EXECUTABLE) + +# DEBIAN/control +# debian policy enforce lower case for package name +# Package: (mandatory) +IF(NOT CPACK_DEBIAN_PACKAGE_NAME) + STRING(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_DEBIAN_PACKAGE_NAME) +ENDIF(NOT CPACK_DEBIAN_PACKAGE_NAME) + +# Section: (recommended) +IF(NOT CPACK_DEBIAN_PACKAGE_SECTION) + SET(CPACK_DEBIAN_PACKAGE_SECTION "devel") +ENDIF(NOT CPACK_DEBIAN_PACKAGE_SECTION) + +# Priority: (recommended) +IF(NOT CPACK_DEBIAN_PACKAGE_PRIORITY) + SET(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") +ENDIF(NOT CPACK_DEBIAN_PACKAGE_PRIORITY) + +file(STRINGS ${CPACK_PACKAGE_DESCRIPTION_FILE} DESC_LINES) +foreach(LINE ${DESC_LINES}) + set(DEB_LONG_DESCRIPTION "${DEB_LONG_DESCRIPTION} ${LINE}\n") +endforeach(LINE ${DESC_LINES}) + +file(REMOVE_RECURSE "${CMAKE_BINARY_DIR}/Debian") +file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/Debian") +set(DEBIAN_SOURCE_ORIG_DIR "${CMAKE_BINARY_DIR}/Debian/${CPACK_DEBIAN_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") + +if( CPACK_DEBIAN_PACKAGE_SOURCE_COPY ) + execute_process(COMMAND ${CPACK_DEBIAN_PACKAGE_SOURCE_COPY} "${CMAKE_SOURCE_DIR}" "${DEBIAN_SOURCE_ORIG_DIR}.orig") +else() + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR} "${DEBIAN_SOURCE_ORIG_DIR}.orig") + execute_process(COMMAND ${CMAKE_COMMAND} -E remove_directory "${DEBIAN_SOURCE_ORIG_DIR}.orig/.git") + execute_process(COMMAND ${CMAKE_COMMAND} -E remove_directory "${DEBIAN_SOURCE_ORIG_DIR}.orig/.svn") +endif() + +# remove unnecessary folders +foreach(REMOVE_DIR ${CPACK_DEBIAN_PACKAGE_REMOVE_SOURCE_FILES}) + file(REMOVE_RECURSE ${DEBIAN_SOURCE_ORIG_DIR}.orig/${REMOVE_DIR}) +endforeach() + +# create the original source tar +execute_process(COMMAND ${CMAKE_COMMAND} -E tar czf "${CPACK_DEBIAN_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}.orig.tar.gz" "${CPACK_DEBIAN_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}.orig" WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/Debian) + +set(DEB_SOURCE_CHANGES) +foreach(RELEASE ${CPACK_DEBIAN_DISTRIBUTION_RELEASES}) + set(DEBIAN_SOURCE_DIR "${DEBIAN_SOURCE_ORIG_DIR}-${CPACK_DEBIAN_DISTRIBUTION_NAME}1~${RELEASE}1") + set(RELEASE_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}-${CPACK_DEBIAN_DISTRIBUTION_NAME}1~${RELEASE}1") + string(TOUPPER ${RELEASE} RELEASE_UPPER) + string(TOUPPER ${CPACK_DEBIAN_DISTRIBUTION_NAME} DISTRIBUTION_NAME_UPPER) + file(MAKE_DIRECTORY ${DEBIAN_SOURCE_DIR}/debian) + ############################################################################## + # debian/control + set(DEBIAN_CONTROL ${DEBIAN_SOURCE_DIR}/debian/control) + file(WRITE ${DEBIAN_CONTROL} + "Source: ${CPACK_DEBIAN_PACKAGE_NAME}\n" + "Section: ${CPACK_DEBIAN_PACKAGE_SECTION}\n" + "Priority: ${CPACK_DEBIAN_PACKAGE_PRIORITY}\n" + "DM-Upload-Allowed: yes\n" + "Maintainer: ${CPACK_PACKAGE_CONTACT}\n" + "Build-Depends: " + ) + + if( CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + foreach(DEP ${CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + file(APPEND ${DEBIAN_CONTROL} "${DEP}, ") + endforeach(DEP ${CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + else( CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + if( CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}}) + file(APPEND ${DEBIAN_CONTROL} "${DEP}, ") + endforeach(DEP ${CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}}) + else( CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_DEBIAN_BUILD_DEPENDS}) + file(APPEND ${DEBIAN_CONTROL} "${DEP}, ") + endforeach(DEP ${CPACK_DEBIAN_BUILD_DEPENDS}) + endif( CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER} ) + endif( CPACK_DEBIAN_BUILD_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + + + file(APPEND ${DEBIAN_CONTROL} "\n" + "Standards-Version: 3.8.4\n" + "Homepage: ${CPACK_PACKAGE_VENDOR}\n" + "\n" + "Package: ${CPACK_DEBIAN_PACKAGE_NAME}\n" + "Architecture: any\n" + "Depends: " + ) + + if( CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + foreach(DEP ${CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + file(APPEND ${DEBIAN_CONTROL} "${DEP}, ") + endforeach(DEP ${CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + else( CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + if( CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}}) + file(APPEND ${DEBIAN_CONTROL} "${DEP}, ") + endforeach(DEP ${CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}}) + else( CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_DEBIAN_PACKAGE_DEPENDS}) + file(APPEND ${DEBIAN_CONTROL} "${DEP}, ") + endforeach(DEP ${CPACK_DEBIAN_PACKAGE_DEPENDS}) + endif( CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER} ) + endif( CPACK_DEBIAN_PACKAGE_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + + file(APPEND ${DEBIAN_CONTROL} "\nRecommends: ") + if( CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + foreach(DEP ${CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + file(APPEND ${DEBIAN_CONTROL} "${DEP}, ") + endforeach(DEP ${CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + else( CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + if( CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}}) + file(APPEND ${DEBIAN_CONTROL} "${DEP}, ") + endforeach(DEP ${CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}}) + else( CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_DEBIAN_PACKAGE_RECOMMENDS}) + file(APPEND ${DEBIAN_CONTROL} "${DEP}, ") + endforeach(DEP ${CPACK_DEBIAN_PACKAGE_RECOMMENDS}) + endif( CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER} ) + endif( CPACK_DEBIAN_PACKAGE_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + + file(APPEND ${DEBIAN_CONTROL} "\nSuggests: ") + if( CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + foreach(DEP ${CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + file(APPEND ${DEBIAN_CONTROL} "${DEP}, ") + endforeach(DEP ${CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + else( CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + if( CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}}) + file(APPEND ${DEBIAN_CONTROL} "${DEP}, ") + endforeach(DEP ${CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}}) + else( CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_DEBIAN_PACKAGE_SUGGESTS}) + file(APPEND ${DEBIAN_CONTROL} "${DEP}, ") + endforeach(DEP ${CPACK_DEBIAN_PACKAGE_SUGGESTS}) + endif( CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER} ) + endif( CPACK_DEBIAN_PACKAGE_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + + file(APPEND ${DEBIAN_CONTROL} "\n" + "Description: ${CPACK_PACKAGE_DISPLAY_NAME} ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}\n" + "${DEB_LONG_DESCRIPTION}" + ) + + foreach(COMPONENT ${CPACK_COMPONENTS_ALL}) + string(TOUPPER ${COMPONENT} UPPER_COMPONENT) + set(DEPENDS "\${shlibs:Depends}") + if( CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + set(DEPENDS "${DEPENDS}, ${DEP}") + endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + else( CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + if( CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}}) + set(DEPENDS "${DEPENDS}, ${DEP}") + endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}}) + else( CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS}) + set(DEPENDS "${DEPENDS}, ${DEP}") + endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS}) + endif( CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER} ) + endif( CPACK_COMPONENT_${UPPER_COMPONENT}_DEPENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + + set(RECOMMENDS) + if( CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + set(RECOMMENDS "${RECOMMENDS} ${DEP}, ") + endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + else( CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + if( CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}}) + set(RECOMMENDS "${RECOMMENDS} ${DEP}, ") + endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}}) + else( CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS}) + set(RECOMMENDS "${RECOMMENDS} ${DEP}, ") + endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS}) + endif( CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER} ) + endif( CPACK_COMPONENT_${UPPER_COMPONENT}_RECOMMENDS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + + set(SUGGESTS) + if( CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + set(SUGGESTS "${SUGGESTS} ${DEP}, ") + endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER}}) + else( CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + if( CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}}) + set(SUGGESTS "${SUGGESTS} ${DEP}, ") + endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}}) + else( CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER} ) + foreach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS}) + set(SUGGESTS "${SUGGESTS} ${DEP}, ") + endforeach(DEP ${CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS}) + endif( CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER} ) + endif( CPACK_COMPONENT_${UPPER_COMPONENT}_SUGGESTS_${DISTRIBUTION_NAME_UPPER}_${RELEASE_UPPER} ) + + file(APPEND ${DEBIAN_CONTROL} "\n" + "Package: ${COMPONENT}\n" + "Architecture: any\n" + "Depends: ${DEPENDS}\n" + "Recommends: ${RECOMMENDS}\n" + "Suggests: ${SUGGESTS}\n" + "Description: ${CPACK_PACKAGE_DISPLAY_NAME} ${CPACK_COMPONENT_${UPPER_COMPONENT}_DISPLAY_NAME}\n" + "${DEB_LONG_DESCRIPTION}" + " .\n" + " ${CPACK_COMPONENT_${UPPER_COMPONENT}_DESCRIPTION}\n" + ) + endforeach(COMPONENT ${CPACK_COMPONENTS_ALL}) + + ############################################################################## + # debian/copyright + set(DEBIAN_COPYRIGHT ${DEBIAN_SOURCE_DIR}/debian/copyright) + execute_process(COMMAND ${CMAKE_COMMAND} -E + copy ${CPACK_RESOURCE_FILE_LICENSE} ${DEBIAN_COPYRIGHT} + ) + + ############################################################################## + # debian/rules + set(DEBIAN_RULES ${DEBIAN_SOURCE_DIR}/debian/rules) + file(WRITE ${DEBIAN_RULES} + "#!/usr/bin/make -f\n" + "\n" + "BUILDDIR = build_dir\n" + "\n" + "build:\n" + " mkdir $(BUILDDIR)\n" + " cd $(BUILDDIR); cmake -DCMAKE_BUILD_TYPE=Release ${CPACK_DEBIAN_CMAKE_OPTIONS} -DCMAKE_INSTALL_PREFIX=/usr ..\n" + " $(MAKE) -C $(BUILDDIR) preinstall\n" + " touch build\n" + "\n" + "binary: binary-indep binary-arch\n" + "\n" + "binary-indep: build\n" + "\n" + "binary-arch: build\n" + " cd $(BUILDDIR); cmake -DCOMPONENT=Unspecified -DCMAKE_INSTALL_PREFIX=../debian/tmp/usr -P cmake_install.cmake\n" + " mkdir -p debian/tmp/DEBIAN\n" + " dpkg-gensymbols -p${CPACK_DEBIAN_PACKAGE_NAME}\n" + ) + + foreach(COMPONENT ${CPACK_COMPONENTS_ALL}) + set(PATH debian/${COMPONENT}) + file(APPEND ${DEBIAN_RULES} + " cd $(BUILDDIR); cmake -DCOMPONENT=${COMPONENT} -DCMAKE_INSTALL_PREFIX=../${PATH}/usr -P cmake_install.cmake\n" + " mkdir -p ${PATH}/DEBIAN\n" + " dpkg-gensymbols -p${COMPONENT} -P${PATH}\n" + ) + endforeach(COMPONENT ${CPACK_COMPONENTS_ALL}) + + file(APPEND ${DEBIAN_RULES} + " dh_shlibdeps\n" + " dh_strip\n" # for reducing size + " dpkg-gencontrol -p${CPACK_DEBIAN_PACKAGE_NAME}\n" + " dpkg --build debian/tmp ..\n" + ) + + foreach(COMPONENT ${CPACK_COMPONENTS_ALL}) + set(PATH debian/${COMPONENT}) + file(APPEND ${DEBIAN_RULES} + " dpkg-gencontrol -p${COMPONENT} -P${PATH} -Tdebian/${COMPONENT}.substvars\n" + " dpkg --build ${PATH} ..\n" + ) + endforeach(COMPONENT ${CPACK_COMPONENTS_ALL}) + + file(APPEND ${DEBIAN_RULES} + "\n" + "clean:\n" + " rm -f build\n" + " rm -rf $(BUILDDIR)\n" + "\n" + ".PHONY: binary binary-arch binary-indep clean\n" + ) + + execute_process(COMMAND chmod +x ${DEBIAN_RULES}) + + ############################################################################## + # debian/compat + file(WRITE ${DEBIAN_SOURCE_DIR}/debian/compat "7") + + ############################################################################## + # debian/source/format + file(WRITE ${DEBIAN_SOURCE_DIR}/debian/source/format "3.0 (quilt)") + + ############################################################################## + # debian/changelog + set(DEBIAN_CHANGELOG ${DEBIAN_SOURCE_DIR}/debian/changelog) + execute_process(COMMAND date -R OUTPUT_VARIABLE DATE_TIME) + file(WRITE ${DEBIAN_CHANGELOG} + "${CPACK_DEBIAN_PACKAGE_NAME} (${RELEASE_PACKAGE_VERSION}) ${RELEASE}; urgency=medium\n\n" + " * Package built with CMake\n\n" + "${CPACK_DEBIAN_CHANGELOG}" + " -- ${CPACK_PACKAGE_CONTACT} ${DATE_TIME}" + ) + + ############################################################################## + # debuild -S + if( DEB_SOURCE_CHANGES ) + set(DEBUILD_OPTIONS "-sd") + else() + set(DEBUILD_OPTIONS "-sa") + endif() + set(SOURCE_CHANGES_FILE "${CPACK_DEBIAN_PACKAGE_NAME}_${RELEASE_PACKAGE_VERSION}_source.changes") + set(DEB_SOURCE_CHANGES ${DEB_SOURCE_CHANGES} "${SOURCE_CHANGES_FILE}") + add_custom_command(OUTPUT "${SOURCE_CHANGES_FILE}" COMMAND ${DEBUILD_EXECUTABLE} -S ${DEBUILD_OPTIONS} WORKING_DIRECTORY ${DEBIAN_SOURCE_DIR}) +endforeach(RELEASE ${CPACK_DEBIAN_DISTRIBUTION_RELEASES}) + +############################################################################## +# dput ppa:your-lp-id/ppa +add_custom_target(dput ${DPUT_EXECUTABLE} ${DPUT_HOST} ${DEB_SOURCE_CHANGES} DEPENDS ${DEB_SOURCE_CHANGES} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/Debian) diff --git a/thirdparty/assimp/cmake-modules/FindDevIL.cmake b/thirdparty/assimp/cmake-modules/FindDevIL.cmake new file mode 100644 index 0000000..381a75d --- /dev/null +++ b/thirdparty/assimp/cmake-modules/FindDevIL.cmake @@ -0,0 +1,72 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#.rst: +# FindDevIL +# --------- +# +# +# +# This module locates the developer's image library. +# http://openil.sourceforge.net/ +# +# This module sets: +# +# :: +# +# IL_LIBRARIES - the name of the IL library. These include the full path to +# the core DevIL library. This one has to be linked into the +# application. +# ILU_LIBRARIES - the name of the ILU library. Again, the full path. This +# library is for filters and effects, not actual loading. It +# doesn't have to be linked if the functionality it provides +# is not used. +# ILUT_LIBRARIES - the name of the ILUT library. Full path. This part of the +# library interfaces with OpenGL. It is not strictly needed +# in applications. +# IL_INCLUDE_DIR - where to find the il.h, ilu.h and ilut.h files. +# IL_FOUND - this is set to TRUE if all the above variables were set. +# This will be set to false if ILU or ILUT are not found, +# even if they are not needed. In most systems, if one +# library is found all the others are as well. That's the +# way the DevIL developers release it. + +# TODO: Add version support. +# Tested under Linux and Windows (MSVC) + +#include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) +include(FindPackageHandleStandardArgs) + +find_path(IL_INCLUDE_DIR il.h + PATH_SUFFIXES include IL + DOC "The path to the directory that contains il.h" +) + +#message("IL_INCLUDE_DIR is ${IL_INCLUDE_DIR}") + +find_library(IL_LIBRARIES + NAMES IL DEVIL + PATH_SUFFIXES lib64 lib lib32 + DOC "The file that corresponds to the base il library." +) + +#message("IL_LIBRARIES is ${IL_LIBRARIES}") + +find_library(ILUT_LIBRARIES + NAMES ILUT + PATH_SUFFIXES lib64 lib lib32 + DOC "The file that corresponds to the il (system?) utility library." +) + +#message("ILUT_LIBRARIES is ${ILUT_LIBRARIES}") + +find_library(ILU_LIBRARIES + NAMES ILU + PATH_SUFFIXES lib64 lib lib32 + DOC "The file that corresponds to the il utility library." +) + +#message("ILU_LIBRARIES is ${ILU_LIBRARIES}") + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(IL DEFAULT_MSG + IL_LIBRARIES IL_INCLUDE_DIR) diff --git a/thirdparty/assimp/cmake-modules/FindDirectX.cmake b/thirdparty/assimp/cmake-modules/FindDirectX.cmake new file mode 100644 index 0000000..7070431 --- /dev/null +++ b/thirdparty/assimp/cmake-modules/FindDirectX.cmake @@ -0,0 +1,101 @@ +#------------------------------------------------------------------- +# This file is part of the CMake build system for OGRE +# (Object-oriented Graphics Rendering Engine) +# For the latest info, see http://www.ogre3d.org/ +# +# The contents of this file are placed in the public domain. Feel +# free to make use of it in any way you like. +#------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# Find DirectX SDK +# Define: +# DirectX_FOUND +# DirectX_INCLUDE_DIR +# DirectX_LIBRARY +# DirectX_ROOT_DIR + +if(WIN32) # The only platform it makes sense to check for DirectX SDK + include(FindPkgMacros) + findpkg_begin(DirectX) + + # Get path, convert backslashes as ${ENV_DXSDK_DIR} + getenv_path(DXSDK_DIR) + getenv_path(DIRECTX_HOME) + getenv_path(DIRECTX_ROOT) + getenv_path(DIRECTX_BASE) + + # construct search paths + set(DirectX_PREFIX_PATH + "${DXSDK_DIR}" "${ENV_DXSDK_DIR}" + "${DIRECTX_HOME}" "${ENV_DIRECTX_HOME}" + "${DIRECTX_ROOT}" "${ENV_DIRECTX_ROOT}" + "${DIRECTX_BASE}" "${ENV_DIRECTX_BASE}" + "C:/apps_x86/Microsoft DirectX SDK*" + "C:/Program Files (x86)/Microsoft DirectX SDK*" + "C:/apps/Microsoft DirectX SDK*" + "C:/Program Files/Microsoft DirectX SDK*" + "C:/Program Files (x86)/Windows Kits/8.1" + "$ENV{ProgramFiles}/Microsoft DirectX SDK*" + ) + create_search_paths(DirectX) + # redo search if prefix path changed + clear_if_changed(DirectX_PREFIX_PATH + DirectX_LIBRARY + DirectX_INCLUDE_DIR + ) + + find_path(DirectX_INCLUDE_DIR NAMES d3d9.h HINTS ${DirectX_INC_SEARCH_PATH}) + # dlls are in DirectX_ROOT_DIR/Developer Runtime/x64|x86 + # lib files are in DirectX_ROOT_DIR/Lib/x64|x86 + if(CMAKE_CL_64) + set(DirectX_LIBPATH_SUFFIX "x64") + else(CMAKE_CL_64) + set(DirectX_LIBPATH_SUFFIX "x86") + endif(CMAKE_CL_64) + find_library(DirectX_LIBRARY NAMES d3d9 HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + find_library(DirectX_D3DX9_LIBRARY NAMES d3dx9 HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + find_library(DirectX_DXERR_LIBRARY NAMES DxErr HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + find_library(DirectX_DXGUID_LIBRARY NAMES dxguid HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + + + # look for dxgi (needed by both 10 and 11) + find_library(DirectX_DXGI_LIBRARY NAMES dxgi HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + + # look for d3dcompiler (needed by 11) + find_library(DirectX_D3DCOMPILER_LIBRARY NAMES d3dcompiler HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + + findpkg_finish(DirectX) + set(DirectX_LIBRARIES ${DirectX_LIBRARIES} + ${DirectX_D3DX9_LIBRARY} + ${DirectX_DXERR_LIBRARY} + ${DirectX_DXGUID_LIBRARY} + ) + + mark_as_advanced(DirectX_D3DX9_LIBRARY DirectX_DXERR_LIBRARY DirectX_DXGUID_LIBRARY + DirectX_DXGI_LIBRARY DirectX_D3DCOMPILER_LIBRARY) + + + # look for D3D11 components + if (DirectX_FOUND) + find_path(DirectX_D3D11_INCLUDE_DIR NAMES D3D11Shader.h HINTS ${DirectX_INC_SEARCH_PATH}) + get_filename_component(DirectX_LIBRARY_DIR "${DirectX_LIBRARY}" PATH) + message(STATUS "DX lib dir: ${DirectX_LIBRARY_DIR}") + find_library(DirectX_D3D11_LIBRARY NAMES d3d11 HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + find_library(DirectX_D3DX11_LIBRARY NAMES d3dx11 HINTS ${DirectX_LIB_SEARCH_PATH} PATH_SUFFIXES ${DirectX_LIBPATH_SUFFIX}) + if (DirectX_D3D11_INCLUDE_DIR AND DirectX_D3D11_LIBRARY) + set(DirectX_D3D11_FOUND TRUE) + set(DirectX_D3D11_INCLUDE_DIR ${DirectX_D3D11_INCLUDE_DIR}) + set(DirectX_D3D11_LIBRARIES ${DirectX_D3D11_LIBRARIES} + ${DirectX_D3D11_LIBRARY} + ${DirectX_D3DX11_LIBRARY} + ${DirectX_DXGI_LIBRARY} + ${DirectX_DXERR_LIBRARY} + ${DirectX_DXGUID_LIBRARY} + ${DirectX_D3DCOMPILER_LIBRARY} + ) + endif () + mark_as_advanced(DirectX_D3D11_INCLUDE_DIR DirectX_D3D11_LIBRARY DirectX_D3DX11_LIBRARY) + endif () + +endif(WIN32) diff --git a/thirdparty/assimp/cmake-modules/FindIrrXML.cmake b/thirdparty/assimp/cmake-modules/FindIrrXML.cmake new file mode 100644 index 0000000..5434e0b --- /dev/null +++ b/thirdparty/assimp/cmake-modules/FindIrrXML.cmake @@ -0,0 +1,17 @@ +# Find IrrXMl from irrlicht project +# +# Find LibIrrXML headers and library +# +# IRRXML_FOUND - IrrXML found +# IRRXML_INCLUDE_DIR - Headers location +# IRRXML_LIBRARY - IrrXML main library + +find_path(IRRXML_INCLUDE_DIR irrXML.h + PATH_SUFFIXES include/irrlicht include/irrxml) +find_library(IRRXML_LIBRARY IrrXML) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(IrrXML REQUIRED_VARS IRRXML_INCLUDE_DIR IRRXML_LIBRARY) + + +mark_as_advanced(IRRXML_INCLUDE_DIR IRRXML_LIBRARY) diff --git a/thirdparty/assimp/cmake-modules/FindPkgMacros.cmake b/thirdparty/assimp/cmake-modules/FindPkgMacros.cmake new file mode 100644 index 0000000..074cce3 --- /dev/null +++ b/thirdparty/assimp/cmake-modules/FindPkgMacros.cmake @@ -0,0 +1,146 @@ +#------------------------------------------------------------------- +# This file is part of the CMake build system for OGRE +# (Object-oriented Graphics Rendering Engine) +# For the latest info, see http://www.ogre3d.org/ +# +# The contents of this file are placed in the public domain. Feel +# free to make use of it in any way you like. +#------------------------------------------------------------------- + +################################################################## +# Provides some common functionality for the FindPackage modules +################################################################## + +# Begin processing of package +macro(findpkg_begin PREFIX) + if (NOT ${PREFIX}_FIND_QUIETLY) + message(STATUS "Looking for ${PREFIX}...") + endif () +endmacro(findpkg_begin) + +# Display a status message unless FIND_QUIETLY is set +macro(pkg_message PREFIX) + if (NOT ${PREFIX}_FIND_QUIETLY) + message(STATUS ${ARGN}) + endif () +endmacro(pkg_message) + +# Get environment variable, define it as ENV_$var and make sure backslashes are converted to forward slashes +macro(getenv_path VAR) + set(ENV_${VAR} $ENV{${VAR}}) + # replace won't work if var is blank + if (ENV_${VAR}) + string( REGEX REPLACE "\\\\" "/" ENV_${VAR} ${ENV_${VAR}} ) + endif () +endmacro(getenv_path) + +# Construct search paths for includes and libraries from a PREFIX_PATH +macro(create_search_paths PREFIX) + foreach(dir ${${PREFIX}_PREFIX_PATH}) + set(${PREFIX}_INC_SEARCH_PATH ${${PREFIX}_INC_SEARCH_PATH} + ${dir}/include ${dir}/include/${PREFIX} ${dir}/Headers) + set(${PREFIX}_LIB_SEARCH_PATH ${${PREFIX}_LIB_SEARCH_PATH} + ${dir}/lib ${dir}/lib/${PREFIX} ${dir}/Libs) + endforeach(dir) + set(${PREFIX}_FRAMEWORK_SEARCH_PATH ${${PREFIX}_PREFIX_PATH}) +endmacro(create_search_paths) + +# clear cache variables if a certain variable changed +macro(clear_if_changed TESTVAR) + # test against internal check variable + if (NOT "${${TESTVAR}}" STREQUAL "${${TESTVAR}_INT_CHECK}") + message(STATUS "${TESTVAR} changed.") + foreach(var ${ARGN}) + set(${var} "NOTFOUND" CACHE STRING "x" FORCE) + endforeach(var) + endif () + set(${TESTVAR}_INT_CHECK ${${TESTVAR}} CACHE INTERNAL "x" FORCE) +endmacro(clear_if_changed) + +# Try to get some hints from pkg-config, if available +macro(use_pkgconfig PREFIX PKGNAME) + # Android does not support PKG_CONFIG so we disable it + IF ( NOT ANDROID ) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(${PREFIX} ${PKGNAME}) + endif () + ENDIF ( NOT ANDROID ) +endmacro (use_pkgconfig) + +# Couple a set of release AND debug libraries (or frameworks) +macro(make_library_set PREFIX) + if (${PREFIX}_FWK) + set(${PREFIX} ${${PREFIX}_FWK}) + elseif (${PREFIX}_REL AND ${PREFIX}_DBG) + set(${PREFIX} optimized ${${PREFIX}_REL} debug ${${PREFIX}_DBG}) + elseif (${PREFIX}_REL) + set(${PREFIX} ${${PREFIX}_REL}) + elseif (${PREFIX}_DBG) + set(${PREFIX} ${${PREFIX}_DBG}) + endif () +endmacro(make_library_set) + +# Generate debug names from given release names +macro(get_debug_names PREFIX) + foreach(i ${${PREFIX}}) + set(${PREFIX}_DBG ${${PREFIX}_DBG} ${i}d ${i}D ${i}_d ${i}_D ${i}_debug ${i}) + endforeach(i) +endmacro(get_debug_names) + +# Add the parent dir from DIR to VAR +macro(add_parent_dir VAR DIR) + get_filename_component(${DIR}_TEMP "${${DIR}}/.." ABSOLUTE) + set(${VAR} ${${VAR}} ${${DIR}_TEMP}) +endmacro(add_parent_dir) + +# Do the final processing for the package find. +macro(findpkg_finish PREFIX) + # skip if already processed during this run + if (NOT ${PREFIX}_FOUND) + if (${PREFIX}_INCLUDE_DIR AND ${PREFIX}_LIBRARY) + set(${PREFIX}_FOUND TRUE) + set(${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIR}) + set(${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARY}) + if (NOT ${PREFIX}_FIND_QUIETLY) + message(STATUS "Found ${PREFIX}: ${${PREFIX}_LIBRARIES}") + endif () + else () + if (NOT ${PREFIX}_FIND_QUIETLY) + message(STATUS "Could not locate ${PREFIX}") + endif () + if (${PREFIX}_FIND_REQUIRED) + message(FATAL_ERROR "Required library ${PREFIX} not found! Install the library (including dev packages) and try again. If the library is already installed, set the missing variables manually in cmake.") + endif () + endif () + + mark_as_advanced(${PREFIX}_INCLUDE_DIR ${PREFIX}_LIBRARY ${PREFIX}_LIBRARY_REL ${PREFIX}_LIBRARY_DBG ${PREFIX}_LIBRARY_FWK) + endif () +endmacro(findpkg_finish) + + +# Slightly customised framework finder +MACRO(findpkg_framework fwk) + IF(APPLE) + SET(${fwk}_FRAMEWORK_PATH + ${${fwk}_FRAMEWORK_SEARCH_PATH} + ${CMAKE_FRAMEWORK_PATH} + ~/Library/Frameworks + /Library/Frameworks + /System/Library/Frameworks + /Network/Library/Frameworks + /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk/System/Library/Frameworks/ + /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk/System/Library/Frameworks/ + ) + FOREACH(dir ${${fwk}_FRAMEWORK_PATH}) + SET(fwkpath ${dir}/${fwk}.framework) + IF(EXISTS ${fwkpath}) + SET(${fwk}_FRAMEWORK_INCLUDES ${${fwk}_FRAMEWORK_INCLUDES} + ${fwkpath}/Headers ${fwkpath}/PrivateHeaders) + if (NOT ${fwk}_LIBRARY_FWK) + SET(${fwk}_LIBRARY_FWK "-framework ${fwk}") + endif () + ENDIF(EXISTS ${fwkpath}) + ENDFOREACH(dir) + ENDIF(APPLE) +ENDMACRO(findpkg_framework) diff --git a/thirdparty/assimp/cmake-modules/FindRT.cmake b/thirdparty/assimp/cmake-modules/FindRT.cmake new file mode 100644 index 0000000..17d5df8 --- /dev/null +++ b/thirdparty/assimp/cmake-modules/FindRT.cmake @@ -0,0 +1,20 @@ +# Try to find real time libraries +# Once done, this will define +# +# RT_FOUND - system has rt library +# RT_LIBRARIES - rt libraries directory +# +# Source: https://gitlab.cern.ch/dss/eos/commit/44070e575faaa46bd998708ef03eedb381506ff0 +# + +if(RT_LIBRARIES) + set(RT_FIND_QUIETLY TRUE) +endif(RT_LIBRARIES) + +find_library(RT_LIBRARY rt) +set(RT_LIBRARIES ${RT_LIBRARY}) +# handle the QUIETLY and REQUIRED arguments and set +# RT_FOUND to TRUE if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(rt DEFAULT_MSG RT_LIBRARY) +mark_as_advanced(RT_LIBRARY) diff --git a/thirdparty/assimp/cmake-modules/FindZLIB.cmake b/thirdparty/assimp/cmake-modules/FindZLIB.cmake new file mode 100644 index 0000000..95eb515 --- /dev/null +++ b/thirdparty/assimp/cmake-modules/FindZLIB.cmake @@ -0,0 +1,48 @@ +#------------------------------------------------------------------- +# This file is part of the CMake build system for OGRE +# (Object-oriented Graphics Rendering Engine) +# For the latest info, see http://www.ogre3d.org/ +# +# The contents of this file are placed in the public domain. Feel +# free to make use of it in any way you like. +#------------------------------------------------------------------- + +# - Try to find ZLIB +# Once done, this will define +# +# ZLIB_FOUND - system has ZLIB +# ZLIB_INCLUDE_DIRS - the ZLIB include directories +# ZLIB_LIBRARIES - link these to use ZLIB + +include(FindPkgMacros) +findpkg_begin(ZLIB) + +# Get path, convert backslashes as ${ENV_${var}} +getenv_path(ZLIB_HOME) + +# construct search paths +set(ZLIB_PREFIX_PATH ${ZLIB_HOME} ${ENV_ZLIB_HOME}) +create_search_paths(ZLIB) +# redo search if prefix path changed +clear_if_changed(ZLIB_PREFIX_PATH + ZLIB_LIBRARY_FWK + ZLIB_LIBRARY_REL + ZLIB_LIBRARY_DBG + ZLIB_INCLUDE_DIR +) + +set(ZLIB_LIBRARY_NAMES z zlib zdll) +get_debug_names(ZLIB_LIBRARY_NAMES) + +use_pkgconfig(ZLIB_PKGC zzip-zlib-config) + +findpkg_framework(ZLIB) + +find_path(ZLIB_INCLUDE_DIR NAMES zlib.h HINTS ${ZLIB_INC_SEARCH_PATH} ${ZLIB_PKGC_INCLUDE_DIRS}) +find_library(ZLIB_LIBRARY_REL NAMES ${ZLIB_LIBRARY_NAMES} HINTS ${ZLIB_LIB_SEARCH_PATH} ${ZLIB_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" release relwithdebinfo minsizerel) +find_library(ZLIB_LIBRARY_DBG NAMES ${ZLIB_LIBRARY_NAMES_DBG} HINTS ${ZLIB_LIB_SEARCH_PATH} ${ZLIB_PKGC_LIBRARY_DIRS} PATH_SUFFIXES "" debug) + +make_library_set(ZLIB_LIBRARY) + +findpkg_finish(ZLIB) + diff --git a/thirdparty/assimp/cmake-modules/Findassimp.cmake b/thirdparty/assimp/cmake-modules/Findassimp.cmake new file mode 100644 index 0000000..95f3250 --- /dev/null +++ b/thirdparty/assimp/cmake-modules/Findassimp.cmake @@ -0,0 +1,81 @@ +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(ASSIMP_ARCHITECTURE "64") +elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(ASSIMP_ARCHITECTURE "32") +endif(CMAKE_SIZEOF_VOID_P EQUAL 8) + +if(WIN32) + set(ASSIMP_ROOT_DIR CACHE PATH "ASSIMP root directory") + + # Find path of each library + find_path(ASSIMP_INCLUDE_DIR + NAMES + assimp/anim.h + HINTS + ${ASSIMP_ROOT_DIR}/include + ) + + if(MSVC12) + set(ASSIMP_MSVC_VERSION "vc120") + elseif(MSVC14) + set(ASSIMP_MSVC_VERSION "vc140") + endif(MSVC12) + + if(MSVC12 OR MSVC14) + + find_path(ASSIMP_LIBRARY_DIR + NAMES + assimp-${ASSIMP_MSVC_VERSION}-mt.lib + HINTS + ${ASSIMP_ROOT_DIR}/lib${ASSIMP_ARCHITECTURE} + ) + + find_library(ASSIMP_LIBRARY_RELEASE assimp-${ASSIMP_MSVC_VERSION}-mt.lib PATHS ${ASSIMP_LIBRARY_DIR}) + find_library(ASSIMP_LIBRARY_DEBUG assimp-${ASSIMP_MSVC_VERSION}-mtd.lib PATHS ${ASSIMP_LIBRARY_DIR}) + + set(ASSIMP_LIBRARY + optimized ${ASSIMP_LIBRARY_RELEASE} + debug ${ASSIMP_LIBRARY_DEBUG} + ) + + set(ASSIMP_LIBRARIES "ASSIMP_LIBRARY_RELEASE" "ASSIMP_LIBRARY_DEBUG") + + FUNCTION(ASSIMP_COPY_BINARIES TargetDirectory) + ADD_CUSTOM_TARGET(AssimpCopyBinaries + COMMAND ${CMAKE_COMMAND} -E copy ${ASSIMP_ROOT_DIR}/bin${ASSIMP_ARCHITECTURE}/assimp-${ASSIMP_MSVC_VERSION}-mtd.dll ${TargetDirectory}/Debug/assimp-${ASSIMP_MSVC_VERSION}-mtd.dll + COMMAND ${CMAKE_COMMAND} -E copy ${ASSIMP_ROOT_DIR}/bin${ASSIMP_ARCHITECTURE}/assimp-${ASSIMP_MSVC_VERSION}-mt.dll ${TargetDirectory}/Release/assimp-${ASSIMP_MSVC_VERSION}-mt.dll + COMMENT "Copying Assimp binaries to '${TargetDirectory}'" + VERBATIM) + ENDFUNCTION(ASSIMP_COPY_BINARIES) + + endif() + +else(WIN32) + + find_path( + assimp_INCLUDE_DIRS + NAMES postprocess.h scene.h version.h config.h cimport.h + PATHS /usr/local/include/ + ) + + find_library( + assimp_LIBRARIES + NAMES assimp + PATHS /usr/local/lib/ + ) + + if (assimp_INCLUDE_DIRS AND assimp_LIBRARIES) + SET(assimp_FOUND TRUE) + ENDIF (assimp_INCLUDE_DIRS AND assimp_LIBRARIES) + + if (assimp_FOUND) + if (NOT assimp_FIND_QUIETLY) + message(STATUS "Found asset importer library: ${assimp_LIBRARIES}") + endif (NOT assimp_FIND_QUIETLY) + else (assimp_FOUND) + if (assimp_FIND_REQUIRED) + message(FATAL_ERROR "Could not find asset importer library") + endif (assimp_FIND_REQUIRED) + endif (assimp_FOUND) + +endif(WIN32) \ No newline at end of file diff --git a/thirdparty/assimp/cmake-modules/MinGW_x86_64.cmake b/thirdparty/assimp/cmake-modules/MinGW_x86_64.cmake new file mode 100644 index 0000000..d5c1f15 --- /dev/null +++ b/thirdparty/assimp/cmake-modules/MinGW_x86_64.cmake @@ -0,0 +1,16 @@ +# this one sets internal to crosscompile (in theory) +SET(CMAKE_SYSTEM_NAME Windows) + +# the minimalistic settings +SET(CMAKE_C_COMPILER "/usr/bin/x86_64-w64-mingw32-gcc") +SET(CMAKE_CXX_COMPILER "/usr/bin/x86_64-w64-mingw32-g++") +SET(CMAKE_RC_COMPILER "/usr/bin/x86_64-w64-mingw32-windres") + +# where is the target (so called staging) environment +SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32) + +# search for programs in the build host directories (default BOTH) +#SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +# for libraries and headers in the target directories +SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/thirdparty/assimp/cmake-modules/PrecompiledHeader.cmake b/thirdparty/assimp/cmake-modules/PrecompiledHeader.cmake new file mode 100644 index 0000000..6af7866 --- /dev/null +++ b/thirdparty/assimp/cmake-modules/PrecompiledHeader.cmake @@ -0,0 +1,25 @@ +MACRO(ADD_MSVC_PRECOMPILED_HEADER PrecompiledHeader PrecompiledSource SourcesVar) + IF(MSVC) + GET_FILENAME_COMPONENT(PrecompiledBasename ${PrecompiledHeader} NAME_WE) + SET(PrecompiledBinary "${CMAKE_CFG_INTDIR}/${PrecompiledBasename}.pch") + SET(Sources ${${SourcesVar}}) + + SET_SOURCE_FILES_PROPERTIES(${PrecompiledSource} + PROPERTIES COMPILE_FLAGS "/Yc\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\"" + OBJECT_OUTPUTS "${PrecompiledBinary}") + + # Do not consider .c files + foreach(fname ${Sources}) + GET_FILENAME_COMPONENT(fext ${fname} EXT) + if(fext STREQUAL ".cpp") + SET_SOURCE_FILES_PROPERTIES(${fname} + PROPERTIES COMPILE_FLAGS "/Yu\"${PrecompiledBinary}\" /FI\"${PrecompiledBinary}\" /Fp\"${PrecompiledBinary}\"" + OBJECT_DEPENDS "${PrecompiledBinary}") + endif(fext STREQUAL ".cpp") + endforeach(fname) + + ENDIF(MSVC) + # Add precompiled header to SourcesVar + LIST(APPEND ${SourcesVar} ${PrecompiledSource}) + +ENDMACRO(ADD_MSVC_PRECOMPILED_HEADER) diff --git a/thirdparty/assimp/cmake-modules/cmake_uninstall.cmake.in b/thirdparty/assimp/cmake-modules/cmake_uninstall.cmake.in new file mode 100644 index 0000000..1670115 --- /dev/null +++ b/thirdparty/assimp/cmake-modules/cmake_uninstall.cmake.in @@ -0,0 +1,17 @@ +IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +STRING(REGEX REPLACE "\n" ";" files "${files}") +FOREACH(file ${files}) + MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + EXEC_PROGRAM( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + IF(NOT "${rm_retval}" STREQUAL 0) + MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + ENDIF(NOT "${rm_retval}" STREQUAL 0) +ENDFOREACH(file) diff --git a/thirdparty/assimp/cmake/HunterGate.cmake b/thirdparty/assimp/cmake/HunterGate.cmake new file mode 100644 index 0000000..887557a --- /dev/null +++ b/thirdparty/assimp/cmake/HunterGate.cmake @@ -0,0 +1,540 @@ +# Copyright (c) 2013-2018, Ruslan Baratov +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# This is a gate file to Hunter package manager. +# Include this file using `include` command and add package you need, example: +# +# cmake_minimum_required(VERSION 3.2) +# +# include("cmake/HunterGate.cmake") +# HunterGate( +# URL "https://github.com/path/to/hunter/archive.tar.gz" +# SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d" +# ) +# +# project(MyProject) +# +# hunter_add_package(Foo) +# hunter_add_package(Boo COMPONENTS Bar Baz) +# +# Projects: +# * https://github.com/hunter-packages/gate/ +# * https://github.com/ruslo/hunter + +option(HUNTER_ENABLED "Enable Hunter package manager support" ON) + +if(HUNTER_ENABLED) + if(CMAKE_VERSION VERSION_LESS "3.2") + message( + FATAL_ERROR + "At least CMake version 3.2 required for Hunter dependency management." + " Update CMake or set HUNTER_ENABLED to OFF." + ) + endif() +endif() + +include(CMakeParseArguments) # cmake_parse_arguments + +option(HUNTER_STATUS_PRINT "Print working status" ON) +option(HUNTER_STATUS_DEBUG "Print a lot info" OFF) +option(HUNTER_TLS_VERIFY "Enable/disable TLS certificate checking on downloads" ON) + +set(HUNTER_WIKI "https://github.com/ruslo/hunter/wiki") + +function(hunter_gate_status_print) + if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG) + foreach(print_message ${ARGV}) + message(STATUS "[hunter] ${print_message}") + endforeach() + endif() +endfunction() + +function(hunter_gate_status_debug) + if(HUNTER_STATUS_DEBUG) + foreach(print_message ${ARGV}) + string(TIMESTAMP timestamp) + message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}") + endforeach() + endif() +endfunction() + +function(hunter_gate_wiki wiki_page) + message("------------------------------ WIKI -------------------------------") + message(" ${HUNTER_WIKI}/${wiki_page}") + message("-------------------------------------------------------------------") + message("") + message(FATAL_ERROR "") +endfunction() + +function(hunter_gate_internal_error) + message("") + foreach(print_message ${ARGV}) + message("[hunter ** INTERNAL **] ${print_message}") + endforeach() + message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") + message("") + hunter_gate_wiki("error.internal") +endfunction() + +function(hunter_gate_fatal_error) + cmake_parse_arguments(hunter "" "WIKI" "" "${ARGV}") + string(COMPARE EQUAL "${hunter_WIKI}" "" have_no_wiki) + if(have_no_wiki) + hunter_gate_internal_error("Expected wiki") + endif() + message("") + foreach(x ${hunter_UNPARSED_ARGUMENTS}) + message("[hunter ** FATAL ERROR **] ${x}") + endforeach() + message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") + message("") + hunter_gate_wiki("${hunter_WIKI}") +endfunction() + +function(hunter_gate_user_error) + hunter_gate_fatal_error(${ARGV} WIKI "error.incorrect.input.data") +endfunction() + +function(hunter_gate_self root version sha1 result) + string(COMPARE EQUAL "${root}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("root is empty") + endif() + + string(COMPARE EQUAL "${version}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("version is empty") + endif() + + string(COMPARE EQUAL "${sha1}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("sha1 is empty") + endif() + + string(SUBSTRING "${sha1}" 0 7 archive_id) + + if(EXISTS "${root}/cmake/Hunter") + set(hunter_self "${root}") + else() + set( + hunter_self + "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked" + ) + endif() + + set("${result}" "${hunter_self}" PARENT_SCOPE) +endfunction() + +# Set HUNTER_GATE_ROOT cmake variable to suitable value. +function(hunter_gate_detect_root) + # Check CMake variable + string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty) + if(not_empty) + set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable") + return() + endif() + + # Check environment variable + string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty) + if(not_empty) + set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT detected by environment variable") + return() + endif() + + # Check HOME environment variable + string(COMPARE NOTEQUAL "$ENV{HOME}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable") + return() + endif() + + # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only) + if(WIN32) + string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug( + "HUNTER_ROOT set using SYSTEMDRIVE environment variable" + ) + return() + endif() + + string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug( + "HUNTER_ROOT set using USERPROFILE environment variable" + ) + return() + endif() + endif() + + hunter_gate_fatal_error( + "Can't detect HUNTER_ROOT" + WIKI "error.detect.hunter.root" + ) +endfunction() + +function(hunter_gate_download dir) + string( + COMPARE + NOTEQUAL + "$ENV{HUNTER_DISABLE_AUTOINSTALL}" + "" + disable_autoinstall + ) + if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL) + hunter_gate_fatal_error( + "Hunter not found in '${dir}'" + "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'" + "Settings:" + " HUNTER_ROOT: ${HUNTER_GATE_ROOT}" + " HUNTER_SHA1: ${HUNTER_GATE_SHA1}" + WIKI "error.run.install" + ) + endif() + string(COMPARE EQUAL "${dir}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("Empty 'dir' argument") + endif() + + string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("HUNTER_GATE_SHA1 empty") + endif() + + string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("HUNTER_GATE_URL empty") + endif() + + set(done_location "${dir}/DONE") + set(sha1_location "${dir}/SHA1") + + set(build_dir "${dir}/Build") + set(cmakelists "${dir}/CMakeLists.txt") + + hunter_gate_status_debug("Locking directory: ${dir}") + file(LOCK "${dir}" DIRECTORY GUARD FUNCTION) + hunter_gate_status_debug("Lock done") + + if(EXISTS "${done_location}") + # while waiting for lock other instance can do all the job + hunter_gate_status_debug("File '${done_location}' found, skip install") + return() + endif() + + file(REMOVE_RECURSE "${build_dir}") + file(REMOVE_RECURSE "${cmakelists}") + + file(MAKE_DIRECTORY "${build_dir}") # check directory permissions + + # Disabling languages speeds up a little bit, reduces noise in the output + # and avoids path too long windows error + file( + WRITE + "${cmakelists}" + "cmake_minimum_required(VERSION 3.2)\n" + "project(HunterDownload LANGUAGES NONE)\n" + "include(ExternalProject)\n" + "ExternalProject_Add(\n" + " Hunter\n" + " URL\n" + " \"${HUNTER_GATE_URL}\"\n" + " URL_HASH\n" + " SHA1=${HUNTER_GATE_SHA1}\n" + " DOWNLOAD_DIR\n" + " \"${dir}\"\n" + " TLS_VERIFY\n" + " ${HUNTER_TLS_VERIFY}\n" + " SOURCE_DIR\n" + " \"${dir}/Unpacked\"\n" + " CONFIGURE_COMMAND\n" + " \"\"\n" + " BUILD_COMMAND\n" + " \"\"\n" + " INSTALL_COMMAND\n" + " \"\"\n" + ")\n" + ) + + if(HUNTER_STATUS_DEBUG) + set(logging_params "") + else() + set(logging_params OUTPUT_QUIET) + endif() + + hunter_gate_status_debug("Run generate") + + # Need to add toolchain file too. + # Otherwise on Visual Studio + MDD this will fail with error: + # "Could not find an appropriate version of the Windows 10 SDK installed on this machine" + if(EXISTS "${CMAKE_TOOLCHAIN_FILE}") + get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE) + set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}") + else() + # 'toolchain_arg' can't be empty + set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=") + endif() + + string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make) + if(no_make) + set(make_arg "") + else() + # Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM + set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}") + endif() + + execute_process( + COMMAND + "${CMAKE_COMMAND}" + "-H${dir}" + "-B${build_dir}" + "-G${CMAKE_GENERATOR}" + "${toolchain_arg}" + ${make_arg} + WORKING_DIRECTORY "${dir}" + RESULT_VARIABLE download_result + ${logging_params} + ) + + if(NOT download_result EQUAL 0) + hunter_gate_internal_error( + "Configure project failed." + "To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}" + "In directory ${dir}" + ) + endif() + + hunter_gate_status_print( + "Initializing Hunter workspace (${HUNTER_GATE_SHA1})" + " ${HUNTER_GATE_URL}" + " -> ${dir}" + ) + execute_process( + COMMAND "${CMAKE_COMMAND}" --build "${build_dir}" + WORKING_DIRECTORY "${dir}" + RESULT_VARIABLE download_result + ${logging_params} + ) + + if(NOT download_result EQUAL 0) + hunter_gate_internal_error("Build project failed") + endif() + + file(REMOVE_RECURSE "${build_dir}") + file(REMOVE_RECURSE "${cmakelists}") + + file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}") + file(WRITE "${done_location}" "DONE") + + hunter_gate_status_debug("Finished") +endfunction() + +# Must be a macro so master file 'cmake/Hunter' can +# apply all variables easily just by 'include' command +# (otherwise PARENT_SCOPE magic needed) +macro(HunterGate) + if(HUNTER_GATE_DONE) + # variable HUNTER_GATE_DONE set explicitly for external project + # (see `hunter_download`) + set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) + endif() + + # First HunterGate command will init Hunter, others will be ignored + get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET) + + if(NOT HUNTER_ENABLED) + # Empty function to avoid error "unknown function" + function(hunter_add_package) + endfunction() + + set( + _hunter_gate_disabled_mode_dir + "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode" + ) + if(EXISTS "${_hunter_gate_disabled_mode_dir}") + hunter_gate_status_debug( + "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}" + ) + list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}") + endif() + elseif(_hunter_gate_done) + hunter_gate_status_debug("Secondary HunterGate (use old settings)") + hunter_gate_self( + "${HUNTER_CACHED_ROOT}" + "${HUNTER_VERSION}" + "${HUNTER_SHA1}" + _hunter_self + ) + include("${_hunter_self}/cmake/Hunter") + else() + set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}") + + string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name) + if(_have_project_name) + hunter_gate_fatal_error( + "Please set HunterGate *before* 'project' command. " + "Detected project: ${PROJECT_NAME}" + WIKI "error.huntergate.before.project" + ) + endif() + + cmake_parse_arguments( + HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV} + ) + + string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1) + string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url) + string( + COMPARE + NOTEQUAL + "${HUNTER_GATE_UNPARSED_ARGUMENTS}" + "" + _have_unparsed + ) + string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global) + string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath) + + if(_have_unparsed) + hunter_gate_user_error( + "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}" + ) + endif() + if(_empty_sha1) + hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory") + endif() + if(_empty_url) + hunter_gate_user_error("URL suboption of HunterGate is mandatory") + endif() + if(_have_global) + if(HUNTER_GATE_LOCAL) + hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)") + endif() + if(_have_filepath) + hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)") + endif() + endif() + if(HUNTER_GATE_LOCAL) + if(_have_global) + hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)") + endif() + if(_have_filepath) + hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)") + endif() + endif() + if(_have_filepath) + if(_have_global) + hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)") + endif() + if(HUNTER_GATE_LOCAL) + hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)") + endif() + endif() + + hunter_gate_detect_root() # set HUNTER_GATE_ROOT + + # Beautify path, fix probable problems with windows path slashes + get_filename_component( + HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE + ) + hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}") + if(NOT HUNTER_ALLOW_SPACES_IN_PATH) + string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces) + if(NOT _contain_spaces EQUAL -1) + hunter_gate_fatal_error( + "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces." + "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error" + "(Use at your own risk!)" + WIKI "error.spaces.in.hunter.root" + ) + endif() + endif() + + string( + REGEX + MATCH + "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*" + HUNTER_GATE_VERSION + "${HUNTER_GATE_URL}" + ) + string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty) + if(_is_empty) + set(HUNTER_GATE_VERSION "unknown") + endif() + + hunter_gate_self( + "${HUNTER_GATE_ROOT}" + "${HUNTER_GATE_VERSION}" + "${HUNTER_GATE_SHA1}" + _hunter_self + ) + + set(_master_location "${_hunter_self}/cmake/Hunter") + if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter") + # Hunter downloaded manually (e.g. by 'git clone') + set(_unused "xxxxxxxxxx") + set(HUNTER_GATE_SHA1 "${_unused}") + set(HUNTER_GATE_VERSION "${_unused}") + else() + get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE) + set(_done_location "${_archive_id_location}/DONE") + set(_sha1_location "${_archive_id_location}/SHA1") + + # Check Hunter already downloaded by HunterGate + if(NOT EXISTS "${_done_location}") + hunter_gate_download("${_archive_id_location}") + endif() + + if(NOT EXISTS "${_done_location}") + hunter_gate_internal_error("hunter_gate_download failed") + endif() + + if(NOT EXISTS "${_sha1_location}") + hunter_gate_internal_error("${_sha1_location} not found") + endif() + file(READ "${_sha1_location}" _sha1_value) + string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal) + if(NOT _is_equal) + hunter_gate_internal_error( + "Short SHA1 collision:" + " ${_sha1_value} (from ${_sha1_location})" + " ${HUNTER_GATE_SHA1} (HunterGate)" + ) + endif() + if(NOT EXISTS "${_master_location}") + hunter_gate_user_error( + "Master file not found:" + " ${_master_location}" + "try to update Hunter/HunterGate" + ) + endif() + endif() + include("${_master_location}") + set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) + endif() +endmacro() diff --git a/thirdparty/assimp/cmake/assimp-hunter-config.cmake.in b/thirdparty/assimp/cmake/assimp-hunter-config.cmake.in new file mode 100644 index 0000000..34762ac --- /dev/null +++ b/thirdparty/assimp/cmake/assimp-hunter-config.cmake.in @@ -0,0 +1,14 @@ +@PACKAGE_INIT@ + +find_package(RapidJSON CONFIG REQUIRED) +find_package(ZLIB CONFIG REQUIRED) +find_package(utf8 CONFIG REQUIRED) +find_package(irrXML CONFIG REQUIRED) +find_package(minizip CONFIG REQUIRED) +find_package(openddlparser CONFIG REQUIRED) +find_package(poly2tri CONFIG REQUIRED) +find_package(polyclipping CONFIG REQUIRED) +find_package(zip CONFIG REQUIRED) + +include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/thirdparty/assimp/code/.#CMakeLists.txt b/thirdparty/assimp/code/.#CMakeLists.txt new file mode 120000 index 0000000..78a3133 --- /dev/null +++ b/thirdparty/assimp/code/.#CMakeLists.txt @@ -0,0 +1 @@ +dane@sweetdee.1202:1610989317 \ No newline at end of file diff --git a/thirdparty/assimp/code/.editorconfig b/thirdparty/assimp/code/.editorconfig new file mode 100644 index 0000000..4a194a3 --- /dev/null +++ b/thirdparty/assimp/code/.editorconfig @@ -0,0 +1,8 @@ +# See for details + +[*.{h,hpp,c,cpp}] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_size = 4 +indent_style = space diff --git a/thirdparty/assimp/code/3DS/3DSConverter.cpp b/thirdparty/assimp/code/3DS/3DSConverter.cpp new file mode 100644 index 0000000..2176b75 --- /dev/null +++ b/thirdparty/assimp/code/3DS/3DSConverter.cpp @@ -0,0 +1,873 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/** @file Implementation of the 3ds importer class */ + + +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +// internal headers +#include "3DSLoader.h" +#include "Common/TargetAnimation.h" +#include +#include +#include +#include +#include + +using namespace Assimp; + +static const unsigned int NotSet = 0xcdcdcdcd; + +// ------------------------------------------------------------------------------------------------ +// Setup final material indices, generae a default material if necessary +void Discreet3DSImporter::ReplaceDefaultMaterial() +{ + // Try to find an existing material that matches the + // typical default material setting: + // - no textures + // - diffuse color (in grey!) + // NOTE: This is here to workaround the fact that some + // exporters are writing a default material, too. + unsigned int idx( NotSet ); + for (unsigned int i = 0; i < mScene->mMaterials.size();++i) + { + std::string &s = mScene->mMaterials[i].mName; + for ( std::string::iterator it = s.begin(); it != s.end(); ++it ) { + *it = static_cast< char >( ::tolower( *it ) ); + } + + if (std::string::npos == s.find("default"))continue; + + if (mScene->mMaterials[i].mDiffuse.r != + mScene->mMaterials[i].mDiffuse.g || + mScene->mMaterials[i].mDiffuse.r != + mScene->mMaterials[i].mDiffuse.b)continue; + + if (mScene->mMaterials[i].sTexDiffuse.mMapName.length() != 0 || + mScene->mMaterials[i].sTexBump.mMapName.length() != 0 || + mScene->mMaterials[i].sTexOpacity.mMapName.length() != 0 || + mScene->mMaterials[i].sTexEmissive.mMapName.length() != 0 || + mScene->mMaterials[i].sTexSpecular.mMapName.length() != 0 || + mScene->mMaterials[i].sTexShininess.mMapName.length() != 0 ) + { + continue; + } + idx = i; + } + if ( NotSet == idx ) { + idx = ( unsigned int )mScene->mMaterials.size(); + } + + // now iterate through all meshes and through all faces and + // find all faces that are using the default material + unsigned int cnt = 0; + for (std::vector::iterator + i = mScene->mMeshes.begin(); + i != mScene->mMeshes.end();++i) + { + for (std::vector::iterator + a = (*i).mFaceMaterials.begin(); + a != (*i).mFaceMaterials.end();++a) + { + // NOTE: The additional check seems to be necessary, + // some exporters seem to generate invalid data here + if (0xcdcdcdcd == (*a)) + { + (*a) = idx; + ++cnt; + } + else if ( (*a) >= mScene->mMaterials.size()) + { + (*a) = idx; + ASSIMP_LOG_WARN("Material index overflow in 3DS file. Using default material"); + ++cnt; + } + } + } + if (cnt && idx == mScene->mMaterials.size()) + { + // We need to create our own default material + D3DS::Material sMat("%%%DEFAULT"); + sMat.mDiffuse = aiColor3D(0.3f,0.3f,0.3f); + mScene->mMaterials.push_back(sMat); + + ASSIMP_LOG_INFO("3DS: Generating default material"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Check whether all indices are valid. Otherwise we'd crash before the validation step is reached +void Discreet3DSImporter::CheckIndices(D3DS::Mesh& sMesh) +{ + for (std::vector< D3DS::Face >::iterator i = sMesh.mFaces.begin(); i != sMesh.mFaces.end();++i) + { + // check whether all indices are in range + for (unsigned int a = 0; a < 3;++a) + { + if ((*i).mIndices[a] >= sMesh.mPositions.size()) + { + ASSIMP_LOG_WARN("3DS: Vertex index overflow)"); + (*i).mIndices[a] = (uint32_t)sMesh.mPositions.size()-1; + } + if ( !sMesh.mTexCoords.empty() && (*i).mIndices[a] >= sMesh.mTexCoords.size()) + { + ASSIMP_LOG_WARN("3DS: Texture coordinate index overflow)"); + (*i).mIndices[a] = (uint32_t)sMesh.mTexCoords.size()-1; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Generate out unique verbose format representation +void Discreet3DSImporter::MakeUnique(D3DS::Mesh& sMesh) +{ + // TODO: really necessary? I don't think. Just a waste of memory and time + // to do it now in a separate buffer. + + // Allocate output storage + std::vector vNew (sMesh.mFaces.size() * 3); + std::vector vNew2; + if (sMesh.mTexCoords.size()) + vNew2.resize(sMesh.mFaces.size() * 3); + + for (unsigned int i = 0, base = 0; i < sMesh.mFaces.size();++i) + { + D3DS::Face& face = sMesh.mFaces[i]; + + // Positions + for (unsigned int a = 0; a < 3;++a,++base) + { + vNew[base] = sMesh.mPositions[face.mIndices[a]]; + if (sMesh.mTexCoords.size()) + vNew2[base] = sMesh.mTexCoords[face.mIndices[a]]; + + face.mIndices[a] = base; + } + } + sMesh.mPositions = vNew; + sMesh.mTexCoords = vNew2; +} + +// ------------------------------------------------------------------------------------------------ +// Convert a 3DS texture to texture keys in an aiMaterial +void CopyTexture(aiMaterial& mat, D3DS::Texture& texture, aiTextureType type) +{ + // Setup the texture name + aiString tex; + tex.Set( texture.mMapName); + mat.AddProperty( &tex, AI_MATKEY_TEXTURE(type,0)); + + // Setup the texture blend factor + if (is_not_qnan(texture.mTextureBlend)) + mat.AddProperty( &texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type,0)); + + // Setup the texture mapping mode + int mapMode = static_cast(texture.mMapMode); + mat.AddProperty(&mapMode,1,AI_MATKEY_MAPPINGMODE_U(type,0)); + mat.AddProperty(&mapMode,1,AI_MATKEY_MAPPINGMODE_V(type,0)); + + // Mirroring - double the scaling values + // FIXME: this is not really correct ... + if (texture.mMapMode == aiTextureMapMode_Mirror) + { + texture.mScaleU *= 2.0; + texture.mScaleV *= 2.0; + texture.mOffsetU /= 2.0; + texture.mOffsetV /= 2.0; + } + + // Setup texture UV transformations + mat.AddProperty(&texture.mOffsetU,5,AI_MATKEY_UVTRANSFORM(type,0)); +} + +// ------------------------------------------------------------------------------------------------ +// Convert a 3DS material to an aiMaterial +void Discreet3DSImporter::ConvertMaterial(D3DS::Material& oldMat, + aiMaterial& mat) +{ + // NOTE: Pass the background image to the viewer by bypassing the + // material system. This is an evil hack, never do it again! + if (0 != mBackgroundImage.length() && bHasBG) + { + aiString tex; + tex.Set( mBackgroundImage); + mat.AddProperty( &tex, AI_MATKEY_GLOBAL_BACKGROUND_IMAGE); + + // Be sure this is only done for the first material + mBackgroundImage = std::string(""); + } + + // At first add the base ambient color of the scene to the material + oldMat.mAmbient.r += mClrAmbient.r; + oldMat.mAmbient.g += mClrAmbient.g; + oldMat.mAmbient.b += mClrAmbient.b; + + aiString name; + name.Set( oldMat.mName); + mat.AddProperty( &name, AI_MATKEY_NAME); + + // Material colors + mat.AddProperty( &oldMat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT); + mat.AddProperty( &oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + mat.AddProperty( &oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); + mat.AddProperty( &oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); + + // Phong shininess and shininess strength + if (D3DS::Discreet3DS::Phong == oldMat.mShading || + D3DS::Discreet3DS::Metal == oldMat.mShading) + { + if (!oldMat.mSpecularExponent || !oldMat.mShininessStrength) + { + oldMat.mShading = D3DS::Discreet3DS::Gouraud; + } + else + { + mat.AddProperty( &oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS); + mat.AddProperty( &oldMat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH); + } + } + + // Opacity + mat.AddProperty( &oldMat.mTransparency,1,AI_MATKEY_OPACITY); + + // Bump height scaling + mat.AddProperty( &oldMat.mBumpHeight,1,AI_MATKEY_BUMPSCALING); + + // Two sided rendering? + if (oldMat.mTwoSided) + { + int i = 1; + mat.AddProperty(&i,1,AI_MATKEY_TWOSIDED); + } + + // Shading mode + aiShadingMode eShading = aiShadingMode_NoShading; + switch (oldMat.mShading) + { + case D3DS::Discreet3DS::Flat: + eShading = aiShadingMode_Flat; break; + + // I don't know what "Wire" shading should be, + // assume it is simple lambertian diffuse shading + case D3DS::Discreet3DS::Wire: + { + // Set the wireframe flag + unsigned int iWire = 1; + mat.AddProperty( (int*)&iWire,1,AI_MATKEY_ENABLE_WIREFRAME); + } + + case D3DS::Discreet3DS::Gouraud: + eShading = aiShadingMode_Gouraud; break; + + // assume cook-torrance shading for metals. + case D3DS::Discreet3DS::Phong : + eShading = aiShadingMode_Phong; break; + + case D3DS::Discreet3DS::Metal : + eShading = aiShadingMode_CookTorrance; break; + + // FIX to workaround a warning with GCC 4 who complained + // about a missing case Blinn: here - Blinn isn't a valid + // value in the 3DS Loader, it is just needed for ASE + case D3DS::Discreet3DS::Blinn : + eShading = aiShadingMode_Blinn; break; + } + int eShading_ = static_cast(eShading); + mat.AddProperty(&eShading_, 1, AI_MATKEY_SHADING_MODEL); + + // DIFFUSE texture + if( oldMat.sTexDiffuse.mMapName.length() > 0) + CopyTexture(mat,oldMat.sTexDiffuse, aiTextureType_DIFFUSE); + + // SPECULAR texture + if( oldMat.sTexSpecular.mMapName.length() > 0) + CopyTexture(mat,oldMat.sTexSpecular, aiTextureType_SPECULAR); + + // OPACITY texture + if( oldMat.sTexOpacity.mMapName.length() > 0) + CopyTexture(mat,oldMat.sTexOpacity, aiTextureType_OPACITY); + + // EMISSIVE texture + if( oldMat.sTexEmissive.mMapName.length() > 0) + CopyTexture(mat,oldMat.sTexEmissive, aiTextureType_EMISSIVE); + + // BUMP texture + if( oldMat.sTexBump.mMapName.length() > 0) + CopyTexture(mat,oldMat.sTexBump, aiTextureType_HEIGHT); + + // SHININESS texture + if( oldMat.sTexShininess.mMapName.length() > 0) + CopyTexture(mat,oldMat.sTexShininess, aiTextureType_SHININESS); + + // REFLECTION texture + if( oldMat.sTexReflective.mMapName.length() > 0) + CopyTexture(mat,oldMat.sTexReflective, aiTextureType_REFLECTION); + + // Store the name of the material itself, too + if( oldMat.mName.length()) { + aiString tex; + tex.Set( oldMat.mName); + mat.AddProperty( &tex, AI_MATKEY_NAME); + } +} + +// ------------------------------------------------------------------------------------------------ +// Split meshes by their materials and generate output aiMesh'es +void Discreet3DSImporter::ConvertMeshes(aiScene* pcOut) +{ + std::vector avOutMeshes; + avOutMeshes.reserve(mScene->mMeshes.size() * 2); + + unsigned int iFaceCnt = 0,num = 0; + aiString name; + + // we need to split all meshes by their materials + for (std::vector::iterator i = mScene->mMeshes.begin(); i != mScene->mMeshes.end();++i) { + std::unique_ptr< std::vector[] > aiSplit(new std::vector[mScene->mMaterials.size()]); + + name.length = ASSIMP_itoa10(name.data,num++); + + unsigned int iNum = 0; + for (std::vector::const_iterator a = (*i).mFaceMaterials.begin(); + a != (*i).mFaceMaterials.end();++a,++iNum) + { + aiSplit[*a].push_back(iNum); + } + // now generate submeshes + for (unsigned int p = 0; p < mScene->mMaterials.size();++p) + { + if (aiSplit[p].empty()) { + continue; + } + aiMesh* meshOut = new aiMesh(); + meshOut->mName = name; + meshOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + // be sure to setup the correct material index + meshOut->mMaterialIndex = p; + + // use the color data as temporary storage + meshOut->mColors[0] = (aiColor4D*)(&*i); + avOutMeshes.push_back(meshOut); + + // convert vertices + meshOut->mNumFaces = (unsigned int)aiSplit[p].size(); + meshOut->mNumVertices = meshOut->mNumFaces*3; + + // allocate enough storage for faces + meshOut->mFaces = new aiFace[meshOut->mNumFaces]; + iFaceCnt += meshOut->mNumFaces; + + meshOut->mVertices = new aiVector3D[meshOut->mNumVertices]; + meshOut->mNormals = new aiVector3D[meshOut->mNumVertices]; + if ((*i).mTexCoords.size()) + { + meshOut->mTextureCoords[0] = new aiVector3D[meshOut->mNumVertices]; + } + for (unsigned int q = 0, base = 0; q < aiSplit[p].size();++q) + { + unsigned int index = aiSplit[p][q]; + aiFace& face = meshOut->mFaces[q]; + + face.mIndices = new unsigned int[3]; + face.mNumIndices = 3; + + for (unsigned int a = 0; a < 3;++a,++base) + { + unsigned int idx = (*i).mFaces[index].mIndices[a]; + meshOut->mVertices[base] = (*i).mPositions[idx]; + meshOut->mNormals [base] = (*i).mNormals[idx]; + + if ((*i).mTexCoords.size()) + meshOut->mTextureCoords[0][base] = (*i).mTexCoords[idx]; + + face.mIndices[a] = base; + } + } + } + } + + // Copy them to the output array + pcOut->mNumMeshes = (unsigned int)avOutMeshes.size(); + pcOut->mMeshes = new aiMesh*[pcOut->mNumMeshes](); + for (unsigned int a = 0; a < pcOut->mNumMeshes;++a) { + pcOut->mMeshes[a] = avOutMeshes[a]; + } + + // We should have at least one face here + if (!iFaceCnt) { + throw DeadlyImportError("No faces loaded. The mesh is empty"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Add a node to the scenegraph and setup its final transformation +void Discreet3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut, + D3DS::Node* pcIn, aiMatrix4x4& /*absTrafo*/) +{ + std::vector iArray; + iArray.reserve(3); + + aiMatrix4x4 abs; + + // Find all meshes with the same name as the node + for (unsigned int a = 0; a < pcSOut->mNumMeshes;++a) + { + const D3DS::Mesh* pcMesh = (const D3DS::Mesh*)pcSOut->mMeshes[a]->mColors[0]; + ai_assert(NULL != pcMesh); + + if (pcIn->mName == pcMesh->mName) + iArray.push_back(a); + } + if (!iArray.empty()) + { + // The matrix should be identical for all meshes with the + // same name. It HAS to be identical for all meshes ..... + D3DS::Mesh* imesh = ((D3DS::Mesh*)pcSOut->mMeshes[iArray[0]]->mColors[0]); + + // Compute the inverse of the transformation matrix to move the + // vertices back to their relative and local space + aiMatrix4x4 mInv = imesh->mMat, mInvTransposed = imesh->mMat; + mInv.Inverse();mInvTransposed.Transpose(); + aiVector3D pivot = pcIn->vPivot; + + pcOut->mNumMeshes = (unsigned int)iArray.size(); + pcOut->mMeshes = new unsigned int[iArray.size()]; + for (unsigned int i = 0;i < iArray.size();++i) { + const unsigned int iIndex = iArray[i]; + aiMesh* const mesh = pcSOut->mMeshes[iIndex]; + + if (mesh->mColors[1] == NULL) + { + // Transform the vertices back into their local space + // fixme: consider computing normals after this, so we don't need to transform them + const aiVector3D* const pvEnd = mesh->mVertices + mesh->mNumVertices; + aiVector3D* pvCurrent = mesh->mVertices, *t2 = mesh->mNormals; + + for (; pvCurrent != pvEnd; ++pvCurrent, ++t2) { + *pvCurrent = mInv * (*pvCurrent); + *t2 = mInvTransposed * (*t2); + } + + // Handle negative transformation matrix determinant -> invert vertex x + if (imesh->mMat.Determinant() < 0.0f) + { + /* we *must* have normals */ + for (pvCurrent = mesh->mVertices, t2 = mesh->mNormals; pvCurrent != pvEnd; ++pvCurrent, ++t2) { + pvCurrent->x *= -1.f; + t2->x *= -1.f; + } + ASSIMP_LOG_INFO("3DS: Flipping mesh X-Axis"); + } + + // Handle pivot point + if (pivot.x || pivot.y || pivot.z) + { + for (pvCurrent = mesh->mVertices; pvCurrent != pvEnd; ++pvCurrent) { + *pvCurrent -= pivot; + } + } + + mesh->mColors[1] = (aiColor4D*)1; + } + else + mesh->mColors[1] = (aiColor4D*)1; + + // Setup the mesh index + pcOut->mMeshes[i] = iIndex; + } + } + + // Setup the name of the node + // First instance keeps its name otherwise something might break, all others will be postfixed with their instance number + if (pcIn->mInstanceNumber > 1) + { + char tmp[12]; + ASSIMP_itoa10(tmp, pcIn->mInstanceNumber); + std::string tempStr = pcIn->mName + "_inst_"; + tempStr += tmp; + pcOut->mName.Set(tempStr); + } + else + pcOut->mName.Set(pcIn->mName); + + // Now build the transformation matrix of the node + // ROTATION + if (pcIn->aRotationKeys.size()){ + + // FIX to get to Assimp's quaternion conventions + for (std::vector::iterator it = pcIn->aRotationKeys.begin(); it != pcIn->aRotationKeys.end(); ++it) { + (*it).mValue.w *= -1.f; + } + + pcOut->mTransformation = aiMatrix4x4( pcIn->aRotationKeys[0].mValue.GetMatrix() ); + } + else if (pcIn->aCameraRollKeys.size()) + { + aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(- pcIn->aCameraRollKeys[0].mValue), + pcOut->mTransformation); + } + + // SCALING + aiMatrix4x4& m = pcOut->mTransformation; + if (pcIn->aScalingKeys.size()) + { + const aiVector3D& v = pcIn->aScalingKeys[0].mValue; + m.a1 *= v.x; m.b1 *= v.x; m.c1 *= v.x; + m.a2 *= v.y; m.b2 *= v.y; m.c2 *= v.y; + m.a3 *= v.z; m.b3 *= v.z; m.c3 *= v.z; + } + + // TRANSLATION + if (pcIn->aPositionKeys.size()) + { + const aiVector3D& v = pcIn->aPositionKeys[0].mValue; + m.a4 += v.x; + m.b4 += v.y; + m.c4 += v.z; + } + + // Generate animation channels for the node + if (pcIn->aPositionKeys.size() > 1 || pcIn->aRotationKeys.size() > 1 || + pcIn->aScalingKeys.size() > 1 || pcIn->aCameraRollKeys.size() > 1 || + pcIn->aTargetPositionKeys.size() > 1) + { + aiAnimation* anim = pcSOut->mAnimations[0]; + ai_assert(nullptr != anim); + + if (pcIn->aCameraRollKeys.size() > 1) + { + ASSIMP_LOG_DEBUG("3DS: Converting camera roll track ..."); + + // Camera roll keys - in fact they're just rotations + // around the camera's z axis. The angles are given + // in degrees (and they're clockwise). + pcIn->aRotationKeys.resize(pcIn->aCameraRollKeys.size()); + for (unsigned int i = 0; i < pcIn->aCameraRollKeys.size();++i) + { + aiQuatKey& q = pcIn->aRotationKeys[i]; + aiFloatKey& f = pcIn->aCameraRollKeys[i]; + + q.mTime = f.mTime; + + // FIX to get to Assimp quaternion conventions + q.mValue = aiQuaternion(0.f,0.f,AI_DEG_TO_RAD( /*-*/ f.mValue)); + } + } +#if 0 + if (pcIn->aTargetPositionKeys.size() > 1) + { + ASSIMP_LOG_DEBUG("3DS: Converting target track ..."); + + // Camera or spot light - need to convert the separate + // target position channel to our representation + TargetAnimationHelper helper; + + if (pcIn->aPositionKeys.empty()) + { + // We can just pass zero here ... + helper.SetFixedMainAnimationChannel(aiVector3D()); + } + else helper.SetMainAnimationChannel(&pcIn->aPositionKeys); + helper.SetTargetAnimationChannel(&pcIn->aTargetPositionKeys); + + // Do the conversion + std::vector distanceTrack; + helper.Process(&distanceTrack); + + // Now add a new node as child, name it .Target + // and assign the distance track to it. This is that the + // information where the target is and how it moves is + // not lost + D3DS::Node* nd = new D3DS::Node(); + pcIn->push_back(nd); + + nd->mName = pcIn->mName + ".Target"; + + aiNodeAnim* nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim(); + nda->mNodeName.Set(nd->mName); + + nda->mNumPositionKeys = (unsigned int)distanceTrack.size(); + nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys]; + ::memcpy(nda->mPositionKeys,&distanceTrack[0], + sizeof(aiVectorKey)*nda->mNumPositionKeys); + } +#endif + + // Cameras or lights define their transformation in their parent node and in the + // corresponding light or camera chunks. However, we read and process the latter + // to to be able to return valid cameras/lights even if no scenegraph is given. + for (unsigned int n = 0; n < pcSOut->mNumCameras;++n) { + if (pcSOut->mCameras[n]->mName == pcOut->mName) { + pcSOut->mCameras[n]->mLookAt = aiVector3D(0.f,0.f,1.f); + } + } + for (unsigned int n = 0; n < pcSOut->mNumLights;++n) { + if (pcSOut->mLights[n]->mName == pcOut->mName) { + pcSOut->mLights[n]->mDirection = aiVector3D(0.f,0.f,1.f); + } + } + + // Allocate a new node anim and setup its name + aiNodeAnim* nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim(); + nda->mNodeName.Set(pcIn->mName); + + // POSITION keys + if (pcIn->aPositionKeys.size() > 0) + { + nda->mNumPositionKeys = (unsigned int)pcIn->aPositionKeys.size(); + nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys]; + ::memcpy(nda->mPositionKeys,&pcIn->aPositionKeys[0], + sizeof(aiVectorKey)*nda->mNumPositionKeys); + } + + // ROTATION keys + if (pcIn->aRotationKeys.size() > 0) + { + nda->mNumRotationKeys = (unsigned int)pcIn->aRotationKeys.size(); + nda->mRotationKeys = new aiQuatKey[nda->mNumRotationKeys]; + + // Rotations are quaternion offsets + aiQuaternion abs1; + for (unsigned int n = 0; n < nda->mNumRotationKeys;++n) + { + const aiQuatKey& q = pcIn->aRotationKeys[n]; + + abs1 = (n ? abs1 * q.mValue : q.mValue); + nda->mRotationKeys[n].mTime = q.mTime; + nda->mRotationKeys[n].mValue = abs1.Normalize(); + } + } + + // SCALING keys + if (pcIn->aScalingKeys.size() > 0) + { + nda->mNumScalingKeys = (unsigned int)pcIn->aScalingKeys.size(); + nda->mScalingKeys = new aiVectorKey[nda->mNumScalingKeys]; + ::memcpy(nda->mScalingKeys,&pcIn->aScalingKeys[0], + sizeof(aiVectorKey)*nda->mNumScalingKeys); + } + } + + // Allocate storage for children + pcOut->mNumChildren = (unsigned int)pcIn->mChildren.size(); + pcOut->mChildren = new aiNode*[pcIn->mChildren.size()]; + + // Recursively process all children + const unsigned int size = static_cast(pcIn->mChildren.size()); + for (unsigned int i = 0; i < size;++i) + { + pcOut->mChildren[i] = new aiNode(); + pcOut->mChildren[i]->mParent = pcOut; + AddNodeToGraph(pcSOut,pcOut->mChildren[i],pcIn->mChildren[i],abs); + } +} + +// ------------------------------------------------------------------------------------------------ +// Find out how many node animation channels we'll have finally +void CountTracks(D3DS::Node* node, unsigned int& cnt) +{ + ////////////////////////////////////////////////////////////////////////////// + // We will never generate more than one channel for a node, so + // this is rather easy here. + + if (node->aPositionKeys.size() > 1 || node->aRotationKeys.size() > 1 || + node->aScalingKeys.size() > 1 || node->aCameraRollKeys.size() > 1 || + node->aTargetPositionKeys.size() > 1) + { + ++cnt; + + // account for the additional channel for the camera/spotlight target position + if (node->aTargetPositionKeys.size() > 1)++cnt; + } + + // Recursively process all children + for (unsigned int i = 0; i < node->mChildren.size();++i) + CountTracks(node->mChildren[i],cnt); +} + +// ------------------------------------------------------------------------------------------------ +// Generate the output node graph +void Discreet3DSImporter::GenerateNodeGraph(aiScene* pcOut) +{ + pcOut->mRootNode = new aiNode(); + if (0 == mRootNode->mChildren.size()) + { + ////////////////////////////////////////////////////////////////////////////// + // It seems the file is so messed up that it has not even a hierarchy. + // generate a flat hiearachy which looks like this: + // + // ROOT_NODE + // | + // ---------------------------------------- + // | | | | | + // MESH_0 MESH_1 MESH_2 ... MESH_N CAMERA_0 .... + // + ASSIMP_LOG_WARN("No hierarchy information has been found in the file. "); + + pcOut->mRootNode->mNumChildren = pcOut->mNumMeshes + + static_cast(mScene->mCameras.size() + mScene->mLights.size()); + + pcOut->mRootNode->mChildren = new aiNode* [ pcOut->mRootNode->mNumChildren ]; + pcOut->mRootNode->mName.Set("<3DSDummyRoot>"); + + // Build dummy nodes for all meshes + unsigned int a = 0; + for (unsigned int i = 0; i < pcOut->mNumMeshes;++i,++a) + { + aiNode* pcNode = pcOut->mRootNode->mChildren[a] = new aiNode(); + pcNode->mParent = pcOut->mRootNode; + pcNode->mMeshes = new unsigned int[1]; + pcNode->mMeshes[0] = i; + pcNode->mNumMeshes = 1; + + // Build a name for the node + pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "3DSMesh_%u",i); + } + + // Build dummy nodes for all cameras + for (unsigned int i = 0; i < (unsigned int )mScene->mCameras.size();++i,++a) + { + aiNode* pcNode = pcOut->mRootNode->mChildren[a] = new aiNode(); + pcNode->mParent = pcOut->mRootNode; + + // Build a name for the node + pcNode->mName = mScene->mCameras[i]->mName; + } + + // Build dummy nodes for all lights + for (unsigned int i = 0; i < (unsigned int )mScene->mLights.size();++i,++a) + { + aiNode* pcNode = pcOut->mRootNode->mChildren[a] = new aiNode(); + pcNode->mParent = pcOut->mRootNode; + + // Build a name for the node + pcNode->mName = mScene->mLights[i]->mName; + } + } + else + { + // First of all: find out how many scaling, rotation and translation + // animation tracks we'll have afterwards + unsigned int numChannel = 0; + CountTracks(mRootNode,numChannel); + + if (numChannel) + { + // Allocate a primary animation channel + pcOut->mNumAnimations = 1; + pcOut->mAnimations = new aiAnimation*[1]; + aiAnimation* anim = pcOut->mAnimations[0] = new aiAnimation(); + + anim->mName.Set("3DSMasterAnim"); + + // Allocate enough storage for all node animation channels, + // but don't set the mNumChannels member - we'll use it to + // index into the array + anim->mChannels = new aiNodeAnim*[numChannel]; + } + + aiMatrix4x4 m; + AddNodeToGraph(pcOut, pcOut->mRootNode, mRootNode,m); + } + + // We used the first and second vertex color set to store some temporary values so we need to cleanup here + for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) + { + pcOut->mMeshes[a]->mColors[0] = NULL; + pcOut->mMeshes[a]->mColors[1] = NULL; + } + + pcOut->mRootNode->mTransformation = aiMatrix4x4( + 1.f,0.f,0.f,0.f, + 0.f,0.f,1.f,0.f, + 0.f,-1.f,0.f,0.f, + 0.f,0.f,0.f,1.f) * pcOut->mRootNode->mTransformation; + + // If the root node is unnamed name it "<3DSRoot>" + if (::strstr( pcOut->mRootNode->mName.data, "UNNAMED" ) || + (pcOut->mRootNode->mName.data[0] == '$' && pcOut->mRootNode->mName.data[1] == '$') ) + { + pcOut->mRootNode->mName.Set("<3DSRoot>"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Convert all meshes in the scene and generate the final output scene. +void Discreet3DSImporter::ConvertScene(aiScene* pcOut) +{ + // Allocate enough storage for all output materials + pcOut->mNumMaterials = (unsigned int)mScene->mMaterials.size(); + pcOut->mMaterials = new aiMaterial*[pcOut->mNumMaterials]; + + // ... and convert the 3DS materials to aiMaterial's + for (unsigned int i = 0; i < pcOut->mNumMaterials;++i) + { + aiMaterial* pcNew = new aiMaterial(); + ConvertMaterial(mScene->mMaterials[i],*pcNew); + pcOut->mMaterials[i] = pcNew; + } + + // Generate the output mesh list + ConvertMeshes(pcOut); + + // Now copy all light sources to the output scene + pcOut->mNumLights = (unsigned int)mScene->mLights.size(); + if (pcOut->mNumLights) + { + pcOut->mLights = new aiLight*[pcOut->mNumLights]; + ::memcpy(pcOut->mLights,&mScene->mLights[0],sizeof(void*)*pcOut->mNumLights); + } + + // Now copy all cameras to the output scene + pcOut->mNumCameras = (unsigned int)mScene->mCameras.size(); + if (pcOut->mNumCameras) + { + pcOut->mCameras = new aiCamera*[pcOut->mNumCameras]; + ::memcpy(pcOut->mCameras,&mScene->mCameras[0],sizeof(void*)*pcOut->mNumCameras); + } +} + +#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER diff --git a/thirdparty/assimp/code/3DS/3DSExporter.cpp b/thirdparty/assimp/code/3DS/3DSExporter.cpp new file mode 100644 index 0000000..1117a52 --- /dev/null +++ b/thirdparty/assimp/code/3DS/3DSExporter.cpp @@ -0,0 +1,582 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_3DS_EXPORTER + +#include "3DS/3DSExporter.h" +#include "3DS/3DSLoader.h" +#include "3DS/3DSHelper.h" +#include "PostProcessing/SplitLargeMeshes.h" + +#include +#include +#include +#include +#include + +#include + +using namespace Assimp; +namespace Assimp { +using namespace D3DS; + +namespace { + + ////////////////////////////////////////////////////////////////////////////////////// + // Scope utility to write a 3DS file chunk. + // + // Upon construction, the chunk header is written with the chunk type (flags) + // filled out, but the chunk size left empty. Upon destruction, the correct chunk + // size based on the then-position of the output stream cursor is filled in. + class ChunkWriter { + enum { + CHUNK_SIZE_NOT_SET = 0xdeadbeef + , SIZE_OFFSET = 2 + }; + public: + + ChunkWriter(StreamWriterLE& writer, uint16_t chunk_type) + : writer(writer) + { + chunk_start_pos = writer.GetCurrentPos(); + writer.PutU2(chunk_type); + writer.PutU4(CHUNK_SIZE_NOT_SET); + } + + ~ChunkWriter() { + std::size_t head_pos = writer.GetCurrentPos(); + + ai_assert(head_pos > chunk_start_pos); + const std::size_t chunk_size = head_pos - chunk_start_pos; + + writer.SetCurrentPos(chunk_start_pos + SIZE_OFFSET); + writer.PutU4(static_cast(chunk_size)); + writer.SetCurrentPos(head_pos); + } + + private: + StreamWriterLE& writer; + std::size_t chunk_start_pos; + }; + + + // Return an unique name for a given |mesh| attached to |node| that + // preserves the mesh's given name if it has one. |index| is the index + // of the mesh in |aiScene::mMeshes|. + std::string GetMeshName(const aiMesh& mesh, unsigned int index, const aiNode& node) { + static const std::string underscore = "_"; + char postfix[10] = {0}; + ASSIMP_itoa10(postfix, index); + + std::string result = node.mName.C_Str(); + if (mesh.mName.length > 0) { + result += underscore + mesh.mName.C_Str(); + } + return result + underscore + postfix; + } + + // Return an unique name for a given |mat| with original position |index| + // in |aiScene::mMaterials|. The name preserves the original material + // name if possible. + std::string GetMaterialName(const aiMaterial& mat, unsigned int index) { + static const std::string underscore = "_"; + char postfix[10] = {0}; + ASSIMP_itoa10(postfix, index); + + aiString mat_name; + if (AI_SUCCESS == mat.Get(AI_MATKEY_NAME, mat_name)) { + return mat_name.C_Str() + underscore + postfix; + } + + return "Material" + underscore + postfix; + } + + // Collect world transformations for each node + void CollectTrafos(const aiNode* node, std::map& trafos) { + const aiMatrix4x4& parent = node->mParent ? trafos[node->mParent] : aiMatrix4x4(); + trafos[node] = parent * node->mTransformation; + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + CollectTrafos(node->mChildren[i], trafos); + } + } + + // Generate a flat list of the meshes (by index) assigned to each node + void CollectMeshes(const aiNode* node, std::multimap& meshes) { + for (unsigned int i = 0; i < node->mNumMeshes; ++i) { + meshes.insert(std::make_pair(node, node->mMeshes[i])); + } + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + CollectMeshes(node->mChildren[i], meshes); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Worker function for exporting a scene to 3DS. Prototyped and registered in Exporter.cpp +void ExportScene3DS(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) +{ + std::shared_ptr outfile (pIOSystem->Open(pFile, "wb")); + if(!outfile) { + throw DeadlyExportError("Could not open output .3ds file: " + std::string(pFile)); + } + + // TODO: This extra copy should be avoided and all of this made a preprocess + // requirement of the 3DS exporter. + // + // 3DS meshes can be max 0xffff (16 Bit) vertices and faces, respectively. + // SplitLargeMeshes can do this, but it requires the correct limit to be set + // which is not possible with the current way of specifying preprocess steps + // in |Exporter::ExportFormatEntry|. + aiScene* scenecopy_tmp; + SceneCombiner::CopyScene(&scenecopy_tmp,pScene); + std::unique_ptr scenecopy(scenecopy_tmp); + + SplitLargeMeshesProcess_Triangle tri_splitter; + tri_splitter.SetLimit(0xffff); + tri_splitter.Execute(scenecopy.get()); + + SplitLargeMeshesProcess_Vertex vert_splitter; + vert_splitter.SetLimit(0xffff); + vert_splitter.Execute(scenecopy.get()); + + // Invoke the actual exporter + Discreet3DSExporter exporter(outfile, scenecopy.get()); +} + +} // end of namespace Assimp + +// ------------------------------------------------------------------------------------------------ +Discreet3DSExporter:: Discreet3DSExporter(std::shared_ptr &outfile, const aiScene* scene) +: scene(scene) +, writer(outfile) +{ + CollectTrafos(scene->mRootNode, trafos); + CollectMeshes(scene->mRootNode, meshes); + + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAIN); + + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_OBJMESH); + WriteMaterials(); + WriteMeshes(); + + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MASTER_SCALE); + writer.PutF4(1.0f); + } + } + + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_KEYFRAMER); + WriteHierarchy(*scene->mRootNode, -1, -1); + } +} + +// ------------------------------------------------------------------------------------------------ +Discreet3DSExporter::~Discreet3DSExporter() { + // empty +} + + +// ------------------------------------------------------------------------------------------------ +int Discreet3DSExporter::WriteHierarchy(const aiNode& node, int seq, int sibling_level) +{ + // 3DS scene hierarchy is serialized as in http://www.martinreddy.net/gfx/3d/3DS.spec + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKINFO); + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME); + + // Assimp node names are unique and distinct from all mesh-node + // names we generate; thus we can use them as-is + WriteString(node.mName); + + // Two unknown int16 values - it is even unclear if 0 is a safe value + // but luckily importers do not know better either. + writer.PutI4(0); + + int16_t hierarchy_pos = static_cast(seq); + if (sibling_level != -1) { + hierarchy_pos = sibling_level; + } + + // Write the hierarchy position + writer.PutI2(hierarchy_pos); + } + } + + // TODO: write transformation chunks + + ++seq; + sibling_level = seq; + + // Write all children + for (unsigned int i = 0; i < node.mNumChildren; ++i) { + seq = WriteHierarchy(*node.mChildren[i], seq, i == 0 ? -1 : sibling_level); + } + + // Write all meshes as separate nodes to be able to reference the meshes by name + for (unsigned int i = 0; i < node.mNumMeshes; ++i) { + const bool first_child = node.mNumChildren == 0 && i == 0; + + const unsigned int mesh_idx = node.mMeshes[i]; + const aiMesh& mesh = *scene->mMeshes[mesh_idx]; + + ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKINFO); + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME); + WriteString(GetMeshName(mesh, mesh_idx, node)); + + writer.PutI4(0); + writer.PutI2(static_cast(first_child ? seq : sibling_level)); + ++seq; + } + } + return seq; +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteMaterials() +{ + for (unsigned int i = 0; i < scene->mNumMaterials; ++i) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MATERIAL); + const aiMaterial& mat = *scene->mMaterials[i]; + + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MATNAME); + const std::string& name = GetMaterialName(mat, i); + WriteString(name); + } + + aiColor3D color; + if (mat.Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_DIFFUSE); + WriteColor(color); + } + + if (mat.Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SPECULAR); + WriteColor(color); + } + + if (mat.Get(AI_MATKEY_COLOR_AMBIENT, color) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_AMBIENT); + WriteColor(color); + } + + if (mat.Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SELF_ILLUM); + WriteColor(color); + } + + aiShadingMode shading_mode = aiShadingMode_Flat; + if (mat.Get(AI_MATKEY_SHADING_MODEL, shading_mode) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHADING); + + Discreet3DS::shadetype3ds shading_mode_out; + switch(shading_mode) { + case aiShadingMode_Flat: + case aiShadingMode_NoShading: + shading_mode_out = Discreet3DS::Flat; + break; + + case aiShadingMode_Gouraud: + case aiShadingMode_Toon: + case aiShadingMode_OrenNayar: + case aiShadingMode_Minnaert: + shading_mode_out = Discreet3DS::Gouraud; + break; + + case aiShadingMode_Phong: + case aiShadingMode_Blinn: + case aiShadingMode_CookTorrance: + case aiShadingMode_Fresnel: + shading_mode_out = Discreet3DS::Phong; + break; + + default: + shading_mode_out = Discreet3DS::Flat; + ai_assert(false); + }; + writer.PutU2(static_cast(shading_mode_out)); + } + + + float f; + if (mat.Get(AI_MATKEY_SHININESS, f) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS); + WritePercentChunk(f); + } + + if (mat.Get(AI_MATKEY_SHININESS_STRENGTH, f) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS_PERCENT); + WritePercentChunk(f); + } + + int twosided; + if (mat.Get(AI_MATKEY_TWOSIDED, twosided) == AI_SUCCESS && twosided != 0) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_TWO_SIDE); + writer.PutI2(1); + } + + WriteTexture(mat, aiTextureType_DIFFUSE, Discreet3DS::CHUNK_MAT_TEXTURE); + WriteTexture(mat, aiTextureType_HEIGHT, Discreet3DS::CHUNK_MAT_BUMPMAP); + WriteTexture(mat, aiTextureType_OPACITY, Discreet3DS::CHUNK_MAT_OPACMAP); + WriteTexture(mat, aiTextureType_SHININESS, Discreet3DS::CHUNK_MAT_MAT_SHINMAP); + WriteTexture(mat, aiTextureType_SPECULAR, Discreet3DS::CHUNK_MAT_SPECMAP); + WriteTexture(mat, aiTextureType_EMISSIVE, Discreet3DS::CHUNK_MAT_SELFIMAP); + WriteTexture(mat, aiTextureType_REFLECTION, Discreet3DS::CHUNK_MAT_REFLMAP); + } +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteTexture(const aiMaterial& mat, aiTextureType type, uint16_t chunk_flags) +{ + aiString path; + aiTextureMapMode map_mode[2] = { + aiTextureMapMode_Wrap, aiTextureMapMode_Wrap + }; + ai_real blend = 1.0; + if (mat.GetTexture(type, 0, &path, NULL, NULL, &blend, NULL, map_mode) != AI_SUCCESS || !path.length) { + return; + } + + // TODO: handle embedded textures properly + if (path.data[0] == '*') { + ASSIMP_LOG_ERROR("Ignoring embedded texture for export: " + std::string(path.C_Str())); + return; + } + + ChunkWriter chunk(writer, chunk_flags); + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAPFILE); + WriteString(path); + } + + WritePercentChunk(blend); + + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MAP_TILING); + uint16_t val = 0; // WRAP + if (map_mode[0] == aiTextureMapMode_Mirror) { + val = 0x2; + } + else if (map_mode[0] == aiTextureMapMode_Decal) { + val = 0x10; + } + writer.PutU2(val); + } + // TODO: export texture transformation (i.e. UV offset, scale, rotation) +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteMeshes() +{ + // NOTE: 3DS allows for instances. However: + // i) not all importers support reading them + // ii) instances are not as flexible as they are in assimp, in particular, + // nodes can carry (and instance) only one mesh. + // + // This exporter currently deep clones all instanced meshes, i.e. for each mesh + // attached to a node a full TRIMESH chunk is written to the file. + // + // Furthermore, the TRIMESH is transformed into world space so that it will + // appear correctly if importers don't read the scene hierarchy at all. + for (MeshesByNodeMap::const_iterator it = meshes.begin(); it != meshes.end(); ++it) { + const aiNode& node = *(*it).first; + const unsigned int mesh_idx = (*it).second; + + const aiMesh& mesh = *scene->mMeshes[mesh_idx]; + + // This should not happen if the SLM step is correctly executed + // before the scene is handed to the exporter + ai_assert(mesh.mNumVertices <= 0xffff); + ai_assert(mesh.mNumFaces <= 0xffff); + + const aiMatrix4x4& trafo = trafos[&node]; + + ChunkWriter chunk(writer, Discreet3DS::CHUNK_OBJBLOCK); + + // Mesh name is tied to the node it is attached to so it can later be referenced + const std::string& name = GetMeshName(mesh, mesh_idx, node); + WriteString(name); + + + // TRIMESH chunk + ChunkWriter chunk2(writer, Discreet3DS::CHUNK_TRIMESH); + + // Vertices in world space + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_VERTLIST); + + const uint16_t count = static_cast(mesh.mNumVertices); + writer.PutU2(count); + for (unsigned int i = 0; i < mesh.mNumVertices; ++i) { + const aiVector3D& v = trafo * mesh.mVertices[i]; + writer.PutF4(v.x); + writer.PutF4(v.y); + writer.PutF4(v.z); + } + } + + // UV coordinates + if (mesh.HasTextureCoords(0)) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAPLIST); + const uint16_t count = static_cast(mesh.mNumVertices); + writer.PutU2(count); + + for (unsigned int i = 0; i < mesh.mNumVertices; ++i) { + const aiVector3D& v = mesh.mTextureCoords[0][i]; + writer.PutF4(v.x); + writer.PutF4(v.y); + } + } + + // Faces (indices) + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_FACELIST); + + ai_assert(mesh.mNumFaces <= 0xffff); + + // Count triangles, discard lines and points + uint16_t count = 0; + for (unsigned int i = 0; i < mesh.mNumFaces; ++i) { + const aiFace& f = mesh.mFaces[i]; + if (f.mNumIndices < 3) { + continue; + } + // TRIANGULATE step is a pre-requisite so we should not see polys here + ai_assert(f.mNumIndices == 3); + ++count; + } + + writer.PutU2(count); + for (unsigned int i = 0; i < mesh.mNumFaces; ++i) { + const aiFace& f = mesh.mFaces[i]; + if (f.mNumIndices < 3) { + continue; + } + + for (unsigned int j = 0; j < 3; ++j) { + ai_assert(f.mIndices[j] <= 0xffff); + writer.PutI2(static_cast(f.mIndices[j])); + } + + // Edge visibility flag + writer.PutI2(0x0); + } + + // TODO: write smoothing groups (CHUNK_SMOOLIST) + + WriteFaceMaterialChunk(mesh); + } + + // Transformation matrix by which the mesh vertices have been pre-transformed with. + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRMATRIX); + for (unsigned int r = 0; r < 4; ++r) { + for (unsigned int c = 0; c < 3; ++c) { + writer.PutF4(trafo[r][c]); + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteFaceMaterialChunk(const aiMesh& mesh) +{ + ChunkWriter chunk(writer, Discreet3DS::CHUNK_FACEMAT); + const std::string& name = GetMaterialName(*scene->mMaterials[mesh.mMaterialIndex], mesh.mMaterialIndex); + WriteString(name); + + // Because assimp splits meshes by material, only a single + // FACEMAT chunk needs to be written + ai_assert(mesh.mNumFaces <= 0xffff); + const uint16_t count = static_cast(mesh.mNumFaces); + writer.PutU2(count); + + for (unsigned int i = 0; i < mesh.mNumFaces; ++i) { + writer.PutU2(static_cast(i)); + } +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteString(const std::string& s) { + for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) { + writer.PutI1(*it); + } + writer.PutI1('\0'); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteString(const aiString& s) { + for (std::size_t i = 0; i < s.length; ++i) { + writer.PutI1(s.data[i]); + } + writer.PutI1('\0'); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteColor(const aiColor3D& color) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_RGBF); + writer.PutF4(color.r); + writer.PutF4(color.g); + writer.PutF4(color.b); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WritePercentChunk(float f) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_PERCENTF); + writer.PutF4(f); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WritePercentChunk(double f) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_PERCENTD); + writer.PutF8(f); +} + + +#endif // ASSIMP_BUILD_NO_3DS_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/thirdparty/assimp/code/3DS/3DSExporter.h b/thirdparty/assimp/code/3DS/3DSExporter.h new file mode 100644 index 0000000..035b562 --- /dev/null +++ b/thirdparty/assimp/code/3DS/3DSExporter.h @@ -0,0 +1,98 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file 3DSExporter.h + * 3DS Exporter Main Header + */ +#ifndef AI_3DSEXPORTER_H_INC +#define AI_3DSEXPORTER_H_INC + +#include +#include + +#include +#include + +struct aiScene; +struct aiNode; +struct aiMaterial; +struct aiMesh; + +namespace Assimp +{ + +// ------------------------------------------------------------------------------------------------ +/** + * @brief Helper class to export a given scene to a 3DS file. + */ +// ------------------------------------------------------------------------------------------------ +class Discreet3DSExporter { +public: + Discreet3DSExporter(std::shared_ptr &outfile, const aiScene* pScene); + ~Discreet3DSExporter(); + +private: + void WriteMeshes(); + void WriteMaterials(); + void WriteTexture(const aiMaterial& mat, aiTextureType type, uint16_t chunk_flags); + void WriteFaceMaterialChunk(const aiMesh& mesh); + int WriteHierarchy(const aiNode& node, int level, int sibling_level); + void WriteString(const std::string& s); + void WriteString(const aiString& s); + void WriteColor(const aiColor3D& color); + void WritePercentChunk(float f); + void WritePercentChunk(double f); + +private: + const aiScene* const scene; + StreamWriterLE writer; + + std::map trafos; + + typedef std::multimap MeshesByNodeMap; + MeshesByNodeMap meshes; + +}; + +} // Namespace Assimp + +#endif // AI_3DSEXPORTER_H_INC diff --git a/thirdparty/assimp/code/3DS/3DSHelper.h b/thirdparty/assimp/code/3DS/3DSHelper.h new file mode 100644 index 0000000..8eb4cd9 --- /dev/null +++ b/thirdparty/assimp/code/3DS/3DSHelper.h @@ -0,0 +1,652 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file Defines helper data structures for the import of 3DS files */ + +#ifndef AI_3DSFILEHELPER_H_INC +#define AI_3DSFILEHELPER_H_INC + +#include +#include +#include +#include +#include +#include +#include +#include +#include //sprintf + +namespace Assimp { +namespace D3DS { + +#include + +// --------------------------------------------------------------------------- +/** Discreet3DS class: Helper class for loading 3ds files. Defines chunks +* and data structures. +*/ +class Discreet3DS { +private: + Discreet3DS() AI_NO_EXCEPT { + // empty + } + + ~Discreet3DS() { + // empty + } + +public: + //! data structure for a single chunk in a .3ds file + struct Chunk { + uint16_t Flag; + uint32_t Size; + } PACK_STRUCT; + + + //! Used for shading field in material3ds structure + //! From AutoDesk 3ds SDK + typedef enum + { + // translated to gouraud shading with wireframe active + Wire = 0x0, + + // if this material is set, no vertex normals will + // be calculated for the model. Face normals + gouraud + Flat = 0x1, + + // standard gouraud shading + Gouraud = 0x2, + + // phong shading + Phong = 0x3, + + // cooktorrance or anistropic phong shading ... + // the exact meaning is unknown, if you know it + // feel free to tell me ;-) + Metal = 0x4, + + // required by the ASE loader + Blinn = 0x5 + } shadetype3ds; + + // Flags for animated keys + enum + { + KEY_USE_TENS = 0x1, + KEY_USE_CONT = 0x2, + KEY_USE_BIAS = 0x4, + KEY_USE_EASE_TO = 0x8, + KEY_USE_EASE_FROM = 0x10 + } ; + + enum + { + + // ******************************************************************** + // Basic chunks which can be found everywhere in the file + CHUNK_VERSION = 0x0002, + CHUNK_RGBF = 0x0010, // float4 R; float4 G; float4 B + CHUNK_RGBB = 0x0011, // int1 R; int1 G; int B + + // Linear color values (gamma = 2.2?) + CHUNK_LINRGBF = 0x0013, // float4 R; float4 G; float4 B + CHUNK_LINRGBB = 0x0012, // int1 R; int1 G; int B + + CHUNK_PERCENTW = 0x0030, // int2 percentage + CHUNK_PERCENTF = 0x0031, // float4 percentage + CHUNK_PERCENTD = 0x0032, // float8 percentage + // ******************************************************************** + + // Prj master chunk + CHUNK_PRJ = 0xC23D, + + // MDLI master chunk + CHUNK_MLI = 0x3DAA, + + // Primary main chunk of the .3ds file + CHUNK_MAIN = 0x4D4D, + + // Mesh main chunk + CHUNK_OBJMESH = 0x3D3D, + + // Specifies the background color of the .3ds file + // This is passed through the material system for + // viewing purposes. + CHUNK_BKGCOLOR = 0x1200, + + // Specifies the ambient base color of the scene. + // This is added to all materials in the file + CHUNK_AMBCOLOR = 0x2100, + + // Specifies the background image for the whole scene + // This value is passed through the material system + // to the viewer + CHUNK_BIT_MAP = 0x1100, + CHUNK_BIT_MAP_EXISTS = 0x1101, + + // ******************************************************************** + // Viewport related stuff. Ignored + CHUNK_DEFAULT_VIEW = 0x3000, + CHUNK_VIEW_TOP = 0x3010, + CHUNK_VIEW_BOTTOM = 0x3020, + CHUNK_VIEW_LEFT = 0x3030, + CHUNK_VIEW_RIGHT = 0x3040, + CHUNK_VIEW_FRONT = 0x3050, + CHUNK_VIEW_BACK = 0x3060, + CHUNK_VIEW_USER = 0x3070, + CHUNK_VIEW_CAMERA = 0x3080, + // ******************************************************************** + + // Mesh chunks + CHUNK_OBJBLOCK = 0x4000, + CHUNK_TRIMESH = 0x4100, + CHUNK_VERTLIST = 0x4110, + CHUNK_VERTFLAGS = 0x4111, + CHUNK_FACELIST = 0x4120, + CHUNK_FACEMAT = 0x4130, + CHUNK_MAPLIST = 0x4140, + CHUNK_SMOOLIST = 0x4150, + CHUNK_TRMATRIX = 0x4160, + CHUNK_MESHCOLOR = 0x4165, + CHUNK_TXTINFO = 0x4170, + CHUNK_LIGHT = 0x4600, + CHUNK_CAMERA = 0x4700, + CHUNK_HIERARCHY = 0x4F00, + + // Specifies the global scaling factor. This is applied + // to the root node's transformation matrix + CHUNK_MASTER_SCALE = 0x0100, + + // ******************************************************************** + // Material chunks + CHUNK_MAT_MATERIAL = 0xAFFF, + + // asciiz containing the name of the material + CHUNK_MAT_MATNAME = 0xA000, + CHUNK_MAT_AMBIENT = 0xA010, // followed by color chunk + CHUNK_MAT_DIFFUSE = 0xA020, // followed by color chunk + CHUNK_MAT_SPECULAR = 0xA030, // followed by color chunk + + // Specifies the shininess of the material + // followed by percentage chunk + CHUNK_MAT_SHININESS = 0xA040, + CHUNK_MAT_SHININESS_PERCENT = 0xA041 , + + // Specifies the shading mode to be used + // followed by a short + CHUNK_MAT_SHADING = 0xA100, + + // NOTE: Emissive color (self illumination) seems not + // to be a color but a single value, type is unknown. + // Make the parser accept both of them. + // followed by percentage chunk (?) + CHUNK_MAT_SELF_ILLUM = 0xA080, + + // Always followed by percentage chunk (?) + CHUNK_MAT_SELF_ILPCT = 0xA084, + + // Always followed by percentage chunk + CHUNK_MAT_TRANSPARENCY = 0xA050, + + // Diffuse texture channel 0 + CHUNK_MAT_TEXTURE = 0xA200, + + // Contains opacity information for each texel + CHUNK_MAT_OPACMAP = 0xA210, + + // Contains a reflection map to be used to reflect + // the environment. This is partially supported. + CHUNK_MAT_REFLMAP = 0xA220, + + // Self Illumination map (emissive colors) + CHUNK_MAT_SELFIMAP = 0xA33d, + + // Bumpmap. Not specified whether it is a heightmap + // or a normal map. Assme it is a heightmap since + // artist normally prefer this format. + CHUNK_MAT_BUMPMAP = 0xA230, + + // Specular map. Seems to influence the specular color + CHUNK_MAT_SPECMAP = 0xA204, + + // Holds shininess data. + CHUNK_MAT_MAT_SHINMAP = 0xA33C, + + // Scaling in U/V direction. + // (need to gen separate UV coordinate set + // and do this by hand) + CHUNK_MAT_MAP_USCALE = 0xA354, + CHUNK_MAT_MAP_VSCALE = 0xA356, + + // Translation in U/V direction. + // (need to gen separate UV coordinate set + // and do this by hand) + CHUNK_MAT_MAP_UOFFSET = 0xA358, + CHUNK_MAT_MAP_VOFFSET = 0xA35a, + + // UV-coordinates rotation around the z-axis + // Assumed to be in radians. + CHUNK_MAT_MAP_ANG = 0xA35C, + + // Tiling flags for 3DS files + CHUNK_MAT_MAP_TILING = 0xa351, + + // Specifies the file name of a texture + CHUNK_MAPFILE = 0xA300, + + // Specifies whether a materail requires two-sided rendering + CHUNK_MAT_TWO_SIDE = 0xA081, + // ******************************************************************** + + // Main keyframer chunk. Contains translation/rotation/scaling data + CHUNK_KEYFRAMER = 0xB000, + + // Supported sub chunks + CHUNK_TRACKINFO = 0xB002, + CHUNK_TRACKOBJNAME = 0xB010, + CHUNK_TRACKDUMMYOBJNAME = 0xB011, + CHUNK_TRACKPIVOT = 0xB013, + CHUNK_TRACKPOS = 0xB020, + CHUNK_TRACKROTATE = 0xB021, + CHUNK_TRACKSCALE = 0xB022, + + // ******************************************************************** + // Keyframes for various other stuff in the file + // Partially ignored + CHUNK_AMBIENTKEY = 0xB001, + CHUNK_TRACKMORPH = 0xB026, + CHUNK_TRACKHIDE = 0xB029, + CHUNK_OBJNUMBER = 0xB030, + CHUNK_TRACKCAMERA = 0xB003, + CHUNK_TRACKFOV = 0xB023, + CHUNK_TRACKROLL = 0xB024, + CHUNK_TRACKCAMTGT = 0xB004, + CHUNK_TRACKLIGHT = 0xB005, + CHUNK_TRACKLIGTGT = 0xB006, + CHUNK_TRACKSPOTL = 0xB007, + CHUNK_FRAMES = 0xB008, + // ******************************************************************** + + // light sub-chunks + CHUNK_DL_OFF = 0x4620, + CHUNK_DL_OUTER_RANGE = 0x465A, + CHUNK_DL_INNER_RANGE = 0x4659, + CHUNK_DL_MULTIPLIER = 0x465B, + CHUNK_DL_EXCLUDE = 0x4654, + CHUNK_DL_ATTENUATE = 0x4625, + CHUNK_DL_SPOTLIGHT = 0x4610, + + // camera sub-chunks + CHUNK_CAM_RANGES = 0x4720 + }; +}; + +// --------------------------------------------------------------------------- +/** Helper structure representing a 3ds mesh face */ +struct Face : public FaceWithSmoothingGroup +{ +}; + +// --------------------------------------------------------------------------- +/** Helper structure representing a texture */ +struct Texture { + //! Default constructor + Texture() AI_NO_EXCEPT + : mOffsetU (0.0) + , mOffsetV (0.0) + , mScaleU (1.0) + , mScaleV (1.0) + , mRotation (0.0) + , mMapMode (aiTextureMapMode_Wrap) + , bPrivate() + , iUVSrc (0) { + mTextureBlend = get_qnan(); + } + + //! Specifies the blend factor for the texture + ai_real mTextureBlend; + + //! Specifies the filename of the texture + std::string mMapName; + + //! Specifies texture coordinate offsets/scaling/rotations + ai_real mOffsetU; + ai_real mOffsetV; + ai_real mScaleU; + ai_real mScaleV; + ai_real mRotation; + + //! Specifies the mapping mode to be used for the texture + aiTextureMapMode mMapMode; + + //! Used internally + bool bPrivate; + int iUVSrc; +}; + +#include + +// --------------------------------------------------------------------------- +/** Helper structure representing a 3ds material */ +struct Material +{ + //! Default constructor has been deleted + Material() = delete; + + + //! Constructor with explicit name + explicit Material(const std::string &name) + : mName(name) + , mDiffuse ( ai_real( 0.6 ), ai_real( 0.6 ), ai_real( 0.6 ) ) // FIX ... we won't want object to be black + , mSpecularExponent ( ai_real( 0.0 ) ) + , mShininessStrength ( ai_real( 1.0 ) ) + , mShading(Discreet3DS::Gouraud) + , mTransparency ( ai_real( 1.0 ) ) + , mBumpHeight ( ai_real( 1.0 ) ) + , mTwoSided (false) + { + } + + + Material(const Material &other) = default; + Material &operator=(const Material &other) = default; + + + //! Move constructor. This is explicitly written because MSVC doesn't support defaulting it + Material(Material &&other) AI_NO_EXCEPT + : mName(std::move(other.mName)) + , mDiffuse(std::move(other.mDiffuse)) + , mSpecularExponent(std::move(other.mSpecularExponent)) + , mShininessStrength(std::move(other.mShininessStrength)) + , mSpecular(std::move(other.mSpecular)) + , mAmbient(std::move(other.mAmbient)) + , mShading(std::move(other.mShading)) + , mTransparency(std::move(other.mTransparency)) + , sTexDiffuse(std::move(other.sTexDiffuse)) + , sTexOpacity(std::move(other.sTexOpacity)) + , sTexSpecular(std::move(other.sTexSpecular)) + , sTexReflective(std::move(other.sTexReflective)) + , sTexBump(std::move(other.sTexBump)) + , sTexEmissive(std::move(other.sTexEmissive)) + , sTexShininess(std::move(other.sTexShininess)) + , mBumpHeight(std::move(other.mBumpHeight)) + , mEmissive(std::move(other.mEmissive)) + , sTexAmbient(std::move(other.sTexAmbient)) + , mTwoSided(std::move(other.mTwoSided)) + { + } + + + Material &operator=(Material &&other) AI_NO_EXCEPT { + if (this == &other) { + return *this; + } + + mName = std::move(other.mName); + mDiffuse = std::move(other.mDiffuse); + mSpecularExponent = std::move(other.mSpecularExponent); + mShininessStrength = std::move(other.mShininessStrength), + mSpecular = std::move(other.mSpecular); + mAmbient = std::move(other.mAmbient); + mShading = std::move(other.mShading); + mTransparency = std::move(other.mTransparency); + sTexDiffuse = std::move(other.sTexDiffuse); + sTexOpacity = std::move(other.sTexOpacity); + sTexSpecular = std::move(other.sTexSpecular); + sTexReflective = std::move(other.sTexReflective); + sTexBump = std::move(other.sTexBump); + sTexEmissive = std::move(other.sTexEmissive); + sTexShininess = std::move(other.sTexShininess); + mBumpHeight = std::move(other.mBumpHeight); + mEmissive = std::move(other.mEmissive); + sTexAmbient = std::move(other.sTexAmbient); + mTwoSided = std::move(other.mTwoSided); + + return *this; + } + + + virtual ~Material() {} + + + //! Name of the material + std::string mName; + //! Diffuse color of the material + aiColor3D mDiffuse; + //! Specular exponent + ai_real mSpecularExponent; + //! Shininess strength, in percent + ai_real mShininessStrength; + //! Specular color of the material + aiColor3D mSpecular; + //! Ambient color of the material + aiColor3D mAmbient; + //! Shading type to be used + Discreet3DS::shadetype3ds mShading; + //! Opacity of the material + ai_real mTransparency; + //! Diffuse texture channel + Texture sTexDiffuse; + //! Opacity texture channel + Texture sTexOpacity; + //! Specular texture channel + Texture sTexSpecular; + //! Reflective texture channel + Texture sTexReflective; + //! Bump texture channel + Texture sTexBump; + //! Emissive texture channel + Texture sTexEmissive; + //! Shininess texture channel + Texture sTexShininess; + //! Scaling factor for the bump values + ai_real mBumpHeight; + //! Emissive color + aiColor3D mEmissive; + //! Ambient texture channel + //! (used by the ASE format) + Texture sTexAmbient; + //! True if the material must be rendered from two sides + bool mTwoSided; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent a 3ds file mesh */ +struct Mesh : public MeshWithSmoothingGroups +{ + //! Default constructor has been deleted + Mesh() = delete; + + //! Constructor with explicit name + explicit Mesh(const std::string &name) + : mName(name) + { + } + + + //! Name of the mesh + std::string mName; + + //! Texture coordinates + std::vector mTexCoords; + + //! Face materials + std::vector mFaceMaterials; + + //! Local transformation matrix + aiMatrix4x4 mMat; +}; + +// --------------------------------------------------------------------------- +/** Float key - quite similar to aiVectorKey and aiQuatKey. Both are in the + C-API, so it would be difficult to make them a template. */ +struct aiFloatKey +{ + double mTime; ///< The time of this key + ai_real mValue; ///< The value of this key + +#ifdef __cplusplus + + // time is not compared + bool operator == (const aiFloatKey& o) const + {return o.mValue == this->mValue;} + + bool operator != (const aiFloatKey& o) const + {return o.mValue != this->mValue;} + + // Only time is compared. This operator is defined + // for use with std::sort + bool operator < (const aiFloatKey& o) const + {return mTime < o.mTime;} + + bool operator > (const aiFloatKey& o) const + {return mTime > o.mTime;} + +#endif +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent a 3ds file node */ +struct Node +{ + Node() = delete; + + explicit Node(const std::string &name) + : mParent(NULL) + , mName(name) + , mInstanceNumber(0) + , mHierarchyPos (0) + , mHierarchyIndex (0) + , mInstanceCount (1) + { + aRotationKeys.reserve (20); + aPositionKeys.reserve (20); + aScalingKeys.reserve (20); + } + + + ~Node() + { + for (unsigned int i = 0; i < mChildren.size();++i) + delete mChildren[i]; + } + + //! Pointer to the parent node + Node* mParent; + + //! Holds all child nodes + std::vector mChildren; + + //! Name of the node + std::string mName; + + //! InstanceNumber of the node + int32_t mInstanceNumber; + + //! Dummy nodes: real name to be combined with the $$$DUMMY + std::string mDummyName; + + //! Position of the node in the hierarchy (tree depth) + int16_t mHierarchyPos; + + //! Index of the node + int16_t mHierarchyIndex; + + //! Rotation keys loaded from the file + std::vector aRotationKeys; + + //! Position keys loaded from the file + std::vector aPositionKeys; + + //! Scaling keys loaded from the file + std::vector aScalingKeys; + + + // For target lights (spot lights and directional lights): + // The position of the target + std::vector< aiVectorKey > aTargetPositionKeys; + + // For cameras: the camera roll angle + std::vector< aiFloatKey > aCameraRollKeys; + + //! Pivot position loaded from the file + aiVector3D vPivot; + + //instance count, will be kept only for the first node + int32_t mInstanceCount; + + //! Add a child node, setup the right parent node for it + //! \param pc Node to be 'adopted' + inline Node& push_back(Node* pc) + { + mChildren.push_back(pc); + pc->mParent = this; + return *this; + } +}; +// --------------------------------------------------------------------------- +/** Helper structure analogue to aiScene */ +struct Scene +{ + //! List of all materials loaded + //! NOTE: 3ds references materials globally + std::vector mMaterials; + + //! List of all meshes loaded + std::vector mMeshes; + + //! List of all cameras loaded + std::vector mCameras; + + //! List of all lights loaded + std::vector mLights; + + //! Pointer to the root node of the scene + // --- moved to main class + // Node* pcRootNode; +}; + + +} // end of namespace D3DS +} // end of namespace Assimp + +#endif // AI_XFILEHELPER_H_INC diff --git a/thirdparty/assimp/code/3DS/3DSLoader.cpp b/thirdparty/assimp/code/3DS/3DSLoader.cpp new file mode 100644 index 0000000..96b80c9 --- /dev/null +++ b/thirdparty/assimp/code/3DS/3DSLoader.cpp @@ -0,0 +1,1432 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/** @file 3DSLoader.cpp + * @brief Implementation of the 3ds importer class + * + * http://www.the-labs.com/Blender/3DS-details.html + */ + + +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +// internal headers +#include "3DSLoader.h" +#include +#include +#include +#include +#include +#include + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Discreet 3DS Importer", + "", + "", + "Limited animation support", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "3ds prj" +}; + + +// ------------------------------------------------------------------------------------------------ +// Begins a new parsing block +// - Reads the current chunk and validates it +// - computes its length +#define ASSIMP_3DS_BEGIN_CHUNK() \ + while (true) { \ + if (stream->GetRemainingSizeToLimit() < sizeof(Discreet3DS::Chunk)){ \ + return; \ + } \ + Discreet3DS::Chunk chunk; \ + ReadChunk(&chunk); \ + int chunkSize = chunk.Size-sizeof(Discreet3DS::Chunk); \ + if(chunkSize <= 0) \ + continue; \ + const unsigned int oldReadLimit = stream->SetReadLimit( \ + stream->GetCurrentPos() + chunkSize); \ + + +// ------------------------------------------------------------------------------------------------ +// End a parsing block +// Must follow at the end of each parsing block, reset chunk end marker to previous value +#define ASSIMP_3DS_END_CHUNK() \ + stream->SkipToReadLimit(); \ + stream->SetReadLimit(oldReadLimit); \ + if (stream->GetRemainingSizeToLimit() == 0) \ + return; \ + } + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +Discreet3DSImporter::Discreet3DSImporter() +: stream() +, mLastNodeIndex() +, mCurrentNode() +, mRootNode() +, mScene() +, mMasterScale() +, bHasBG() +, bIsPrj() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +Discreet3DSImporter::~Discreet3DSImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool Discreet3DSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const { + std::string extension = GetExtension(pFile); + if(extension == "3ds" || extension == "prj") { + return true; + } + + if (!extension.length() || checkSig) { + uint16_t token[3]; + token[0] = 0x4d4d; + token[1] = 0x3dc2; + //token[2] = 0x3daa; + return CheckMagicToken(pIOHandler,pFile,token,2,0,2); + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +// Loader registry entry +const aiImporterDesc* Discreet3DSImporter::GetInfo () const +{ + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties +void Discreet3DSImporter::SetupProperties(const Importer* /*pImp*/) +{ + // nothing to be done for the moment +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void Discreet3DSImporter::InternReadFile( const std::string& pFile, + aiScene* pScene, IOSystem* pIOHandler) +{ + StreamReaderLE stream(pIOHandler->Open(pFile,"rb")); + + // We should have at least one chunk + if (stream.GetRemainingSize() < 16) { + throw DeadlyImportError("3DS file is either empty or corrupt: " + pFile); + } + this->stream = &stream; + + // Allocate our temporary 3DS representation + D3DS::Scene _scene; + mScene = &_scene; + + // Initialize members + D3DS::Node _rootNode("UNNAMED"); + mLastNodeIndex = -1; + mCurrentNode = &_rootNode; + mRootNode = mCurrentNode; + mRootNode->mHierarchyPos = -1; + mRootNode->mHierarchyIndex = -1; + mRootNode->mParent = NULL; + mMasterScale = 1.0f; + mBackgroundImage = ""; + bHasBG = false; + bIsPrj = false; + + // Parse the file + ParseMainChunk(); + + // Process all meshes in the file. First check whether all + // face indices have valid values. The generate our + // internal verbose representation. Finally compute normal + // vectors from the smoothing groups we read from the + // file. + for (auto &mesh : mScene->mMeshes) { + if (mesh.mFaces.size() > 0 && mesh.mPositions.size() == 0) { + throw DeadlyImportError("3DS file contains faces but no vertices: " + pFile); + } + CheckIndices(mesh); + MakeUnique (mesh); + ComputeNormalsWithSmoothingsGroups(mesh); + } + + // Replace all occurences of the default material with a + // valid material. Generate it if no material containing + // DEFAULT in its name has been found in the file + ReplaceDefaultMaterial(); + + // Convert the scene from our internal representation to an + // aiScene object. This involves copying all meshes, lights + // and cameras to the scene + ConvertScene(pScene); + + // Generate the node graph for the scene. This is a little bit + // tricky since we'll need to split some meshes into sub-meshes + GenerateNodeGraph(pScene); + + // Now apply the master scaling factor to the scene + ApplyMasterScale(pScene); + + // Our internal scene representation and the root + // node will be automatically deleted, so the whole hierarchy will follow + + AI_DEBUG_INVALIDATE_PTR(mRootNode); + AI_DEBUG_INVALIDATE_PTR(mScene); + AI_DEBUG_INVALIDATE_PTR(this->stream); +} + +// ------------------------------------------------------------------------------------------------ +// Applies a master-scaling factor to the imported scene +void Discreet3DSImporter::ApplyMasterScale(aiScene* pScene) +{ + // There are some 3DS files with a zero scaling factor + if (!mMasterScale)mMasterScale = 1.0f; + else mMasterScale = 1.0f / mMasterScale; + + // Construct an uniform scaling matrix and multiply with it + pScene->mRootNode->mTransformation *= aiMatrix4x4( + mMasterScale,0.0f, 0.0f, 0.0f, + 0.0f, mMasterScale,0.0f, 0.0f, + 0.0f, 0.0f, mMasterScale,0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + + // Check whether a scaling track is assigned to the root node. +} + +// ------------------------------------------------------------------------------------------------ +// Reads a new chunk from the file +void Discreet3DSImporter::ReadChunk(Discreet3DS::Chunk* pcOut) +{ + ai_assert(pcOut != nullptr); + + pcOut->Flag = stream->GetI2(); + pcOut->Size = stream->GetI4(); + + if (pcOut->Size - sizeof(Discreet3DS::Chunk) > stream->GetRemainingSize()) { + throw DeadlyImportError("Chunk is too large"); + } + + if (pcOut->Size - sizeof(Discreet3DS::Chunk) > stream->GetRemainingSizeToLimit()) { + ASSIMP_LOG_ERROR("3DS: Chunk overflow"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Skip a chunk +void Discreet3DSImporter::SkipChunk() +{ + Discreet3DS::Chunk psChunk; + ReadChunk(&psChunk); + + stream->IncPtr(psChunk.Size-sizeof(Discreet3DS::Chunk)); + return; +} + +// ------------------------------------------------------------------------------------------------ +// Process the primary chunk of the file +void Discreet3DSImporter::ParseMainChunk() +{ + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) + { + + case Discreet3DS::CHUNK_PRJ: + bIsPrj = true; + case Discreet3DS::CHUNK_MAIN: + ParseEditorChunk(); + break; + }; + + ASSIMP_3DS_END_CHUNK(); + // recursively continue processing this hierarchy level + return ParseMainChunk(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseEditorChunk() +{ + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) + { + case Discreet3DS::CHUNK_OBJMESH: + + ParseObjectChunk(); + break; + + // NOTE: In several documentations in the internet this + // chunk appears at different locations + case Discreet3DS::CHUNK_KEYFRAMER: + + ParseKeyframeChunk(); + break; + + case Discreet3DS::CHUNK_VERSION: + { + // print the version number + char buff[10]; + ASSIMP_itoa10(buff,stream->GetI2()); + ASSIMP_LOG_INFO_F(std::string("3DS file format version: "), buff); + } + break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseObjectChunk() +{ + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) + { + case Discreet3DS::CHUNK_OBJBLOCK: + { + unsigned int cnt = 0; + const char* sz = (const char*)stream->GetPtr(); + + // Get the name of the geometry object + while (stream->GetI1())++cnt; + ParseChunk(sz,cnt); + } + break; + + case Discreet3DS::CHUNK_MAT_MATERIAL: + + // Add a new material to the list + mScene->mMaterials.push_back(D3DS::Material(std::string("UNNAMED_" + to_string(mScene->mMaterials.size())))); + ParseMaterialChunk(); + break; + + case Discreet3DS::CHUNK_AMBCOLOR: + + // This is the ambient base color of the scene. + // We add it to the ambient color of all materials + ParseColorChunk(&mClrAmbient,true); + if (is_qnan(mClrAmbient.r)) + { + // We failed to read the ambient base color. + ASSIMP_LOG_ERROR("3DS: Failed to read ambient base color"); + mClrAmbient.r = mClrAmbient.g = mClrAmbient.b = 0.0f; + } + break; + + case Discreet3DS::CHUNK_BIT_MAP: + { + // Specifies the background image. The string should already be + // properly 0 terminated but we need to be sure + unsigned int cnt = 0; + const char* sz = (const char*)stream->GetPtr(); + while (stream->GetI1())++cnt; + mBackgroundImage = std::string(sz,cnt); + } + break; + + case Discreet3DS::CHUNK_BIT_MAP_EXISTS: + bHasBG = true; + break; + + case Discreet3DS::CHUNK_MASTER_SCALE: + // Scene master scaling factor + mMasterScale = stream->GetF4(); + break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseChunk(const char* name, unsigned int num) +{ + ASSIMP_3DS_BEGIN_CHUNK(); + + // IMPLEMENTATION NOTE; + // Cameras or lights define their transformation in their parent node and in the + // corresponding light or camera chunks. However, we read and process the latter + // to to be able to return valid cameras/lights even if no scenegraph is given. + + // get chunk type + switch (chunk.Flag) + { + case Discreet3DS::CHUNK_TRIMESH: + { + // this starts a new triangle mesh + mScene->mMeshes.push_back(D3DS::Mesh(std::string(name, num))); + + // Read mesh chunks + ParseMeshChunk(); + } + break; + + case Discreet3DS::CHUNK_LIGHT: + { + // This starts a new light + aiLight* light = new aiLight(); + mScene->mLights.push_back(light); + + light->mName.Set(std::string(name, num)); + + // First read the position of the light + light->mPosition.x = stream->GetF4(); + light->mPosition.y = stream->GetF4(); + light->mPosition.z = stream->GetF4(); + + light->mColorDiffuse = aiColor3D(1.f,1.f,1.f); + + // Now check for further subchunks + if (!bIsPrj) /* fixme */ + ParseLightChunk(); + + // The specular light color is identical the the diffuse light color. The ambient light color + // is equal to the ambient base color of the whole scene. + light->mColorSpecular = light->mColorDiffuse; + light->mColorAmbient = mClrAmbient; + + if (light->mType == aiLightSource_UNDEFINED) + { + // It must be a point light + light->mType = aiLightSource_POINT; + }} + break; + + case Discreet3DS::CHUNK_CAMERA: + { + // This starts a new camera + aiCamera* camera = new aiCamera(); + mScene->mCameras.push_back(camera); + camera->mName.Set(std::string(name, num)); + + // First read the position of the camera + camera->mPosition.x = stream->GetF4(); + camera->mPosition.y = stream->GetF4(); + camera->mPosition.z = stream->GetF4(); + + // Then the camera target + camera->mLookAt.x = stream->GetF4() - camera->mPosition.x; + camera->mLookAt.y = stream->GetF4() - camera->mPosition.y; + camera->mLookAt.z = stream->GetF4() - camera->mPosition.z; + ai_real len = camera->mLookAt.Length(); + if (len < 1e-5) { + + // There are some files with lookat == position. Don't know why or whether it's ok or not. + ASSIMP_LOG_ERROR("3DS: Unable to read proper camera look-at vector"); + camera->mLookAt = aiVector3D(0.0,1.0,0.0); + + } + else camera->mLookAt /= len; + + // And finally - the camera rotation angle, in counter clockwise direction + const ai_real angle = AI_DEG_TO_RAD( stream->GetF4() ); + aiQuaternion quat(camera->mLookAt,angle); + camera->mUp = quat.GetMatrix() * aiVector3D(0.0,1.0,0.0); + + // Read the lense angle + camera->mHorizontalFOV = AI_DEG_TO_RAD ( stream->GetF4() ); + if (camera->mHorizontalFOV < 0.001f) { + camera->mHorizontalFOV = AI_DEG_TO_RAD(45.f); + } + + // Now check for further subchunks + if (!bIsPrj) /* fixme */ { + ParseCameraChunk(); + }} + break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseLightChunk() +{ + ASSIMP_3DS_BEGIN_CHUNK(); + aiLight* light = mScene->mLights.back(); + + // get chunk type + switch (chunk.Flag) + { + case Discreet3DS::CHUNK_DL_SPOTLIGHT: + // Now we can be sure that the light is a spot light + light->mType = aiLightSource_SPOT; + + // We wouldn't need to normalize here, but we do it + light->mDirection.x = stream->GetF4() - light->mPosition.x; + light->mDirection.y = stream->GetF4() - light->mPosition.y; + light->mDirection.z = stream->GetF4() - light->mPosition.z; + light->mDirection.Normalize(); + + // Now the hotspot and falloff angles - in degrees + light->mAngleInnerCone = AI_DEG_TO_RAD( stream->GetF4() ); + + // FIX: the falloff angle is just an offset + light->mAngleOuterCone = light->mAngleInnerCone+AI_DEG_TO_RAD( stream->GetF4() ); + break; + + // intensity multiplier + case Discreet3DS::CHUNK_DL_MULTIPLIER: + light->mColorDiffuse = light->mColorDiffuse * stream->GetF4(); + break; + + // light color + case Discreet3DS::CHUNK_RGBF: + case Discreet3DS::CHUNK_LINRGBF: + light->mColorDiffuse.r *= stream->GetF4(); + light->mColorDiffuse.g *= stream->GetF4(); + light->mColorDiffuse.b *= stream->GetF4(); + break; + + // light attenuation + case Discreet3DS::CHUNK_DL_ATTENUATE: + light->mAttenuationLinear = stream->GetF4(); + break; + }; + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseCameraChunk() +{ + ASSIMP_3DS_BEGIN_CHUNK(); + aiCamera* camera = mScene->mCameras.back(); + + // get chunk type + switch (chunk.Flag) + { + // near and far clip plane + case Discreet3DS::CHUNK_CAM_RANGES: + camera->mClipPlaneNear = stream->GetF4(); + camera->mClipPlaneFar = stream->GetF4(); + break; + } + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseKeyframeChunk() +{ + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) + { + case Discreet3DS::CHUNK_TRACKCAMTGT: + case Discreet3DS::CHUNK_TRACKSPOTL: + case Discreet3DS::CHUNK_TRACKCAMERA: + case Discreet3DS::CHUNK_TRACKINFO: + case Discreet3DS::CHUNK_TRACKLIGHT: + case Discreet3DS::CHUNK_TRACKLIGTGT: + + // this starts a new mesh hierarchy chunk + ParseHierarchyChunk(chunk.Flag); + break; + }; + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Little helper function for ParseHierarchyChunk +void Discreet3DSImporter::InverseNodeSearch(D3DS::Node* pcNode,D3DS::Node* pcCurrent) +{ + if (!pcCurrent) { + mRootNode->push_back(pcNode); + return; + } + + if (pcCurrent->mHierarchyPos == pcNode->mHierarchyPos) { + if(pcCurrent->mParent) { + pcCurrent->mParent->push_back(pcNode); + } + else pcCurrent->push_back(pcNode); + return; + } + return InverseNodeSearch(pcNode,pcCurrent->mParent); +} + +// ------------------------------------------------------------------------------------------------ +// Find a node with a specific name in the import hierarchy +D3DS::Node* FindNode(D3DS::Node* root, const std::string& name) +{ + if (root->mName == name) + return root; + for (std::vector::iterator it = root->mChildren.begin();it != root->mChildren.end(); ++it) { + D3DS::Node* nd; + if (( nd = FindNode(*it,name))) + return nd; + } + return NULL; +} + +// ------------------------------------------------------------------------------------------------ +// Binary predicate for std::unique() +template +bool KeyUniqueCompare(const T& first, const T& second) +{ + return first.mTime == second.mTime; +} + +// ------------------------------------------------------------------------------------------------ +// Skip some additional import data. +void Discreet3DSImporter::SkipTCBInfo() +{ + unsigned int flags = stream->GetI2(); + + if (!flags) { + // Currently we can't do anything with these values. They occur + // quite rare, so it wouldn't be worth the effort implementing + // them. 3DS is not really suitable for complex animations, + // so full support is not required. + ASSIMP_LOG_WARN("3DS: Skipping TCB animation info"); + } + + if (flags & Discreet3DS::KEY_USE_TENS) { + stream->IncPtr(4); + } + if (flags & Discreet3DS::KEY_USE_BIAS) { + stream->IncPtr(4); + } + if (flags & Discreet3DS::KEY_USE_CONT) { + stream->IncPtr(4); + } + if (flags & Discreet3DS::KEY_USE_EASE_FROM) { + stream->IncPtr(4); + } + if (flags & Discreet3DS::KEY_USE_EASE_TO) { + stream->IncPtr(4); + } +} + +// ------------------------------------------------------------------------------------------------ +// Read hierarchy and keyframe info +void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent) +{ + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) + { + case Discreet3DS::CHUNK_TRACKOBJNAME: + + // This is the name of the object to which the track applies. The chunk also + // defines the position of this object in the hierarchy. + { + + // First of all: get the name of the object + unsigned int cnt = 0; + const char* sz = (const char*)stream->GetPtr(); + + while (stream->GetI1())++cnt; + std::string name = std::string(sz,cnt); + + // Now find out whether we have this node already (target animation channels + // are stored with a separate object ID) + D3DS::Node* pcNode = FindNode(mRootNode,name); + int instanceNumber = 1; + + if ( pcNode) + { + // if the source is not a CHUNK_TRACKINFO block it won't be an object instance + if (parent != Discreet3DS::CHUNK_TRACKINFO) + { + mCurrentNode = pcNode; + break; + } + pcNode->mInstanceCount++; + instanceNumber = pcNode->mInstanceCount; + } + pcNode = new D3DS::Node(name); + pcNode->mInstanceNumber = instanceNumber; + + // There are two unknown values which we can safely ignore + stream->IncPtr(4); + + // Now read the hierarchy position of the object + uint16_t hierarchy = stream->GetI2() + 1; + pcNode->mHierarchyPos = hierarchy; + pcNode->mHierarchyIndex = mLastNodeIndex; + + // And find a proper position in the graph for it + if (mCurrentNode && mCurrentNode->mHierarchyPos == hierarchy) { + + // add to the parent of the last touched node + mCurrentNode->mParent->push_back(pcNode); + mLastNodeIndex++; + } + else if(hierarchy >= mLastNodeIndex) { + + // place it at the current position in the hierarchy + mCurrentNode->push_back(pcNode); + mLastNodeIndex = hierarchy; + } + else { + // need to go back to the specified position in the hierarchy. + InverseNodeSearch(pcNode,mCurrentNode); + mLastNodeIndex++; + } + // Make this node the current node + mCurrentNode = pcNode; + } + break; + + case Discreet3DS::CHUNK_TRACKDUMMYOBJNAME: + + // This is the "real" name of a $$$DUMMY object + { + const char* sz = (const char*) stream->GetPtr(); + while (stream->GetI1()); + + // If object name is DUMMY, take this one instead + if (mCurrentNode->mName == "$$$DUMMY") { + mCurrentNode->mName = std::string(sz); + break; + } + } + break; + + case Discreet3DS::CHUNK_TRACKPIVOT: + + if ( Discreet3DS::CHUNK_TRACKINFO != parent) + { + ASSIMP_LOG_WARN("3DS: Skipping pivot subchunk for non usual object"); + break; + } + + // Pivot = origin of rotation and scaling + mCurrentNode->vPivot.x = stream->GetF4(); + mCurrentNode->vPivot.y = stream->GetF4(); + mCurrentNode->vPivot.z = stream->GetF4(); + break; + + + // //////////////////////////////////////////////////////////////////// + // POSITION KEYFRAME + case Discreet3DS::CHUNK_TRACKPOS: + { + stream->IncPtr(10); + const unsigned int numFrames = stream->GetI4(); + bool sortKeys = false; + + // This could also be meant as the target position for + // (targeted) lights and cameras + std::vector* l; + if ( Discreet3DS::CHUNK_TRACKCAMTGT == parent || Discreet3DS::CHUNK_TRACKLIGTGT == parent) { + l = & mCurrentNode->aTargetPositionKeys; + } + else l = & mCurrentNode->aPositionKeys; + + l->reserve(numFrames); + for (unsigned int i = 0; i < numFrames;++i) { + const unsigned int fidx = stream->GetI4(); + + // Setup a new position key + aiVectorKey v; + v.mTime = (double)fidx; + + SkipTCBInfo(); + v.mValue.x = stream->GetF4(); + v.mValue.y = stream->GetF4(); + v.mValue.z = stream->GetF4(); + + // check whether we'll need to sort the keys + if (!l->empty() && v.mTime <= l->back().mTime) + sortKeys = true; + + // Add the new keyframe to the list + l->push_back(v); + } + + // Sort all keys with ascending time values and remove duplicates? + if (sortKeys) { + std::stable_sort(l->begin(),l->end()); + l->erase ( std::unique (l->begin(),l->end(),&KeyUniqueCompare), l->end() ); + }} + + break; + + // //////////////////////////////////////////////////////////////////// + // CAMERA ROLL KEYFRAME + case Discreet3DS::CHUNK_TRACKROLL: + { + // roll keys are accepted for cameras only + if (parent != Discreet3DS::CHUNK_TRACKCAMERA) { + ASSIMP_LOG_WARN("3DS: Ignoring roll track for non-camera object"); + break; + } + bool sortKeys = false; + std::vector* l = &mCurrentNode->aCameraRollKeys; + + stream->IncPtr(10); + const unsigned int numFrames = stream->GetI4(); + l->reserve(numFrames); + for (unsigned int i = 0; i < numFrames;++i) { + const unsigned int fidx = stream->GetI4(); + + // Setup a new position key + aiFloatKey v; + v.mTime = (double)fidx; + + // This is just a single float + SkipTCBInfo(); + v.mValue = stream->GetF4(); + + // Check whether we'll need to sort the keys + if (!l->empty() && v.mTime <= l->back().mTime) + sortKeys = true; + + // Add the new keyframe to the list + l->push_back(v); + } + + // Sort all keys with ascending time values and remove duplicates? + if (sortKeys) { + std::stable_sort(l->begin(),l->end()); + l->erase ( std::unique (l->begin(),l->end(),&KeyUniqueCompare), l->end() ); + }} + break; + + + // //////////////////////////////////////////////////////////////////// + // CAMERA FOV KEYFRAME + case Discreet3DS::CHUNK_TRACKFOV: + { + ASSIMP_LOG_ERROR("3DS: Skipping FOV animation track. " + "This is not supported"); + } + break; + + + // //////////////////////////////////////////////////////////////////// + // ROTATION KEYFRAME + case Discreet3DS::CHUNK_TRACKROTATE: + { + stream->IncPtr(10); + const unsigned int numFrames = stream->GetI4(); + + bool sortKeys = false; + std::vector* l = &mCurrentNode->aRotationKeys; + l->reserve(numFrames); + + for (unsigned int i = 0; i < numFrames;++i) { + const unsigned int fidx = stream->GetI4(); + SkipTCBInfo(); + + aiQuatKey v; + v.mTime = (double)fidx; + + // The rotation keyframe is given as an axis-angle pair + const float rad = stream->GetF4(); + aiVector3D axis; + axis.x = stream->GetF4(); + axis.y = stream->GetF4(); + axis.z = stream->GetF4(); + + if (!axis.x && !axis.y && !axis.z) + axis.y = 1.f; + + // Construct a rotation quaternion from the axis-angle pair + v.mValue = aiQuaternion(axis,rad); + + // Check whether we'll need to sort the keys + if (!l->empty() && v.mTime <= l->back().mTime) + sortKeys = true; + + // add the new keyframe to the list + l->push_back(v); + } + // Sort all keys with ascending time values and remove duplicates? + if (sortKeys) { + std::stable_sort(l->begin(),l->end()); + l->erase ( std::unique (l->begin(),l->end(),&KeyUniqueCompare), l->end() ); + }} + break; + + // //////////////////////////////////////////////////////////////////// + // SCALING KEYFRAME + case Discreet3DS::CHUNK_TRACKSCALE: + { + stream->IncPtr(10); + const unsigned int numFrames = stream->GetI2(); + stream->IncPtr(2); + + bool sortKeys = false; + std::vector* l = &mCurrentNode->aScalingKeys; + l->reserve(numFrames); + + for (unsigned int i = 0; i < numFrames;++i) { + const unsigned int fidx = stream->GetI4(); + SkipTCBInfo(); + + // Setup a new key + aiVectorKey v; + v.mTime = (double)fidx; + + // ... and read its value + v.mValue.x = stream->GetF4(); + v.mValue.y = stream->GetF4(); + v.mValue.z = stream->GetF4(); + + // check whether we'll need to sort the keys + if (!l->empty() && v.mTime <= l->back().mTime) + sortKeys = true; + + // Remove zero-scalings on singular axes - they've been reported to be there erroneously in some strange files + if (!v.mValue.x) v.mValue.x = 1.f; + if (!v.mValue.y) v.mValue.y = 1.f; + if (!v.mValue.z) v.mValue.z = 1.f; + + l->push_back(v); + } + // Sort all keys with ascending time values and remove duplicates? + if (sortKeys) { + std::stable_sort(l->begin(),l->end()); + l->erase ( std::unique (l->begin(),l->end(),&KeyUniqueCompare), l->end() ); + }} + break; + }; + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a face chunk - it contains smoothing groups and material assignments +void Discreet3DSImporter::ParseFaceChunk() +{ + ASSIMP_3DS_BEGIN_CHUNK(); + + // Get the mesh we're currently working on + D3DS::Mesh& mMesh = mScene->mMeshes.back(); + + // Get chunk type + switch (chunk.Flag) + { + case Discreet3DS::CHUNK_SMOOLIST: + { + // This is the list of smoothing groups - a bitfield for every face. + // Up to 32 smoothing groups assigned to a single face. + unsigned int num = chunkSize/4, m = 0; + if (num > mMesh.mFaces.size()) { + throw DeadlyImportError("3DS: More smoothing groups than faces"); + } + for (std::vector::iterator i = mMesh.mFaces.begin(); m != num;++i, ++m) { + // nth bit is set for nth smoothing group + (*i).iSmoothGroup = stream->GetI4(); + }} + break; + + case Discreet3DS::CHUNK_FACEMAT: + { + // at fist an asciiz with the material name + const char* sz = (const char*)stream->GetPtr(); + while (stream->GetI1()); + + // find the index of the material + unsigned int idx = 0xcdcdcdcd, cnt = 0; + for (std::vector::const_iterator i = mScene->mMaterials.begin();i != mScene->mMaterials.end();++i,++cnt) { + // use case independent comparisons. hopefully it will work. + if ((*i).mName.length() && !ASSIMP_stricmp(sz, (*i).mName.c_str())) { + idx = cnt; + break; + } + } + if (0xcdcdcdcd == idx) { + ASSIMP_LOG_ERROR_F( "3DS: Unknown material: ", sz); + } + + // Now continue and read all material indices + cnt = (uint16_t)stream->GetI2(); + for (unsigned int i = 0; i < cnt;++i) { + unsigned int fidx = (uint16_t)stream->GetI2(); + + // check range + if (fidx >= mMesh.mFaceMaterials.size()) { + ASSIMP_LOG_ERROR("3DS: Invalid face index in face material list"); + } + else mMesh.mFaceMaterials[fidx] = idx; + }} + break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a mesh chunk. Here's the actual mesh data +void Discreet3DSImporter::ParseMeshChunk() +{ + ASSIMP_3DS_BEGIN_CHUNK(); + + // Get the mesh we're currently working on + D3DS::Mesh& mMesh = mScene->mMeshes.back(); + + // get chunk type + switch (chunk.Flag) + { + case Discreet3DS::CHUNK_VERTLIST: + { + // This is the list of all vertices in the current mesh + int num = (int)(uint16_t)stream->GetI2(); + mMesh.mPositions.reserve(num); + while (num-- > 0) { + aiVector3D v; + v.x = stream->GetF4(); + v.y = stream->GetF4(); + v.z = stream->GetF4(); + mMesh.mPositions.push_back(v); + }} + break; + case Discreet3DS::CHUNK_TRMATRIX: + { + // This is the RLEATIVE transformation matrix of the current mesh. Vertices are + // pretransformed by this matrix wonder. + mMesh.mMat.a1 = stream->GetF4(); + mMesh.mMat.b1 = stream->GetF4(); + mMesh.mMat.c1 = stream->GetF4(); + mMesh.mMat.a2 = stream->GetF4(); + mMesh.mMat.b2 = stream->GetF4(); + mMesh.mMat.c2 = stream->GetF4(); + mMesh.mMat.a3 = stream->GetF4(); + mMesh.mMat.b3 = stream->GetF4(); + mMesh.mMat.c3 = stream->GetF4(); + mMesh.mMat.a4 = stream->GetF4(); + mMesh.mMat.b4 = stream->GetF4(); + mMesh.mMat.c4 = stream->GetF4(); + } + break; + + case Discreet3DS::CHUNK_MAPLIST: + { + // This is the list of all UV coords in the current mesh + int num = (int)(uint16_t)stream->GetI2(); + mMesh.mTexCoords.reserve(num); + while (num-- > 0) { + aiVector3D v; + v.x = stream->GetF4(); + v.y = stream->GetF4(); + mMesh.mTexCoords.push_back(v); + }} + break; + + case Discreet3DS::CHUNK_FACELIST: + { + // This is the list of all faces in the current mesh + int num = (int)(uint16_t)stream->GetI2(); + mMesh.mFaces.reserve(num); + while (num-- > 0) { + // 3DS faces are ALWAYS triangles + mMesh.mFaces.push_back(D3DS::Face()); + D3DS::Face& sFace = mMesh.mFaces.back(); + + sFace.mIndices[0] = (uint16_t)stream->GetI2(); + sFace.mIndices[1] = (uint16_t)stream->GetI2(); + sFace.mIndices[2] = (uint16_t)stream->GetI2(); + + stream->IncPtr(2); // skip edge visibility flag + } + + // Resize the material array (0xcdcdcdcd marks the default material; so if a face is + // not referenced by a material, $$DEFAULT will be assigned to it) + mMesh.mFaceMaterials.resize(mMesh.mFaces.size(),0xcdcdcdcd); + + // Larger 3DS files could have multiple FACE chunks here + chunkSize = stream->GetRemainingSizeToLimit(); + if ( chunkSize > (int) sizeof(Discreet3DS::Chunk ) ) + ParseFaceChunk(); + } + break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a 3DS material chunk +void Discreet3DSImporter::ParseMaterialChunk() +{ + ASSIMP_3DS_BEGIN_CHUNK(); + switch (chunk.Flag) + { + case Discreet3DS::CHUNK_MAT_MATNAME: + + { + // The material name string is already zero-terminated, but we need to be sure ... + const char* sz = (const char*)stream->GetPtr(); + unsigned int cnt = 0; + while (stream->GetI1()) + ++cnt; + + if (!cnt) { + // This may not be, we use the default name instead + ASSIMP_LOG_ERROR("3DS: Empty material name"); + } + else mScene->mMaterials.back().mName = std::string(sz,cnt); + } + break; + + case Discreet3DS::CHUNK_MAT_DIFFUSE: + { + // This is the diffuse material color + aiColor3D* pc = &mScene->mMaterials.back().mDiffuse; + ParseColorChunk(pc); + if (is_qnan(pc->r)) { + // color chunk is invalid. Simply ignore it + ASSIMP_LOG_ERROR("3DS: Unable to read DIFFUSE chunk"); + pc->r = pc->g = pc->b = 1.0f; + }} + break; + + case Discreet3DS::CHUNK_MAT_SPECULAR: + { + // This is the specular material color + aiColor3D* pc = &mScene->mMaterials.back().mSpecular; + ParseColorChunk(pc); + if (is_qnan(pc->r)) { + // color chunk is invalid. Simply ignore it + ASSIMP_LOG_ERROR("3DS: Unable to read SPECULAR chunk"); + pc->r = pc->g = pc->b = 1.0f; + }} + break; + + case Discreet3DS::CHUNK_MAT_AMBIENT: + { + // This is the ambient material color + aiColor3D* pc = &mScene->mMaterials.back().mAmbient; + ParseColorChunk(pc); + if (is_qnan(pc->r)) { + // color chunk is invalid. Simply ignore it + ASSIMP_LOG_ERROR("3DS: Unable to read AMBIENT chunk"); + pc->r = pc->g = pc->b = 0.0f; + }} + break; + + case Discreet3DS::CHUNK_MAT_SELF_ILLUM: + { + // This is the emissive material color + aiColor3D* pc = &mScene->mMaterials.back().mEmissive; + ParseColorChunk(pc); + if (is_qnan(pc->r)) { + // color chunk is invalid. Simply ignore it + ASSIMP_LOG_ERROR("3DS: Unable to read EMISSIVE chunk"); + pc->r = pc->g = pc->b = 0.0f; + }} + break; + + case Discreet3DS::CHUNK_MAT_TRANSPARENCY: + { + // This is the material's transparency + ai_real* pcf = &mScene->mMaterials.back().mTransparency; + *pcf = ParsePercentageChunk(); + + // NOTE: transparency, not opacity + if (is_qnan(*pcf)) + *pcf = ai_real( 1.0 ); + else + *pcf = ai_real( 1.0 ) - *pcf * (ai_real)0xFFFF / ai_real( 100.0 ); + } + break; + + case Discreet3DS::CHUNK_MAT_SHADING: + // This is the material shading mode + mScene->mMaterials.back().mShading = (D3DS::Discreet3DS::shadetype3ds)stream->GetI2(); + break; + + case Discreet3DS::CHUNK_MAT_TWO_SIDE: + // This is the two-sided flag + mScene->mMaterials.back().mTwoSided = true; + break; + + case Discreet3DS::CHUNK_MAT_SHININESS: + { // This is the shininess of the material + ai_real* pcf = &mScene->mMaterials.back().mSpecularExponent; + *pcf = ParsePercentageChunk(); + if (is_qnan(*pcf)) + *pcf = 0.0; + else *pcf *= (ai_real)0xFFFF; + } + break; + + case Discreet3DS::CHUNK_MAT_SHININESS_PERCENT: + { // This is the shininess strength of the material + ai_real* pcf = &mScene->mMaterials.back().mShininessStrength; + *pcf = ParsePercentageChunk(); + if (is_qnan(*pcf)) + *pcf = ai_real( 0.0 ); + else + *pcf *= (ai_real)0xffff / ai_real( 100.0 ); + } + break; + + case Discreet3DS::CHUNK_MAT_SELF_ILPCT: + { // This is the self illumination strength of the material + ai_real f = ParsePercentageChunk(); + if (is_qnan(f)) + f = ai_real( 0.0 ); + else + f *= (ai_real)0xFFFF / ai_real( 100.0 ); + mScene->mMaterials.back().mEmissive = aiColor3D(f,f,f); + } + break; + + // Parse texture chunks + case Discreet3DS::CHUNK_MAT_TEXTURE: + // Diffuse texture + ParseTextureChunk(&mScene->mMaterials.back().sTexDiffuse); + break; + case Discreet3DS::CHUNK_MAT_BUMPMAP: + // Height map + ParseTextureChunk(&mScene->mMaterials.back().sTexBump); + break; + case Discreet3DS::CHUNK_MAT_OPACMAP: + // Opacity texture + ParseTextureChunk(&mScene->mMaterials.back().sTexOpacity); + break; + case Discreet3DS::CHUNK_MAT_MAT_SHINMAP: + // Shininess map + ParseTextureChunk(&mScene->mMaterials.back().sTexShininess); + break; + case Discreet3DS::CHUNK_MAT_SPECMAP: + // Specular map + ParseTextureChunk(&mScene->mMaterials.back().sTexSpecular); + break; + case Discreet3DS::CHUNK_MAT_SELFIMAP: + // Self-illumination (emissive) map + ParseTextureChunk(&mScene->mMaterials.back().sTexEmissive); + break; + case Discreet3DS::CHUNK_MAT_REFLMAP: + // Reflection map + ParseTextureChunk(&mScene->mMaterials.back().sTexReflective); + break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseTextureChunk(D3DS::Texture* pcOut) +{ + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) + { + case Discreet3DS::CHUNK_MAPFILE: + { + // The material name string is already zero-terminated, but we need to be sure ... + const char* sz = (const char*)stream->GetPtr(); + unsigned int cnt = 0; + while (stream->GetI1()) + ++cnt; + pcOut->mMapName = std::string(sz,cnt); + } + break; + + + case Discreet3DS::CHUNK_PERCENTD: + // Manually parse the blend factor + pcOut->mTextureBlend = ai_real( stream->GetF8() ); + break; + + case Discreet3DS::CHUNK_PERCENTF: + // Manually parse the blend factor + pcOut->mTextureBlend = stream->GetF4(); + break; + + case Discreet3DS::CHUNK_PERCENTW: + // Manually parse the blend factor + pcOut->mTextureBlend = (ai_real)((uint16_t)stream->GetI2()) / ai_real( 100.0 ); + break; + + case Discreet3DS::CHUNK_MAT_MAP_USCALE: + // Texture coordinate scaling in the U direction + pcOut->mScaleU = stream->GetF4(); + if (0.0f == pcOut->mScaleU) + { + ASSIMP_LOG_WARN("Texture coordinate scaling in the x direction is zero. Assuming 1."); + pcOut->mScaleU = 1.0f; + } + break; + case Discreet3DS::CHUNK_MAT_MAP_VSCALE: + // Texture coordinate scaling in the V direction + pcOut->mScaleV = stream->GetF4(); + if (0.0f == pcOut->mScaleV) + { + ASSIMP_LOG_WARN("Texture coordinate scaling in the y direction is zero. Assuming 1."); + pcOut->mScaleV = 1.0f; + } + break; + + case Discreet3DS::CHUNK_MAT_MAP_UOFFSET: + // Texture coordinate offset in the U direction + pcOut->mOffsetU = -stream->GetF4(); + break; + + case Discreet3DS::CHUNK_MAT_MAP_VOFFSET: + // Texture coordinate offset in the V direction + pcOut->mOffsetV = stream->GetF4(); + break; + + case Discreet3DS::CHUNK_MAT_MAP_ANG: + // Texture coordinate rotation, CCW in DEGREES + pcOut->mRotation = -AI_DEG_TO_RAD( stream->GetF4() ); + break; + + case Discreet3DS::CHUNK_MAT_MAP_TILING: + { + const uint16_t iFlags = stream->GetI2(); + + // Get the mapping mode (for both axes) + if (iFlags & 0x2u) + pcOut->mMapMode = aiTextureMapMode_Mirror; + + else if (iFlags & 0x10u) + pcOut->mMapMode = aiTextureMapMode_Decal; + + // wrapping in all remaining cases + else pcOut->mMapMode = aiTextureMapMode_Wrap; + } + break; + }; + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a percentage chunk +ai_real Discreet3DSImporter::ParsePercentageChunk() { + Discreet3DS::Chunk chunk; + ReadChunk(&chunk); + + if (Discreet3DS::CHUNK_PERCENTF == chunk.Flag) { + return stream->GetF4() * ai_real(100) / ai_real(0xFFFF); + } else if (Discreet3DS::CHUNK_PERCENTW == chunk.Flag) { + return (ai_real)((uint16_t)stream->GetI2()) / (ai_real)0xFFFF; + } + + return get_qnan(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a color chunk. If a percentage chunk is found instead it is read as a grayscale color +void Discreet3DSImporter::ParseColorChunk( aiColor3D* out, bool acceptPercent ) +{ + ai_assert(out != NULL); + + // error return value + const ai_real qnan = get_qnan(); + static const aiColor3D clrError = aiColor3D(qnan,qnan,qnan); + + Discreet3DS::Chunk chunk; + ReadChunk(&chunk); + const unsigned int diff = chunk.Size - sizeof(Discreet3DS::Chunk); + + bool bGamma = false; + + // Get the type of the chunk + switch(chunk.Flag) + { + case Discreet3DS::CHUNK_LINRGBF: + bGamma = true; + + case Discreet3DS::CHUNK_RGBF: + if (sizeof(float) * 3 > diff) { + *out = clrError; + return; + } + out->r = stream->GetF4(); + out->g = stream->GetF4(); + out->b = stream->GetF4(); + break; + + case Discreet3DS::CHUNK_LINRGBB: + bGamma = true; + case Discreet3DS::CHUNK_RGBB: + { + if ( sizeof( char ) * 3 > diff ) { + *out = clrError; + return; + } + const ai_real invVal = ai_real( 1.0 ) / ai_real( 255.0 ); + out->r = ( ai_real ) ( uint8_t ) stream->GetI1() * invVal; + out->g = ( ai_real ) ( uint8_t ) stream->GetI1() * invVal; + out->b = ( ai_real ) ( uint8_t ) stream->GetI1() * invVal; + } + break; + + // Percentage chunks are accepted, too. + case Discreet3DS::CHUNK_PERCENTF: + if (acceptPercent && 4 <= diff) { + out->g = out->b = out->r = stream->GetF4(); + break; + } + *out = clrError; + return; + + case Discreet3DS::CHUNK_PERCENTW: + if (acceptPercent && 1 <= diff) { + out->g = out->b = out->r = (ai_real)(uint8_t)stream->GetI1() / ai_real( 255.0 ); + break; + } + *out = clrError; + return; + + default: + stream->IncPtr(diff); + // Skip unknown chunks, hope this won't cause any problems. + return ParseColorChunk(out,acceptPercent); + }; + (void)bGamma; +} + +#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER diff --git a/thirdparty/assimp/code/3DS/3DSLoader.h b/thirdparty/assimp/code/3DS/3DSLoader.h new file mode 100644 index 0000000..f57e6a8 --- /dev/null +++ b/thirdparty/assimp/code/3DS/3DSLoader.h @@ -0,0 +1,284 @@ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file 3DSLoader.h + * @brief 3DS File format loader + */ +#ifndef AI_3DSIMPORTER_H_INC +#define AI_3DSIMPORTER_H_INC + +#include +#include + +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +#include "3DSHelper.h" +#include + +struct aiNode; + +namespace Assimp { + + +using namespace D3DS; + +// --------------------------------------------------------------------------------- +/** Importer class for 3D Studio r3 and r4 3DS files + */ +class Discreet3DSImporter : public BaseImporter +{ +public: + + Discreet3DSImporter(); + ~Discreet3DSImporter(); + +public: + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool checkSig) const; + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer* pImp); + +protected: + + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc* GetInfo () const; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler); + + // ------------------------------------------------------------------- + /** Converts a temporary material to the outer representation + */ + void ConvertMaterial(D3DS::Material& p_cMat, + aiMaterial& p_pcOut); + + // ------------------------------------------------------------------- + /** Read a chunk + * + * @param pcOut Receives the current chunk + */ + void ReadChunk(Discreet3DS::Chunk* pcOut); + + // ------------------------------------------------------------------- + /** Parse a percentage chunk. mCurrent will point to the next + * chunk behind afterwards. If no percentage chunk is found + * QNAN is returned. + */ + ai_real ParsePercentageChunk(); + + // ------------------------------------------------------------------- + /** Parse a color chunk. mCurrent will point to the next + * chunk behind afterwards. If no color chunk is found + * QNAN is returned in all members. + */ + void ParseColorChunk(aiColor3D* p_pcOut, + bool p_bAcceptPercent = true); + + + // ------------------------------------------------------------------- + /** Skip a chunk in the file + */ + void SkipChunk(); + + // ------------------------------------------------------------------- + /** Generate the nodegraph + */ + void GenerateNodeGraph(aiScene* pcOut); + + // ------------------------------------------------------------------- + /** Parse a main top-level chunk in the file + */ + void ParseMainChunk(); + + // ------------------------------------------------------------------- + /** Parse a top-level chunk in the file + */ + void ParseChunk(const char* name, unsigned int num); + + // ------------------------------------------------------------------- + /** Parse a top-level editor chunk in the file + */ + void ParseEditorChunk(); + + // ------------------------------------------------------------------- + /** Parse a top-level object chunk in the file + */ + void ParseObjectChunk(); + + // ------------------------------------------------------------------- + /** Parse a material chunk in the file + */ + void ParseMaterialChunk(); + + // ------------------------------------------------------------------- + /** Parse a mesh chunk in the file + */ + void ParseMeshChunk(); + + // ------------------------------------------------------------------- + /** Parse a light chunk in the file + */ + void ParseLightChunk(); + + // ------------------------------------------------------------------- + /** Parse a camera chunk in the file + */ + void ParseCameraChunk(); + + // ------------------------------------------------------------------- + /** Parse a face list chunk in the file + */ + void ParseFaceChunk(); + + // ------------------------------------------------------------------- + /** Parse a keyframe chunk in the file + */ + void ParseKeyframeChunk(); + + // ------------------------------------------------------------------- + /** Parse a hierarchy chunk in the file + */ + void ParseHierarchyChunk(uint16_t parent); + + // ------------------------------------------------------------------- + /** Parse a texture chunk in the file + */ + void ParseTextureChunk(D3DS::Texture* pcOut); + + // ------------------------------------------------------------------- + /** Convert the meshes in the file + */ + void ConvertMeshes(aiScene* pcOut); + + // ------------------------------------------------------------------- + /** Replace the default material in the scene + */ + void ReplaceDefaultMaterial(); + + // ------------------------------------------------------------------- + /** Convert the whole scene + */ + void ConvertScene(aiScene* pcOut); + + // ------------------------------------------------------------------- + /** generate unique vertices for a mesh + */ + void MakeUnique(D3DS::Mesh& sMesh); + + // ------------------------------------------------------------------- + /** Add a node to the node graph + */ + void AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,D3DS::Node* pcIn, + aiMatrix4x4& absTrafo); + + // ------------------------------------------------------------------- + /** Search for a node in the graph. + * Called recursively + */ + void InverseNodeSearch(D3DS::Node* pcNode,D3DS::Node* pcCurrent); + + // ------------------------------------------------------------------- + /** Apply the master scaling factor to the mesh + */ + void ApplyMasterScale(aiScene* pScene); + + // ------------------------------------------------------------------- + /** Clamp all indices in the file to a valid range + */ + void CheckIndices(D3DS::Mesh& sMesh); + + // ------------------------------------------------------------------- + /** Skip the TCB info in a track key + */ + void SkipTCBInfo(); + +protected: + + /** Stream to read from */ + StreamReaderLE* stream; + + /** Last touched node index */ + short mLastNodeIndex; + + /** Current node, root node */ + D3DS::Node* mCurrentNode, *mRootNode; + + /** Scene under construction */ + D3DS::Scene* mScene; + + /** Ambient base color of the scene */ + aiColor3D mClrAmbient; + + /** Master scaling factor of the scene */ + ai_real mMasterScale; + + /** Path to the background image of the scene */ + std::string mBackgroundImage; + bool bHasBG; + + /** true if PRJ file */ + bool bIsPrj; +}; + +} // end of namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER + +#endif // AI_3DSIMPORTER_H_INC diff --git a/thirdparty/assimp/code/3MF/3MFXmlTags.h b/thirdparty/assimp/code/3MF/3MFXmlTags.h new file mode 100644 index 0000000..ea6aeed --- /dev/null +++ b/thirdparty/assimp/code/3MF/3MFXmlTags.h @@ -0,0 +1,104 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#pragma once + +namespace Assimp { +namespace D3MF { + +namespace XmlTag { + // Meta-data + static const std::string meta = "metadata"; + static const std::string meta_name = "name"; + + // Model-data specific tags + static const std::string model = "model"; + static const std::string model_unit = "unit"; + static const std::string metadata = "metadata"; + static const std::string resources = "resources"; + static const std::string object = "object"; + static const std::string mesh = "mesh"; + static const std::string vertices = "vertices"; + static const std::string vertex = "vertex"; + static const std::string triangles = "triangles"; + static const std::string triangle = "triangle"; + static const std::string x = "x"; + static const std::string y = "y"; + static const std::string z = "z"; + static const std::string v1 = "v1"; + static const std::string v2 = "v2"; + static const std::string v3 = "v3"; + static const std::string id = "id"; + static const std::string pid = "pid"; + static const std::string p1 = "p1"; + static const std::string name = "name"; + static const std::string type = "type"; + static const std::string build = "build"; + static const std::string item = "item"; + static const std::string objectid = "objectid"; + static const std::string transform = "transform"; + + // Material definitions + static const std::string basematerials = "basematerials"; + static const std::string basematerials_id = "id"; + static const std::string basematerials_base = "base"; + static const std::string basematerials_name = "name"; + static const std::string basematerials_displaycolor = "displaycolor"; + + // Meta info tags + static const std::string CONTENT_TYPES_ARCHIVE = "[Content_Types].xml"; + static const std::string ROOT_RELATIONSHIPS_ARCHIVE = "_rels/.rels"; + static const std::string SCHEMA_CONTENTTYPES = "http://schemas.openxmlformats.org/package/2006/content-types"; + static const std::string SCHEMA_RELATIONSHIPS = "http://schemas.openxmlformats.org/package/2006/relationships"; + static const std::string RELS_RELATIONSHIP_CONTAINER = "Relationships"; + static const std::string RELS_RELATIONSHIP_NODE = "Relationship"; + static const std::string RELS_ATTRIB_TARGET = "Target"; + static const std::string RELS_ATTRIB_TYPE = "Type"; + static const std::string RELS_ATTRIB_ID = "Id"; + static const std::string PACKAGE_START_PART_RELATIONSHIP_TYPE = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"; + static const std::string PACKAGE_PRINT_TICKET_RELATIONSHIP_TYPE = "http://schemas.microsoft.com/3dmanufacturing/2013/01/printticket"; + static const std::string PACKAGE_TEXTURE_RELATIONSHIP_TYPE = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dtexture"; + static const std::string PACKAGE_CORE_PROPERTIES_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"; + static const std::string PACKAGE_THUMBNAIL_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail"; +} + +} // Namespace D3MF +} // Namespace Assimp diff --git a/thirdparty/assimp/code/3MF/D3MFExporter.cpp b/thirdparty/assimp/code/3MF/D3MFExporter.cpp new file mode 100644 index 0000000..1f388ad --- /dev/null +++ b/thirdparty/assimp/code/3MF/D3MFExporter.cpp @@ -0,0 +1,402 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_3MF_EXPORTER + +#include "D3MFExporter.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "3MFXmlTags.h" +#include "D3MFOpcPackage.h" + +#ifdef ASSIMP_USE_HUNTER +# include +#else +# include +#endif + +namespace Assimp { + +void ExportScene3MF( const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/ ) { + if ( nullptr == pIOSystem ) { + throw DeadlyExportError( "Could not export 3MP archive: " + std::string( pFile ) ); + } + D3MF::D3MFExporter myExporter( pFile, pScene ); + if ( myExporter.validate() ) { + if ( pIOSystem->Exists( pFile ) ) { + if ( !pIOSystem->DeleteFile( pFile ) ) { + throw DeadlyExportError( "File exists, cannot override : " + std::string( pFile ) ); + } + } + bool ok = myExporter.exportArchive(pFile); + if ( !ok ) { + throw DeadlyExportError( "Could not export 3MP archive: " + std::string( pFile ) ); + } + } +} + +namespace D3MF { + +D3MFExporter::D3MFExporter( const char* pFile, const aiScene* pScene ) +: mArchiveName( pFile ) +, m_zipArchive( nullptr ) +, mScene( pScene ) +, mModelOutput() +, mRelOutput() +, mContentOutput() +, mBuildItems() +, mRelations() { + // empty +} + +D3MFExporter::~D3MFExporter() { + for ( size_t i = 0; i < mRelations.size(); ++i ) { + delete mRelations[ i ]; + } + mRelations.clear(); +} + +bool D3MFExporter::validate() { + if ( mArchiveName.empty() ) { + return false; + } + + if ( nullptr == mScene ) { + return false; + } + + return true; +} + +bool D3MFExporter::exportArchive( const char *file ) { + bool ok( true ); + + m_zipArchive = zip_open( file, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w' ); + if ( nullptr == m_zipArchive ) { + return false; + } + + ok |= exportContentTypes(); + ok |= export3DModel(); + ok |= exportRelations(); + + zip_close( m_zipArchive ); + m_zipArchive = nullptr; + + return ok; +} + +bool D3MFExporter::exportContentTypes() { + mContentOutput.clear(); + + mContentOutput << ""; + mContentOutput << std::endl; + mContentOutput << ""; + mContentOutput << std::endl; + mContentOutput << ""; + mContentOutput << std::endl; + mContentOutput << ""; + mContentOutput << std::endl; + mContentOutput << ""; + mContentOutput << std::endl; + exportContentTyp( XmlTag::CONTENT_TYPES_ARCHIVE ); + + return true; +} + +bool D3MFExporter::exportRelations() { + mRelOutput.clear(); + + mRelOutput << ""; + mRelOutput << std::endl; + mRelOutput << ""; + + for ( size_t i = 0; i < mRelations.size(); ++i ) { + if ( mRelations[ i ]->target[ 0 ] == '/' ) { + mRelOutput << "target << "\" "; + } else { + mRelOutput << "target << "\" "; + } + mRelOutput << "Id=\"" << mRelations[i]->id << "\" "; + mRelOutput << "Type=\"" << mRelations[ i ]->type << "\" />"; + mRelOutput << std::endl; + } + mRelOutput << ""; + mRelOutput << std::endl; + + writeRelInfoToFile( "_rels", ".rels" ); + mRelOutput.flush(); + + return true; +} + +bool D3MFExporter::export3DModel() { + mModelOutput.clear(); + + writeHeader(); + mModelOutput << "<" << XmlTag::model << " " << XmlTag::model_unit << "=\"millimeter\"" + << "xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\">" + << std::endl; + mModelOutput << "<" << XmlTag::resources << ">"; + mModelOutput << std::endl; + + writeMetaData(); + + writeBaseMaterials(); + + writeObjects(); + + + mModelOutput << ""; + mModelOutput << std::endl; + writeBuild(); + + mModelOutput << "\n"; + + OpcPackageRelationship *info = new OpcPackageRelationship; + info->id = "rel0"; + info->target = "/3D/3DModel.model"; + info->type = XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE; + mRelations.push_back( info ); + + writeModelToArchive( "3D", "3DModel.model" ); + mModelOutput.flush(); + + return true; +} + +void D3MFExporter::writeHeader() { + mModelOutput << ""; + mModelOutput << std::endl; +} + +void D3MFExporter::writeMetaData() { + if ( nullptr == mScene->mMetaData ) { + return; + } + + const unsigned int numMetaEntries( mScene->mMetaData->mNumProperties ); + if ( 0 == numMetaEntries ) { + return; + } + + const aiString *key = nullptr; + const aiMetadataEntry *entry(nullptr); + for ( size_t i = 0; i < numMetaEntries; ++i ) { + mScene->mMetaData->Get( i, key, entry ); + std::string k( key->C_Str() ); + aiString value; + mScene->mMetaData->Get( k, value ); + mModelOutput << "<" << XmlTag::meta << " " << XmlTag::meta_name << "=\"" << key->C_Str() << "\">"; + mModelOutput << value.C_Str(); + mModelOutput << "" << std::endl; + } +} + +void D3MFExporter::writeBaseMaterials() { + mModelOutput << "\n"; + std::string strName, hexDiffuseColor , tmp; + for ( size_t i = 0; i < mScene->mNumMaterials; ++i ) { + aiMaterial *mat = mScene->mMaterials[ i ]; + aiString name; + if ( mat->Get( AI_MATKEY_NAME, name ) != aiReturn_SUCCESS ) { + strName = "basemat_" + to_string( i ); + } else { + strName = name.C_Str(); + } + aiColor4D color; + if ( mat->Get( AI_MATKEY_COLOR_DIFFUSE, color ) == aiReturn_SUCCESS ) { + hexDiffuseColor.clear(); + tmp.clear(); + hexDiffuseColor = "#"; + + tmp = DecimalToHexa( color.r ); + hexDiffuseColor += tmp; + tmp = DecimalToHexa( color.g ); + hexDiffuseColor += tmp; + tmp = DecimalToHexa( color.b ); + hexDiffuseColor += tmp; + tmp = DecimalToHexa( color.a ); + hexDiffuseColor += tmp; + } else { + hexDiffuseColor = "#FFFFFFFF"; + } + + mModelOutput << "\n"; + } + mModelOutput << "\n"; +} + +void D3MFExporter::writeObjects() { + if ( nullptr == mScene->mRootNode ) { + return; + } + + aiNode *root = mScene->mRootNode; + for ( unsigned int i = 0; i < root->mNumChildren; ++i ) { + aiNode *currentNode( root->mChildren[ i ] ); + if ( nullptr == currentNode ) { + continue; + } + mModelOutput << "<" << XmlTag::object << " id=\"" << currentNode->mName.C_Str() << "\" type=\"model\">"; + mModelOutput << std::endl; + for ( unsigned int j = 0; j < currentNode->mNumMeshes; ++j ) { + aiMesh *currentMesh = mScene->mMeshes[ currentNode->mMeshes[ j ] ]; + if ( nullptr == currentMesh ) { + continue; + } + writeMesh( currentMesh ); + } + mBuildItems.push_back( i ); + + mModelOutput << ""; + mModelOutput << std::endl; + } +} + +void D3MFExporter::writeMesh( aiMesh *mesh ) { + if ( nullptr == mesh ) { + return; + } + + mModelOutput << "<" << XmlTag::mesh << ">" << std::endl; + mModelOutput << "<" << XmlTag::vertices << ">" << std::endl; + for ( unsigned int i = 0; i < mesh->mNumVertices; ++i ) { + writeVertex( mesh->mVertices[ i ] ); + } + mModelOutput << "" << std::endl; + + const unsigned int matIdx( mesh->mMaterialIndex ); + + writeFaces( mesh, matIdx ); + + mModelOutput << "" << std::endl; +} + +void D3MFExporter::writeVertex( const aiVector3D &pos ) { + mModelOutput << "<" << XmlTag::vertex << " x=\"" << pos.x << "\" y=\"" << pos.y << "\" z=\"" << pos.z << "\" />"; + mModelOutput << std::endl; +} + +void D3MFExporter::writeFaces( aiMesh *mesh, unsigned int matIdx ) { + if ( nullptr == mesh ) { + return; + } + + if ( !mesh->HasFaces() ) { + return; + } + mModelOutput << "<" << XmlTag::triangles << ">" << std::endl; + for ( unsigned int i = 0; i < mesh->mNumFaces; ++i ) { + aiFace ¤tFace = mesh->mFaces[ i ]; + mModelOutput << "<" << XmlTag::triangle << " v1=\"" << currentFace.mIndices[ 0 ] << "\" v2=\"" + << currentFace.mIndices[ 1 ] << "\" v3=\"" << currentFace.mIndices[ 2 ] + << "\" pid=\"1\" p1=\""+to_string(matIdx)+"\" />"; + mModelOutput << std::endl; + } + mModelOutput << ""; + mModelOutput << std::endl; +} + +void D3MFExporter::writeBuild() { + mModelOutput << "<" << XmlTag::build << ">" << std::endl; + + for ( size_t i = 0; i < mBuildItems.size(); ++i ) { + mModelOutput << "<" << XmlTag::item << " objectid=\"" << i + 1 << "\"/>"; + mModelOutput << std::endl; + } + mModelOutput << ""; + mModelOutput << std::endl; +} + +void D3MFExporter::exportContentTyp( const std::string &filename ) { + if ( nullptr == m_zipArchive ) { + throw DeadlyExportError( "3MF-Export: Zip archive not valid, nullptr." ); + } + const std::string entry = filename; + zip_entry_open( m_zipArchive, entry.c_str() ); + + const std::string &exportTxt( mContentOutput.str() ); + zip_entry_write( m_zipArchive, exportTxt.c_str(), exportTxt.size() ); + + zip_entry_close( m_zipArchive ); +} + +void D3MFExporter::writeModelToArchive( const std::string &folder, const std::string &modelName ) { + if ( nullptr == m_zipArchive ) { + throw DeadlyExportError( "3MF-Export: Zip archive not valid, nullptr." ); + } + const std::string entry = folder + "/" + modelName; + zip_entry_open( m_zipArchive, entry.c_str() ); + + const std::string &exportTxt( mModelOutput.str() ); + zip_entry_write( m_zipArchive, exportTxt.c_str(), exportTxt.size() ); + + zip_entry_close( m_zipArchive ); +} + +void D3MFExporter::writeRelInfoToFile( const std::string &folder, const std::string &relName ) { + if ( nullptr == m_zipArchive ) { + throw DeadlyExportError( "3MF-Export: Zip archive not valid, nullptr." ); + } + const std::string entry = folder + "/" + relName; + zip_entry_open( m_zipArchive, entry.c_str() ); + + const std::string &exportTxt( mRelOutput.str() ); + zip_entry_write( m_zipArchive, exportTxt.c_str(), exportTxt.size() ); + + zip_entry_close( m_zipArchive ); +} + + +} // Namespace D3MF +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_3MF_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/thirdparty/assimp/code/3MF/D3MFExporter.h b/thirdparty/assimp/code/3MF/D3MFExporter.h new file mode 100644 index 0000000..e821202 --- /dev/null +++ b/thirdparty/assimp/code/3MF/D3MFExporter.h @@ -0,0 +1,106 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#pragma once + +#include +#include +#include +#include + +struct aiScene; +struct aiNode; +struct aiMaterial; +struct aiMesh; + +struct zip_t; + +namespace Assimp { + +class IOStream; + +namespace D3MF { + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_3MF_EXPORTER + +struct OpcPackageRelationship; + +class D3MFExporter { +public: + D3MFExporter( const char* pFile, const aiScene* pScene ); + ~D3MFExporter(); + bool validate(); + bool exportArchive( const char *file ); + bool exportContentTypes(); + bool exportRelations(); + bool export3DModel(); + +protected: + void writeHeader(); + void writeMetaData(); + void writeBaseMaterials(); + void writeObjects(); + void writeMesh( aiMesh *mesh ); + void writeVertex( const aiVector3D &pos ); + void writeFaces( aiMesh *mesh, unsigned int matIdx ); + void writeBuild(); + void exportContentTyp( const std::string &filename ); + void writeModelToArchive( const std::string &folder, const std::string &modelName ); + void writeRelInfoToFile( const std::string &folder, const std::string &relName ); + +private: + std::string mArchiveName; + zip_t *m_zipArchive; + const aiScene *mScene; + std::ostringstream mModelOutput; + std::ostringstream mRelOutput; + std::ostringstream mContentOutput; + std::vector mBuildItems; + std::vector mRelations; +}; + +#endif // ASSIMP_BUILD_NO_3MF_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT + +} // Namespace D3MF +} // Namespace Assimp + diff --git a/thirdparty/assimp/code/3MF/D3MFImporter.cpp b/thirdparty/assimp/code/3MF/D3MFImporter.cpp new file mode 100644 index 0000000..682de68 --- /dev/null +++ b/thirdparty/assimp/code/3MF/D3MFImporter.cpp @@ -0,0 +1,483 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_3MF_IMPORTER + +#include "D3MFImporter.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "D3MFOpcPackage.h" +#include +#include "3MFXmlTags.h" +#include + +#include + +namespace Assimp { +namespace D3MF { + +class XmlSerializer { +public: + using MatArray = std::vector; + using MatId2MatArray = std::map>; + + XmlSerializer(XmlReader* xmlReader) + : mMeshes() + , mMatArray() + , mActiveMatGroup( 99999999 ) + , mMatId2MatArray() + , xmlReader(xmlReader){ + // empty + } + + ~XmlSerializer() { + // empty + } + + void ImportXml(aiScene* scene) { + if ( nullptr == scene ) { + return; + } + + scene->mRootNode = new aiNode(); + std::vector children; + + std::string nodeName; + while(ReadToEndElement(D3MF::XmlTag::model)) { + nodeName = xmlReader->getNodeName(); + if( nodeName == D3MF::XmlTag::object) { + children.push_back(ReadObject(scene)); + } else if( nodeName == D3MF::XmlTag::build) { + // + } else if ( nodeName == D3MF::XmlTag::basematerials ) { + ReadBaseMaterials(); + } else if ( nodeName == D3MF::XmlTag::meta ) { + ReadMetadata(); + } + } + + if ( scene->mRootNode->mName.length == 0 ) { + scene->mRootNode->mName.Set( "3MF" ); + } + + // import the metadata + if ( !mMetaData.empty() ) { + const size_t numMeta( mMetaData.size() ); + scene->mMetaData = aiMetadata::Alloc(static_cast( numMeta ) ); + for ( size_t i = 0; i < numMeta; ++i ) { + aiString val( mMetaData[ i ].value ); + scene->mMetaData->Set(static_cast( i ), mMetaData[ i ].name, val ); + } + } + + // import the meshes + scene->mNumMeshes = static_cast( mMeshes.size()); + scene->mMeshes = new aiMesh*[scene->mNumMeshes](); + std::copy( mMeshes.begin(), mMeshes.end(), scene->mMeshes); + + // import the materials + scene->mNumMaterials = static_cast( mMatArray.size() ); + if ( 0 != scene->mNumMaterials ) { + scene->mMaterials = new aiMaterial*[ scene->mNumMaterials ]; + std::copy( mMatArray.begin(), mMatArray.end(), scene->mMaterials ); + } + + // create the scenegraph + scene->mRootNode->mNumChildren = static_cast(children.size()); + scene->mRootNode->mChildren = new aiNode*[scene->mRootNode->mNumChildren](); + std::copy(children.begin(), children.end(), scene->mRootNode->mChildren); + } + +private: + aiNode* ReadObject(aiScene* scene) { + std::unique_ptr node(new aiNode()); + + std::vector meshIds; + + const char *attrib( nullptr ); + std::string name, type; + attrib = xmlReader->getAttributeValue( D3MF::XmlTag::id.c_str() ); + if ( nullptr != attrib ) { + name = attrib; + } + attrib = xmlReader->getAttributeValue( D3MF::XmlTag::type.c_str() ); + if ( nullptr != attrib ) { + type = attrib; + } + + node->mParent = scene->mRootNode; + node->mName.Set(name); + + size_t meshIdx = mMeshes.size(); + + while(ReadToEndElement(D3MF::XmlTag::object)) { + if(xmlReader->getNodeName() == D3MF::XmlTag::mesh) { + auto mesh = ReadMesh(); + + mesh->mName.Set(name); + mMeshes.push_back(mesh); + meshIds.push_back(static_cast(meshIdx)); + ++meshIdx; + } + } + + node->mNumMeshes = static_cast(meshIds.size()); + + node->mMeshes = new unsigned int[node->mNumMeshes]; + + std::copy(meshIds.begin(), meshIds.end(), node->mMeshes); + + return node.release(); + } + + aiMesh *ReadMesh() { + aiMesh* mesh = new aiMesh(); + while(ReadToEndElement(D3MF::XmlTag::mesh)) { + if(xmlReader->getNodeName() == D3MF::XmlTag::vertices) { + ImportVertices(mesh); + } else if(xmlReader->getNodeName() == D3MF::XmlTag::triangles) { + ImportTriangles(mesh); + } + } + + return mesh; + } + + void ReadMetadata() { + const std::string name = xmlReader->getAttributeValue( D3MF::XmlTag::meta_name.c_str() ); + xmlReader->read(); + const std::string value = xmlReader->getNodeData(); + + if ( name.empty() ) { + return; + } + + MetaEntry entry; + entry.name = name; + entry.value = value; + mMetaData.push_back( entry ); + } + + void ImportVertices(aiMesh* mesh) { + std::vector vertices; + while(ReadToEndElement(D3MF::XmlTag::vertices)) { + if(xmlReader->getNodeName() == D3MF::XmlTag::vertex) { + vertices.push_back(ReadVertex()); + } + } + mesh->mNumVertices = static_cast(vertices.size()); + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + + std::copy(vertices.begin(), vertices.end(), mesh->mVertices); + } + + aiVector3D ReadVertex() { + aiVector3D vertex; + + vertex.x = ai_strtof(xmlReader->getAttributeValue(D3MF::XmlTag::x.c_str()), nullptr); + vertex.y = ai_strtof(xmlReader->getAttributeValue(D3MF::XmlTag::y.c_str()), nullptr); + vertex.z = ai_strtof(xmlReader->getAttributeValue(D3MF::XmlTag::z.c_str()), nullptr); + + return vertex; + } + + void ImportTriangles(aiMesh* mesh) { + std::vector faces; + + while(ReadToEndElement(D3MF::XmlTag::triangles)) { + const std::string nodeName( xmlReader->getNodeName() ); + if(xmlReader->getNodeName() == D3MF::XmlTag::triangle) { + faces.push_back(ReadTriangle()); + const char *pidToken( xmlReader->getAttributeValue( D3MF::XmlTag::p1.c_str() ) ); + if ( nullptr != pidToken ) { + int matIdx( std::atoi( pidToken ) ); + mesh->mMaterialIndex = matIdx; + } + } + } + + mesh->mNumFaces = static_cast(faces.size()); + mesh->mFaces = new aiFace[mesh->mNumFaces]; + mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + std::copy(faces.begin(), faces.end(), mesh->mFaces); + } + + aiFace ReadTriangle() { + aiFace face; + + face.mNumIndices = 3; + face.mIndices = new unsigned int[face.mNumIndices]; + face.mIndices[0] = static_cast(std::atoi(xmlReader->getAttributeValue(D3MF::XmlTag::v1.c_str()))); + face.mIndices[1] = static_cast(std::atoi(xmlReader->getAttributeValue(D3MF::XmlTag::v2.c_str()))); + face.mIndices[2] = static_cast(std::atoi(xmlReader->getAttributeValue(D3MF::XmlTag::v3.c_str()))); + + return face; + } + + void ReadBaseMaterials() { + std::vector MatIdArray; + const char *baseMaterialId( xmlReader->getAttributeValue( D3MF::XmlTag::basematerials_id.c_str() ) ); + if ( nullptr != baseMaterialId ) { + unsigned int id = std::atoi( baseMaterialId ); + const size_t newMatIdx( mMatArray.size() ); + if ( id != mActiveMatGroup ) { + mActiveMatGroup = id; + MatId2MatArray::const_iterator it( mMatId2MatArray.find( id ) ); + if ( mMatId2MatArray.end() == it ) { + MatIdArray.clear(); + mMatId2MatArray[ id ] = MatIdArray; + } else { + MatIdArray = it->second; + } + } + MatIdArray.push_back( static_cast( newMatIdx ) ); + mMatId2MatArray[ mActiveMatGroup ] = MatIdArray; + } + + while ( ReadToEndElement( D3MF::XmlTag::basematerials ) ) { + mMatArray.push_back( readMaterialDef() ); + } + } + + bool parseColor( const char *color, aiColor4D &diffuse ) { + if ( nullptr == color ) { + return false; + } + + //format of the color string: #RRGGBBAA or #RRGGBB (3MF Core chapter 5.1.1) + const size_t len( strlen( color ) ); + if ( 9 != len && 7 != len) { + return false; + } + + const char *buf( color ); + if ( '#' != *buf ) { + return false; + } + ++buf; + char comp[ 3 ] = { 0,0,'\0' }; + + comp[ 0 ] = *buf; + ++buf; + comp[ 1 ] = *buf; + ++buf; + diffuse.r = static_cast( strtol( comp, NULL, 16 ) ) / ai_real(255.0); + + + comp[ 0 ] = *buf; + ++buf; + comp[ 1 ] = *buf; + ++buf; + diffuse.g = static_cast< ai_real >( strtol( comp, NULL, 16 ) ) / ai_real(255.0); + + comp[ 0 ] = *buf; + ++buf; + comp[ 1 ] = *buf; + ++buf; + diffuse.b = static_cast< ai_real >( strtol( comp, NULL, 16 ) ) / ai_real(255.0); + + if(7 == len) + return true; + comp[ 0 ] = *buf; + ++buf; + comp[ 1 ] = *buf; + ++buf; + diffuse.a = static_cast< ai_real >( strtol( comp, NULL, 16 ) ) / ai_real(255.0); + + return true; + } + + void assignDiffuseColor( aiMaterial *mat ) { + const char *color = xmlReader->getAttributeValue( D3MF::XmlTag::basematerials_displaycolor.c_str() ); + aiColor4D diffuse; + if ( parseColor( color, diffuse ) ) { + mat->AddProperty( &diffuse, 1, AI_MATKEY_COLOR_DIFFUSE ); + } + + } + aiMaterial *readMaterialDef() { + aiMaterial *mat( nullptr ); + const char *name( nullptr ); + const std::string nodeName( xmlReader->getNodeName() ); + if ( nodeName == D3MF::XmlTag::basematerials_base ) { + name = xmlReader->getAttributeValue( D3MF::XmlTag::basematerials_name.c_str() ); + std::string stdMatName; + aiString matName; + std::string strId( to_string( mActiveMatGroup ) ); + stdMatName += "id"; + stdMatName += strId; + stdMatName += "_"; + if ( nullptr != name ) { + stdMatName += std::string( name ); + } else { + stdMatName += "basemat"; + } + matName.Set( stdMatName ); + + mat = new aiMaterial; + mat->AddProperty( &matName, AI_MATKEY_NAME ); + + assignDiffuseColor( mat ); + } + + return mat; + } + +private: + bool ReadToStartElement(const std::string& startTag) { + while(xmlReader->read()) { + const std::string &nodeName( xmlReader->getNodeName() ); + if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT && nodeName == startTag) { + return true; + } else if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT_END && nodeName == startTag) { + return false; + } + } + + return false; + } + + bool ReadToEndElement(const std::string& closeTag) { + while(xmlReader->read()) { + const std::string &nodeName( xmlReader->getNodeName() ); + if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT) { + return true; + } else if (xmlReader->getNodeType() == irr::io::EXN_ELEMENT_END && nodeName == closeTag) { + return false; + } + } + ASSIMP_LOG_ERROR("unexpected EOF, expected closing <" + closeTag + "> tag"); + + return false; + } + +private: + struct MetaEntry { + std::string name; + std::string value; + }; + std::vector mMetaData; + std::vector mMeshes; + MatArray mMatArray; + unsigned int mActiveMatGroup; + MatId2MatArray mMatId2MatArray; + XmlReader* xmlReader; +}; + +} //namespace D3MF + +static const aiImporterDesc desc = { + "3mf Importer", + "", + "", + "http://3mf.io/", + aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour, + 0, + 0, + 0, + 0, + "3mf" +}; + +D3MFImporter::D3MFImporter() +: BaseImporter() { + // empty +} + +D3MFImporter::~D3MFImporter() { + // empty +} + +bool D3MFImporter::CanRead(const std::string &filename, IOSystem *pIOHandler, bool checkSig) const { + const std::string extension( GetExtension( filename ) ); + if(extension == desc.mFileExtensions ) { + return true; + } else if ( !extension.length() || checkSig ) { + if ( nullptr == pIOHandler ) { + return false; + } + if ( !ZipArchiveIOSystem::isZipArchive( pIOHandler, filename ) ) { + return false; + } + D3MF::D3MFOpcPackage opcPackage( pIOHandler, filename ); + return opcPackage.validate(); + } + + return false; +} + +void D3MFImporter::SetupProperties(const Importer * /*pImp*/) { + // empty +} + +const aiImporterDesc *D3MFImporter::GetInfo() const { + return &desc; +} + +void D3MFImporter::InternReadFile( const std::string &filename, aiScene *pScene, IOSystem *pIOHandler ) { + D3MF::D3MFOpcPackage opcPackage(pIOHandler, filename); + + std::unique_ptr xmlStream(new CIrrXML_IOStreamReader(opcPackage.RootStream())); + std::unique_ptr xmlReader(irr::io::createIrrXMLReader(xmlStream.get())); + + D3MF::XmlSerializer xmlSerializer(xmlReader.get()); + + xmlSerializer.ImportXml(pScene); +} + +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_3MF_IMPORTER diff --git a/thirdparty/assimp/code/3MF/D3MFImporter.h b/thirdparty/assimp/code/3MF/D3MFImporter.h new file mode 100644 index 0000000..3dbdf07 --- /dev/null +++ b/thirdparty/assimp/code/3MF/D3MFImporter.h @@ -0,0 +1,65 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#ifndef AI_D3MFLOADER_H_INCLUDED +#define AI_D3MFLOADER_H_INCLUDED + +#include + +namespace Assimp { + +class D3MFImporter : public BaseImporter { +public: + // BaseImporter interface + D3MFImporter(); + ~D3MFImporter(); + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const; + void SetupProperties(const Importer *pImp); + const aiImporterDesc *GetInfo() const; + +protected: + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler); +}; + +} // Namespace Assimp + +#endif // AI_D3MFLOADER_H_INCLUDED diff --git a/thirdparty/assimp/code/3MF/D3MFOpcPackage.cpp b/thirdparty/assimp/code/3MF/D3MFOpcPackage.cpp new file mode 100644 index 0000000..873ba8e --- /dev/null +++ b/thirdparty/assimp/code/3MF/D3MFOpcPackage.cpp @@ -0,0 +1,207 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_3MF_IMPORTER + +#include "D3MFOpcPackage.h" +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "3MFXmlTags.h" + +namespace Assimp { + +namespace D3MF { +// ------------------------------------------------------------------------------------------------ + +typedef std::shared_ptr OpcPackageRelationshipPtr; + +class OpcPackageRelationshipReader { +public: + OpcPackageRelationshipReader(XmlReader* xmlReader) { + while(xmlReader->read()) { + if(xmlReader->getNodeType() == irr::io::EXN_ELEMENT && + xmlReader->getNodeName() == XmlTag::RELS_RELATIONSHIP_CONTAINER) + { + ParseRootNode(xmlReader); + } + } + } + + void ParseRootNode(XmlReader* xmlReader) + { + ParseAttributes(xmlReader); + + while(xmlReader->read()) + { + if(xmlReader->getNodeType() == irr::io::EXN_ELEMENT && + xmlReader->getNodeName() == XmlTag::RELS_RELATIONSHIP_NODE) + { + ParseChildNode(xmlReader); + } + } + } + + void ParseAttributes(XmlReader*) { + // empty + } + + bool validateRels( OpcPackageRelationshipPtr &relPtr ) { + if ( relPtr->id.empty() || relPtr->type.empty() || relPtr->target.empty() ) { + return false; + } + return true; + } + + void ParseChildNode(XmlReader* xmlReader) { + OpcPackageRelationshipPtr relPtr(new OpcPackageRelationship()); + + relPtr->id = xmlReader->getAttributeValueSafe(XmlTag::RELS_ATTRIB_ID.c_str()); + relPtr->type = xmlReader->getAttributeValueSafe(XmlTag::RELS_ATTRIB_TYPE.c_str()); + relPtr->target = xmlReader->getAttributeValueSafe(XmlTag::RELS_ATTRIB_TARGET.c_str()); + if ( validateRels( relPtr ) ) { + m_relationShips.push_back( relPtr ); + } + } + + std::vector m_relationShips; +}; + +// ------------------------------------------------------------------------------------------------ +D3MFOpcPackage::D3MFOpcPackage(IOSystem* pIOHandler, const std::string& rFile) +: mRootStream(nullptr) +, mZipArchive() { + mZipArchive.reset( new ZipArchiveIOSystem( pIOHandler, rFile ) ); + if(!mZipArchive->isOpen()) { + throw DeadlyImportError("Failed to open file " + rFile+ "."); + } + + std::vector fileList; + mZipArchive->getFileList(fileList); + + for (auto& file: fileList) { + if(file == D3MF::XmlTag::ROOT_RELATIONSHIPS_ARCHIVE) { + //PkgRelationshipReader pkgRelReader(file, archive); + ai_assert(mZipArchive->Exists(file.c_str())); + + IOStream *fileStream = mZipArchive->Open(file.c_str()); + + ai_assert(fileStream != nullptr); + + std::string rootFile = ReadPackageRootRelationship(fileStream); + if ( rootFile.size() > 0 && rootFile[ 0 ] == '/' ) { + rootFile = rootFile.substr( 1 ); + if ( rootFile[ 0 ] == '/' ) { + // deal with zip-bug + rootFile = rootFile.substr( 1 ); + } + } + + ASSIMP_LOG_DEBUG(rootFile); + + mZipArchive->Close(fileStream); + + mRootStream = mZipArchive->Open(rootFile.c_str()); + ai_assert( mRootStream != nullptr ); + if ( nullptr == mRootStream ) { + throw DeadlyExportError( "Cannot open root-file in archive : " + rootFile ); + } + + } else if( file == D3MF::XmlTag::CONTENT_TYPES_ARCHIVE) { + ASSIMP_LOG_WARN_F("Ignored file of unsupported type CONTENT_TYPES_ARCHIVES",file); + } else { + ASSIMP_LOG_WARN_F("Ignored file of unknown type: ",file); + } + + } +} + +D3MFOpcPackage::~D3MFOpcPackage() { + mZipArchive->Close(mRootStream); +} + +IOStream* D3MFOpcPackage::RootStream() const { + return mRootStream; +} + +static const std::string ModelRef = "3D/3dmodel.model"; + +bool D3MFOpcPackage::validate() { + if ( nullptr == mRootStream || nullptr == mZipArchive ) { + return false; + } + + return mZipArchive->Exists( ModelRef.c_str() ); +} + +std::string D3MFOpcPackage::ReadPackageRootRelationship(IOStream* stream) { + std::unique_ptr xmlStream(new CIrrXML_IOStreamReader(stream)); + std::unique_ptr xml(irr::io::createIrrXMLReader(xmlStream.get())); + + OpcPackageRelationshipReader reader(xml.get()); + + auto itr = std::find_if(reader.m_relationShips.begin(), reader.m_relationShips.end(), [](const OpcPackageRelationshipPtr& rel){ + return rel->type == XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE; + }); + + if ( itr == reader.m_relationShips.end() ) { + throw DeadlyImportError( "Cannot find " + XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE ); + } + + return (*itr)->target; +} + +} // Namespace D3MF +} // Namespace Assimp + +#endif //ASSIMP_BUILD_NO_3MF_IMPORTER diff --git a/thirdparty/assimp/code/3MF/D3MFOpcPackage.h b/thirdparty/assimp/code/3MF/D3MFOpcPackage.h new file mode 100644 index 0000000..87d1721 --- /dev/null +++ b/thirdparty/assimp/code/3MF/D3MFOpcPackage.h @@ -0,0 +1,83 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#ifndef D3MFOPCPACKAGE_H +#define D3MFOPCPACKAGE_H + +#include + +#include +#include + +namespace Assimp { + class ZipArchiveIOSystem; + +namespace D3MF { + +using XmlReader = irr::io::IrrXMLReader ; +using XmlReaderPtr = std::shared_ptr ; + +struct OpcPackageRelationship { + std::string id; + std::string type; + std::string target; +}; + +class D3MFOpcPackage { +public: + D3MFOpcPackage( IOSystem* pIOHandler, const std::string& rFile ); + ~D3MFOpcPackage(); + IOStream* RootStream() const; + bool validate(); + +protected: + std::string ReadPackageRootRelationship(IOStream* stream); + +private: + IOStream* mRootStream; + std::unique_ptr mZipArchive; +}; + +} // Namespace D3MF +} // Namespace Assimp + +#endif // D3MFOPCPACKAGE_H diff --git a/thirdparty/assimp/code/AC/ACLoader.cpp b/thirdparty/assimp/code/AC/ACLoader.cpp new file mode 100644 index 0000000..d4c4bd1 --- /dev/null +++ b/thirdparty/assimp/code/AC/ACLoader.cpp @@ -0,0 +1,910 @@ + +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/** @file Implementation of the AC3D importer class */ + + + +#ifndef ASSIMP_BUILD_NO_AC_IMPORTER + +// internal headers +#include "ACLoader.h" +#include +#include +#include +#include "Common/Importer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Assimp; + +static const aiImporterDesc desc = { + "AC3D Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "ac acc ac3d" +}; + +// ------------------------------------------------------------------------------------------------ +// skip to the next token +#define AI_AC_SKIP_TO_NEXT_TOKEN() \ + if (!SkipSpaces(&buffer)) \ + { \ + ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL"); \ + continue; \ + } + +// ------------------------------------------------------------------------------------------------ +// read a string (may be enclosed in double quotation marks). buffer must point to " +#define AI_AC_GET_STRING(out) \ + if (*buffer == '\0') { \ + throw DeadlyImportError("AC3D: Unexpected EOF in string"); \ + } \ + ++buffer; \ + const char* sz = buffer; \ + while ('\"' != *buffer) \ + { \ + if (IsLineEnd( *buffer )) \ + { \ + ASSIMP_LOG_ERROR("AC3D: Unexpected EOF/EOL in string"); \ + out = "ERROR"; \ + break; \ + } \ + ++buffer; \ + } \ + if (IsLineEnd( *buffer ))continue; \ + out = std::string(sz,(unsigned int)(buffer-sz)); \ + ++buffer; + + +// ------------------------------------------------------------------------------------------------ +// read 1 to n floats prefixed with an optional predefined identifier +#define AI_AC_CHECKED_LOAD_FLOAT_ARRAY(name,name_length,num,out) \ + AI_AC_SKIP_TO_NEXT_TOKEN(); \ + if (name_length) \ + { \ + if (strncmp(buffer,name,name_length) || !IsSpace(buffer[name_length])) \ + { \ + ASSIMP_LOG_ERROR("AC3D: Unexpexted token. " name " was expected."); \ + continue; \ + } \ + buffer += name_length+1; \ + } \ + for (unsigned int i = 0; i < num;++i) \ + { \ + AI_AC_SKIP_TO_NEXT_TOKEN(); \ + buffer = fast_atoreal_move(buffer,((float*)out)[i]); \ + } + + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +AC3DImporter::AC3DImporter() + : buffer(), + configSplitBFCull(), + configEvalSubdivision(), + mNumMeshes(), + mLights(), + lights(), + groups(), + polys(), + worlds() +{ + // nothing to be done here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +AC3DImporter::~AC3DImporter() +{ + // nothing to be done here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool AC3DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const +{ + std::string extension = GetExtension(pFile); + + // fixme: are acc and ac3d *really* used? Some sources say they are + if(extension == "ac" || extension == "ac3d" || extension == "acc") { + return true; + } + if (!extension.length() || checkSig) { + uint32_t token = AI_MAKE_MAGIC("AC3D"); + return CheckMagicToken(pIOHandler,pFile,&token,1,0); + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +// Loader meta information +const aiImporterDesc* AC3DImporter::GetInfo () const +{ + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Get a pointer to the next line from the file +bool AC3DImporter::GetNextLine( ) +{ + SkipLine(&buffer); + return SkipSpaces(&buffer); +} + +// ------------------------------------------------------------------------------------------------ +// Parse an object section in an AC file +void AC3DImporter::LoadObjectSection(std::vector& objects) +{ + if (!TokenMatch(buffer,"OBJECT",6)) + return; + + SkipSpaces(&buffer); + + ++mNumMeshes; + + objects.push_back(Object()); + Object& obj = objects.back(); + + aiLight* light = NULL; + if (!ASSIMP_strincmp(buffer,"light",5)) + { + // This is a light source. Add it to the list + mLights->push_back(light = new aiLight()); + + // Return a point light with no attenuation + light->mType = aiLightSource_POINT; + light->mColorDiffuse = light->mColorSpecular = aiColor3D(1.f,1.f,1.f); + light->mAttenuationConstant = 1.f; + + // Generate a default name for both the light source and the node + // FIXME - what's the right way to print a size_t? Is 'zu' universally available? stick with the safe version. + light->mName.length = ::ai_snprintf(light->mName.data, MAXLEN, "ACLight_%i",static_cast(mLights->size())-1); + obj.name = std::string( light->mName.data ); + + ASSIMP_LOG_DEBUG("AC3D: Light source encountered"); + obj.type = Object::Light; + } + else if (!ASSIMP_strincmp(buffer,"group",5)) + { + obj.type = Object::Group; + } + else if (!ASSIMP_strincmp(buffer,"world",5)) + { + obj.type = Object::World; + } + else obj.type = Object::Poly; + while (GetNextLine()) + { + if (TokenMatch(buffer,"kids",4)) + { + SkipSpaces(&buffer); + unsigned int num = strtoul10(buffer,&buffer); + GetNextLine(); + if (num) + { + // load the children of this object recursively + obj.children.reserve(num); + for (unsigned int i = 0; i < num; ++i) + LoadObjectSection(obj.children); + } + return; + } + else if (TokenMatch(buffer,"name",4)) + { + SkipSpaces(&buffer); + AI_AC_GET_STRING(obj.name); + + // If this is a light source, we'll also need to store + // the name of the node in it. + if (light) + { + light->mName.Set(obj.name); + } + } + else if (TokenMatch(buffer,"texture",7)) + { + SkipSpaces(&buffer); + AI_AC_GET_STRING(obj.texture); + } + else if (TokenMatch(buffer,"texrep",6)) + { + SkipSpaces(&buffer); + AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,2,&obj.texRepeat); + if (!obj.texRepeat.x || !obj.texRepeat.y) + obj.texRepeat = aiVector2D (1.f,1.f); + } + else if (TokenMatch(buffer,"texoff",6)) + { + SkipSpaces(&buffer); + AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,2,&obj.texOffset); + } + else if (TokenMatch(buffer,"rot",3)) + { + SkipSpaces(&buffer); + AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,9,&obj.rotation); + } + else if (TokenMatch(buffer,"loc",3)) + { + SkipSpaces(&buffer); + AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,3,&obj.translation); + } + else if (TokenMatch(buffer,"subdiv",6)) + { + SkipSpaces(&buffer); + obj.subDiv = strtoul10(buffer,&buffer); + } + else if (TokenMatch(buffer,"crease",6)) + { + SkipSpaces(&buffer); + obj.crease = fast_atof(buffer); + } + else if (TokenMatch(buffer,"numvert",7)) + { + SkipSpaces(&buffer); + + unsigned int t = strtoul10(buffer,&buffer); + if (t >= AI_MAX_ALLOC(aiVector3D)) { + throw DeadlyImportError("AC3D: Too many vertices, would run out of memory"); + } + obj.vertices.reserve(t); + for (unsigned int i = 0; i < t;++i) + { + if (!GetNextLine()) + { + ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: not all vertices have been parsed yet"); + break; + } + else if (!IsNumeric(*buffer)) + { + ASSIMP_LOG_ERROR("AC3D: Unexpected token: not all vertices have been parsed yet"); + --buffer; // make sure the line is processed a second time + break; + } + obj.vertices.push_back(aiVector3D()); + aiVector3D& v = obj.vertices.back(); + AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,3,&v.x); + } + } + else if (TokenMatch(buffer,"numsurf",7)) + { + SkipSpaces(&buffer); + + bool Q3DWorkAround = false; + + const unsigned int t = strtoul10(buffer,&buffer); + obj.surfaces.reserve(t); + for (unsigned int i = 0; i < t;++i) + { + GetNextLine(); + if (!TokenMatch(buffer,"SURF",4)) + { + // FIX: this can occur for some files - Quick 3D for + // example writes no surf chunks + if (!Q3DWorkAround) + { + ASSIMP_LOG_WARN("AC3D: SURF token was expected"); + ASSIMP_LOG_DEBUG("Continuing with Quick3D Workaround enabled"); + } + --buffer; // make sure the line is processed a second time + // break; --- see fix notes above + + Q3DWorkAround = true; + } + SkipSpaces(&buffer); + obj.surfaces.push_back(Surface()); + Surface& surf = obj.surfaces.back(); + surf.flags = strtoul_cppstyle(buffer); + + while (1) + { + if(!GetNextLine()) + { + throw DeadlyImportError("AC3D: Unexpected EOF: surface is incomplete"); + } + if (TokenMatch(buffer,"mat",3)) + { + SkipSpaces(&buffer); + surf.mat = strtoul10(buffer); + } + else if (TokenMatch(buffer,"refs",4)) + { + // --- see fix notes above + if (Q3DWorkAround) + { + if (!surf.entries.empty()) + { + buffer -= 6; + break; + } + } + + SkipSpaces(&buffer); + const unsigned int m = strtoul10(buffer); + surf.entries.reserve(m); + + obj.numRefs += m; + + for (unsigned int k = 0; k < m; ++k) + { + if(!GetNextLine()) + { + ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: surface references are incomplete"); + break; + } + surf.entries.push_back(Surface::SurfaceEntry()); + Surface::SurfaceEntry& entry = surf.entries.back(); + + entry.first = strtoul10(buffer,&buffer); + SkipSpaces(&buffer); + AI_AC_CHECKED_LOAD_FLOAT_ARRAY("",0,2,&entry.second); + } + } + else + { + + --buffer; // make sure the line is processed a second time + break; + } + } + } + } + } + ASSIMP_LOG_ERROR("AC3D: Unexpected EOF: \'kids\' line was expected"); +} + +// ------------------------------------------------------------------------------------------------ +// Convert a material from AC3DImporter::Material to aiMaterial +void AC3DImporter::ConvertMaterial(const Object& object, + const Material& matSrc, + aiMaterial& matDest) +{ + aiString s; + + if (matSrc.name.length()) + { + s.Set(matSrc.name); + matDest.AddProperty(&s,AI_MATKEY_NAME); + } + if (object.texture.length()) + { + s.Set(object.texture); + matDest.AddProperty(&s,AI_MATKEY_TEXTURE_DIFFUSE(0)); + + // UV transformation + if (1.f != object.texRepeat.x || 1.f != object.texRepeat.y || + object.texOffset.x || object.texOffset.y) + { + aiUVTransform transform; + transform.mScaling = object.texRepeat; + transform.mTranslation = object.texOffset; + matDest.AddProperty(&transform,1,AI_MATKEY_UVTRANSFORM_DIFFUSE(0)); + } + } + + matDest.AddProperty(&matSrc.rgb,1, AI_MATKEY_COLOR_DIFFUSE); + matDest.AddProperty(&matSrc.amb,1, AI_MATKEY_COLOR_AMBIENT); + matDest.AddProperty(&matSrc.emis,1,AI_MATKEY_COLOR_EMISSIVE); + matDest.AddProperty(&matSrc.spec,1,AI_MATKEY_COLOR_SPECULAR); + + int n; + if (matSrc.shin) + { + n = aiShadingMode_Phong; + matDest.AddProperty(&matSrc.shin,1,AI_MATKEY_SHININESS); + } + else n = aiShadingMode_Gouraud; + matDest.AddProperty(&n,1,AI_MATKEY_SHADING_MODEL); + + float f = 1.f - matSrc.trans; + matDest.AddProperty(&f,1,AI_MATKEY_OPACITY); +} + +// ------------------------------------------------------------------------------------------------ +// Converts the loaded data to the internal verbose representation +aiNode* AC3DImporter::ConvertObjectSection(Object& object, + std::vector& meshes, + std::vector& outMaterials, + const std::vector& materials, + aiNode* parent) +{ + aiNode* node = new aiNode(); + node->mParent = parent; + if (object.vertices.size()) + { + if (!object.surfaces.size() || !object.numRefs) + { + /* " An object with 7 vertices (no surfaces, no materials defined). + This is a good way of getting point data into AC3D. + The Vertex->create convex-surface/object can be used on these + vertices to 'wrap' a 3d shape around them " + (http://www.opencity.info/html/ac3dfileformat.html) + + therefore: if no surfaces are defined return point data only + */ + + ASSIMP_LOG_INFO("AC3D: No surfaces defined in object definition, " + "a point list is returned"); + + meshes.push_back(new aiMesh()); + aiMesh* mesh = meshes.back(); + + mesh->mNumFaces = mesh->mNumVertices = (unsigned int)object.vertices.size(); + aiFace* faces = mesh->mFaces = new aiFace[mesh->mNumFaces]; + aiVector3D* verts = mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + + for (unsigned int i = 0; i < mesh->mNumVertices;++i,++faces,++verts) + { + *verts = object.vertices[i]; + faces->mNumIndices = 1; + faces->mIndices = new unsigned int[1]; + faces->mIndices[0] = i; + } + + // use the primary material in this case. this should be the + // default material if all objects of the file contain points + // and no faces. + mesh->mMaterialIndex = 0; + outMaterials.push_back(new aiMaterial()); + ConvertMaterial(object, materials[0], *outMaterials.back()); + } + else + { + // need to generate one or more meshes for this object. + // find out how many different materials we have + typedef std::pair< unsigned int, unsigned int > IntPair; + typedef std::vector< IntPair > MatTable; + MatTable needMat(materials.size(),IntPair(0,0)); + + std::vector::iterator it,end = object.surfaces.end(); + std::vector::iterator it2,end2; + + for (it = object.surfaces.begin(); it != end; ++it) + { + unsigned int idx = (*it).mat; + if (idx >= needMat.size()) + { + ASSIMP_LOG_ERROR("AC3D: material index is out of range"); + idx = 0; + } + if ((*it).entries.empty()) + { + ASSIMP_LOG_WARN("AC3D: surface her zero vertex references"); + } + + // validate all vertex indices to make sure we won't crash here + for (it2 = (*it).entries.begin(), + end2 = (*it).entries.end(); it2 != end2; ++it2) + { + if ((*it2).first >= object.vertices.size()) + { + ASSIMP_LOG_WARN("AC3D: Invalid vertex reference"); + (*it2).first = 0; + } + } + + if (!needMat[idx].first)++node->mNumMeshes; + + switch ((*it).flags & 0xf) + { + // closed line + case 0x1: + + needMat[idx].first += (unsigned int)(*it).entries.size(); + needMat[idx].second += (unsigned int)(*it).entries.size()<<1u; + break; + + // unclosed line + case 0x2: + + needMat[idx].first += (unsigned int)(*it).entries.size()-1; + needMat[idx].second += ((unsigned int)(*it).entries.size()-1)<<1u; + break; + + // 0 == polygon, else unknown + default: + + if ((*it).flags & 0xf) + { + ASSIMP_LOG_WARN("AC3D: The type flag of a surface is unknown"); + (*it).flags &= ~(0xf); + } + + // the number of faces increments by one, the number + // of vertices by surface.numref. + needMat[idx].first++; + needMat[idx].second += (unsigned int)(*it).entries.size(); + }; + } + unsigned int* pip = node->mMeshes = new unsigned int[node->mNumMeshes]; + unsigned int mat = 0; + const size_t oldm = meshes.size(); + for (MatTable::const_iterator cit = needMat.begin(), cend = needMat.end(); + cit != cend; ++cit, ++mat) + { + if (!(*cit).first)continue; + + // allocate a new aiMesh object + *pip++ = (unsigned int)meshes.size(); + aiMesh* mesh = new aiMesh(); + meshes.push_back(mesh); + + mesh->mMaterialIndex = (unsigned int)outMaterials.size(); + outMaterials.push_back(new aiMaterial()); + ConvertMaterial(object, materials[mat], *outMaterials.back()); + + // allocate storage for vertices and normals + mesh->mNumFaces = (*cit).first; + if (mesh->mNumFaces == 0) { + throw DeadlyImportError("AC3D: No faces"); + } else if (mesh->mNumFaces > AI_MAX_ALLOC(aiFace)) { + throw DeadlyImportError("AC3D: Too many faces, would run out of memory"); + } + aiFace* faces = mesh->mFaces = new aiFace[mesh->mNumFaces]; + + mesh->mNumVertices = (*cit).second; + if (mesh->mNumVertices == 0) { + throw DeadlyImportError("AC3D: No vertices"); + } else if (mesh->mNumVertices > AI_MAX_ALLOC(aiVector3D)) { + throw DeadlyImportError("AC3D: Too many vertices, would run out of memory"); + } + aiVector3D* vertices = mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + unsigned int cur = 0; + + // allocate UV coordinates, but only if the texture name for the + // surface is not empty + aiVector3D* uv = NULL; + if(object.texture.length()) + { + uv = mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; + mesh->mNumUVComponents[0] = 2; + } + + for (it = object.surfaces.begin(); it != end; ++it) + { + if (mat == (*it).mat) + { + const Surface& src = *it; + + // closed polygon + unsigned int type = (*it).flags & 0xf; + if (!type) + { + aiFace& face = *faces++; + if((face.mNumIndices = (unsigned int)src.entries.size())) + { + face.mIndices = new unsigned int[face.mNumIndices]; + for (unsigned int i = 0; i < face.mNumIndices;++i,++vertices) + { + const Surface::SurfaceEntry& entry = src.entries[i]; + face.mIndices[i] = cur++; + + // copy vertex positions + if (static_cast(vertices - mesh->mVertices) >= mesh->mNumVertices) { + throw DeadlyImportError("AC3D: Invalid number of vertices"); + } + *vertices = object.vertices[entry.first] + object.translation; + + + // copy texture coordinates + if (uv) + { + uv->x = entry.second.x; + uv->y = entry.second.y; + ++uv; + } + } + } + } + else + { + + it2 = (*it).entries.begin(); + + // either a closed or an unclosed line + unsigned int tmp = (unsigned int)(*it).entries.size(); + if (0x2 == type)--tmp; + for (unsigned int m = 0; m < tmp;++m) + { + aiFace& face = *faces++; + + face.mNumIndices = 2; + face.mIndices = new unsigned int[2]; + face.mIndices[0] = cur++; + face.mIndices[1] = cur++; + + // copy vertex positions + if (it2 == (*it).entries.end() ) { + throw DeadlyImportError("AC3D: Bad line"); + } + ai_assert((*it2).first < object.vertices.size()); + *vertices++ = object.vertices[(*it2).first]; + + // copy texture coordinates + if (uv) + { + uv->x = (*it2).second.x; + uv->y = (*it2).second.y; + ++uv; + } + + + if (0x1 == type && tmp-1 == m) + { + // if this is a closed line repeat its beginning now + it2 = (*it).entries.begin(); + } + else ++it2; + + // second point + *vertices++ = object.vertices[(*it2).first]; + + if (uv) + { + uv->x = (*it2).second.x; + uv->y = (*it2).second.y; + ++uv; + } + } + } + } + } + } + + // Now apply catmull clark subdivision if necessary. We split meshes into + // materials which is not done by AC3D during smoothing, so we need to + // collect all meshes using the same material group. + if (object.subDiv) { + if (configEvalSubdivision) { + std::unique_ptr div(Subdivider::Create(Subdivider::CATMULL_CLARKE)); + ASSIMP_LOG_INFO("AC3D: Evaluating subdivision surface: "+object.name); + + std::vector cpy(meshes.size()-oldm,NULL); + div->Subdivide(&meshes[oldm],cpy.size(),&cpy.front(),object.subDiv,true); + std::copy(cpy.begin(),cpy.end(),meshes.begin()+oldm); + + // previous meshes are deleted vy Subdivide(). + } + else { + ASSIMP_LOG_INFO("AC3D: Letting the subdivision surface untouched due to my configuration: " + +object.name); + } + } + } + } + + if (object.name.length()) + node->mName.Set(object.name); + else + { + // generate a name depending on the type of the node + switch (object.type) + { + case Object::Group: + node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACGroup_%i",groups++); + break; + case Object::Poly: + node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACPoly_%i",polys++); + break; + case Object::Light: + node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACLight_%i",lights++); + break; + + // there shouldn't be more than one world, but we don't care + case Object::World: + node->mName.length = ::ai_snprintf(node->mName.data, MAXLEN, "ACWorld_%i",worlds++); + break; + } + } + + + // setup the local transformation matrix of the object + // compute the transformation offset to the parent node + node->mTransformation = aiMatrix4x4 ( object.rotation ); + + if (object.type == Object::Group || !object.numRefs) + { + node->mTransformation.a4 = object.translation.x; + node->mTransformation.b4 = object.translation.y; + node->mTransformation.c4 = object.translation.z; + } + + // add children to the object + if (object.children.size()) + { + node->mNumChildren = (unsigned int)object.children.size(); + node->mChildren = new aiNode*[node->mNumChildren]; + for (unsigned int i = 0; i < node->mNumChildren;++i) + { + node->mChildren[i] = ConvertObjectSection(object.children[i],meshes,outMaterials,materials,node); + } + } + + return node; +} + +// ------------------------------------------------------------------------------------------------ +void AC3DImporter::SetupProperties(const Importer* pImp) +{ + configSplitBFCull = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_SEPARATE_BFCULL,1) ? true : false; + configEvalSubdivision = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_AC_EVAL_SUBDIVISION,1) ? true : false; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void AC3DImporter::InternReadFile( const std::string& pFile, + aiScene* pScene, IOSystem* pIOHandler) +{ + std::unique_ptr file( pIOHandler->Open( pFile, "rb")); + + // Check whether we can read from the file + if( file.get() == NULL) + throw DeadlyImportError( "Failed to open AC3D file " + pFile + "."); + + // allocate storage and copy the contents of the file to a memory buffer + std::vector mBuffer2; + TextFileToBuffer(file.get(),mBuffer2); + + buffer = &mBuffer2[0]; + mNumMeshes = 0; + + lights = polys = worlds = groups = 0; + + if (::strncmp(buffer,"AC3D",4)) { + throw DeadlyImportError("AC3D: No valid AC3D file, magic sequence not found"); + } + + // print the file format version to the console + unsigned int version = HexDigitToDecimal( buffer[4] ); + char msg[3]; + ASSIMP_itoa10(msg,3,version); + ASSIMP_LOG_INFO_F("AC3D file format version: ", msg); + + std::vector materials; + materials.reserve(5); + + std::vector rootObjects; + rootObjects.reserve(5); + + std::vector lights; + mLights = & lights; + + while (GetNextLine()) + { + if (TokenMatch(buffer,"MATERIAL",8)) + { + materials.push_back(Material()); + Material& mat = materials.back(); + + // manually parse the material ... sscanf would use the buldin atof ... + // Format: (name) rgb %f %f %f amb %f %f %f emis %f %f %f spec %f %f %f shi %d trans %f + + AI_AC_SKIP_TO_NEXT_TOKEN(); + if ('\"' == *buffer) + { + AI_AC_GET_STRING(mat.name); + AI_AC_SKIP_TO_NEXT_TOKEN(); + } + + AI_AC_CHECKED_LOAD_FLOAT_ARRAY("rgb",3,3,&mat.rgb); + AI_AC_CHECKED_LOAD_FLOAT_ARRAY("amb",3,3,&mat.amb); + AI_AC_CHECKED_LOAD_FLOAT_ARRAY("emis",4,3,&mat.emis); + AI_AC_CHECKED_LOAD_FLOAT_ARRAY("spec",4,3,&mat.spec); + AI_AC_CHECKED_LOAD_FLOAT_ARRAY("shi",3,1,&mat.shin); + AI_AC_CHECKED_LOAD_FLOAT_ARRAY("trans",5,1,&mat.trans); + } + LoadObjectSection(rootObjects); + } + + if (rootObjects.empty() || !mNumMeshes) + { + throw DeadlyImportError("AC3D: No meshes have been loaded"); + } + if (materials.empty()) + { + ASSIMP_LOG_WARN("AC3D: No material has been found"); + materials.push_back(Material()); + } + + mNumMeshes += (mNumMeshes>>2u) + 1; + std::vector meshes; + meshes.reserve(mNumMeshes); + + std::vector omaterials; + materials.reserve(mNumMeshes); + + // generate a dummy root if there are multiple objects on the top layer + Object* root; + if (1 == rootObjects.size()) + root = &rootObjects[0]; + else + { + root = new Object(); + } + + // now convert the imported stuff to our output data structure + pScene->mRootNode = ConvertObjectSection(*root,meshes,omaterials,materials); + if (1 != rootObjects.size())delete root; + + if (!::strncmp( pScene->mRootNode->mName.data, "Node", 4)) + pScene->mRootNode->mName.Set(""); + + // copy meshes + if (meshes.empty()) + { + throw DeadlyImportError("An unknown error occurred during converting"); + } + pScene->mNumMeshes = (unsigned int)meshes.size(); + pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; + ::memcpy(pScene->mMeshes,&meshes[0],pScene->mNumMeshes*sizeof(void*)); + + // copy materials + pScene->mNumMaterials = (unsigned int)omaterials.size(); + pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]; + ::memcpy(pScene->mMaterials,&omaterials[0],pScene->mNumMaterials*sizeof(void*)); + + // copy lights + pScene->mNumLights = (unsigned int)lights.size(); + if (lights.size()) + { + pScene->mLights = new aiLight*[lights.size()]; + ::memcpy(pScene->mLights,&lights[0],lights.size()*sizeof(void*)); + } +} + +#endif //!defined ASSIMP_BUILD_NO_AC_IMPORTER diff --git a/thirdparty/assimp/code/AC/ACLoader.h b/thirdparty/assimp/code/AC/ACLoader.h new file mode 100644 index 0000000..cab2c3a --- /dev/null +++ b/thirdparty/assimp/code/AC/ACLoader.h @@ -0,0 +1,276 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file ACLoader.h + * @brief Declaration of the .ac importer class. + */ +#ifndef AI_AC3DLOADER_H_INCLUDED +#define AI_AC3DLOADER_H_INCLUDED + +#include + +#include +#include + +struct aiNode; +struct aiMesh; +struct aiMaterial; +struct aiLight; + + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** AC3D (*.ac) importer class +*/ +class AC3DImporter : public BaseImporter +{ +public: + AC3DImporter(); + ~AC3DImporter(); + + + + // Represents an AC3D material + struct Material + { + Material() + : rgb (0.6f,0.6f,0.6f) + , spec (1.f,1.f,1.f) + , shin (0.f) + , trans (0.f) + {} + + // base color of the material + aiColor3D rgb; + + // ambient color of the material + aiColor3D amb; + + // emissive color of the material + aiColor3D emis; + + // specular color of the material + aiColor3D spec; + + // shininess exponent + float shin; + + // transparency. 0 == opaque + float trans; + + // name of the material. optional. + std::string name; + }; + + // Represents an AC3D surface + struct Surface + { + Surface() + : mat (0) + , flags (0) + {} + + unsigned int mat,flags; + + typedef std::pair SurfaceEntry; + std::vector< SurfaceEntry > entries; + }; + + // Represents an AC3D object + struct Object + { + Object() + : type (World) + , name( "" ) + , children() + , texture( "" ) + , texRepeat( 1.f, 1.f ) + , texOffset( 0.0f, 0.0f ) + , rotation() + , translation() + , vertices() + , surfaces() + , numRefs (0) + , subDiv (0) + , crease() + {} + + // Type description + enum Type + { + World = 0x0, + Poly = 0x1, + Group = 0x2, + Light = 0x4 + } type; + + // name of the object + std::string name; + + // object children + std::vector children; + + // texture to be assigned to all surfaces of the object + std::string texture; + + // texture repat factors (scaling for all coordinates) + aiVector2D texRepeat, texOffset; + + // rotation matrix + aiMatrix3x3 rotation; + + // translation vector + aiVector3D translation; + + // vertices + std::vector vertices; + + // surfaces + std::vector surfaces; + + // number of indices (= num verts in verbose format) + unsigned int numRefs; + + // number of subdivisions to be performed on the + // imported data + unsigned int subDiv; + + // max angle limit for smoothing + float crease; + }; + + +public: + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool checkSig) const; + +protected: + + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details */ + const aiImporterDesc* GetInfo () const; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details*/ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler); + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list.*/ + void SetupProperties(const Importer* pImp); + +private: + + // ------------------------------------------------------------------- + /** Get the next line from the file. + * @return false if the end of the file was reached*/ + bool GetNextLine(); + + // ------------------------------------------------------------------- + /** Load the object section. This method is called recursively to + * load subobjects, the method returns after a 'kids 0' was + * encountered. + * @objects List of output objects*/ + void LoadObjectSection(std::vector& objects); + + // ------------------------------------------------------------------- + /** Convert all objects into meshes and nodes. + * @param object Current object to work on + * @param meshes Pointer to the list of output meshes + * @param outMaterials List of output materials + * @param materials Material list + * @param Scenegraph node for the object */ + aiNode* ConvertObjectSection(Object& object, + std::vector& meshes, + std::vector& outMaterials, + const std::vector& materials, + aiNode* parent = NULL); + + // ------------------------------------------------------------------- + /** Convert a material + * @param object Current object + * @param matSrc Source material description + * @param matDest Destination material to be filled */ + void ConvertMaterial(const Object& object, + const Material& matSrc, + aiMaterial& matDest); + +private: + + + // points to the next data line + const char* buffer; + + // Configuration option: if enabled, up to two meshes + // are generated per material: those faces who have + // their bf cull flags set are separated. + bool configSplitBFCull; + + // Configuration switch: subdivision surfaces are only + // evaluated if the value is true. + bool configEvalSubdivision; + + // counts how many objects we have in the tree. + // basing on this information we can find a + // good estimate how many meshes we'll have in the final scene. + unsigned int mNumMeshes; + + // current list of light sources + std::vector* mLights; + + // name counters + unsigned int lights, groups, polys, worlds; +}; + +} // end of namespace Assimp + +#endif // AI_AC3DIMPORTER_H_INC diff --git a/thirdparty/assimp/code/AMF/AMFImporter.cpp b/thirdparty/assimp/code/AMF/AMFImporter.cpp new file mode 100644 index 0000000..dedb6dc --- /dev/null +++ b/thirdparty/assimp/code/AMF/AMFImporter.cpp @@ -0,0 +1,705 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/// \file AMFImporter.cpp +/// \brief AMF-format files importer for Assimp: main algorithm implementation. +/// \date 2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER + +// Header files, Assimp. +#include "AMFImporter.hpp" +#include "AMFImporter_Macro.hpp" + +#include +#include + +// Header files, stdlib. +#include + +namespace Assimp +{ + +/// \var aiImporterDesc AMFImporter::Description +/// Conastant which hold importer description +const aiImporterDesc AMFImporter::Description = { + "Additive manufacturing file format(AMF) Importer", + "smalcom", + "", + "See documentation in source code. Chapter: Limitations.", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, + 0, + 0, + 0, + 0, + "amf" +}; + +void AMFImporter::Clear() +{ + mNodeElement_Cur = nullptr; + mUnit.clear(); + mMaterial_Converted.clear(); + mTexture_Converted.clear(); + // Delete all elements + if(!mNodeElement_List.empty()) + { + for(CAMFImporter_NodeElement* ne: mNodeElement_List) { delete ne; } + + mNodeElement_List.clear(); + } +} + +AMFImporter::~AMFImporter() +{ + if(mReader != nullptr) delete mReader; + // Clear() is accounting if data already is deleted. So, just check again if all data is deleted. + Clear(); +} + +/*********************************************************************************************************************************************/ +/************************************************************ Functions: find set ************************************************************/ +/*********************************************************************************************************************************************/ + +bool AMFImporter::Find_NodeElement(const std::string& pID, const CAMFImporter_NodeElement::EType pType, CAMFImporter_NodeElement** pNodeElement) const +{ + for(CAMFImporter_NodeElement* ne: mNodeElement_List) + { + if((ne->ID == pID) && (ne->Type == pType)) + { + if(pNodeElement != nullptr) *pNodeElement = ne; + + return true; + } + }// for(CAMFImporter_NodeElement* ne: mNodeElement_List) + + return false; +} + +bool AMFImporter::Find_ConvertedNode(const std::string& pID, std::list& pNodeList, aiNode** pNode) const +{ +aiString node_name(pID.c_str()); + + for(aiNode* node: pNodeList) + { + if(node->mName == node_name) + { + if(pNode != nullptr) *pNode = node; + + return true; + } + }// for(aiNode* node: pNodeList) + + return false; +} + +bool AMFImporter::Find_ConvertedMaterial(const std::string& pID, const SPP_Material** pConvertedMaterial) const +{ + for(const SPP_Material& mat: mMaterial_Converted) + { + if(mat.ID == pID) + { + if(pConvertedMaterial != nullptr) *pConvertedMaterial = &mat; + + return true; + } + }// for(const SPP_Material& mat: mMaterial_Converted) + + return false; +} + +/*********************************************************************************************************************************************/ +/************************************************************ Functions: throw set ***********************************************************/ +/*********************************************************************************************************************************************/ + +void AMFImporter::Throw_CloseNotFound(const std::string& pNode) +{ + throw DeadlyImportError("Close tag for node <" + pNode + "> not found. Seems file is corrupt."); +} + +void AMFImporter::Throw_IncorrectAttr(const std::string& pAttrName) +{ + throw DeadlyImportError("Node <" + std::string(mReader->getNodeName()) + "> has incorrect attribute \"" + pAttrName + "\"."); +} + +void AMFImporter::Throw_IncorrectAttrValue(const std::string& pAttrName) +{ + throw DeadlyImportError("Attribute \"" + pAttrName + "\" in node <" + std::string(mReader->getNodeName()) + "> has incorrect value."); +} + +void AMFImporter::Throw_MoreThanOnceDefined(const std::string& pNodeType, const std::string& pDescription) +{ + throw DeadlyImportError("\"" + pNodeType + "\" node can be used only once in " + mReader->getNodeName() + ". Description: " + pDescription); +} + +void AMFImporter::Throw_ID_NotFound(const std::string& pID) const +{ + throw DeadlyImportError("Not found node with name \"" + pID + "\"."); +} + +/*********************************************************************************************************************************************/ +/************************************************************* Functions: XML set ************************************************************/ +/*********************************************************************************************************************************************/ + +void AMFImporter::XML_CheckNode_MustHaveChildren() +{ + if(mReader->isEmptyElement()) throw DeadlyImportError(std::string("Node <") + mReader->getNodeName() + "> must have children."); +} + +void AMFImporter::XML_CheckNode_SkipUnsupported(const std::string& pParentNodeName) +{ + static const size_t Uns_Skip_Len = 3; + const char* Uns_Skip[Uns_Skip_Len] = { "composite", "edge", "normal" }; + + static bool skipped_before[Uns_Skip_Len] = { false, false, false }; + + std::string nn(mReader->getNodeName()); + bool found = false; + bool close_found = false; + size_t sk_idx; + + for(sk_idx = 0; sk_idx < Uns_Skip_Len; sk_idx++) + { + if(nn != Uns_Skip[sk_idx]) continue; + + found = true; + if(mReader->isEmptyElement()) + { + close_found = true; + + goto casu_cres; + } + + while(mReader->read()) + { + if((mReader->getNodeType() == irr::io::EXN_ELEMENT_END) && (nn == mReader->getNodeName())) + { + close_found = true; + + goto casu_cres; + } + } + }// for(sk_idx = 0; sk_idx < Uns_Skip_Len; sk_idx++) + +casu_cres: + + if(!found) throw DeadlyImportError("Unknown node \"" + nn + "\" in " + pParentNodeName + "."); + if(!close_found) Throw_CloseNotFound(nn); + + if(!skipped_before[sk_idx]) + { + skipped_before[sk_idx] = true; + ASSIMP_LOG_WARN_F("Skipping node \"", nn, "\" in ", pParentNodeName, "."); + } +} + +bool AMFImporter::XML_SearchNode(const std::string& pNodeName) +{ + while(mReader->read()) + { + if((mReader->getNodeType() == irr::io::EXN_ELEMENT) && XML_CheckNode_NameEqual(pNodeName)) return true; + } + + return false; +} + +bool AMFImporter::XML_ReadNode_GetAttrVal_AsBool(const int pAttrIdx) +{ + std::string val(mReader->getAttributeValue(pAttrIdx)); + + if((val == "false") || (val == "0")) + return false; + else if((val == "true") || (val == "1")) + return true; + else + throw DeadlyImportError("Bool attribute value can contain \"false\"/\"0\" or \"true\"/\"1\" not the \"" + val + "\""); +} + +float AMFImporter::XML_ReadNode_GetAttrVal_AsFloat(const int pAttrIdx) +{ + std::string val; + float tvalf; + + ParseHelper_FixTruncatedFloatString(mReader->getAttributeValue(pAttrIdx), val); + fast_atoreal_move(val.c_str(), tvalf, false); + + return tvalf; +} + +uint32_t AMFImporter::XML_ReadNode_GetAttrVal_AsU32(const int pAttrIdx) +{ + return strtoul10(mReader->getAttributeValue(pAttrIdx)); +} + +float AMFImporter::XML_ReadNode_GetVal_AsFloat() +{ + std::string val; + float tvalf; + + if(!mReader->read()) throw DeadlyImportError("XML_ReadNode_GetVal_AsFloat. No data, seems file is corrupt."); + if(mReader->getNodeType() != irr::io::EXN_TEXT) throw DeadlyImportError("XML_ReadNode_GetVal_AsFloat. Invalid type of XML element, seems file is corrupt."); + + ParseHelper_FixTruncatedFloatString(mReader->getNodeData(), val); + fast_atoreal_move(val.c_str(), tvalf, false); + + return tvalf; +} + +uint32_t AMFImporter::XML_ReadNode_GetVal_AsU32() +{ + if(!mReader->read()) throw DeadlyImportError("XML_ReadNode_GetVal_AsU32. No data, seems file is corrupt."); + if(mReader->getNodeType() != irr::io::EXN_TEXT) throw DeadlyImportError("XML_ReadNode_GetVal_AsU32. Invalid type of XML element, seems file is corrupt."); + + return strtoul10(mReader->getNodeData()); +} + +void AMFImporter::XML_ReadNode_GetVal_AsString(std::string& pValue) +{ + if(!mReader->read()) throw DeadlyImportError("XML_ReadNode_GetVal_AsString. No data, seems file is corrupt."); + if(mReader->getNodeType() != irr::io::EXN_TEXT) + throw DeadlyImportError("XML_ReadNode_GetVal_AsString. Invalid type of XML element, seems file is corrupt."); + + pValue = mReader->getNodeData(); +} + +/*********************************************************************************************************************************************/ +/************************************************************ Functions: parse set ***********************************************************/ +/*********************************************************************************************************************************************/ + +void AMFImporter::ParseHelper_Node_Enter(CAMFImporter_NodeElement* pNode) +{ + mNodeElement_Cur->Child.push_back(pNode);// add new element to current element child list. + mNodeElement_Cur = pNode;// switch current element to new one. +} + +void AMFImporter::ParseHelper_Node_Exit() +{ + // check if we can walk up. + if(mNodeElement_Cur != nullptr) mNodeElement_Cur = mNodeElement_Cur->Parent; +} + +void AMFImporter::ParseHelper_FixTruncatedFloatString(const char* pInStr, std::string& pOutString) +{ + size_t instr_len; + + pOutString.clear(); + instr_len = strlen(pInStr); + if(!instr_len) return; + + pOutString.reserve(instr_len * 3 / 2); + // check and correct floats in format ".x". Must be "x.y". + if(pInStr[0] == '.') pOutString.push_back('0'); + + pOutString.push_back(pInStr[0]); + for(size_t ci = 1; ci < instr_len; ci++) + { + if((pInStr[ci] == '.') && ((pInStr[ci - 1] == ' ') || (pInStr[ci - 1] == '-') || (pInStr[ci - 1] == '+') || (pInStr[ci - 1] == '\t'))) + { + pOutString.push_back('0'); + pOutString.push_back('.'); + } + else + { + pOutString.push_back(pInStr[ci]); + } + } +} + +static bool ParseHelper_Decode_Base64_IsBase64(const char pChar) +{ + return (isalnum(pChar) || (pChar == '+') || (pChar == '/')); +} + +void AMFImporter::ParseHelper_Decode_Base64(const std::string& pInputBase64, std::vector& pOutputData) const +{ + // With help from + // René Nyffenegger http://www.adp-gmbh.ch/cpp/common/base64.html + const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + uint8_t tidx = 0; + uint8_t arr4[4], arr3[3]; + + // check input data + if(pInputBase64.size() % 4) throw DeadlyImportError("Base64-encoded data must have size multiply of four."); + // prepare output place + pOutputData.clear(); + pOutputData.reserve(pInputBase64.size() / 4 * 3); + + for(size_t in_len = pInputBase64.size(), in_idx = 0; (in_len > 0) && (pInputBase64[in_idx] != '='); in_len--) + { + if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx])) + { + arr4[tidx++] = pInputBase64[in_idx++]; + if(tidx == 4) + { + for(tidx = 0; tidx < 4; tidx++) arr4[tidx] = (uint8_t)base64_chars.find(arr4[tidx]); + + arr3[0] = (arr4[0] << 2) + ((arr4[1] & 0x30) >> 4); + arr3[1] = ((arr4[1] & 0x0F) << 4) + ((arr4[2] & 0x3C) >> 2); + arr3[2] = ((arr4[2] & 0x03) << 6) + arr4[3]; + for(tidx = 0; tidx < 3; tidx++) pOutputData.push_back(arr3[tidx]); + + tidx = 0; + }// if(tidx == 4) + }// if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx])) + else + { + in_idx++; + }// if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx])) else + } + + if(tidx) + { + for(uint8_t i = tidx; i < 4; i++) arr4[i] = 0; + for(uint8_t i = 0; i < 4; i++) arr4[i] = (uint8_t)(base64_chars.find(arr4[i])); + + arr3[0] = (arr4[0] << 2) + ((arr4[1] & 0x30) >> 4); + arr3[1] = ((arr4[1] & 0x0F) << 4) + ((arr4[2] & 0x3C) >> 2); + arr3[2] = ((arr4[2] & 0x03) << 6) + arr4[3]; + for(uint8_t i = 0; i < (tidx - 1); i++) pOutputData.push_back(arr3[i]); + } +} + +void AMFImporter::ParseFile(const std::string& pFile, IOSystem* pIOHandler) +{ + irr::io::IrrXMLReader* OldReader = mReader;// store current XMLreader. + std::unique_ptr file(pIOHandler->Open(pFile, "rb")); + + // Check whether we can read from the file + if(file.get() == NULL) throw DeadlyImportError("Failed to open AMF file " + pFile + "."); + + // generate a XML reader for it + std::unique_ptr mIOWrapper(new CIrrXML_IOStreamReader(file.get())); + mReader = irr::io::createIrrXMLReader(mIOWrapper.get()); + if(!mReader) throw DeadlyImportError("Failed to create XML reader for file" + pFile + "."); + // + // start reading + // search for root tag + if(XML_SearchNode("amf")) + ParseNode_Root(); + else + throw DeadlyImportError("Root node \"amf\" not found."); + + delete mReader; + // restore old XMLreader + mReader = OldReader; +} + +// +// +// Root XML element. +// Multi elements - No. +void AMFImporter::ParseNode_Root() +{ + std::string unit, version; + CAMFImporter_NodeElement *ne( nullptr ); + + // Read attributes for node . + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECK_RET("unit", unit, mReader->getAttributeValue); + MACRO_ATTRREAD_CHECK_RET("version", version, mReader->getAttributeValue); + MACRO_ATTRREAD_LOOPEND_WSKIP; + + // Check attributes + if(!mUnit.empty()) + { + if((mUnit != "inch") && (mUnit != "millimeter") && (mUnit != "meter") && (mUnit != "feet") && (mUnit != "micron")) Throw_IncorrectAttrValue("unit"); + } + + // create root node element. + ne = new CAMFImporter_NodeElement_Root(nullptr); + mNodeElement_Cur = ne;// set first "current" element + // and assign attribute's values + ((CAMFImporter_NodeElement_Root*)ne)->Unit = unit; + ((CAMFImporter_NodeElement_Root*)ne)->Version = version; + + // Check for child nodes + if(!mReader->isEmptyElement()) + { + MACRO_NODECHECK_LOOPBEGIN("amf"); + if(XML_CheckNode_NameEqual("object")) { ParseNode_Object(); continue; } + if(XML_CheckNode_NameEqual("material")) { ParseNode_Material(); continue; } + if(XML_CheckNode_NameEqual("texture")) { ParseNode_Texture(); continue; } + if(XML_CheckNode_NameEqual("constellation")) { ParseNode_Constellation(); continue; } + if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; } + MACRO_NODECHECK_LOOPEND("amf"); + mNodeElement_Cur = ne;// force restore "current" element + }// if(!mReader->isEmptyElement()) + + mNodeElement_List.push_back(ne);// add to node element list because its a new object in graph. +} + +// +// +// A collection of objects or constellations with specific relative locations. +// Multi elements - Yes. +// Parent element - . +void AMFImporter::ParseNode_Constellation() +{ + std::string id; + CAMFImporter_NodeElement* ne( nullptr ); + + // Read attributes for node . + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECK_RET("id", id, mReader->getAttributeValue); + MACRO_ATTRREAD_LOOPEND; + + // create and if needed - define new grouping object. + ne = new CAMFImporter_NodeElement_Constellation(mNodeElement_Cur); + + CAMFImporter_NodeElement_Constellation& als = *((CAMFImporter_NodeElement_Constellation*)ne);// alias for convenience + + if(!id.empty()) als.ID = id; + // Check for child nodes + if(!mReader->isEmptyElement()) + { + ParseHelper_Node_Enter(ne); + MACRO_NODECHECK_LOOPBEGIN("constellation"); + if(XML_CheckNode_NameEqual("instance")) { ParseNode_Instance(); continue; } + if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; } + MACRO_NODECHECK_LOOPEND("constellation"); + ParseHelper_Node_Exit(); + }// if(!mReader->isEmptyElement()) + else + { + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + }// if(!mReader->isEmptyElement()) else + + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +// +// +// A collection of objects or constellations with specific relative locations. +// Multi elements - Yes. +// Parent element - . +void AMFImporter::ParseNode_Instance() +{ + std::string objectid; + CAMFImporter_NodeElement* ne( nullptr ); + + // Read attributes for node . + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECK_RET("objectid", objectid, mReader->getAttributeValue); + MACRO_ATTRREAD_LOOPEND; + + // used object id must be defined, check that. + if(objectid.empty()) throw DeadlyImportError("\"objectid\" in must be defined."); + // create and define new grouping object. + ne = new CAMFImporter_NodeElement_Instance(mNodeElement_Cur); + + CAMFImporter_NodeElement_Instance& als = *((CAMFImporter_NodeElement_Instance*)ne);// alias for convenience + + als.ObjectID = objectid; + // Check for child nodes + if(!mReader->isEmptyElement()) + { + bool read_flag[6] = { false, false, false, false, false, false }; + + als.Delta.Set(0, 0, 0); + als.Rotation.Set(0, 0, 0); + ParseHelper_Node_Enter(ne); + MACRO_NODECHECK_LOOPBEGIN("instance"); + MACRO_NODECHECK_READCOMP_F("deltax", read_flag[0], als.Delta.x); + MACRO_NODECHECK_READCOMP_F("deltay", read_flag[1], als.Delta.y); + MACRO_NODECHECK_READCOMP_F("deltaz", read_flag[2], als.Delta.z); + MACRO_NODECHECK_READCOMP_F("rx", read_flag[3], als.Rotation.x); + MACRO_NODECHECK_READCOMP_F("ry", read_flag[4], als.Rotation.y); + MACRO_NODECHECK_READCOMP_F("rz", read_flag[5], als.Rotation.z); + MACRO_NODECHECK_LOOPEND("instance"); + ParseHelper_Node_Exit(); + // also convert degrees to radians. + als.Rotation.x = AI_MATH_PI_F * als.Rotation.x / 180.0f; + als.Rotation.y = AI_MATH_PI_F * als.Rotation.y / 180.0f; + als.Rotation.z = AI_MATH_PI_F * als.Rotation.z / 180.0f; + }// if(!mReader->isEmptyElement()) + else + { + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + }// if(!mReader->isEmptyElement()) else + + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +// +// +// An object definition. +// Multi elements - Yes. +// Parent element - . +void AMFImporter::ParseNode_Object() +{ + std::string id; + CAMFImporter_NodeElement* ne( nullptr ); + + // Read attributes for node . + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECK_RET("id", id, mReader->getAttributeValue); + MACRO_ATTRREAD_LOOPEND; + + // create and if needed - define new geometry object. + ne = new CAMFImporter_NodeElement_Object(mNodeElement_Cur); + + CAMFImporter_NodeElement_Object& als = *((CAMFImporter_NodeElement_Object*)ne);// alias for convenience + + if(!id.empty()) als.ID = id; + // Check for child nodes + if(!mReader->isEmptyElement()) + { + bool col_read = false; + + ParseHelper_Node_Enter(ne); + MACRO_NODECHECK_LOOPBEGIN("object"); + if(XML_CheckNode_NameEqual("color")) + { + // Check if color already defined for object. + if(col_read) Throw_MoreThanOnceDefined("color", "Only one color can be defined for ."); + // read data and set flag about it + ParseNode_Color(); + col_read = true; + + continue; + } + + if(XML_CheckNode_NameEqual("mesh")) { ParseNode_Mesh(); continue; } + if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; } + MACRO_NODECHECK_LOOPEND("object"); + ParseHelper_Node_Exit(); + }// if(!mReader->isEmptyElement()) + else + { + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + }// if(!mReader->isEmptyElement()) else + + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +// +// +// Specify additional information about an entity. +// Multi elements - Yes. +// Parent element - , , , , . +// +// Reserved types are: +// "Name" - The alphanumeric label of the entity, to be used by the interpreter if interacting with the user. +// "Description" - A description of the content of the entity +// "URL" - A link to an external resource relating to the entity +// "Author" - Specifies the name(s) of the author(s) of the entity +// "Company" - Specifying the company generating the entity +// "CAD" - specifies the name of the originating CAD software and version +// "Revision" - specifies the revision of the entity +// "Tolerance" - specifies the desired manufacturing tolerance of the entity in entity's unit system +// "Volume" - specifies the total volume of the entity, in the entity's unit system, to be used for verification (object and volume only) +void AMFImporter::ParseNode_Metadata() +{ + std::string type, value; + CAMFImporter_NodeElement* ne( nullptr ); + + // read attribute + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECK_RET("type", type, mReader->getAttributeValue); + MACRO_ATTRREAD_LOOPEND; + // and value of node. + value = mReader->getNodeData(); + // Create node element and assign read data. + ne = new CAMFImporter_NodeElement_Metadata(mNodeElement_Cur); + ((CAMFImporter_NodeElement_Metadata*)ne)->Type = type; + ((CAMFImporter_NodeElement_Metadata*)ne)->Value = value; + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +/*********************************************************************************************************************************************/ +/******************************************************** Functions: BaseImporter set ********************************************************/ +/*********************************************************************************************************************************************/ + +bool AMFImporter::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool pCheckSig) const +{ + const std::string extension = GetExtension(pFile); + + if ( extension == "amf" ) { + return true; + } + + if(!extension.length() || pCheckSig) + { + const char* tokens[] = { "& pExtensionList) +{ + pExtensionList.insert("amf"); +} + +const aiImporterDesc* AMFImporter::GetInfo () const +{ + return &Description; +} + +void AMFImporter::InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) +{ + Clear();// delete old graph. + ParseFile(pFile, pIOHandler); + Postprocess_BuildScene(pScene); + // scene graph is ready, exit. +} + +}// namespace Assimp + +#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER diff --git a/thirdparty/assimp/code/AMF/AMFImporter.hpp b/thirdparty/assimp/code/AMF/AMFImporter.hpp new file mode 100644 index 0000000..2b8086a --- /dev/null +++ b/thirdparty/assimp/code/AMF/AMFImporter.hpp @@ -0,0 +1,432 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/// \file AMFImporter.hpp +/// \brief AMF-format files importer for Assimp. +/// \date 2016 +/// \author smal.root@gmail.com +// Thanks to acorn89 for support. + +#pragma once +#ifndef INCLUDED_AI_AMF_IMPORTER_H +#define INCLUDED_AI_AMF_IMPORTER_H + +#include "AMFImporter_Node.hpp" + +// Header files, Assimp. +#include +#include +#include "assimp/types.h" +#include +#include + +// Header files, stdlib. +#include + +namespace Assimp { + +/// \class AMFImporter +/// Class that holding scene graph which include: geometry, metadata, materials etc. +/// +/// Implementing features. +/// +/// Limitations. +/// +/// 1. When for texture mapping used set of source textures (r, g, b, a) not only one then attribute "tiled" for all set will be true if it true in any of +/// source textures. +/// Example. Triangle use for texture mapping three textures. Two of them has "tiled" set to false and one - set to true. In scene all three textures +/// will be tiled. +/// +/// Unsupported features: +/// 1. Node , formulas in and . For implementing this feature can be used expression parser "muParser" like in project +/// "amf_tools". +/// 2. Attribute "profile" in node . +/// 3. Curved geometry: , and children nodes of them. +/// 4. Attributes: "unit" and "version" in read but do nothing. +/// 5. stored only for root node . +/// 6. Color averaging of vertices for which 's set different colors. +/// +/// Supported nodes: +/// General: +/// ; ; and children , , , , , ; ; +/// +/// Geometry: +/// ; ; ; ; and children , , ; ; and children , , ; +/// +/// Material: +/// and children , , , ; ; ; +/// two variants of texture coordinates: +/// new - and children , , , , , +/// old - and children , , , , , +/// +class AMFImporter : public BaseImporter { +private: + struct SPP_Material;// forward declaration + + /// \struct SPP_Composite + /// Data type for post-processing step. More suitable container for part of material's composition. + struct SPP_Composite { + SPP_Material* Material;///< Pointer to material - part of composition. + std::string Formula;///< Formula for calculating ratio of \ref Material. + }; + + /// \struct SPP_Material + /// Data type for post-processing step. More suitable container for material. + struct SPP_Material { + std::string ID;///< Material ID. + std::list Metadata;///< Metadata of material. + CAMFImporter_NodeElement_Color* Color;///< Color of material. + std::list Composition;///< List of child materials if current material is composition of few another. + + /// Return color calculated for specified coordinate. + /// \param [in] pX - "x" coordinate. + /// \param [in] pY - "y" coordinate. + /// \param [in] pZ - "z" coordinate. + /// \return calculated color. + aiColor4D GetColor(const float pX, const float pY, const float pZ) const; + }; + + /// Data type for post-processing step. More suitable container for texture. + struct SPP_Texture { + std::string ID; + size_t Width, Height, Depth; + bool Tiled; + char FormatHint[9];// 8 for string + 1 for terminator. + uint8_t *Data; + }; + + /// Data type for post-processing step. Contain face data. + struct SComplexFace { + aiFace Face;///< Face vertices. + const CAMFImporter_NodeElement_Color* Color;///< Face color. Equal to nullptr if color is not set for the face. + const CAMFImporter_NodeElement_TexMap* TexMap;///< Face texture mapping data. Equal to nullptr if texture mapping is not set for the face. + }; + + /// Clear all temporary data. + void Clear(); + + /***********************************************/ + /************* Functions: find set *************/ + /***********************************************/ + + /// Find specified node element in node elements list ( \ref mNodeElement_List). + /// \param [in] pID - ID(name) of requested node element. + /// \param [in] pType - type of node element. + /// \param [out] pNode - pointer to pointer to item found. + /// \return true - if the node element is found, else - false. + bool Find_NodeElement(const std::string& pID, const CAMFImporter_NodeElement::EType pType, CAMFImporter_NodeElement** pNodeElement) const; + + /// Find requested aiNode in node list. + /// \param [in] pID - ID(name) of requested node. + /// \param [in] pNodeList - list of nodes where to find the node. + /// \param [out] pNode - pointer to pointer to item found. + /// \return true - if the node is found, else - false. + bool Find_ConvertedNode(const std::string& pID, std::list& pNodeList, aiNode** pNode) const; + + /// Find material in list for converted materials. Use at postprocessing step. + /// \param [in] pID - material ID. + /// \param [out] pConvertedMaterial - pointer to found converted material (\ref SPP_Material). + /// \return true - if the material is found, else - false. + bool Find_ConvertedMaterial(const std::string& pID, const SPP_Material** pConvertedMaterial) const; + + /// Find texture in list of converted textures. Use at postprocessing step, + /// \param [in] pID_R - ID of source "red" texture. + /// \param [in] pID_G - ID of source "green" texture. + /// \param [in] pID_B - ID of source "blue" texture. + /// \param [in] pID_A - ID of source "alpha" texture. Use empty string to find RGB-texture. + /// \param [out] pConvertedTextureIndex - pointer where index in list of found texture will be written. If equivalent to nullptr then nothing will be + /// written. + /// \return true - if the texture is found, else - false. + bool Find_ConvertedTexture(const std::string& pID_R, const std::string& pID_G, const std::string& pID_B, const std::string& pID_A, + uint32_t* pConvertedTextureIndex = nullptr) const; + + + /// Get data stored in and place it to arrays. + /// \param [in] pNodeElement - reference to node element which kept data. + /// \param [in] pVertexCoordinateArray - reference to vertices coordinates kept in . + /// \param [in] pVertexColorArray - reference to vertices colors for all & pVertexCoordinateArray, + std::vector& pVertexColorArray) const; + + /// Return converted texture ID which related to specified source textures ID's. If converted texture does not exist then it will be created and ID on new + /// converted texture will be returned. Conversion: set of textures from \ref CAMFImporter_NodeElement_Texture to one \ref SPP_Texture and place it + /// to converted textures list. + /// Any of source ID's can be absent(empty string) or even one ID only specified. But at least one ID must be specified. + /// \param [in] pID_R - ID of source "red" texture. + /// \param [in] pID_G - ID of source "green" texture. + /// \param [in] pID_B - ID of source "blue" texture. + /// \param [in] pID_A - ID of source "alpha" texture. + /// \return index of the texture in array of the converted textures. + size_t PostprocessHelper_GetTextureID_Or_Create(const std::string& pID_R, const std::string& pID_G, const std::string& pID_B, const std::string& pID_A); + + /// Separate input list by texture IDs. This step is needed because aiMesh can contain mesh which is use only one texture (or set: diffuse, bump etc). + /// \param [in] pInputList - input list with faces. Some of them can contain color or texture mapping, or both of them, or nothing. Will be cleared after + /// processing. + /// \param [out] pOutputList_Separated - output list of the faces lists. Separated faces list by used texture IDs. Will be cleared before processing. + void PostprocessHelper_SplitFacesByTextureID(std::list& pInputList, std::list >& pOutputList_Separated); + + /// Check if child elements of node element is metadata and add it to scene node. + /// \param [in] pMetadataList - reference to list with collected metadata. + /// \param [out] pSceneNode - scene node in which metadata will be added. + void Postprocess_AddMetadata(const std::list& pMetadataList, aiNode& pSceneNode) const; + + /// To create aiMesh and aiNode for it from . + /// \param [in] pNodeElement - reference to node element which kept data. + /// \param [out] pMeshList - reference to a list with all aiMesh of the scene. + /// \param [out] pSceneNode - pointer to place where new aiNode will be created. + void Postprocess_BuildNodeAndObject(const CAMFImporter_NodeElement_Object& pNodeElement, std::list& pMeshList, aiNode** pSceneNode); + + /// Create mesh for every in . + /// \param [in] pNodeElement - reference to node element which kept data. + /// \param [in] pVertexCoordinateArray - reference to vertices coordinates for all 's. + /// \param [in] pVertexColorArray - reference to vertices colors for all 's. If color for vertex is not set then corresponding member of array + /// contain nullptr. + /// \param [in] pObjectColor - pointer to colors for . If color is not set then argument contain nullptr. + /// \param [in] pMaterialList - reference to a list with defined materials. + /// \param [out] pMeshList - reference to a list with all aiMesh of the scene. + /// \param [out] pSceneNode - reference to aiNode which will own new aiMesh's. + void Postprocess_BuildMeshSet(const CAMFImporter_NodeElement_Mesh& pNodeElement, const std::vector& pVertexCoordinateArray, + const std::vector& pVertexColorArray, const CAMFImporter_NodeElement_Color* pObjectColor, + std::list& pMeshList, aiNode& pSceneNode); + + /// Convert material from \ref CAMFImporter_NodeElement_Material to \ref SPP_Material. + /// \param [in] pMaterial - source CAMFImporter_NodeElement_Material. + void Postprocess_BuildMaterial(const CAMFImporter_NodeElement_Material& pMaterial); + + /// Create and add to aiNode's list new part of scene graph defined by . + /// \param [in] pConstellation - reference to node. + /// \param [out] pNodeList - reference to aiNode's list. + void Postprocess_BuildConstellation(CAMFImporter_NodeElement_Constellation& pConstellation, std::list& pNodeList) const; + + /// Build Assimp scene graph in aiScene from collected data. + /// \param [out] pScene - pointer to aiScene where tree will be built. + void Postprocess_BuildScene(aiScene* pScene); + + + /// Call that function when close tag of node not found and exception must be raised. + /// E.g.: + /// + /// + /// + /// \throw DeadlyImportError. + /// \param [in] pNode - node name in which exception happened. + void Throw_CloseNotFound(const std::string& pNode); + + /// Call that function when attribute name is incorrect and exception must be raised. + /// \param [in] pAttrName - attribute name. + /// \throw DeadlyImportError. + void Throw_IncorrectAttr(const std::string& pAttrName); + + /// Call that function when attribute value is incorrect and exception must be raised. + /// \param [in] pAttrName - attribute name. + /// \throw DeadlyImportError. + void Throw_IncorrectAttrValue(const std::string& pAttrName); + + /// Call that function when some type of nodes are defined twice or more when must be used only once and exception must be raised. + /// E.g.: + /// + /// ... + /// ... + /// + /// \throw DeadlyImportError. + /// \param [in] pNodeType - type of node which defined one more time. + /// \param [in] pDescription - message about error. E.g. what the node defined while exception raised. + void Throw_MoreThanOnceDefined(const std::string& pNodeType, const std::string& pDescription); + + /// Call that function when referenced element ID are not found in graph and exception must be raised. + /// \param [in] pID - ID of of element which not found. + /// \throw DeadlyImportError. + void Throw_ID_NotFound(const std::string& pID) const; + + /// Check if current node have children: .... If not then exception will throwed. + void XML_CheckNode_MustHaveChildren(); + + /// Check if current node name is equal to pNodeName. + /// \param [in] pNodeName - name for checking. + /// return true if current node name is equal to pNodeName, else - false. + bool XML_CheckNode_NameEqual(const std::string& pNodeName) { return mReader->getNodeName() == pNodeName; } + + /// Skip unsupported node and report about that. Depend on node name can be skipped begin tag of node all whole node. + /// \param [in] pParentNodeName - parent node name. Used for reporting. + void XML_CheckNode_SkipUnsupported(const std::string& pParentNodeName); + + /// Search for specified node in file. XML file read pointer(mReader) will point to found node or file end after search is end. + /// \param [in] pNodeName - requested node name. + /// return true - if node is found, else - false. + bool XML_SearchNode(const std::string& pNodeName); + + /// Read attribute value. + /// \param [in] pAttrIdx - attribute index (\ref mReader->getAttribute* set). + /// \return read data. + bool XML_ReadNode_GetAttrVal_AsBool(const int pAttrIdx); + + /// Read attribute value. + /// \param [in] pAttrIdx - attribute index (\ref mReader->getAttribute* set). + /// \return read data. + float XML_ReadNode_GetAttrVal_AsFloat(const int pAttrIdx); + + /// Read attribute value. + /// \param [in] pAttrIdx - attribute index (\ref mReader->getAttribute* set). + /// \return read data. + uint32_t XML_ReadNode_GetAttrVal_AsU32(const int pAttrIdx); + + /// Read node value. + /// \return read data. + float XML_ReadNode_GetVal_AsFloat(); + + /// Read node value. + /// \return read data. + uint32_t XML_ReadNode_GetVal_AsU32(); + + /// Read node value. + /// \return read data. + void XML_ReadNode_GetVal_AsString(std::string& pValue); + + /// Make pNode as current and enter deeper for parsing child nodes. At end \ref ParseHelper_Node_Exit must be called. + /// \param [in] pNode - new current node. + void ParseHelper_Node_Enter(CAMFImporter_NodeElement* pNode); + + /// This function must be called when exiting from grouping node. \ref ParseHelper_Group_Begin. + void ParseHelper_Node_Exit(); + + /// Attribute values of floating point types can take form ".x"(without leading zero). irrXMLReader can not read this form of values and it + /// must be converted to right form - "0.xxx". + /// \param [in] pInStr - pointer to input string which can contain incorrect form of values. + /// \param [out[ pOutString - output string with right form of values. + void ParseHelper_FixTruncatedFloatString(const char* pInStr, std::string& pOutString); + + /// Decode Base64-encoded data. + /// \param [in] pInputBase64 - reference to input Base64-encoded string. + /// \param [out] pOutputData - reference to output array for decoded data. + void ParseHelper_Decode_Base64(const std::string& pInputBase64, std::vector& pOutputData) const; + + /// Parse node of the file. + void ParseNode_Root(); + + /// Parse node of the file. + void ParseNode_Constellation(); + + /// Parse node of the file. + void ParseNode_Instance(); + + /// Parse node of the file. + void ParseNode_Material(); + + /// Parse node. + void ParseNode_Metadata(); + + /// Parse node of the file. + void ParseNode_Object(); + + /// Parse node of the file. + void ParseNode_Texture(); + + /// Parse node of the file. + void ParseNode_Coordinates(); + + /// Parse node of the file. + void ParseNode_Edge(); + + /// Parse node of the file. + void ParseNode_Mesh(); + + /// Parse node of the file. + void ParseNode_Triangle(); + + /// Parse node of the file. + void ParseNode_Vertex(); + + /// Parse node of the file. + void ParseNode_Vertices(); + + /// Parse node of the file. + void ParseNode_Volume(); + + /// Parse node of the file. + void ParseNode_Color(); + + /// Parse of node of the file. + /// \param [in] pUseOldName - if true then use old name of node(and children) - , instead of new name - . + void ParseNode_TexMap(const bool pUseOldName = false); + +public: + /// Default constructor. + AMFImporter() AI_NO_EXCEPT + : mNodeElement_Cur(nullptr) + , mReader(nullptr) { + // empty + } + + /// Default destructor. + ~AMFImporter(); + + /// Parse AMF file and fill scene graph. The function has no return value. Result can be found by analyzing the generated graph. + /// Also exception can be thrown if trouble will found. + /// \param [in] pFile - name of file to be parsed. + /// \param [in] pIOHandler - pointer to IO helper object. + void ParseFile(const std::string& pFile, IOSystem* pIOHandler); + + bool CanRead(const std::string& pFile, IOSystem* pIOHandler, bool pCheckSig) const; + void GetExtensionList(std::set& pExtensionList); + void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); + const aiImporterDesc* GetInfo ()const; + + AMFImporter(const AMFImporter& pScene) = delete; + AMFImporter& operator=(const AMFImporter& pScene) = delete; + +private: + static const aiImporterDesc Description; + + CAMFImporter_NodeElement* mNodeElement_Cur;///< Current element. + std::list mNodeElement_List;///< All elements of scene graph. + irr::io::IrrXMLReader* mReader;///< Pointer to XML-reader object + std::string mUnit; + std::list mMaterial_Converted;///< List of converted materials for postprocessing step. + std::list mTexture_Converted;///< List of converted textures for postprocessing step. + +}; + +}// namespace Assimp + +#endif // INCLUDED_AI_AMF_IMPORTER_H diff --git a/thirdparty/assimp/code/AMF/AMFImporter_Geometry.cpp b/thirdparty/assimp/code/AMF/AMFImporter_Geometry.cpp new file mode 100644 index 0000000..f1538e3 --- /dev/null +++ b/thirdparty/assimp/code/AMF/AMFImporter_Geometry.cpp @@ -0,0 +1,357 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/// \file AMFImporter_Geometry.cpp +/// \brief Parsing data from geometry nodes. +/// \date 2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER + +#include "AMFImporter.hpp" +#include "AMFImporter_Macro.hpp" + +namespace Assimp +{ + +// +// +// A 3D mesh hull. +// Multi elements - Yes. +// Parent element - . +void AMFImporter::ParseNode_Mesh() +{ +CAMFImporter_NodeElement* ne; + + // create new mesh object. + ne = new CAMFImporter_NodeElement_Mesh(mNodeElement_Cur); + // Check for child nodes + if(!mReader->isEmptyElement()) + { + bool vert_read = false; + + ParseHelper_Node_Enter(ne); + MACRO_NODECHECK_LOOPBEGIN("mesh"); + if(XML_CheckNode_NameEqual("vertices")) + { + // Check if data already defined. + if(vert_read) Throw_MoreThanOnceDefined("vertices", "Only one vertices set can be defined for ."); + // read data and set flag about it + ParseNode_Vertices(); + vert_read = true; + + continue; + } + + if(XML_CheckNode_NameEqual("volume")) { ParseNode_Volume(); continue; } + MACRO_NODECHECK_LOOPEND("mesh"); + ParseHelper_Node_Exit(); + }// if(!mReader->isEmptyElement()) + else + { + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + }// if(!mReader->isEmptyElement()) else + + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +// +// +// The list of vertices to be used in defining triangles. +// Multi elements - No. +// Parent element - . +void AMFImporter::ParseNode_Vertices() +{ +CAMFImporter_NodeElement* ne; + + // create new mesh object. + ne = new CAMFImporter_NodeElement_Vertices(mNodeElement_Cur); + // Check for child nodes + if(!mReader->isEmptyElement()) + { + ParseHelper_Node_Enter(ne); + MACRO_NODECHECK_LOOPBEGIN("vertices"); + if(XML_CheckNode_NameEqual("vertex")) { ParseNode_Vertex(); continue; } + MACRO_NODECHECK_LOOPEND("vertices"); + ParseHelper_Node_Exit(); + }// if(!mReader->isEmptyElement()) + else + { + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + }// if(!mReader->isEmptyElement()) else + + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +// +// +// A vertex to be referenced in triangles. +// Multi elements - Yes. +// Parent element - . +void AMFImporter::ParseNode_Vertex() +{ +CAMFImporter_NodeElement* ne; + + // create new mesh object. + ne = new CAMFImporter_NodeElement_Vertex(mNodeElement_Cur); + // Check for child nodes + if(!mReader->isEmptyElement()) + { + bool col_read = false; + bool coord_read = false; + + ParseHelper_Node_Enter(ne); + MACRO_NODECHECK_LOOPBEGIN("vertex"); + if(XML_CheckNode_NameEqual("color")) + { + // Check if data already defined. + if(col_read) Throw_MoreThanOnceDefined("color", "Only one color can be defined for ."); + // read data and set flag about it + ParseNode_Color(); + col_read = true; + + continue; + } + + if(XML_CheckNode_NameEqual("coordinates")) + { + // Check if data already defined. + if(coord_read) Throw_MoreThanOnceDefined("coordinates", "Only one coordinates set can be defined for ."); + // read data and set flag about it + ParseNode_Coordinates(); + coord_read = true; + + continue; + } + + if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; } + MACRO_NODECHECK_LOOPEND("vertex"); + ParseHelper_Node_Exit(); + }// if(!mReader->isEmptyElement()) + else + { + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + }// if(!mReader->isEmptyElement()) else + + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +// +// +// Specifies the 3D location of this vertex. +// Multi elements - No. +// Parent element - . +// +// Children elements: +// , , +// Multi elements - No. +// X, Y, or Z coordinate, respectively, of a vertex position in space. +void AMFImporter::ParseNode_Coordinates() +{ +CAMFImporter_NodeElement* ne; + + // create new color object. + ne = new CAMFImporter_NodeElement_Coordinates(mNodeElement_Cur); + + CAMFImporter_NodeElement_Coordinates& als = *((CAMFImporter_NodeElement_Coordinates*)ne);// alias for convenience + + // Check for child nodes + if(!mReader->isEmptyElement()) + { + bool read_flag[3] = { false, false, false }; + + ParseHelper_Node_Enter(ne); + MACRO_NODECHECK_LOOPBEGIN("coordinates"); + MACRO_NODECHECK_READCOMP_F("x", read_flag[0], als.Coordinate.x); + MACRO_NODECHECK_READCOMP_F("y", read_flag[1], als.Coordinate.y); + MACRO_NODECHECK_READCOMP_F("z", read_flag[2], als.Coordinate.z); + MACRO_NODECHECK_LOOPEND("coordinates"); + ParseHelper_Node_Exit(); + // check that all components was defined + if((read_flag[0] && read_flag[1] && read_flag[2]) == 0) throw DeadlyImportError("Not all coordinate's components are defined."); + + }// if(!mReader->isEmptyElement()) + else + { + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + }// if(!mReader->isEmptyElement()) else + + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +// +// +// Defines a volume from the established vertex list. +// Multi elements - Yes. +// Parent element - . +void AMFImporter::ParseNode_Volume() +{ +std::string materialid; +std::string type; +CAMFImporter_NodeElement* ne; + + // Read attributes for node . + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECK_RET("materialid", materialid, mReader->getAttributeValue); + MACRO_ATTRREAD_CHECK_RET("type", type, mReader->getAttributeValue); + MACRO_ATTRREAD_LOOPEND; + + // create new object. + ne = new CAMFImporter_NodeElement_Volume(mNodeElement_Cur); + // and assign read data + ((CAMFImporter_NodeElement_Volume*)ne)->MaterialID = materialid; + ((CAMFImporter_NodeElement_Volume*)ne)->Type = type; + // Check for child nodes + if(!mReader->isEmptyElement()) + { + bool col_read = false; + + ParseHelper_Node_Enter(ne); + MACRO_NODECHECK_LOOPBEGIN("volume"); + if(XML_CheckNode_NameEqual("color")) + { + // Check if data already defined. + if(col_read) Throw_MoreThanOnceDefined("color", "Only one color can be defined for ."); + // read data and set flag about it + ParseNode_Color(); + col_read = true; + + continue; + } + + if(XML_CheckNode_NameEqual("triangle")) { ParseNode_Triangle(); continue; } + if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; } + MACRO_NODECHECK_LOOPEND("volume"); + ParseHelper_Node_Exit(); + }// if(!mReader->isEmptyElement()) + else + { + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + }// if(!mReader->isEmptyElement()) else + + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +// +// +// Defines a 3D triangle from three vertices, according to the right-hand rule (counter-clockwise when looking from the outside). +// Multi elements - Yes. +// Parent element - . +// +// Children elements: +// , , +// Multi elements - No. +// Index of the desired vertices in a triangle or edge. +void AMFImporter::ParseNode_Triangle() +{ +CAMFImporter_NodeElement* ne; + + // create new color object. + ne = new CAMFImporter_NodeElement_Triangle(mNodeElement_Cur); + + CAMFImporter_NodeElement_Triangle& als = *((CAMFImporter_NodeElement_Triangle*)ne);// alias for convenience + + // Check for child nodes + if(!mReader->isEmptyElement()) + { + bool col_read = false, tex_read = false; + bool read_flag[3] = { false, false, false }; + + ParseHelper_Node_Enter(ne); + MACRO_NODECHECK_LOOPBEGIN("triangle"); + if(XML_CheckNode_NameEqual("color")) + { + // Check if data already defined. + if(col_read) Throw_MoreThanOnceDefined("color", "Only one color can be defined for ."); + // read data and set flag about it + ParseNode_Color(); + col_read = true; + + continue; + } + + if(XML_CheckNode_NameEqual("texmap"))// new name of node: "texmap". + { + // Check if data already defined. + if(tex_read) Throw_MoreThanOnceDefined("texmap", "Only one texture coordinate can be defined for ."); + // read data and set flag about it + ParseNode_TexMap(); + tex_read = true; + + continue; + } + else if(XML_CheckNode_NameEqual("map"))// old name of node: "map". + { + // Check if data already defined. + if(tex_read) Throw_MoreThanOnceDefined("map", "Only one texture coordinate can be defined for ."); + // read data and set flag about it + ParseNode_TexMap(true); + tex_read = true; + + continue; + } + + MACRO_NODECHECK_READCOMP_U32("v1", read_flag[0], als.V[0]); + MACRO_NODECHECK_READCOMP_U32("v2", read_flag[1], als.V[1]); + MACRO_NODECHECK_READCOMP_U32("v3", read_flag[2], als.V[2]); + MACRO_NODECHECK_LOOPEND("triangle"); + ParseHelper_Node_Exit(); + // check that all components was defined + if((read_flag[0] && read_flag[1] && read_flag[2]) == 0) throw DeadlyImportError("Not all vertices of the triangle are defined."); + + }// if(!mReader->isEmptyElement()) + else + { + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + }// if(!mReader->isEmptyElement()) else + + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +}// namespace Assimp + +#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER diff --git a/thirdparty/assimp/code/AMF/AMFImporter_Macro.hpp b/thirdparty/assimp/code/AMF/AMFImporter_Macro.hpp new file mode 100644 index 0000000..f60c5fb --- /dev/null +++ b/thirdparty/assimp/code/AMF/AMFImporter_Macro.hpp @@ -0,0 +1,166 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/// \file AMFImporter_Macro.hpp +/// \brief Useful macrodefines. +/// \date 2016 +/// \author smal.root@gmail.com + +#pragma once +#ifndef AMFIMPORTER_MACRO_HPP_INCLUDED +#define AMFIMPORTER_MACRO_HPP_INCLUDED + +/// \def MACRO_ATTRREAD_LOOPBEG +/// Begin of loop that read attributes values. +#define MACRO_ATTRREAD_LOOPBEG \ + for(int idx = 0, idx_end = mReader->getAttributeCount(); idx < idx_end; idx++) \ + { \ + std::string an(mReader->getAttributeName(idx)); + +/// \def MACRO_ATTRREAD_LOOPEND +/// End of loop that read attributes values. +#define MACRO_ATTRREAD_LOOPEND \ + Throw_IncorrectAttr(an); \ + } + +/// \def MACRO_ATTRREAD_LOOPEND_WSKIP +/// End of loop that read attributes values. Difference from \ref MACRO_ATTRREAD_LOOPEND in that: current macro skip unknown attributes, but +/// \ref MACRO_ATTRREAD_LOOPEND throw an exception. +#define MACRO_ATTRREAD_LOOPEND_WSKIP \ + continue; \ + } + +/// \def MACRO_ATTRREAD_CHECK_REF +/// Check current attribute name and if it equal to requested then read value. Result write to output variable by reference. If result was read then +/// "continue" will called. +/// \param [in] pAttrName - attribute name. +/// \param [out] pVarName - output variable name. +/// \param [in] pFunction - function which read attribute value and write it to pVarName. +#define MACRO_ATTRREAD_CHECK_REF(pAttrName, pVarName, pFunction) \ + if(an == pAttrName) \ + { \ + pFunction(idx, pVarName); \ + continue; \ + } + +/// \def MACRO_ATTRREAD_CHECK_RET +/// Check current attribute name and if it equal to requested then read value. Result write to output variable using return value of \ref pFunction. +/// If result was read then "continue" will called. +/// \param [in] pAttrName - attribute name. +/// \param [out] pVarName - output variable name. +/// \param [in] pFunction - function which read attribute value and write it to pVarName. +#define MACRO_ATTRREAD_CHECK_RET(pAttrName, pVarName, pFunction) \ + if(an == pAttrName) \ + { \ + pVarName = pFunction(idx); \ + continue; \ + } + +/// \def MACRO_NODECHECK_LOOPBEGIN(pNodeName) +/// Begin of loop of parsing child nodes. Do not add ';' at end. +/// \param [in] pNodeName - current node name. +#define MACRO_NODECHECK_LOOPBEGIN(pNodeName) \ + do { \ + bool close_found = false; \ + \ + while(mReader->read()) \ + { \ + if(mReader->getNodeType() == irr::io::EXN_ELEMENT) \ + { + +/// \def MACRO_NODECHECK_LOOPEND(pNodeName) +/// End of loop of parsing child nodes. +/// \param [in] pNodeName - current node name. +#define MACRO_NODECHECK_LOOPEND(pNodeName) \ + XML_CheckNode_SkipUnsupported(pNodeName); \ + }/* if(mReader->getNodeType() == irr::io::EXN_ELEMENT) */ \ + else if(mReader->getNodeType() == irr::io::EXN_ELEMENT_END) \ + { \ + if(XML_CheckNode_NameEqual(pNodeName)) \ + { \ + close_found = true; \ + \ + break; \ + } \ + }/* else if(mReader->getNodeType() == irr::io::EXN_ELEMENT_END) */ \ + }/* while(mReader->read()) */ \ + \ + if(!close_found) Throw_CloseNotFound(pNodeName); \ + \ + } while(false) + +/// \def MACRO_NODECHECK_READCOMP_F +/// Check current node name and if it equal to requested then read value. Result write to output variable of type "float". +/// If result was read then "continue" will called. Also check if node data already read then raise exception. +/// \param [in] pNodeName - node name. +/// \param [in, out] pReadFlag - read flag. +/// \param [out] pVarName - output variable name. +#define MACRO_NODECHECK_READCOMP_F(pNodeName, pReadFlag, pVarName) \ + if(XML_CheckNode_NameEqual(pNodeName)) \ + { \ + /* Check if field already read before. */ \ + if(pReadFlag) Throw_MoreThanOnceDefined(pNodeName, "Only one component can be defined."); \ + /* Read color component and assign it to object. */ \ + pVarName = XML_ReadNode_GetVal_AsFloat(); \ + pReadFlag = true; \ + continue; \ + } + +/// \def MACRO_NODECHECK_READCOMP_U32 +/// Check current node name and if it equal to requested then read value. Result write to output variable of type "uint32_t". +/// If result was read then "continue" will called. Also check if node data already read then raise exception. +/// \param [in] pNodeName - node name. +/// \param [in, out] pReadFlag - read flag. +/// \param [out] pVarName - output variable name. +#define MACRO_NODECHECK_READCOMP_U32(pNodeName, pReadFlag, pVarName) \ + if(XML_CheckNode_NameEqual(pNodeName)) \ + { \ + /* Check if field already read before. */ \ + if(pReadFlag) Throw_MoreThanOnceDefined(pNodeName, "Only one component can be defined."); \ + /* Read color component and assign it to object. */ \ + pVarName = XML_ReadNode_GetVal_AsU32(); \ + pReadFlag = true; \ + continue; \ + } + +#endif // AMFIMPORTER_MACRO_HPP_INCLUDED diff --git a/thirdparty/assimp/code/AMF/AMFImporter_Material.cpp b/thirdparty/assimp/code/AMF/AMFImporter_Material.cpp new file mode 100644 index 0000000..2f36df0 --- /dev/null +++ b/thirdparty/assimp/code/AMF/AMFImporter_Material.cpp @@ -0,0 +1,329 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/// \file AMFImporter_Material.cpp +/// \brief Parsing data from material nodes. +/// \date 2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER + +#include "AMFImporter.hpp" +#include "AMFImporter_Macro.hpp" + +namespace Assimp +{ + +// , and . +// > +// +// A color definition. +// Multi elements - No. +// Parent element - , , , , . +// +// "profile" can be one of "sRGB", "AdobeRGB", "Wide-Gamut-RGB", "CIERGB", "CIELAB", or "CIEXYZ". +// Children elements: +// , , , +// Multi elements - No. +// Red, Greed, Blue and Alpha (transparency) component of a color in sRGB space, values ranging from 0 to 1. The +// values can be specified as constants, or as a formula depending on the coordinates. +void AMFImporter::ParseNode_Color() { + std::string profile; + CAMFImporter_NodeElement* ne; + + // Read attributes for node . + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECK_RET("profile", profile, mReader->getAttributeValue); + MACRO_ATTRREAD_LOOPEND; + + // create new color object. + ne = new CAMFImporter_NodeElement_Color(mNodeElement_Cur); + + CAMFImporter_NodeElement_Color& als = *((CAMFImporter_NodeElement_Color*)ne);// alias for convenience + + als.Profile = profile; + // Check for child nodes + if(!mReader->isEmptyElement()) + { + bool read_flag[4] = { false, false, false, false }; + + ParseHelper_Node_Enter(ne); + MACRO_NODECHECK_LOOPBEGIN("color"); + MACRO_NODECHECK_READCOMP_F("r", read_flag[0], als.Color.r); + MACRO_NODECHECK_READCOMP_F("g", read_flag[1], als.Color.g); + MACRO_NODECHECK_READCOMP_F("b", read_flag[2], als.Color.b); + MACRO_NODECHECK_READCOMP_F("a", read_flag[3], als.Color.a); + MACRO_NODECHECK_LOOPEND("color"); + ParseHelper_Node_Exit(); + // check that all components was defined + if (!(read_flag[0] && read_flag[1] && read_flag[2])) { + throw DeadlyImportError("Not all color components are defined."); + } + + // check if is absent. Then manually add "a == 1". + if (!read_flag[3]) { + als.Color.a = 1; + } + } + else + { + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + } + + als.Composed = false; + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +// +// +// An available material. +// Multi elements - Yes. +// Parent element - . +void AMFImporter::ParseNode_Material() { + std::string id; + CAMFImporter_NodeElement* ne; + + // Read attributes for node . + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECK_RET("id", id, mReader->getAttributeValue); + MACRO_ATTRREAD_LOOPEND; + + // create new object. + ne = new CAMFImporter_NodeElement_Material(mNodeElement_Cur); + + // and assign read data + ((CAMFImporter_NodeElement_Material*)ne)->ID = id; + + // Check for child nodes + if(!mReader->isEmptyElement()) + { + bool col_read = false; + + ParseHelper_Node_Enter(ne); + MACRO_NODECHECK_LOOPBEGIN("material"); + if(XML_CheckNode_NameEqual("color")) + { + // Check if data already defined. + if(col_read) Throw_MoreThanOnceDefined("color", "Only one color can be defined for ."); + // read data and set flag about it + ParseNode_Color(); + col_read = true; + + continue; + } + + if(XML_CheckNode_NameEqual("metadata")) { ParseNode_Metadata(); continue; } + MACRO_NODECHECK_LOOPEND("material"); + ParseHelper_Node_Exit(); + } + else + { + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + } + + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +// +// +// Specifies an texture data to be used as a map. Lists a sequence of Base64 values specifying values for pixels from left to right then top to bottom, +// then layer by layer. +// Multi elements - Yes. +// Parent element - . +void AMFImporter::ParseNode_Texture() +{ + std::string id; + uint32_t width = 0; + uint32_t height = 0; + uint32_t depth = 1; + std::string type; + bool tiled = false; + std::string enc64_data; + + // Read attributes for node . + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECK_RET("id", id, mReader->getAttributeValue); + MACRO_ATTRREAD_CHECK_RET("width", width, XML_ReadNode_GetAttrVal_AsU32); + MACRO_ATTRREAD_CHECK_RET("height", height, XML_ReadNode_GetAttrVal_AsU32); + MACRO_ATTRREAD_CHECK_RET("depth", depth, XML_ReadNode_GetAttrVal_AsU32); + MACRO_ATTRREAD_CHECK_RET("type", type, mReader->getAttributeValue); + MACRO_ATTRREAD_CHECK_RET("tiled", tiled, XML_ReadNode_GetAttrVal_AsBool); + MACRO_ATTRREAD_LOOPEND; + + // create new texture object. + CAMFImporter_NodeElement *ne = new CAMFImporter_NodeElement_Texture(mNodeElement_Cur); + + CAMFImporter_NodeElement_Texture& als = *((CAMFImporter_NodeElement_Texture*)ne);// alias for convenience + + // Check for child nodes + if (!mReader->isEmptyElement()) { + XML_ReadNode_GetVal_AsString(enc64_data); + } + + // check that all components was defined + if (id.empty()) { + throw DeadlyImportError("ID for texture must be defined."); + } + if (width < 1) { + Throw_IncorrectAttrValue("width"); + } + if (height < 1) { + Throw_IncorrectAttrValue("height"); + } + if (depth < 1) { + Throw_IncorrectAttrValue("depth"); + } + if (type != "grayscale") { + Throw_IncorrectAttrValue("type"); + } + if (enc64_data.empty()) { + throw DeadlyImportError("Texture data not defined."); + } + // copy data + als.ID = id; + als.Width = width; + als.Height = height; + als.Depth = depth; + als.Tiled = tiled; + ParseHelper_Decode_Base64(enc64_data, als.Data); + + // check data size + if ((width * height * depth) != als.Data.size()) { + throw DeadlyImportError("Texture has incorrect data size."); + } + + mNodeElement_Cur->Child.push_back(ne);// Add element to child list of current element + mNodeElement_List.push_back(ne);// and to node element list because its a new object in graph. +} + +// +// , old name: +// Specifies texture coordinates for triangle. +// Multi elements - No. +// Parent element - . +// Children elements: +// , , , , , . Old name: , , , , , . +// Multi elements - No. +// Texture coordinates for every vertex of triangle. +void AMFImporter::ParseNode_TexMap(const bool pUseOldName) { + std::string rtexid, gtexid, btexid, atexid; + + // Read attributes for node . + MACRO_ATTRREAD_LOOPBEG; + MACRO_ATTRREAD_CHECK_RET("rtexid", rtexid, mReader->getAttributeValue); + MACRO_ATTRREAD_CHECK_RET("gtexid", gtexid, mReader->getAttributeValue); + MACRO_ATTRREAD_CHECK_RET("btexid", btexid, mReader->getAttributeValue); + MACRO_ATTRREAD_CHECK_RET("atexid", atexid, mReader->getAttributeValue); + MACRO_ATTRREAD_LOOPEND; + + // create new texture coordinates object. + CAMFImporter_NodeElement *ne = new CAMFImporter_NodeElement_TexMap(mNodeElement_Cur); + + CAMFImporter_NodeElement_TexMap& als = *((CAMFImporter_NodeElement_TexMap*)ne);// alias for convenience + // check data + if(rtexid.empty() && gtexid.empty() && btexid.empty()) throw DeadlyImportError("ParseNode_TexMap. At least one texture ID must be defined."); + // Check for children nodes + XML_CheckNode_MustHaveChildren(); + // read children nodes + bool read_flag[6] = { false, false, false, false, false, false }; + + ParseHelper_Node_Enter(ne); + if(!pUseOldName) + { + MACRO_NODECHECK_LOOPBEGIN("texmap"); + MACRO_NODECHECK_READCOMP_F("utex1", read_flag[0], als.TextureCoordinate[0].x); + MACRO_NODECHECK_READCOMP_F("utex2", read_flag[1], als.TextureCoordinate[1].x); + MACRO_NODECHECK_READCOMP_F("utex3", read_flag[2], als.TextureCoordinate[2].x); + MACRO_NODECHECK_READCOMP_F("vtex1", read_flag[3], als.TextureCoordinate[0].y); + MACRO_NODECHECK_READCOMP_F("vtex2", read_flag[4], als.TextureCoordinate[1].y); + MACRO_NODECHECK_READCOMP_F("vtex3", read_flag[5], als.TextureCoordinate[2].y); + MACRO_NODECHECK_LOOPEND("texmap"); + } + else + { + MACRO_NODECHECK_LOOPBEGIN("map"); + MACRO_NODECHECK_READCOMP_F("u1", read_flag[0], als.TextureCoordinate[0].x); + MACRO_NODECHECK_READCOMP_F("u2", read_flag[1], als.TextureCoordinate[1].x); + MACRO_NODECHECK_READCOMP_F("u3", read_flag[2], als.TextureCoordinate[2].x); + MACRO_NODECHECK_READCOMP_F("v1", read_flag[3], als.TextureCoordinate[0].y); + MACRO_NODECHECK_READCOMP_F("v2", read_flag[4], als.TextureCoordinate[1].y); + MACRO_NODECHECK_READCOMP_F("v3", read_flag[5], als.TextureCoordinate[2].y); + MACRO_NODECHECK_LOOPEND("map"); + }// if(!pUseOldName) else + + ParseHelper_Node_Exit(); + + // check that all components was defined + if(!(read_flag[0] && read_flag[1] && read_flag[2] && read_flag[3] && read_flag[4] && read_flag[5])) + throw DeadlyImportError("Not all texture coordinates are defined."); + + // copy attributes data + als.TextureID_R = rtexid; + als.TextureID_G = gtexid; + als.TextureID_B = btexid; + als.TextureID_A = atexid; + + mNodeElement_List.push_back(ne);// add to node element list because its a new object in graph. +} + +}// namespace Assimp + +#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER diff --git a/thirdparty/assimp/code/AMF/AMFImporter_Node.hpp b/thirdparty/assimp/code/AMF/AMFImporter_Node.hpp new file mode 100644 index 0000000..a1bf9f0 --- /dev/null +++ b/thirdparty/assimp/code/AMF/AMFImporter_Node.hpp @@ -0,0 +1,340 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/// \file AMFImporter_Node.hpp +/// \brief Elements of scene graph. +/// \date 2016 +/// \author smal.root@gmail.com + +#pragma once +#ifndef INCLUDED_AI_AMF_IMPORTER_NODE_H +#define INCLUDED_AI_AMF_IMPORTER_NODE_H + +// Header files, stdlib. +#include +#include +#include + +// Header files, Assimp. +#include "assimp/types.h" +#include "assimp/scene.h" + +/// \class CAMFImporter_NodeElement +/// Base class for elements of nodes. +class CAMFImporter_NodeElement { +public: + /// Define what data type contain node element. + enum EType { + ENET_Color, ///< Color element: . + ENET_Constellation,///< Grouping element: . + ENET_Coordinates, ///< Coordinates element: . + ENET_Edge, ///< Edge element: . + ENET_Instance, ///< Grouping element: . + ENET_Material, ///< Material element: . + ENET_Metadata, ///< Metadata element: . + ENET_Mesh, ///< Metadata element: . + ENET_Object, ///< Element which hold object: . + ENET_Root, ///< Root element: . + ENET_Triangle, ///< Triangle element: . + ENET_TexMap, ///< Texture coordinates element: or . + ENET_Texture, ///< Texture element: . + ENET_Vertex, ///< Vertex element: . + ENET_Vertices, ///< Vertex element: . + ENET_Volume, ///< Volume element: . + + ENET_Invalid ///< Element has invalid type and possible contain invalid data. + }; + + const EType Type;///< Type of element. + std::string ID;///< ID of element. + CAMFImporter_NodeElement* Parent;///< Parent element. If nullptr then this node is root. + std::list Child;///< Child elements. + +public: /// Destructor, virtual.. + virtual ~CAMFImporter_NodeElement() { + // empty + } + + /// Disabled copy constructor and co. + CAMFImporter_NodeElement(const CAMFImporter_NodeElement& pNodeElement) = delete; + CAMFImporter_NodeElement(CAMFImporter_NodeElement&&) = delete; + CAMFImporter_NodeElement& operator=(const CAMFImporter_NodeElement& pNodeElement) = delete; + CAMFImporter_NodeElement() = delete; + +protected: + /// In constructor inheritor must set element type. + /// \param [in] pType - element type. + /// \param [in] pParent - parent element. + CAMFImporter_NodeElement(const EType pType, CAMFImporter_NodeElement* pParent) + : Type(pType) + , ID() + , Parent(pParent) + , Child() { + // empty + } +};// class IAMFImporter_NodeElement + +/// \struct CAMFImporter_NodeElement_Constellation +/// A collection of objects or constellations with specific relative locations. +struct CAMFImporter_NodeElement_Constellation : public CAMFImporter_NodeElement { + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Constellation(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Constellation, pParent) + {} + +};// struct CAMFImporter_NodeElement_Constellation + +/// \struct CAMFImporter_NodeElement_Instance +/// Part of constellation. +struct CAMFImporter_NodeElement_Instance : public CAMFImporter_NodeElement { + + std::string ObjectID;///< ID of object for instantiation. + /// \var Delta - The distance of translation in the x, y, or z direction, respectively, in the referenced object's coordinate system, to + /// create an instance of the object in the current constellation. + aiVector3D Delta; + + /// \var Rotation - The rotation, in degrees, to rotate the referenced object about its x, y, and z axes, respectively, to create an + /// instance of the object in the current constellation. Rotations shall be executed in order of x first, then y, then z. + aiVector3D Rotation; + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Instance(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Instance, pParent) + {} +}; + +/// \struct CAMFImporter_NodeElement_Metadata +/// Structure that define metadata node. +struct CAMFImporter_NodeElement_Metadata : public CAMFImporter_NodeElement { + + std::string Type;///< Type of "Value". + std::string Value;///< Value. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Metadata(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Metadata, pParent) + {} +}; + +/// \struct CAMFImporter_NodeElement_Root +/// Structure that define root node. +struct CAMFImporter_NodeElement_Root : public CAMFImporter_NodeElement { + + std::string Unit;///< The units to be used. May be "inch", "millimeter", "meter", "feet", or "micron". + std::string Version;///< Version of format. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Root(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Root, pParent) + {} +}; + +/// \struct CAMFImporter_NodeElement_Color +/// Structure that define object node. +struct CAMFImporter_NodeElement_Color : public CAMFImporter_NodeElement { + bool Composed; ///< Type of color stored: if true then look for formula in \ref Color_Composed[4], else - in \ref Color. + std::string Color_Composed[4]; ///< By components formulas of composed color. [0..3] - RGBA. + aiColor4D Color; ///< Constant color. + std::string Profile; ///< The ICC color space used to interpret the three color channels r, g and b.. + + /// @brief Constructor. + /// @param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Color(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Color, pParent) + , Composed( false ) + , Color() + , Profile() { + // empty + } +}; + +/// \struct CAMFImporter_NodeElement_Material +/// Structure that define material node. +struct CAMFImporter_NodeElement_Material : public CAMFImporter_NodeElement { + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Material(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Material, pParent) + {} + +}; + +/// \struct CAMFImporter_NodeElement_Object +/// Structure that define object node. +struct CAMFImporter_NodeElement_Object : public CAMFImporter_NodeElement { + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Object(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Object, pParent) + {} +}; + +/// \struct CAMFImporter_NodeElement_Mesh +/// Structure that define mesh node. +struct CAMFImporter_NodeElement_Mesh : public CAMFImporter_NodeElement { + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Mesh(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Mesh, pParent) + {} +}; + +/// \struct CAMFImporter_NodeElement_Vertex +/// Structure that define vertex node. +struct CAMFImporter_NodeElement_Vertex : public CAMFImporter_NodeElement { + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Vertex(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Vertex, pParent) + {} +}; + +/// \struct CAMFImporter_NodeElement_Edge +/// Structure that define edge node. +struct CAMFImporter_NodeElement_Edge : public CAMFImporter_NodeElement { + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Edge(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Edge, pParent) + {} + +}; + +/// \struct CAMFImporter_NodeElement_Vertices +/// Structure that define vertices node. +struct CAMFImporter_NodeElement_Vertices : public CAMFImporter_NodeElement { + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Vertices(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Vertices, pParent) + {} +}; + +/// \struct CAMFImporter_NodeElement_Volume +/// Structure that define volume node. +struct CAMFImporter_NodeElement_Volume : public CAMFImporter_NodeElement { + std::string MaterialID;///< Which material to use. + std::string Type;///< What this volume describes can be “region” or “support”. If none specified, “object” is assumed. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Volume(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Volume, pParent) + {} +}; + +/// \struct CAMFImporter_NodeElement_Coordinates +/// Structure that define coordinates node. +struct CAMFImporter_NodeElement_Coordinates : public CAMFImporter_NodeElement +{ + aiVector3D Coordinate;///< Coordinate. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Coordinates(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Coordinates, pParent) + {} + +}; + +/// \struct CAMFImporter_NodeElement_TexMap +/// Structure that define texture coordinates node. +struct CAMFImporter_NodeElement_TexMap : public CAMFImporter_NodeElement { + aiVector3D TextureCoordinate[3];///< Texture coordinates. + std::string TextureID_R;///< Texture ID for red color component. + std::string TextureID_G;///< Texture ID for green color component. + std::string TextureID_B;///< Texture ID for blue color component. + std::string TextureID_A;///< Texture ID for alpha color component. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_TexMap(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_TexMap, pParent) + , TextureCoordinate{} + , TextureID_R() + , TextureID_G() + , TextureID_B() + , TextureID_A() { + // empty + } +}; + +/// \struct CAMFImporter_NodeElement_Triangle +/// Structure that define triangle node. +struct CAMFImporter_NodeElement_Triangle : public CAMFImporter_NodeElement { + size_t V[3];///< Triangle vertices. + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Triangle(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Triangle, pParent) { + // empty + } +}; + +/// Structure that define texture node. +struct CAMFImporter_NodeElement_Texture : public CAMFImporter_NodeElement { + size_t Width, Height, Depth;///< Size of the texture. + std::vector Data;///< Data of the texture. + bool Tiled; + + /// Constructor. + /// \param [in] pParent - pointer to parent node. + CAMFImporter_NodeElement_Texture(CAMFImporter_NodeElement* pParent) + : CAMFImporter_NodeElement(ENET_Texture, pParent) + , Width( 0 ) + , Height( 0 ) + , Depth( 0 ) + , Data() + , Tiled( false ){ + // empty + } +}; + +#endif // INCLUDED_AI_AMF_IMPORTER_NODE_H diff --git a/thirdparty/assimp/code/AMF/AMFImporter_Postprocess.cpp b/thirdparty/assimp/code/AMF/AMFImporter_Postprocess.cpp new file mode 100644 index 0000000..79b5e15 --- /dev/null +++ b/thirdparty/assimp/code/AMF/AMFImporter_Postprocess.cpp @@ -0,0 +1,978 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/// \file AMFImporter_Postprocess.cpp +/// \brief Convert built scenegraph and objects to Assimp scenegraph. +/// \date 2016 +/// \author smal.root@gmail.com + +#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER + +#include "AMFImporter.hpp" + +// Header files, Assimp. +#include +#include +#include + +// Header files, stdlib. +#include + +namespace Assimp +{ + +aiColor4D AMFImporter::SPP_Material::GetColor(const float /*pX*/, const float /*pY*/, const float /*pZ*/) const +{ + aiColor4D tcol; + + // Check if stored data are supported. + if(!Composition.empty()) + { + throw DeadlyImportError("IME. GetColor for composition"); + } + else if(Color->Composed) + { + throw DeadlyImportError("IME. GetColor, composed color"); + } + else + { + tcol = Color->Color; + } + + // Check if default color must be used + if((tcol.r == 0) && (tcol.g == 0) && (tcol.b == 0) && (tcol.a == 0)) + { + tcol.r = 0.5f; + tcol.g = 0.5f; + tcol.b = 0.5f; + tcol.a = 1; + } + + return tcol; +} + +void AMFImporter::PostprocessHelper_CreateMeshDataArray(const CAMFImporter_NodeElement_Mesh& pNodeElement, std::vector& pVertexCoordinateArray, + std::vector& pVertexColorArray) const +{ + CAMFImporter_NodeElement_Vertices* vn = nullptr; + size_t col_idx; + + // All data stored in "vertices", search for it. + for(CAMFImporter_NodeElement* ne_child: pNodeElement.Child) + { + if(ne_child->Type == CAMFImporter_NodeElement::ENET_Vertices) vn = (CAMFImporter_NodeElement_Vertices*)ne_child; + } + + // If "vertices" not found then no work for us. + if(vn == nullptr) return; + + pVertexCoordinateArray.reserve(vn->Child.size());// all coordinates stored as child and we need to reserve space for future push_back's. + pVertexColorArray.resize(vn->Child.size());// colors count equal vertices count. + col_idx = 0; + // Inside vertices collect all data and place to arrays + for(CAMFImporter_NodeElement* vn_child: vn->Child) + { + // vertices, colors + if(vn_child->Type == CAMFImporter_NodeElement::ENET_Vertex) + { + // by default clear color for current vertex + pVertexColorArray[col_idx] = nullptr; + + for(CAMFImporter_NodeElement* vtx: vn_child->Child) + { + if(vtx->Type == CAMFImporter_NodeElement::ENET_Coordinates) + { + pVertexCoordinateArray.push_back(((CAMFImporter_NodeElement_Coordinates*)vtx)->Coordinate); + + continue; + } + + if(vtx->Type == CAMFImporter_NodeElement::ENET_Color) + { + pVertexColorArray[col_idx] = (CAMFImporter_NodeElement_Color*)vtx; + + continue; + } + }// for(CAMFImporter_NodeElement* vtx: vn_child->Child) + + col_idx++; + }// if(vn_child->Type == CAMFImporter_NodeElement::ENET_Vertex) + }// for(CAMFImporter_NodeElement* vn_child: vn->Child) +} + +size_t AMFImporter::PostprocessHelper_GetTextureID_Or_Create(const std::string& pID_R, const std::string& pID_G, const std::string& pID_B, + const std::string& pID_A) +{ + size_t TextureConverted_Index; + std::string TextureConverted_ID; + + // check input data + if(pID_R.empty() && pID_G.empty() && pID_B.empty() && pID_A.empty()) + throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. At least one texture ID must be defined."); + + // Create ID + TextureConverted_ID = pID_R + "_" + pID_G + "_" + pID_B + "_" + pID_A; + // Check if texture specified by set of IDs is converted already. + TextureConverted_Index = 0; + for(const SPP_Texture& tex_convd: mTexture_Converted) + { + if ( tex_convd.ID == TextureConverted_ID ) { + return TextureConverted_Index; + } else { + ++TextureConverted_Index; + } + } + + // + // Converted texture not found, create it. + // + CAMFImporter_NodeElement_Texture* src_texture[4]{nullptr}; + std::vector src_texture_4check; + SPP_Texture converted_texture; + + {// find all specified source textures + CAMFImporter_NodeElement* t_tex; + + // R + if(!pID_R.empty()) + { + if(!Find_NodeElement(pID_R, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_R); + + src_texture[0] = (CAMFImporter_NodeElement_Texture*)t_tex; + src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex); + } + else + { + src_texture[0] = nullptr; + } + + // G + if(!pID_G.empty()) + { + if(!Find_NodeElement(pID_G, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_G); + + src_texture[1] = (CAMFImporter_NodeElement_Texture*)t_tex; + src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex); + } + else + { + src_texture[1] = nullptr; + } + + // B + if(!pID_B.empty()) + { + if(!Find_NodeElement(pID_B, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_B); + + src_texture[2] = (CAMFImporter_NodeElement_Texture*)t_tex; + src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex); + } + else + { + src_texture[2] = nullptr; + } + + // A + if(!pID_A.empty()) + { + if(!Find_NodeElement(pID_A, CAMFImporter_NodeElement::ENET_Texture, &t_tex)) Throw_ID_NotFound(pID_A); + + src_texture[3] = (CAMFImporter_NodeElement_Texture*)t_tex; + src_texture_4check.push_back((CAMFImporter_NodeElement_Texture*)t_tex); + } + else + { + src_texture[3] = nullptr; + } + }// END: find all specified source textures + + // check that all textures has same size + if(src_texture_4check.size() > 1) + { + for (size_t i = 0, i_e = (src_texture_4check.size() - 1); i < i_e; i++) + { + if((src_texture_4check[i]->Width != src_texture_4check[i + 1]->Width) || (src_texture_4check[i]->Height != src_texture_4check[i + 1]->Height) || + (src_texture_4check[i]->Depth != src_texture_4check[i + 1]->Depth)) + { + throw DeadlyImportError("PostprocessHelper_GetTextureID_Or_Create. Source texture must has the same size."); + } + } + }// if(src_texture_4check.size() > 1) + + // set texture attributes + converted_texture.Width = src_texture_4check[0]->Width; + converted_texture.Height = src_texture_4check[0]->Height; + converted_texture.Depth = src_texture_4check[0]->Depth; + // if one of source texture is tiled then converted texture is tiled too. + converted_texture.Tiled = false; + for(uint8_t i = 0; i < src_texture_4check.size(); i++) converted_texture.Tiled |= src_texture_4check[i]->Tiled; + + // Create format hint. + strcpy(converted_texture.FormatHint, "rgba0000");// copy initial string. + if(!pID_R.empty()) converted_texture.FormatHint[4] = '8'; + if(!pID_G.empty()) converted_texture.FormatHint[5] = '8'; + if(!pID_B.empty()) converted_texture.FormatHint[6] = '8'; + if(!pID_A.empty()) converted_texture.FormatHint[7] = '8'; + + // + // Сopy data of textures. + // + size_t tex_size = 0; + size_t step = 0; + size_t off_g = 0; + size_t off_b = 0; + + // Calculate size of the target array and rule how data will be copied. + if(!pID_R.empty() && nullptr != src_texture[ 0 ] ) { + tex_size += src_texture[0]->Data.size(); step++, off_g++, off_b++; + } + if(!pID_G.empty() && nullptr != src_texture[ 1 ] ) { + tex_size += src_texture[1]->Data.size(); step++, off_b++; + } + if(!pID_B.empty() && nullptr != src_texture[ 2 ] ) { + tex_size += src_texture[2]->Data.size(); step++; + } + if(!pID_A.empty() && nullptr != src_texture[ 3 ] ) { + tex_size += src_texture[3]->Data.size(); step++; + } + + // Create target array. + converted_texture.Data = new uint8_t[tex_size]; + // And copy data + auto CopyTextureData = [&](const std::string& pID, const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void + { + if(!pID.empty()) + { + for(size_t idx_target = pOffset, idx_src = 0; idx_target < tex_size; idx_target += pStep, idx_src++) { + CAMFImporter_NodeElement_Texture* tex = src_texture[pSrcTexNum]; + ai_assert(tex); + converted_texture.Data[idx_target] = tex->Data.at(idx_src); + } + } + };// auto CopyTextureData = [&](const size_t pOffset, const size_t pStep, const uint8_t pSrcTexNum) -> void + + CopyTextureData(pID_R, 0, step, 0); + CopyTextureData(pID_G, off_g, step, 1); + CopyTextureData(pID_B, off_b, step, 2); + CopyTextureData(pID_A, step - 1, step, 3); + + // Store new converted texture ID + converted_texture.ID = TextureConverted_ID; + // Store new converted texture + mTexture_Converted.push_back(converted_texture); + + return TextureConverted_Index; +} + +void AMFImporter::PostprocessHelper_SplitFacesByTextureID(std::list& pInputList, std::list >& pOutputList_Separated) +{ + auto texmap_is_equal = [](const CAMFImporter_NodeElement_TexMap* pTexMap1, const CAMFImporter_NodeElement_TexMap* pTexMap2) -> bool + { + if((pTexMap1 == nullptr) && (pTexMap2 == nullptr)) return true; + if(pTexMap1 == nullptr) return false; + if(pTexMap2 == nullptr) return false; + + if(pTexMap1->TextureID_R != pTexMap2->TextureID_R) return false; + if(pTexMap1->TextureID_G != pTexMap2->TextureID_G) return false; + if(pTexMap1->TextureID_B != pTexMap2->TextureID_B) return false; + if(pTexMap1->TextureID_A != pTexMap2->TextureID_A) return false; + + return true; + }; + + pOutputList_Separated.clear(); + if(pInputList.empty()) return; + + do + { + SComplexFace face_start = pInputList.front(); + std::list face_list_cur; + + for(std::list::iterator it = pInputList.begin(), it_end = pInputList.end(); it != it_end;) + { + if(texmap_is_equal(face_start.TexMap, it->TexMap)) + { + auto it_old = it; + + ++it; + face_list_cur.push_back(*it_old); + pInputList.erase(it_old); + } + else + { + ++it; + } + } + + if(!face_list_cur.empty()) pOutputList_Separated.push_back(face_list_cur); + + } while(!pInputList.empty()); +} + +void AMFImporter::Postprocess_AddMetadata(const std::list& metadataList, aiNode& sceneNode) const +{ + if ( !metadataList.empty() ) + { + if(sceneNode.mMetaData != nullptr) throw DeadlyImportError("Postprocess. MetaData member in node are not nullptr. Something went wrong."); + + // copy collected metadata to output node. + sceneNode.mMetaData = aiMetadata::Alloc( static_cast(metadataList.size()) ); + size_t meta_idx( 0 ); + + for(const CAMFImporter_NodeElement_Metadata& metadata: metadataList) + { + sceneNode.mMetaData->Set(static_cast(meta_idx++), metadata.Type, aiString(metadata.Value)); + } + }// if(!metadataList.empty()) +} + +void AMFImporter::Postprocess_BuildNodeAndObject(const CAMFImporter_NodeElement_Object& pNodeElement, std::list& pMeshList, aiNode** pSceneNode) +{ +CAMFImporter_NodeElement_Color* object_color = nullptr; + + // create new aiNode and set name as has. + *pSceneNode = new aiNode; + (*pSceneNode)->mName = pNodeElement.ID; + // read mesh and color + for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child) + { + std::vector vertex_arr; + std::vector color_arr; + + // color for object + if(ne_child->Type == CAMFImporter_NodeElement::ENET_Color) object_color = (CAMFImporter_NodeElement_Color*)ne_child; + + if(ne_child->Type == CAMFImporter_NodeElement::ENET_Mesh) + { + // Create arrays from children of mesh: vertices. + PostprocessHelper_CreateMeshDataArray(*((CAMFImporter_NodeElement_Mesh*)ne_child), vertex_arr, color_arr); + // Use this arrays as a source when creating every aiMesh + Postprocess_BuildMeshSet(*((CAMFImporter_NodeElement_Mesh*)ne_child), vertex_arr, color_arr, object_color, pMeshList, **pSceneNode); + } + }// for(const CAMFImporter_NodeElement* ne_child: pNodeElement) +} + +void AMFImporter::Postprocess_BuildMeshSet(const CAMFImporter_NodeElement_Mesh& pNodeElement, const std::vector& pVertexCoordinateArray, + const std::vector& pVertexColorArray, + const CAMFImporter_NodeElement_Color* pObjectColor, std::list& pMeshList, aiNode& pSceneNode) +{ +std::list mesh_idx; + + // all data stored in "volume", search for it. + for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child) + { + const CAMFImporter_NodeElement_Color* ne_volume_color = nullptr; + const SPP_Material* cur_mat = nullptr; + + if(ne_child->Type == CAMFImporter_NodeElement::ENET_Volume) + { + /******************* Get faces *******************/ + const CAMFImporter_NodeElement_Volume* ne_volume = reinterpret_cast(ne_child); + + std::list complex_faces_list;// List of the faces of the volume. + std::list > complex_faces_toplist;// List of the face list for every mesh. + + // check if volume use material + if(!ne_volume->MaterialID.empty()) + { + if(!Find_ConvertedMaterial(ne_volume->MaterialID, &cur_mat)) Throw_ID_NotFound(ne_volume->MaterialID); + } + + // inside "volume" collect all data and place to arrays or create new objects + for(const CAMFImporter_NodeElement* ne_volume_child: ne_volume->Child) + { + // color for volume + if(ne_volume_child->Type == CAMFImporter_NodeElement::ENET_Color) + { + ne_volume_color = reinterpret_cast(ne_volume_child); + } + else if(ne_volume_child->Type == CAMFImporter_NodeElement::ENET_Triangle)// triangles, triangles colors + { + const CAMFImporter_NodeElement_Triangle& tri_al = *reinterpret_cast(ne_volume_child); + + SComplexFace complex_face; + + // initialize pointers + complex_face.Color = nullptr; + complex_face.TexMap = nullptr; + // get data from triangle children: color, texture coordinates. + if(tri_al.Child.size()) + { + for(const CAMFImporter_NodeElement* ne_triangle_child: tri_al.Child) + { + if(ne_triangle_child->Type == CAMFImporter_NodeElement::ENET_Color) + complex_face.Color = reinterpret_cast(ne_triangle_child); + else if(ne_triangle_child->Type == CAMFImporter_NodeElement::ENET_TexMap) + complex_face.TexMap = reinterpret_cast(ne_triangle_child); + } + }// if(tri_al.Child.size()) + + // create new face and store it. + complex_face.Face.mNumIndices = 3; + complex_face.Face.mIndices = new unsigned int[3]; + complex_face.Face.mIndices[0] = static_cast(tri_al.V[0]); + complex_face.Face.mIndices[1] = static_cast(tri_al.V[1]); + complex_face.Face.mIndices[2] = static_cast(tri_al.V[2]); + complex_faces_list.push_back(complex_face); + } + }// for(const CAMFImporter_NodeElement* ne_volume_child: ne_volume->Child) + + /**** Split faces list: one list per mesh ****/ + PostprocessHelper_SplitFacesByTextureID(complex_faces_list, complex_faces_toplist); + + /***** Create mesh for every faces list ******/ + for(std::list& face_list_cur: complex_faces_toplist) + { + auto VertexIndex_GetMinimal = [](const std::list& pFaceList, const size_t* pBiggerThan) -> size_t + { + size_t rv; + + if(pBiggerThan != nullptr) + { + bool found = false; + + for(const SComplexFace& face: pFaceList) + { + for(size_t idx_vert = 0; idx_vert < face.Face.mNumIndices; idx_vert++) + { + if(face.Face.mIndices[idx_vert] > *pBiggerThan) + { + rv = face.Face.mIndices[idx_vert]; + found = true; + + break; + } + } + + if(found) break; + } + + if(!found) return *pBiggerThan; + } + else + { + rv = pFaceList.front().Face.mIndices[0]; + }// if(pBiggerThan != nullptr) else + + for(const SComplexFace& face: pFaceList) + { + for(size_t vi = 0; vi < face.Face.mNumIndices; vi++) + { + if(face.Face.mIndices[vi] < rv) + { + if(pBiggerThan != nullptr) + { + if(face.Face.mIndices[vi] > *pBiggerThan) rv = face.Face.mIndices[vi]; + } + else + { + rv = face.Face.mIndices[vi]; + } + } + } + }// for(const SComplexFace& face: pFaceList) + + return rv; + };// auto VertexIndex_GetMinimal = [](const std::list& pFaceList, const size_t* pBiggerThan) -> size_t + + auto VertexIndex_Replace = [](std::list& pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void + { + for(const SComplexFace& face: pFaceList) + { + for(size_t vi = 0; vi < face.Face.mNumIndices; vi++) + { + if(face.Face.mIndices[vi] == pIdx_From) face.Face.mIndices[vi] = static_cast(pIdx_To); + } + } + };// auto VertexIndex_Replace = [](std::list& pFaceList, const size_t pIdx_From, const size_t pIdx_To) -> void + + auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D + { + // Color priorities(In descending order): + // 1. triangle color; + // 2. vertex color; + // 3. volume color; + // 4. object color; + // 5. material; + // 6. default - invisible coat. + // + // Fill vertices colors in color priority list above that's points from 1 to 6. + if((pIdx < pVertexColorArray.size()) && (pVertexColorArray[pIdx] != nullptr))// check for vertex color + { + if(pVertexColorArray[pIdx]->Composed) + throw DeadlyImportError("IME: vertex color composed"); + else + return pVertexColorArray[pIdx]->Color; + } + else if(ne_volume_color != nullptr)// check for volume color + { + if(ne_volume_color->Composed) + throw DeadlyImportError("IME: volume color composed"); + else + return ne_volume_color->Color; + } + else if(pObjectColor != nullptr)// check for object color + { + if(pObjectColor->Composed) + throw DeadlyImportError("IME: object color composed"); + else + return pObjectColor->Color; + } + else if(cur_mat != nullptr)// check for material + { + return cur_mat->GetColor(pVertexCoordinateArray.at(pIdx).x, pVertexCoordinateArray.at(pIdx).y, pVertexCoordinateArray.at(pIdx).z); + } + else// set default color. + { + return {0, 0, 0, 0}; + }// if((vi < pVertexColorArray.size()) && (pVertexColorArray[vi] != nullptr)) else + + };// auto Vertex_CalculateColor = [&](const size_t pIdx) -> aiColor4D + + aiMesh* tmesh = new aiMesh; + + tmesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;// Only triangles is supported by AMF. + // + // set geometry and colors (vertices) + // + // copy faces/triangles + tmesh->mNumFaces = static_cast(face_list_cur.size()); + tmesh->mFaces = new aiFace[tmesh->mNumFaces]; + + // Create vertices list and optimize indices. Optimisation mean following.In AMF all volumes use one big list of vertices. And one volume + // can use only part of vertices list, for example: vertices list contain few thousands of vertices and volume use vertices 1, 3, 10. + // Do you need all this thousands of garbage? Of course no. So, optimisation step transformate sparse indices set to continuous. + size_t VertexCount_Max = tmesh->mNumFaces * 3;// 3 - triangles. + std::vector vert_arr, texcoord_arr; + std::vector col_arr; + + vert_arr.reserve(VertexCount_Max * 2);// "* 2" - see below TODO. + col_arr.reserve(VertexCount_Max * 2); + + {// fill arrays + size_t vert_idx_from, vert_idx_to; + + // first iteration. + vert_idx_to = 0; + vert_idx_from = VertexIndex_GetMinimal(face_list_cur, nullptr); + vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from)); + col_arr.push_back(Vertex_CalculateColor(vert_idx_from)); + if(vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to); + + // rest iterations + do + { + vert_idx_from = VertexIndex_GetMinimal(face_list_cur, &vert_idx_to); + if(vert_idx_from == vert_idx_to) break;// all indices are transferred, + + vert_arr.push_back(pVertexCoordinateArray.at(vert_idx_from)); + col_arr.push_back(Vertex_CalculateColor(vert_idx_from)); + vert_idx_to++; + if(vert_idx_from != vert_idx_to) VertexIndex_Replace(face_list_cur, vert_idx_from, vert_idx_to); + + } while(true); + }// fill arrays. END. + + // + // check if triangle colors are used and create additional faces if needed. + // + for(const SComplexFace& face_cur: face_list_cur) + { + if(face_cur.Color != nullptr) + { + aiColor4D face_color; + size_t vert_idx_new = vert_arr.size(); + + if(face_cur.Color->Composed) + throw DeadlyImportError("IME: face color composed"); + else + face_color = face_cur.Color->Color; + + for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++) + { + vert_arr.push_back(vert_arr.at(face_cur.Face.mIndices[idx_ind])); + col_arr.push_back(face_color); + face_cur.Face.mIndices[idx_ind] = static_cast(vert_idx_new++); + } + }// if(face_cur.Color != nullptr) + }// for(const SComplexFace& face_cur: face_list_cur) + + // + // if texture is used then copy texture coordinates too. + // + if(face_list_cur.front().TexMap != nullptr) + { + size_t idx_vert_new = vert_arr.size(); + ///TODO: clean unused vertices. "* 2": in certain cases - mesh full of triangle colors - vert_arr will contain duplicated vertices for + /// colored triangles and initial vertices (for colored vertices) which in real became unused. This part need more thinking about + /// optimisation. + bool* idx_vert_used; + + idx_vert_used = new bool[VertexCount_Max * 2]; + for(size_t i = 0, i_e = VertexCount_Max * 2; i < i_e; i++) idx_vert_used[i] = false; + + // This ID's will be used when set materials ID in scene. + tmesh->mMaterialIndex = static_cast(PostprocessHelper_GetTextureID_Or_Create(face_list_cur.front().TexMap->TextureID_R, + face_list_cur.front().TexMap->TextureID_G, + face_list_cur.front().TexMap->TextureID_B, + face_list_cur.front().TexMap->TextureID_A)); + texcoord_arr.resize(VertexCount_Max * 2); + for(const SComplexFace& face_cur: face_list_cur) + { + for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++) + { + const size_t idx_vert = face_cur.Face.mIndices[idx_ind]; + + if(!idx_vert_used[idx_vert]) + { + texcoord_arr.at(idx_vert) = face_cur.TexMap->TextureCoordinate[idx_ind]; + idx_vert_used[idx_vert] = true; + } + else if(texcoord_arr.at(idx_vert) != face_cur.TexMap->TextureCoordinate[idx_ind]) + { + // in that case one vertex is shared with many texture coordinates. We need to duplicate vertex with another texture + // coordinates. + vert_arr.push_back(vert_arr.at(idx_vert)); + col_arr.push_back(col_arr.at(idx_vert)); + texcoord_arr.at(idx_vert_new) = face_cur.TexMap->TextureCoordinate[idx_ind]; + face_cur.Face.mIndices[idx_ind] = static_cast(idx_vert_new++); + } + }// for(size_t idx_ind = 0; idx_ind < face_cur.Face.mNumIndices; idx_ind++) + }// for(const SComplexFace& face_cur: face_list_cur) + + delete [] idx_vert_used; + // shrink array + texcoord_arr.resize(idx_vert_new); + }// if(face_list_cur.front().TexMap != nullptr) + + // + // copy collected data to mesh + // + tmesh->mNumVertices = static_cast(vert_arr.size()); + tmesh->mVertices = new aiVector3D[tmesh->mNumVertices]; + tmesh->mColors[0] = new aiColor4D[tmesh->mNumVertices]; + + memcpy(tmesh->mVertices, vert_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D)); + memcpy(tmesh->mColors[0], col_arr.data(), tmesh->mNumVertices * sizeof(aiColor4D)); + if(texcoord_arr.size() > 0) + { + tmesh->mTextureCoords[0] = new aiVector3D[tmesh->mNumVertices]; + memcpy(tmesh->mTextureCoords[0], texcoord_arr.data(), tmesh->mNumVertices * sizeof(aiVector3D)); + tmesh->mNumUVComponents[0] = 2;// U and V stored in "x", "y" of aiVector3D. + } + + size_t idx_face = 0; + for(const SComplexFace& face_cur: face_list_cur) tmesh->mFaces[idx_face++] = face_cur.Face; + + // store new aiMesh + mesh_idx.push_back(static_cast(pMeshList.size())); + pMeshList.push_back(tmesh); + }// for(const std::list& face_list_cur: complex_faces_toplist) + }// if(ne_child->Type == CAMFImporter_NodeElement::ENET_Volume) + }// for(const CAMFImporter_NodeElement* ne_child: pNodeElement.Child) + + // if meshes was created then assign new indices with current aiNode + if(!mesh_idx.empty()) + { + std::list::const_iterator mit = mesh_idx.begin(); + + pSceneNode.mNumMeshes = static_cast(mesh_idx.size()); + pSceneNode.mMeshes = new unsigned int[pSceneNode.mNumMeshes]; + for(size_t i = 0; i < pSceneNode.mNumMeshes; i++) pSceneNode.mMeshes[i] = *mit++; + }// if(mesh_idx.size() > 0) +} + +void AMFImporter::Postprocess_BuildMaterial(const CAMFImporter_NodeElement_Material& pMaterial) +{ +SPP_Material new_mat; + + new_mat.ID = pMaterial.ID; + for(const CAMFImporter_NodeElement* mat_child: pMaterial.Child) + { + if(mat_child->Type == CAMFImporter_NodeElement::ENET_Color) + { + new_mat.Color = (CAMFImporter_NodeElement_Color*)mat_child; + } + else if(mat_child->Type == CAMFImporter_NodeElement::ENET_Metadata) + { + new_mat.Metadata.push_back((CAMFImporter_NodeElement_Metadata*)mat_child); + } + }// for(const CAMFImporter_NodeElement* mat_child; pMaterial.Child) + + // place converted material to special list + mMaterial_Converted.push_back(new_mat); +} + +void AMFImporter::Postprocess_BuildConstellation(CAMFImporter_NodeElement_Constellation& pConstellation, std::list& pNodeList) const +{ +aiNode* con_node; +std::list ch_node; + + // We will build next hierarchy: + // aiNode as parent () for set of nodes as a children + // |- aiNode for transformation ( -> , ) - aiNode for pointing to object ("objectid") + // ... + // \_ aiNode for transformation ( -> , ) - aiNode for pointing to object ("objectid") + con_node = new aiNode; + con_node->mName = pConstellation.ID; + // Walk through children and search for instances of another objects, constellations. + for(const CAMFImporter_NodeElement* ne: pConstellation.Child) + { + aiMatrix4x4 tmat; + aiNode* t_node; + aiNode* found_node; + + if(ne->Type == CAMFImporter_NodeElement::ENET_Metadata) continue; + if(ne->Type != CAMFImporter_NodeElement::ENET_Instance) throw DeadlyImportError("Only nodes can be in ."); + + // create alias for conveniance + CAMFImporter_NodeElement_Instance& als = *((CAMFImporter_NodeElement_Instance*)ne); + // find referenced object + if(!Find_ConvertedNode(als.ObjectID, pNodeList, &found_node)) Throw_ID_NotFound(als.ObjectID); + + // create node for applying transformation + t_node = new aiNode; + t_node->mParent = con_node; + // apply transformation + aiMatrix4x4::Translation(als.Delta, tmat), t_node->mTransformation *= tmat; + aiMatrix4x4::RotationX(als.Rotation.x, tmat), t_node->mTransformation *= tmat; + aiMatrix4x4::RotationY(als.Rotation.y, tmat), t_node->mTransformation *= tmat; + aiMatrix4x4::RotationZ(als.Rotation.z, tmat), t_node->mTransformation *= tmat; + // create array for one child node + t_node->mNumChildren = 1; + t_node->mChildren = new aiNode*[t_node->mNumChildren]; + SceneCombiner::Copy(&t_node->mChildren[0], found_node); + t_node->mChildren[0]->mParent = t_node; + ch_node.push_back(t_node); + }// for(const CAMFImporter_NodeElement* ne: pConstellation.Child) + + // copy found aiNode's as children + if(ch_node.empty()) throw DeadlyImportError(" must have at least one ."); + + size_t ch_idx = 0; + + con_node->mNumChildren = static_cast(ch_node.size()); + con_node->mChildren = new aiNode*[con_node->mNumChildren]; + for(aiNode* node: ch_node) con_node->mChildren[ch_idx++] = node; + + // and place "root" of node to node list + pNodeList.push_back(con_node); +} + +void AMFImporter::Postprocess_BuildScene(aiScene* pScene) +{ +std::list node_list; +std::list mesh_list; +std::list meta_list; + + // + // Because for AMF "material" is just complex colors mixing so aiMaterial will not be used. + // For building aiScene we are must to do few steps: + // at first creating root node for aiScene. + pScene->mRootNode = new aiNode; + pScene->mRootNode->mParent = nullptr; + pScene->mFlags |= AI_SCENE_FLAGS_ALLOW_SHARED; + // search for root() element + CAMFImporter_NodeElement* root_el = nullptr; + + for(CAMFImporter_NodeElement* ne: mNodeElement_List) + { + if(ne->Type != CAMFImporter_NodeElement::ENET_Root) continue; + + root_el = ne; + + break; + }// for(const CAMFImporter_NodeElement* ne: mNodeElement_List) + + // Check if root element are found. + if(root_el == nullptr) throw DeadlyImportError("Root() element not found."); + + // after that walk through children of root and collect data. Five types of nodes can be placed at top level - in : , , , + // and . But at first we must read and because they will be used in . can be read + // at any moment. + // + // 1. + // 2. will be converted later when processing triangles list. \sa Postprocess_BuildMeshSet + for(const CAMFImporter_NodeElement* root_child: root_el->Child) + { + if(root_child->Type == CAMFImporter_NodeElement::ENET_Material) Postprocess_BuildMaterial(*((CAMFImporter_NodeElement_Material*)root_child)); + } + + // After "appearance" nodes we must read because it will be used in -> . + // + // 3. + for(const CAMFImporter_NodeElement* root_child: root_el->Child) + { + if(root_child->Type == CAMFImporter_NodeElement::ENET_Object) + { + aiNode* tnode = nullptr; + + // for mesh and node must be built: object ID assigned to aiNode name and will be used in future for + Postprocess_BuildNodeAndObject(*((CAMFImporter_NodeElement_Object*)root_child), mesh_list, &tnode); + if(tnode != nullptr) node_list.push_back(tnode); + + } + }// for(const CAMFImporter_NodeElement* root_child: root_el->Child) + + // And finally read rest of nodes. + // + for(const CAMFImporter_NodeElement* root_child: root_el->Child) + { + // 4. + if(root_child->Type == CAMFImporter_NodeElement::ENET_Constellation) + { + // and at top of self abstraction use aiNode. So we can use only aiNode list for creating new aiNode's. + Postprocess_BuildConstellation(*((CAMFImporter_NodeElement_Constellation*)root_child), node_list); + } + + // 5, + if(root_child->Type == CAMFImporter_NodeElement::ENET_Metadata) meta_list.push_back((CAMFImporter_NodeElement_Metadata*)root_child); + }// for(const CAMFImporter_NodeElement* root_child: root_el->Child) + + // at now we can add collected metadata to root node + Postprocess_AddMetadata(meta_list, *pScene->mRootNode); + // + // Check constellation children + // + // As said in specification: + // "When multiple objects and constellations are defined in a single file, only the top level objects and constellations are available for printing." + // What that means? For example: if some object is used in constellation then you must show only constellation but not original object. + // And at this step we are checking that relations. +nl_clean_loop: + + if(node_list.size() > 1) + { + // walk through all nodes + for(std::list::iterator nl_it = node_list.begin(); nl_it != node_list.end(); ++nl_it) + { + // and try to find them in another top nodes. + std::list::const_iterator next_it = nl_it; + + ++next_it; + for(; next_it != node_list.end(); ++next_it) + { + if((*next_it)->FindNode((*nl_it)->mName) != nullptr) + { + // if current top node(nl_it) found in another top node then erase it from node_list and restart search loop. + node_list.erase(nl_it); + + goto nl_clean_loop; + } + }// for(; next_it != node_list.end(); next_it++) + }// for(std::list::const_iterator nl_it = node_list.begin(); nl_it != node_list.end(); nl_it++) + } + + // + // move created objects to aiScene + // + // + // Nodes + if(!node_list.empty()) + { + std::list::const_iterator nl_it = node_list.begin(); + + pScene->mRootNode->mNumChildren = static_cast(node_list.size()); + pScene->mRootNode->mChildren = new aiNode*[pScene->mRootNode->mNumChildren]; + for(size_t i = 0; i < pScene->mRootNode->mNumChildren; i++) + { + // Objects and constellation that must be showed placed at top of hierarchy in node. So all aiNode's in node_list must have + // mRootNode only as parent. + (*nl_it)->mParent = pScene->mRootNode; + pScene->mRootNode->mChildren[i] = *nl_it++; + } + }// if(node_list.size() > 0) + + // + // Meshes + if(!mesh_list.empty()) + { + std::list::const_iterator ml_it = mesh_list.begin(); + + pScene->mNumMeshes = static_cast(mesh_list.size()); + pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; + for(size_t i = 0; i < pScene->mNumMeshes; i++) pScene->mMeshes[i] = *ml_it++; + }// if(mesh_list.size() > 0) + + // + // Textures + pScene->mNumTextures = static_cast(mTexture_Converted.size()); + if(pScene->mNumTextures > 0) + { + size_t idx; + + idx = 0; + pScene->mTextures = new aiTexture*[pScene->mNumTextures]; + for(const SPP_Texture& tex_convd: mTexture_Converted) + { + pScene->mTextures[idx] = new aiTexture; + pScene->mTextures[idx]->mWidth = static_cast(tex_convd.Width); + pScene->mTextures[idx]->mHeight = static_cast(tex_convd.Height); + pScene->mTextures[idx]->pcData = (aiTexel*)tex_convd.Data; + // texture format description. + strcpy(pScene->mTextures[idx]->achFormatHint, tex_convd.FormatHint); + idx++; + }// for(const SPP_Texture& tex_convd: mTexture_Converted) + + // Create materials for embedded textures. + idx = 0; + pScene->mNumMaterials = static_cast(mTexture_Converted.size()); + pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]; + for(const SPP_Texture& tex_convd: mTexture_Converted) + { + const aiString texture_id(AI_EMBEDDED_TEXNAME_PREFIX + to_string(idx)); + const int mode = aiTextureOp_Multiply; + const int repeat = tex_convd.Tiled ? 1 : 0; + + pScene->mMaterials[idx] = new aiMaterial; + pScene->mMaterials[idx]->AddProperty(&texture_id, AI_MATKEY_TEXTURE_DIFFUSE(0)); + pScene->mMaterials[idx]->AddProperty(&mode, 1, AI_MATKEY_TEXOP_DIFFUSE(0)); + pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_U_DIFFUSE(0)); + pScene->mMaterials[idx]->AddProperty(&repeat, 1, AI_MATKEY_MAPPINGMODE_V_DIFFUSE(0)); + idx++; + } + }// if(pScene->mNumTextures > 0) +}// END: after that walk through children of root and collect data + +}// namespace Assimp + +#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER diff --git a/thirdparty/assimp/code/ASE/ASELoader.cpp b/thirdparty/assimp/code/ASE/ASELoader.cpp new file mode 100644 index 0000000..8e99214 --- /dev/null +++ b/thirdparty/assimp/code/ASE/ASELoader.cpp @@ -0,0 +1,1328 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/** @file ASELoader.cpp + * @brief Implementation of the ASE importer class + */ + +#ifndef ASSIMP_BUILD_NO_ASE_IMPORTER + +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +// internal headers +#include "ASELoader.h" +#include +#include +#include "Common/TargetAnimation.h" + +#include +#include +#include +#include +#include + +#include + +// utilities +#include + +using namespace Assimp; +using namespace Assimp::ASE; + +static const aiImporterDesc desc = { + "ASE Importer", + "", + "", + "Similar to 3DS but text-encoded", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "ase ask" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +ASEImporter::ASEImporter() +: mParser() +, mBuffer() +, pcScene() +, configRecomputeNormals() +, noSkeletonMesh() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +ASEImporter::~ASEImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool ASEImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const { + // check file extension + const std::string extension = GetExtension(pFile); + + if (extension == "ase" || extension == "ask") { + return true; + } + + if ((!extension.length() || cs) && pIOHandler) { + const char* tokens[] = {"*3dsmax_asciiexport"}; + return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1); + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +// Loader meta information +const aiImporterDesc* ASEImporter::GetInfo () const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration options +void ASEImporter::SetupProperties(const Importer* pImp) { + configRecomputeNormals = (pImp->GetPropertyInteger( + AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS,1) ? true : false); + + noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void ASEImporter::InternReadFile( const std::string& pFile, + aiScene* pScene, IOSystem* pIOHandler) { + std::unique_ptr file( pIOHandler->Open( pFile, "rb")); + + // Check whether we can read from the file + if( file.get() == nullptr) { + throw DeadlyImportError( "Failed to open ASE file " + pFile + "."); + } + + // Allocate storage and copy the contents of the file to a memory buffer + std::vector mBuffer2; + TextFileToBuffer(file.get(),mBuffer2); + + this->mBuffer = &mBuffer2[0]; + this->pcScene = pScene; + + // ------------------------------------------------------------------ + // Guess the file format by looking at the extension + // ASC is considered to be the older format 110, + // ASE is the actual version 200 (that is currently written by max) + // ------------------------------------------------------------------ + unsigned int defaultFormat; + std::string::size_type s = pFile.length()-1; + switch (pFile.c_str()[s]) { + + case 'C': + case 'c': + defaultFormat = AI_ASE_OLD_FILE_FORMAT; + break; + default: + defaultFormat = AI_ASE_NEW_FILE_FORMAT; + }; + + // Construct an ASE parser and parse the file + ASE::Parser parser(mBuffer,defaultFormat); + mParser = &parser; + mParser->Parse(); + + //------------------------------------------------------------------ + // Check whether we god at least one mesh. If we did - generate + // materials and copy meshes. + // ------------------------------------------------------------------ + if ( !mParser->m_vMeshes.empty()) { + + // If absolutely no material has been loaded from the file + // we need to generate a default material + GenerateDefaultMaterial(); + + // process all meshes + bool tookNormals = false; + std::vector avOutMeshes; + avOutMeshes.reserve(mParser->m_vMeshes.size()*2); + for (std::vector::iterator i = mParser->m_vMeshes.begin();i != mParser->m_vMeshes.end();++i) { + if ((*i).bSkip) { + continue; + } + BuildUniqueRepresentation(*i); + + // Need to generate proper vertex normals if necessary + if(GenerateNormals(*i)) { + tookNormals = true; + } + + // Convert all meshes to aiMesh objects + ConvertMeshes(*i,avOutMeshes); + } + if (tookNormals) { + ASSIMP_LOG_DEBUG("ASE: Taking normals from the file. Use " + "the AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS setting if you " + "experience problems"); + } + + // Now build the output mesh list. Remove dummies + pScene->mNumMeshes = (unsigned int)avOutMeshes.size(); + aiMesh** pp = pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; + for (std::vector::const_iterator i = avOutMeshes.begin();i != avOutMeshes.end();++i) { + if (!(*i)->mNumFaces) { + continue; + } + *pp++ = *i; + } + pScene->mNumMeshes = (unsigned int)(pp - pScene->mMeshes); + + // Build final material indices (remove submaterials and setup + // the final list) + BuildMaterialIndices(); + } + + // ------------------------------------------------------------------ + // Copy all scene graph nodes - lights, cameras, dummies and meshes + // into one huge list. + //------------------------------------------------------------------ + std::vector nodes; + nodes.reserve(mParser->m_vMeshes.size() +mParser->m_vLights.size() + + mParser->m_vCameras.size() + mParser->m_vDummies.size()); + + // Lights + for (auto &light : mParser->m_vLights)nodes.push_back(&light); + // Cameras + for (auto &camera : mParser->m_vCameras)nodes.push_back(&camera); + // Meshes + for (auto &mesh : mParser->m_vMeshes)nodes.push_back(&mesh); + // Dummies + for (auto &dummy : mParser->m_vDummies)nodes.push_back(&dummy); + + // build the final node graph + BuildNodes(nodes); + + // build output animations + BuildAnimations(nodes); + + // build output cameras + BuildCameras(); + + // build output lights + BuildLights(); + + // ------------------------------------------------------------------ + // If we have no meshes use the SkeletonMeshBuilder helper class + // to build a mesh for the animation skeleton + // FIXME: very strange results + // ------------------------------------------------------------------ + if (!pScene->mNumMeshes) { + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + if (!noSkeletonMesh) { + SkeletonMeshBuilder skeleton(pScene); + } + } +} +// ------------------------------------------------------------------------------------------------ +void ASEImporter::GenerateDefaultMaterial() +{ + ai_assert(NULL != mParser); + + bool bHas = false; + for (std::vector::iterator i = mParser->m_vMeshes.begin();i != mParser->m_vMeshes.end();++i) { + if ((*i).bSkip)continue; + if (ASE::Face::DEFAULT_MATINDEX == (*i).iMaterialIndex) { + (*i).iMaterialIndex = (unsigned int)mParser->m_vMaterials.size(); + bHas = true; + } + } + if (bHas || mParser->m_vMaterials.empty()) { + // add a simple material without submaterials to the parser's list + mParser->m_vMaterials.push_back ( ASE::Material(AI_DEFAULT_MATERIAL_NAME) ); + ASE::Material& mat = mParser->m_vMaterials.back(); + + mat.mDiffuse = aiColor3D(0.6f,0.6f,0.6f); + mat.mSpecular = aiColor3D(1.0f,1.0f,1.0f); + mat.mAmbient = aiColor3D(0.05f,0.05f,0.05f); + mat.mShading = Discreet3DS::Gouraud; + } +} + +// ------------------------------------------------------------------------------------------------ +void ASEImporter::BuildAnimations(const std::vector& nodes) +{ + // check whether we have at least one mesh which has animations + std::vector::const_iterator i = nodes.begin(); + unsigned int iNum = 0; + for (;i != nodes.end();++i) { + + // TODO: Implement Bezier & TCB support + if ((*i)->mAnim.mPositionType != ASE::Animation::TRACK) { + ASSIMP_LOG_WARN("ASE: Position controller uses Bezier/TCB keys. " + "This is not supported."); + } + if ((*i)->mAnim.mRotationType != ASE::Animation::TRACK) { + ASSIMP_LOG_WARN("ASE: Rotation controller uses Bezier/TCB keys. " + "This is not supported."); + } + if ((*i)->mAnim.mScalingType != ASE::Animation::TRACK) { + ASSIMP_LOG_WARN("ASE: Position controller uses Bezier/TCB keys. " + "This is not supported."); + } + + // We compare against 1 here - firstly one key is not + // really an animation and secondly MAX writes dummies + // that represent the node transformation. + if ((*i)->mAnim.akeyPositions.size()>1 || (*i)->mAnim.akeyRotations.size()>1 || (*i)->mAnim.akeyScaling.size()>1){ + ++iNum; + } + if ((*i)->mTargetAnim.akeyPositions.size() > 1 && is_not_qnan( (*i)->mTargetPosition.x )) { + ++iNum; + } + } + if (iNum) { + // Generate a new animation channel and setup everything for it + pcScene->mNumAnimations = 1; + pcScene->mAnimations = new aiAnimation*[1]; + aiAnimation* pcAnim = pcScene->mAnimations[0] = new aiAnimation(); + pcAnim->mNumChannels = iNum; + pcAnim->mChannels = new aiNodeAnim*[iNum]; + pcAnim->mTicksPerSecond = mParser->iFrameSpeed * mParser->iTicksPerFrame; + + iNum = 0; + + // Now iterate through all meshes and collect all data we can find + for (i = nodes.begin();i != nodes.end();++i) { + + ASE::BaseNode* me = *i; + if ( me->mTargetAnim.akeyPositions.size() > 1 && is_not_qnan( me->mTargetPosition.x )) { + // Generate an extra channel for the camera/light target. + // BuildNodes() does also generate an extra node, named + // .Target. + aiNodeAnim* nd = pcAnim->mChannels[iNum++] = new aiNodeAnim(); + nd->mNodeName.Set(me->mName + ".Target"); + + // If there is no input position channel we will need + // to supply the default position from the node's + // local transformation matrix. + /*TargetAnimationHelper helper; + if (me->mAnim.akeyPositions.empty()) + { + aiMatrix4x4& mat = (*i)->mTransform; + helper.SetFixedMainAnimationChannel(aiVector3D( + mat.a4, mat.b4, mat.c4)); + } + else helper.SetMainAnimationChannel (&me->mAnim.akeyPositions); + helper.SetTargetAnimationChannel (&me->mTargetAnim.akeyPositions); + + helper.Process(&me->mTargetAnim.akeyPositions);*/ + + // Allocate the key array and fill it + nd->mNumPositionKeys = (unsigned int) me->mTargetAnim.akeyPositions.size(); + nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys]; + + ::memcpy(nd->mPositionKeys,&me->mTargetAnim.akeyPositions[0], + nd->mNumPositionKeys * sizeof(aiVectorKey)); + } + + if (me->mAnim.akeyPositions.size() > 1 || me->mAnim.akeyRotations.size() > 1 || me->mAnim.akeyScaling.size() > 1) { + // Begin a new node animation channel for this node + aiNodeAnim* nd = pcAnim->mChannels[iNum++] = new aiNodeAnim(); + nd->mNodeName.Set(me->mName); + + // copy position keys + if (me->mAnim.akeyPositions.size() > 1 ) + { + // Allocate the key array and fill it + nd->mNumPositionKeys = (unsigned int) me->mAnim.akeyPositions.size(); + nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys]; + + ::memcpy(nd->mPositionKeys,&me->mAnim.akeyPositions[0], + nd->mNumPositionKeys * sizeof(aiVectorKey)); + } + // copy rotation keys + if (me->mAnim.akeyRotations.size() > 1 ) { + // Allocate the key array and fill it + nd->mNumRotationKeys = (unsigned int) me->mAnim.akeyRotations.size(); + nd->mRotationKeys = new aiQuatKey[nd->mNumRotationKeys]; + + // -------------------------------------------------------------------- + // Rotation keys are offsets to the previous keys. + // We have the quaternion representations of all + // of them, so we just need to concatenate all + // (unit-length) quaternions to get the absolute + // rotations. + // Rotation keys are ABSOLUTE for older files + // -------------------------------------------------------------------- + + aiQuaternion cur; + for (unsigned int a = 0; a < nd->mNumRotationKeys;++a) { + aiQuatKey q = me->mAnim.akeyRotations[a]; + + if (mParser->iFileFormat > 110) { + cur = (a ? cur*q.mValue : q.mValue); + q.mValue = cur.Normalize(); + } + nd->mRotationKeys[a] = q; + + // need this to get to Assimp quaternion conventions + nd->mRotationKeys[a].mValue.w *= -1.f; + } + } + // copy scaling keys + if (me->mAnim.akeyScaling.size() > 1 ) { + // Allocate the key array and fill it + nd->mNumScalingKeys = (unsigned int) me->mAnim.akeyScaling.size(); + nd->mScalingKeys = new aiVectorKey[nd->mNumScalingKeys]; + + ::memcpy(nd->mScalingKeys,&me->mAnim.akeyScaling[0], + nd->mNumScalingKeys * sizeof(aiVectorKey)); + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Build output cameras +void ASEImporter::BuildCameras() +{ + if (!mParser->m_vCameras.empty()) { + pcScene->mNumCameras = (unsigned int)mParser->m_vCameras.size(); + pcScene->mCameras = new aiCamera*[pcScene->mNumCameras]; + + for (unsigned int i = 0; i < pcScene->mNumCameras;++i) { + aiCamera* out = pcScene->mCameras[i] = new aiCamera(); + ASE::Camera& in = mParser->m_vCameras[i]; + + // copy members + out->mClipPlaneFar = in.mFar; + out->mClipPlaneNear = (in.mNear ? in.mNear : 0.1f); + out->mHorizontalFOV = in.mFOV; + + out->mName.Set(in.mName); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Build output lights +void ASEImporter::BuildLights() +{ + if (!mParser->m_vLights.empty()) { + pcScene->mNumLights = (unsigned int)mParser->m_vLights.size(); + pcScene->mLights = new aiLight*[pcScene->mNumLights]; + + for (unsigned int i = 0; i < pcScene->mNumLights;++i) { + aiLight* out = pcScene->mLights[i] = new aiLight(); + ASE::Light& in = mParser->m_vLights[i]; + + // The direction is encoded in the transformation matrix of the node. + // In 3DS MAX the light source points into negative Z direction if + // the node transformation is the identity. + out->mDirection = aiVector3D(0.f,0.f,-1.f); + + out->mName.Set(in.mName); + switch (in.mLightType) + { + case ASE::Light::TARGET: + out->mType = aiLightSource_SPOT; + out->mAngleInnerCone = AI_DEG_TO_RAD(in.mAngle); + out->mAngleOuterCone = (in.mFalloff ? AI_DEG_TO_RAD(in.mFalloff) : out->mAngleInnerCone); + break; + + case ASE::Light::DIRECTIONAL: + out->mType = aiLightSource_DIRECTIONAL; + break; + + default: + //case ASE::Light::OMNI: + out->mType = aiLightSource_POINT; + break; + }; + out->mColorDiffuse = out->mColorSpecular = in.mColor * in.mIntensity; + } + } +} + +// ------------------------------------------------------------------------------------------------ +void ASEImporter::AddNodes(const std::vector& nodes, + aiNode* pcParent,const char* szName) +{ + aiMatrix4x4 m; + AddNodes(nodes,pcParent,szName,m); +} + +// ------------------------------------------------------------------------------------------------ +// Add meshes to a given node +void ASEImporter::AddMeshes(const ASE::BaseNode* snode,aiNode* node) +{ + for (unsigned int i = 0; i < pcScene->mNumMeshes;++i) { + // Get the name of the mesh (the mesh instance has been temporarily stored in the third vertex color) + const aiMesh* pcMesh = pcScene->mMeshes[i]; + const ASE::Mesh* mesh = (const ASE::Mesh*)pcMesh->mColors[2]; + + if (mesh == snode) { + ++node->mNumMeshes; + } + } + + if(node->mNumMeshes) { + node->mMeshes = new unsigned int[node->mNumMeshes]; + for (unsigned int i = 0, p = 0; i < pcScene->mNumMeshes;++i) { + + const aiMesh* pcMesh = pcScene->mMeshes[i]; + const ASE::Mesh* mesh = (const ASE::Mesh*)pcMesh->mColors[2]; + if (mesh == snode) { + node->mMeshes[p++] = i; + + // Transform all vertices of the mesh back into their local space -> + // at the moment they are pretransformed + aiMatrix4x4 m = mesh->mTransform; + m.Inverse(); + + aiVector3D* pvCurPtr = pcMesh->mVertices; + const aiVector3D* pvEndPtr = pvCurPtr + pcMesh->mNumVertices; + while (pvCurPtr != pvEndPtr) { + *pvCurPtr = m * (*pvCurPtr); + pvCurPtr++; + } + + // Do the same for the normal vectors, if we have them. + // As always, inverse transpose. + if (pcMesh->mNormals) { + aiMatrix3x3 m3 = aiMatrix3x3( mesh->mTransform ); + m3.Transpose(); + + pvCurPtr = pcMesh->mNormals; + pvEndPtr = pvCurPtr + pcMesh->mNumVertices; + while (pvCurPtr != pvEndPtr) { + *pvCurPtr = m3 * (*pvCurPtr); + pvCurPtr++; + } + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Add child nodes to a given parent node +void ASEImporter::AddNodes (const std::vector& nodes, + aiNode* pcParent, const char* szName, + const aiMatrix4x4& mat) +{ + const size_t len = szName ? ::strlen(szName) : 0; + ai_assert(4 <= AI_MAX_NUMBER_OF_COLOR_SETS); + + // Receives child nodes for the pcParent node + std::vector apcNodes; + + // Now iterate through all nodes in the scene and search for one + // which has *us* as parent. + for (std::vector::const_iterator it = nodes.begin(), end = nodes.end(); it != end; ++it) { + const BaseNode* snode = *it; + if (szName) { + if (len != snode->mParent.length() || ::strcmp(szName,snode->mParent.c_str())) + continue; + } + else if (snode->mParent.length()) + continue; + + (*it)->mProcessed = true; + + // Allocate a new node and add it to the output data structure + apcNodes.push_back(new aiNode()); + aiNode* node = apcNodes.back(); + + node->mName.Set((snode->mName.length() ? snode->mName.c_str() : "Unnamed_Node")); + node->mParent = pcParent; + + // Setup the transformation matrix of the node + aiMatrix4x4 mParentAdjust = mat; + mParentAdjust.Inverse(); + node->mTransformation = mParentAdjust*snode->mTransform; + + // Add sub nodes - prevent stack overflow due to recursive parenting + if (node->mName != node->mParent->mName && node->mName != node->mParent->mParent->mName ) { + AddNodes(nodes,node,node->mName.data,snode->mTransform); + } + + // Further processing depends on the type of the node + if (snode->mType == ASE::BaseNode::Mesh) { + // If the type of this node is "Mesh" we need to search + // the list of output meshes in the data structure for + // all those that belonged to this node once. This is + // slightly inconvinient here and a better solution should + // be used when this code is refactored next. + AddMeshes(snode,node); + } + else if (is_not_qnan( snode->mTargetPosition.x )) { + // If this is a target camera or light we generate a small + // child node which marks the position of the camera + // target (the direction information is contained in *this* + // node's animation track but the exact target position + // would be lost otherwise) + if (!node->mNumChildren) { + node->mChildren = new aiNode*[1]; + } + + aiNode* nd = new aiNode(); + + nd->mName.Set ( snode->mName + ".Target" ); + + nd->mTransformation.a4 = snode->mTargetPosition.x - snode->mTransform.a4; + nd->mTransformation.b4 = snode->mTargetPosition.y - snode->mTransform.b4; + nd->mTransformation.c4 = snode->mTargetPosition.z - snode->mTransform.c4; + + nd->mParent = node; + + // The .Target node is always the first child node + for (unsigned int m = 0; m < node->mNumChildren;++m) + node->mChildren[m+1] = node->mChildren[m]; + + node->mChildren[0] = nd; + node->mNumChildren++; + + // What we did is so great, it is at least worth a debug message + ASSIMP_LOG_DEBUG("ASE: Generating separate target node ("+snode->mName+")"); + } + } + + // Allocate enough space for the child nodes + // We allocate one slot more in case this is a target camera/light + pcParent->mNumChildren = (unsigned int)apcNodes.size(); + if (pcParent->mNumChildren) { + pcParent->mChildren = new aiNode*[apcNodes.size()+1 /* PLUS ONE !!! */]; + + // now build all nodes for our nice new children + for (unsigned int p = 0; p < apcNodes.size();++p) + pcParent->mChildren[p] = apcNodes[p]; + } + return; +} + +// ------------------------------------------------------------------------------------------------ +// Build the output node graph +void ASEImporter::BuildNodes(std::vector& nodes) { + ai_assert(NULL != pcScene); + + // allocate the one and only root node + aiNode* root = pcScene->mRootNode = new aiNode(); + root->mName.Set(""); + + // Setup the coordinate system transformation + pcScene->mRootNode->mNumChildren = 1; + pcScene->mRootNode->mChildren = new aiNode*[1]; + aiNode* ch = pcScene->mRootNode->mChildren[0] = new aiNode(); + ch->mParent = root; + + // Change the transformation matrix of all nodes + for (BaseNode *node : nodes) { + aiMatrix4x4& m = node->mTransform; + m.Transpose(); // row-order vs column-order + } + + // add all nodes + AddNodes(nodes,ch,NULL); + + // now iterate through al nodes and find those that have not yet + // been added to the nodegraph (= their parent could not be recognized) + std::vector aiList; + for (std::vector::iterator it = nodes.begin(), end = nodes.end();it != end; ++it) { + if ((*it)->mProcessed) { + continue; + } + + // check whether our parent is known + bool bKnowParent = false; + + // search the list another time, starting *here* and try to find out whether + // there is a node that references *us* as a parent + for (std::vector::const_iterator it2 = nodes.begin();it2 != end; ++it2) { + if (it2 == it) { + continue; + } + + if ((*it2)->mName == (*it)->mParent) { + bKnowParent = true; + break; + } + } + if (!bKnowParent) { + aiList.push_back(*it); + } + } + + // Are there ane orphaned nodes? + if (!aiList.empty()) { + std::vector apcNodes; + apcNodes.reserve(aiList.size() + pcScene->mRootNode->mNumChildren); + + for (unsigned int i = 0; i < pcScene->mRootNode->mNumChildren;++i) + apcNodes.push_back(pcScene->mRootNode->mChildren[i]); + + delete[] pcScene->mRootNode->mChildren; + for (std::vector::/*const_*/iterator i = aiList.begin();i != aiList.end();++i) { + const ASE::BaseNode* src = *i; + + // The parent is not known, so we can assume that we must add + // this node to the root node of the whole scene + aiNode* pcNode = new aiNode(); + pcNode->mParent = pcScene->mRootNode; + pcNode->mName.Set(src->mName); + AddMeshes(src,pcNode); + AddNodes(nodes,pcNode,pcNode->mName.data); + apcNodes.push_back(pcNode); + } + + // Regenerate our output array + pcScene->mRootNode->mChildren = new aiNode*[apcNodes.size()]; + for (unsigned int i = 0; i < apcNodes.size();++i) + pcScene->mRootNode->mChildren[i] = apcNodes[i]; + + pcScene->mRootNode->mNumChildren = (unsigned int)apcNodes.size(); + } + + // Reset the third color set to NULL - we used this field to store a temporary pointer + for (unsigned int i = 0; i < pcScene->mNumMeshes;++i) + pcScene->mMeshes[i]->mColors[2] = NULL; + + // The root node should not have at least one child or the file is valid + if (!pcScene->mRootNode->mNumChildren) { + throw DeadlyImportError("ASE: No nodes loaded. The file is either empty or corrupt"); + } + + // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system + pcScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f, + 0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f); +} + +// ------------------------------------------------------------------------------------------------ +// Convert the imported data to the internal verbose representation +void ASEImporter::BuildUniqueRepresentation(ASE::Mesh& mesh) { + // allocate output storage + std::vector mPositions; + std::vector amTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + std::vector mVertexColors; + std::vector mNormals; + std::vector mBoneVertices; + + unsigned int iSize = (unsigned int)mesh.mFaces.size() * 3; + mPositions.resize(iSize); + + // optional texture coordinates + for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS;++i) { + if (!mesh.amTexCoords[i].empty()) { + amTexCoords[i].resize(iSize); + } + } + // optional vertex colors + if (!mesh.mVertexColors.empty()) { + mVertexColors.resize(iSize); + } + + // optional vertex normals (vertex normals can simply be copied) + if (!mesh.mNormals.empty()) { + mNormals.resize(iSize); + } + // bone vertices. There is no need to change the bone list + if (!mesh.mBoneVertices.empty()) { + mBoneVertices.resize(iSize); + } + + // iterate through all faces in the mesh + unsigned int iCurrent = 0, fi = 0; + for (std::vector::iterator i = mesh.mFaces.begin();i != mesh.mFaces.end();++i,++fi) { + for (unsigned int n = 0; n < 3;++n,++iCurrent) + { + mPositions[iCurrent] = mesh.mPositions[(*i).mIndices[n]]; + + // add texture coordinates + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) { + if (mesh.amTexCoords[c].empty())break; + amTexCoords[c][iCurrent] = mesh.amTexCoords[c][(*i).amUVIndices[c][n]]; + } + // add vertex colors + if (!mesh.mVertexColors.empty()) { + mVertexColors[iCurrent] = mesh.mVertexColors[(*i).mColorIndices[n]]; + } + // add normal vectors + if (!mesh.mNormals.empty()) { + mNormals[iCurrent] = mesh.mNormals[fi*3+n]; + mNormals[iCurrent].Normalize(); + } + + // handle bone vertices + if ((*i).mIndices[n] < mesh.mBoneVertices.size()) { + // (sometimes this will cause bone verts to be duplicated + // however, I' quite sure Schrompf' JoinVerticesStep + // will fix that again ...) + mBoneVertices[iCurrent] = mesh.mBoneVertices[(*i).mIndices[n]]; + } + (*i).mIndices[n] = iCurrent; + } + } + + // replace the old arrays + mesh.mNormals = mNormals; + mesh.mPositions = mPositions; + mesh.mVertexColors = mVertexColors; + + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) + mesh.amTexCoords[c] = amTexCoords[c]; +} + +// ------------------------------------------------------------------------------------------------ +// Copy a texture from the ASE structs to the output material +void CopyASETexture(aiMaterial& mat, ASE::Texture& texture, aiTextureType type) +{ + // Setup the texture name + aiString tex; + tex.Set( texture.mMapName); + mat.AddProperty( &tex, AI_MATKEY_TEXTURE(type,0)); + + // Setup the texture blend factor + if (is_not_qnan(texture.mTextureBlend)) + mat.AddProperty( &texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type,0)); + + // Setup texture UV transformations + mat.AddProperty(&texture.mOffsetU,5,AI_MATKEY_UVTRANSFORM(type,0)); +} + +// ------------------------------------------------------------------------------------------------ +// Convert from ASE material to output material +void ASEImporter::ConvertMaterial(ASE::Material& mat) +{ + // LARGE TODO: Much code her is copied from 3DS ... join them maybe? + + // Allocate the output material + mat.pcInstance = new aiMaterial(); + + // At first add the base ambient color of the + // scene to the material + mat.mAmbient.r += mParser->m_clrAmbient.r; + mat.mAmbient.g += mParser->m_clrAmbient.g; + mat.mAmbient.b += mParser->m_clrAmbient.b; + + aiString name; + name.Set( mat.mName); + mat.pcInstance->AddProperty( &name, AI_MATKEY_NAME); + + // material colors + mat.pcInstance->AddProperty( &mat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT); + mat.pcInstance->AddProperty( &mat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + mat.pcInstance->AddProperty( &mat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); + mat.pcInstance->AddProperty( &mat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); + + // shininess + if (0.0f != mat.mSpecularExponent && 0.0f != mat.mShininessStrength) + { + mat.pcInstance->AddProperty( &mat.mSpecularExponent, 1, AI_MATKEY_SHININESS); + mat.pcInstance->AddProperty( &mat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH); + } + // If there is no shininess, we can disable phong lighting + else if (D3DS::Discreet3DS::Metal == mat.mShading || + D3DS::Discreet3DS::Phong == mat.mShading || + D3DS::Discreet3DS::Blinn == mat.mShading) + { + mat.mShading = D3DS::Discreet3DS::Gouraud; + } + + // opacity + mat.pcInstance->AddProperty( &mat.mTransparency,1,AI_MATKEY_OPACITY); + + // Two sided rendering? + if (mat.mTwoSided) + { + int i = 1; + mat.pcInstance->AddProperty(&i,1,AI_MATKEY_TWOSIDED); + } + + // shading mode + aiShadingMode eShading = aiShadingMode_NoShading; + switch (mat.mShading) + { + case D3DS::Discreet3DS::Flat: + eShading = aiShadingMode_Flat; break; + case D3DS::Discreet3DS::Phong : + eShading = aiShadingMode_Phong; break; + case D3DS::Discreet3DS::Blinn : + eShading = aiShadingMode_Blinn; break; + + // I don't know what "Wire" shading should be, + // assume it is simple lambertian diffuse (L dot N) shading + case D3DS::Discreet3DS::Wire: + { + // set the wireframe flag + unsigned int iWire = 1; + mat.pcInstance->AddProperty( (int*)&iWire,1,AI_MATKEY_ENABLE_WIREFRAME); + } + case D3DS::Discreet3DS::Gouraud: + eShading = aiShadingMode_Gouraud; break; + case D3DS::Discreet3DS::Metal : + eShading = aiShadingMode_CookTorrance; break; + } + mat.pcInstance->AddProperty( (int*)&eShading,1,AI_MATKEY_SHADING_MODEL); + + // DIFFUSE texture + if( mat.sTexDiffuse.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance,mat.sTexDiffuse, aiTextureType_DIFFUSE); + + // SPECULAR texture + if( mat.sTexSpecular.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance,mat.sTexSpecular, aiTextureType_SPECULAR); + + // AMBIENT texture + if( mat.sTexAmbient.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance,mat.sTexAmbient, aiTextureType_AMBIENT); + + // OPACITY texture + if( mat.sTexOpacity.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance,mat.sTexOpacity, aiTextureType_OPACITY); + + // EMISSIVE texture + if( mat.sTexEmissive.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance,mat.sTexEmissive, aiTextureType_EMISSIVE); + + // BUMP texture + if( mat.sTexBump.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance,mat.sTexBump, aiTextureType_HEIGHT); + + // SHININESS texture + if( mat.sTexShininess.mMapName.length() > 0) + CopyASETexture(*mat.pcInstance,mat.sTexShininess, aiTextureType_SHININESS); + + // store the name of the material itself, too + if( mat.mName.length() > 0) { + aiString tex;tex.Set( mat.mName); + mat.pcInstance->AddProperty( &tex, AI_MATKEY_NAME); + } + return; +} + +// ------------------------------------------------------------------------------------------------ +// Build output meshes +void ASEImporter::ConvertMeshes(ASE::Mesh& mesh, std::vector& avOutMeshes) +{ + // validate the material index of the mesh + if (mesh.iMaterialIndex >= mParser->m_vMaterials.size()) { + mesh.iMaterialIndex = (unsigned int)mParser->m_vMaterials.size()-1; + ASSIMP_LOG_WARN("Material index is out of range"); + } + + // If the material the mesh is assigned to is consisting of submeshes, split it + if (!mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials.empty()) { + std::vector vSubMaterials = mParser-> + m_vMaterials[mesh.iMaterialIndex].avSubMaterials; + + std::vector* aiSplit = new std::vector[vSubMaterials.size()]; + + // build a list of all faces per sub-material + for (unsigned int i = 0; i < mesh.mFaces.size();++i) { + // check range + if (mesh.mFaces[i].iMaterial >= vSubMaterials.size()) { + ASSIMP_LOG_WARN("Submaterial index is out of range"); + + // use the last material instead + aiSplit[vSubMaterials.size()-1].push_back(i); + } + else aiSplit[mesh.mFaces[i].iMaterial].push_back(i); + } + + // now generate submeshes + for (unsigned int p = 0; p < vSubMaterials.size();++p) { + if (!aiSplit[p].empty()) { + + aiMesh* p_pcOut = new aiMesh(); + p_pcOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + // let the sub material index + p_pcOut->mMaterialIndex = p; + + // we will need this material + mParser->m_vMaterials[mesh.iMaterialIndex].avSubMaterials[p].bNeed = true; + + // store the real index here ... color channel 3 + p_pcOut->mColors[3] = (aiColor4D*)(uintptr_t)mesh.iMaterialIndex; + + // store a pointer to the mesh in color channel 2 + p_pcOut->mColors[2] = (aiColor4D*) &mesh; + avOutMeshes.push_back(p_pcOut); + + // convert vertices + p_pcOut->mNumVertices = (unsigned int)aiSplit[p].size()*3; + p_pcOut->mNumFaces = (unsigned int)aiSplit[p].size(); + + // receive output vertex weights + std::vector > *avOutputBones = NULL; + if (!mesh.mBones.empty()) { + avOutputBones = new std::vector >[mesh.mBones.size()]; + } + + // allocate enough storage for faces + p_pcOut->mFaces = new aiFace[p_pcOut->mNumFaces]; + + unsigned int iBase = 0,iIndex; + if (p_pcOut->mNumVertices) { + p_pcOut->mVertices = new aiVector3D[p_pcOut->mNumVertices]; + p_pcOut->mNormals = new aiVector3D[p_pcOut->mNumVertices]; + for (unsigned int q = 0; q < aiSplit[p].size();++q) { + + iIndex = aiSplit[p][q]; + + p_pcOut->mFaces[q].mIndices = new unsigned int[3]; + p_pcOut->mFaces[q].mNumIndices = 3; + + for (unsigned int t = 0; t < 3;++t, ++iBase) { + const uint32_t iIndex2 = mesh.mFaces[iIndex].mIndices[t]; + + p_pcOut->mVertices[iBase] = mesh.mPositions [iIndex2]; + p_pcOut->mNormals [iBase] = mesh.mNormals [iIndex2]; + + // convert bones, if existing + if (!mesh.mBones.empty()) { + ai_assert(avOutputBones); + // check whether there is a vertex weight for this vertex index + if (iIndex2 < mesh.mBoneVertices.size()) { + + for (std::vector >::const_iterator + blubb = mesh.mBoneVertices[iIndex2].mBoneWeights.begin(); + blubb != mesh.mBoneVertices[iIndex2].mBoneWeights.end();++blubb) { + + // NOTE: illegal cases have already been filtered out + avOutputBones[(*blubb).first].push_back(std::pair( + iBase,(*blubb).second)); + } + } + } + p_pcOut->mFaces[q].mIndices[t] = iBase; + } + } + } + // convert texture coordinates (up to AI_MAX_NUMBER_OF_TEXTURECOORDS sets supported) + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) { + if (!mesh.amTexCoords[c].empty()) + { + p_pcOut->mTextureCoords[c] = new aiVector3D[p_pcOut->mNumVertices]; + iBase = 0; + for (unsigned int q = 0; q < aiSplit[p].size();++q) { + iIndex = aiSplit[p][q]; + for (unsigned int t = 0; t < 3;++t) { + p_pcOut->mTextureCoords[c][iBase++] = mesh.amTexCoords[c][mesh.mFaces[iIndex].mIndices[t]]; + } + } + // Setup the number of valid vertex components + p_pcOut->mNumUVComponents[c] = mesh.mNumUVComponents[c]; + } + } + + // Convert vertex colors (only one set supported) + if (!mesh.mVertexColors.empty()){ + p_pcOut->mColors[0] = new aiColor4D[p_pcOut->mNumVertices]; + iBase = 0; + for (unsigned int q = 0; q < aiSplit[p].size();++q) { + iIndex = aiSplit[p][q]; + for (unsigned int t = 0; t < 3;++t) { + p_pcOut->mColors[0][iBase++] = mesh.mVertexColors[mesh.mFaces[iIndex].mIndices[t]]; + } + } + } + // Copy bones + if (!mesh.mBones.empty()) { + p_pcOut->mNumBones = 0; + for (unsigned int mrspock = 0; mrspock < mesh.mBones.size();++mrspock) + if (!avOutputBones[mrspock].empty())p_pcOut->mNumBones++; + + p_pcOut->mBones = new aiBone* [ p_pcOut->mNumBones ]; + aiBone** pcBone = p_pcOut->mBones; + for (unsigned int mrspock = 0; mrspock < mesh.mBones.size();++mrspock) + { + if (!avOutputBones[mrspock].empty()) { + // we will need this bone. add it to the output mesh and + // add all per-vertex weights + aiBone* pc = *pcBone = new aiBone(); + pc->mName.Set(mesh.mBones[mrspock].mName); + + pc->mNumWeights = (unsigned int)avOutputBones[mrspock].size(); + pc->mWeights = new aiVertexWeight[pc->mNumWeights]; + + for (unsigned int captainkirk = 0; captainkirk < pc->mNumWeights;++captainkirk) + { + const std::pair& ref = avOutputBones[mrspock][captainkirk]; + pc->mWeights[captainkirk].mVertexId = ref.first; + pc->mWeights[captainkirk].mWeight = ref.second; + } + ++pcBone; + } + } + // delete allocated storage + delete[] avOutputBones; + } + } + } + // delete storage + delete[] aiSplit; + } + else + { + // Otherwise we can simply copy the data to one output mesh + // This codepath needs less memory and uses fast memcpy()s + // to do the actual copying. So I think it is worth the + // effort here. + + aiMesh* p_pcOut = new aiMesh(); + p_pcOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + // set an empty sub material index + p_pcOut->mMaterialIndex = ASE::Face::DEFAULT_MATINDEX; + mParser->m_vMaterials[mesh.iMaterialIndex].bNeed = true; + + // store the real index here ... in color channel 3 + p_pcOut->mColors[3] = (aiColor4D*)(uintptr_t)mesh.iMaterialIndex; + + // store a pointer to the mesh in color channel 2 + p_pcOut->mColors[2] = (aiColor4D*) &mesh; + avOutMeshes.push_back(p_pcOut); + + // If the mesh hasn't faces or vertices, there are two cases + // possible: 1. the model is invalid. 2. This is a dummy + // helper object which we are going to remove later ... + if (mesh.mFaces.empty() || mesh.mPositions.empty()) { + return; + } + + // convert vertices + p_pcOut->mNumVertices = (unsigned int)mesh.mPositions.size(); + p_pcOut->mNumFaces = (unsigned int)mesh.mFaces.size(); + + // allocate enough storage for faces + p_pcOut->mFaces = new aiFace[p_pcOut->mNumFaces]; + + // copy vertices + p_pcOut->mVertices = new aiVector3D[mesh.mPositions.size()]; + memcpy(p_pcOut->mVertices,&mesh.mPositions[0], + mesh.mPositions.size() * sizeof(aiVector3D)); + + // copy normals + p_pcOut->mNormals = new aiVector3D[mesh.mNormals.size()]; + memcpy(p_pcOut->mNormals,&mesh.mNormals[0], + mesh.mNormals.size() * sizeof(aiVector3D)); + + // copy texture coordinates + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) { + if (!mesh.amTexCoords[c].empty()) { + p_pcOut->mTextureCoords[c] = new aiVector3D[mesh.amTexCoords[c].size()]; + memcpy(p_pcOut->mTextureCoords[c],&mesh.amTexCoords[c][0], + mesh.amTexCoords[c].size() * sizeof(aiVector3D)); + + // setup the number of valid vertex components + p_pcOut->mNumUVComponents[c] = mesh.mNumUVComponents[c]; + } + } + + // copy vertex colors + if (!mesh.mVertexColors.empty()) { + p_pcOut->mColors[0] = new aiColor4D[mesh.mVertexColors.size()]; + memcpy(p_pcOut->mColors[0],&mesh.mVertexColors[0], + mesh.mVertexColors.size() * sizeof(aiColor4D)); + } + + // copy faces + for (unsigned int iFace = 0; iFace < p_pcOut->mNumFaces;++iFace) { + p_pcOut->mFaces[iFace].mNumIndices = 3; + p_pcOut->mFaces[iFace].mIndices = new unsigned int[3]; + + // copy indices + p_pcOut->mFaces[iFace].mIndices[0] = mesh.mFaces[iFace].mIndices[0]; + p_pcOut->mFaces[iFace].mIndices[1] = mesh.mFaces[iFace].mIndices[1]; + p_pcOut->mFaces[iFace].mIndices[2] = mesh.mFaces[iFace].mIndices[2]; + } + + // copy vertex bones + if (!mesh.mBones.empty() && !mesh.mBoneVertices.empty()) { + std::vector > avBonesOut( mesh.mBones.size() ); + + // find all vertex weights for this bone + unsigned int quak = 0; + for (std::vector::const_iterator harrypotter = mesh.mBoneVertices.begin(); + harrypotter != mesh.mBoneVertices.end();++harrypotter,++quak) { + + for (std::vector >::const_iterator + ronaldweasley = (*harrypotter).mBoneWeights.begin(); + ronaldweasley != (*harrypotter).mBoneWeights.end();++ronaldweasley) + { + aiVertexWeight weight; + weight.mVertexId = quak; + weight.mWeight = (*ronaldweasley).second; + avBonesOut[(*ronaldweasley).first].push_back(weight); + } + } + + // now build a final bone list + p_pcOut->mNumBones = 0; + for (unsigned int jfkennedy = 0; jfkennedy < mesh.mBones.size();++jfkennedy) + if (!avBonesOut[jfkennedy].empty())p_pcOut->mNumBones++; + + p_pcOut->mBones = new aiBone*[p_pcOut->mNumBones]; + aiBone** pcBone = p_pcOut->mBones; + for (unsigned int jfkennedy = 0; jfkennedy < mesh.mBones.size();++jfkennedy) { + if (!avBonesOut[jfkennedy].empty()) { + aiBone* pc = *pcBone = new aiBone(); + pc->mName.Set(mesh.mBones[jfkennedy].mName); + pc->mNumWeights = (unsigned int)avBonesOut[jfkennedy].size(); + pc->mWeights = new aiVertexWeight[pc->mNumWeights]; + ::memcpy(pc->mWeights,&avBonesOut[jfkennedy][0], + sizeof(aiVertexWeight) * pc->mNumWeights); + ++pcBone; + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Setup proper material indices and build output materials +void ASEImporter::BuildMaterialIndices() +{ + ai_assert(NULL != pcScene); + + // iterate through all materials and check whether we need them + for (unsigned int iMat = 0; iMat < mParser->m_vMaterials.size();++iMat) + { + ASE::Material& mat = mParser->m_vMaterials[iMat]; + if (mat.bNeed) { + // Convert it to the aiMaterial layout + ConvertMaterial(mat); + ++pcScene->mNumMaterials; + } + for (unsigned int iSubMat = 0; iSubMat < mat.avSubMaterials.size();++iSubMat) + { + ASE::Material& submat = mat.avSubMaterials[iSubMat]; + if (submat.bNeed) { + // Convert it to the aiMaterial layout + ConvertMaterial(submat); + ++pcScene->mNumMaterials; + } + } + } + + // allocate the output material array + pcScene->mMaterials = new aiMaterial*[pcScene->mNumMaterials]; + D3DS::Material** pcIntMaterials = new D3DS::Material*[pcScene->mNumMaterials]; + + unsigned int iNum = 0; + for (unsigned int iMat = 0; iMat < mParser->m_vMaterials.size();++iMat) { + ASE::Material& mat = mParser->m_vMaterials[iMat]; + if (mat.bNeed) + { + ai_assert(NULL != mat.pcInstance); + pcScene->mMaterials[iNum] = mat.pcInstance; + + // Store the internal material, too + pcIntMaterials[iNum] = &mat; + + // Iterate through all meshes and search for one which is using + // this top-level material index + for (unsigned int iMesh = 0; iMesh < pcScene->mNumMeshes;++iMesh) + { + aiMesh* mesh = pcScene->mMeshes[iMesh]; + if (ASE::Face::DEFAULT_MATINDEX == mesh->mMaterialIndex && + iMat == (uintptr_t)mesh->mColors[3]) + { + mesh->mMaterialIndex = iNum; + mesh->mColors[3] = NULL; + } + } + iNum++; + } + for (unsigned int iSubMat = 0; iSubMat < mat.avSubMaterials.size();++iSubMat) { + ASE::Material& submat = mat.avSubMaterials[iSubMat]; + if (submat.bNeed) { + ai_assert(NULL != submat.pcInstance); + pcScene->mMaterials[iNum] = submat.pcInstance; + + // Store the internal material, too + pcIntMaterials[iNum] = &submat; + + // Iterate through all meshes and search for one which is using + // this sub-level material index + for (unsigned int iMesh = 0; iMesh < pcScene->mNumMeshes;++iMesh) { + aiMesh* mesh = pcScene->mMeshes[iMesh]; + + if (iSubMat == mesh->mMaterialIndex && iMat == (uintptr_t)mesh->mColors[3]) { + mesh->mMaterialIndex = iNum; + mesh->mColors[3] = NULL; + } + } + iNum++; + } + } + } + + // Delete our temporary array + delete[] pcIntMaterials; +} + +// ------------------------------------------------------------------------------------------------ +// Generate normal vectors basing on smoothing groups +bool ASEImporter::GenerateNormals(ASE::Mesh& mesh) { + + if (!mesh.mNormals.empty() && !configRecomputeNormals) + { + // Check whether there are only uninitialized normals. If there are + // some, skip all normals from the file and compute them on our own + for (std::vector::const_iterator qq = mesh.mNormals.begin();qq != mesh.mNormals.end();++qq) { + if ((*qq).x || (*qq).y || (*qq).z) + { + return true; + } + } + } + // The array is reused. + ComputeNormalsWithSmoothingsGroups(mesh); + return false; +} + +#endif // ASSIMP_BUILD_NO_3DS_IMPORTER + +#endif // !! ASSIMP_BUILD_NO_BASE_IMPORTER diff --git a/thirdparty/assimp/code/ASE/ASELoader.h b/thirdparty/assimp/code/ASE/ASELoader.h new file mode 100644 index 0000000..b497aa4 --- /dev/null +++ b/thirdparty/assimp/code/ASE/ASELoader.h @@ -0,0 +1,207 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file ASELoader.h + * @brief Definition of the .ASE importer class. + */ +#ifndef AI_ASELOADER_H_INCLUDED +#define AI_ASELOADER_H_INCLUDED + +#include +#include +#include "ASEParser.h" + +struct aiNode; + +namespace Assimp { + +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +// -------------------------------------------------------------------------------- +/** Importer class for the 3DS ASE ASCII format. + * + */ +class ASEImporter : public BaseImporter { +public: + ASEImporter(); + ~ASEImporter(); + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, + bool checkSig) const; + +protected: + + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + const aiImporterDesc* GetInfo () const; + + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler); + + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer* pImp); + + +private: + + // ------------------------------------------------------------------- + /** Generate normal vectors basing on smoothing groups + * (in some cases the normal are already contained in the file) + * \param mesh Mesh to work on + * \return false if the normals have been recomputed + */ + bool GenerateNormals(ASE::Mesh& mesh); + + + // ------------------------------------------------------------------- + /** Create valid vertex/normal/UV/color/face lists. + * All elements are unique, faces have only one set of indices + * after this step occurs. + * \param mesh Mesh to work on + */ + void BuildUniqueRepresentation(ASE::Mesh& mesh); + + + /** Create one-material-per-mesh meshes ;-) + * \param mesh Mesh to work with + * \param Receives the list of all created meshes + */ + void ConvertMeshes(ASE::Mesh& mesh, std::vector& avOut); + + + // ------------------------------------------------------------------- + /** Convert a material to a aiMaterial object + * \param mat Input material + */ + void ConvertMaterial(ASE::Material& mat); + + + // ------------------------------------------------------------------- + /** Setup the final material indices for each mesh + */ + void BuildMaterialIndices(); + + + // ------------------------------------------------------------------- + /** Build the node graph + */ + void BuildNodes(std::vector& nodes); + + + // ------------------------------------------------------------------- + /** Build output cameras + */ + void BuildCameras(); + + + // ------------------------------------------------------------------- + /** Build output lights + */ + void BuildLights(); + + + // ------------------------------------------------------------------- + /** Build output animations + */ + void BuildAnimations(const std::vector& nodes); + + + // ------------------------------------------------------------------- + /** Add sub nodes to a node + * \param pcParent parent node to be filled + * \param szName Name of the parent node + * \param matrix Current transform + */ + void AddNodes(const std::vector& nodes, + aiNode* pcParent,const char* szName); + + void AddNodes(const std::vector& nodes, + aiNode* pcParent,const char* szName, + const aiMatrix4x4& matrix); + + void AddMeshes(const ASE::BaseNode* snode,aiNode* node); + + // ------------------------------------------------------------------- + /** Generate a default material and add it to the parser's list + * Called if no material has been found in the file (rare for ASE, + * but not impossible) + */ + void GenerateDefaultMaterial(); + +protected: + + /** Parser instance */ + ASE::Parser* mParser; + + /** Buffer to hold the loaded file */ + char* mBuffer; + + /** Scene to be filled */ + aiScene* pcScene; + + /** Config options: Recompute the normals in every case - WA + for 3DS Max broken ASE normal export */ + bool configRecomputeNormals; + bool noSkeletonMesh; +}; + +#endif // ASSIMP_BUILD_NO_3DS_IMPORTER + +} // end of namespace Assimp + + +#endif // AI_3DSIMPORTER_H_INC diff --git a/thirdparty/assimp/code/ASE/ASEParser.cpp b/thirdparty/assimp/code/ASE/ASEParser.cpp new file mode 100644 index 0000000..913e7b1 --- /dev/null +++ b/thirdparty/assimp/code/ASE/ASEParser.cpp @@ -0,0 +1,2159 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/** @file ASEParser.cpp + * @brief Implementation of the ASE parser class + */ + +#ifndef ASSIMP_BUILD_NO_ASE_IMPORTER +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +// internal headers +#include "PostProcessing/TextureTransform.h" +#include "ASELoader.h" + +#include +#include + +using namespace Assimp; +using namespace Assimp::ASE; + +// ------------------------------------------------------------------------------------------------ +// Begin an ASE parsing function + +#define AI_ASE_PARSER_INIT() \ + int iDepth = 0; + +// ------------------------------------------------------------------------------------------------ +// Handle a "top-level" section in the file. EOF is no error in this case. + +#define AI_ASE_HANDLE_TOP_LEVEL_SECTION() \ + else if ('{' == *filePtr)iDepth++; \ + else if ('}' == *filePtr) \ + { \ + if (0 == --iDepth) \ + { \ + ++filePtr; \ + SkipToNextToken(); \ + return; \ + } \ + } \ + else if ('\0' == *filePtr) \ + { \ + return; \ + } \ + if(IsLineEnd(*filePtr) && !bLastWasEndLine) \ + { \ + ++iLineNumber; \ + bLastWasEndLine = true; \ + } else bLastWasEndLine = false; \ + ++filePtr; + +// ------------------------------------------------------------------------------------------------ +// Handle a nested section in the file. EOF is an error in this case +// @param level "Depth" of the section +// @param msg Full name of the section (including the asterisk) + +#define AI_ASE_HANDLE_SECTION(level, msg) \ + if ('{' == *filePtr)iDepth++; \ + else if ('}' == *filePtr) \ + { \ + if (0 == --iDepth) \ + { \ + ++filePtr; \ + SkipToNextToken(); \ + return; \ + } \ + } \ + else if ('\0' == *filePtr) \ + { \ + LogError("Encountered unexpected EOL while parsing a " msg \ + " chunk (Level " level ")"); \ + } \ + if(IsLineEnd(*filePtr) && !bLastWasEndLine) \ + { \ + ++iLineNumber; \ + bLastWasEndLine = true; \ + } else bLastWasEndLine = false; \ + ++filePtr; + +// ------------------------------------------------------------------------------------------------ +Parser::Parser (const char* szFile, unsigned int fileFormatDefault) +{ + ai_assert(NULL != szFile); + filePtr = szFile; + iFileFormat = fileFormatDefault; + + // make sure that the color values are invalid + m_clrBackground.r = get_qnan(); + m_clrAmbient.r = get_qnan(); + + // setup some default values + iLineNumber = 0; + iFirstFrame = 0; + iLastFrame = 0; + iFrameSpeed = 30; // use 30 as default value for this property + iTicksPerFrame = 1; // use 1 as default value for this property + bLastWasEndLine = false; // need to handle \r\n seqs due to binary file mapping +} + +// ------------------------------------------------------------------------------------------------ +void Parser::LogWarning(const char* szWarn) +{ + ai_assert(NULL != szWarn); + + char szTemp[1024]; +#if _MSC_VER >= 1400 + sprintf_s(szTemp, "Line %u: %s",iLineNumber,szWarn); +#else + ai_snprintf(szTemp,1024,"Line %u: %s",iLineNumber,szWarn); +#endif + + // output the warning to the logger ... + ASSIMP_LOG_WARN(szTemp); +} + +// ------------------------------------------------------------------------------------------------ +void Parser::LogInfo(const char* szWarn) +{ + ai_assert(NULL != szWarn); + + char szTemp[1024]; +#if _MSC_VER >= 1400 + sprintf_s(szTemp,"Line %u: %s",iLineNumber,szWarn); +#else + ai_snprintf(szTemp,1024,"Line %u: %s",iLineNumber,szWarn); +#endif + + // output the information to the logger ... + ASSIMP_LOG_INFO(szTemp); +} + +// ------------------------------------------------------------------------------------------------ +AI_WONT_RETURN void Parser::LogError(const char* szWarn) +{ + ai_assert(NULL != szWarn); + + char szTemp[1024]; +#if _MSC_VER >= 1400 + sprintf_s(szTemp,"Line %u: %s",iLineNumber,szWarn); +#else + ai_snprintf(szTemp,1024,"Line %u: %s",iLineNumber,szWarn); +#endif + + // throw an exception + throw DeadlyImportError(szTemp); +} + +// ------------------------------------------------------------------------------------------------ +bool Parser::SkipToNextToken() +{ + while (true) + { + char me = *filePtr; + + // increase the line number counter if necessary + if (IsLineEnd(me) && !bLastWasEndLine) + { + ++iLineNumber; + bLastWasEndLine = true; + } + else bLastWasEndLine = false; + if ('*' == me || '}' == me || '{' == me)return true; + if ('\0' == me)return false; + + ++filePtr; + } +} + +// ------------------------------------------------------------------------------------------------ +bool Parser::SkipSection() +{ + // must handle subsections ... + int iCnt = 0; + while (true) + { + if ('}' == *filePtr) + { + --iCnt; + if (0 == iCnt) + { + // go to the next valid token ... + ++filePtr; + SkipToNextToken(); + return true; + } + } + else if ('{' == *filePtr) + { + ++iCnt; + } + else if ('\0' == *filePtr) + { + LogWarning("Unable to parse block: Unexpected EOF, closing bracket \'}\' was expected [#1]"); + return false; + } + else if(IsLineEnd(*filePtr))++iLineNumber; + ++filePtr; + } +} + +// ------------------------------------------------------------------------------------------------ +void Parser::Parse() +{ + AI_ASE_PARSER_INIT(); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + // Version should be 200. Validate this ... + if (TokenMatch(filePtr,"3DSMAX_ASCIIEXPORT",18)) + { + unsigned int fmt; + ParseLV4MeshLong(fmt); + + if (fmt > 200) + { + LogWarning("Unknown file format version: *3DSMAX_ASCIIEXPORT should \ + be <= 200"); + } + // ************************************************************* + // - fmt will be 0 if we're unable to read the version number + // there are some faulty files without a version number ... + // in this case we'll guess the exact file format by looking + // at the file extension (ASE, ASK, ASC) + // ************************************************************* + + if ( fmt ) { + iFileFormat = fmt; + } + continue; + } + // main scene information + if (TokenMatch(filePtr,"SCENE",5)) + { + ParseLV1SceneBlock(); + continue; + } + // "group" - no implementation yet, in facte + // we're just ignoring them for the moment + if (TokenMatch(filePtr,"GROUP",5)) + { + Parse(); + continue; + } + // material list + if (TokenMatch(filePtr,"MATERIAL_LIST",13)) + { + ParseLV1MaterialListBlock(); + continue; + } + // geometric object (mesh) + if (TokenMatch(filePtr,"GEOMOBJECT",10)) + + { + m_vMeshes.push_back(Mesh("UNNAMED")); + ParseLV1ObjectBlock(m_vMeshes.back()); + continue; + } + // helper object = dummy in the hierarchy + if (TokenMatch(filePtr,"HELPEROBJECT",12)) + + { + m_vDummies.push_back(Dummy()); + ParseLV1ObjectBlock(m_vDummies.back()); + continue; + } + // light object + if (TokenMatch(filePtr,"LIGHTOBJECT",11)) + + { + m_vLights.push_back(Light("UNNAMED")); + ParseLV1ObjectBlock(m_vLights.back()); + continue; + } + // camera object + if (TokenMatch(filePtr,"CAMERAOBJECT",12)) + { + m_vCameras.push_back(Camera("UNNAMED")); + ParseLV1ObjectBlock(m_vCameras.back()); + continue; + } + // comment - print it on the console + if (TokenMatch(filePtr,"COMMENT",7)) + { + std::string out = ""; + ParseString(out,"*COMMENT"); + LogInfo(("Comment: " + out).c_str()); + continue; + } + // ASC bone weights + if (AI_ASE_IS_OLD_FILE_FORMAT() && TokenMatch(filePtr,"MESH_SOFTSKINVERTS",18)) + { + ParseLV1SoftSkinBlock(); + } + } + AI_ASE_HANDLE_TOP_LEVEL_SECTION(); + } + return; +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV1SoftSkinBlock() +{ + // TODO: fix line counting here + + // ************************************************************** + // The soft skin block is formatted differently. There are no + // nested sections supported and the single elements aren't + // marked by keywords starting with an asterisk. + + /** + FORMAT BEGIN + + *MESH_SOFTSKINVERTS { + + + + [for times:] + [for times:] + } + + FORMAT END + */ + // ************************************************************** + while (true) + { + if (*filePtr == '}' ) {++filePtr;return;} + else if (*filePtr == '\0') return; + else if (*filePtr == '{' ) ++filePtr; + + else // if (!IsSpace(*filePtr) && !IsLineEnd(*filePtr)) + { + ASE::Mesh* curMesh = NULL; + unsigned int numVerts = 0; + + const char* sz = filePtr; + while (!IsSpaceOrNewLine(*filePtr))++filePtr; + + const unsigned int diff = (unsigned int)(filePtr-sz); + if (diff) + { + std::string name = std::string(sz,diff); + for (std::vector::iterator it = m_vMeshes.begin(); + it != m_vMeshes.end(); ++it) + { + if ((*it).mName == name) + { + curMesh = & (*it); + break; + } + } + if (!curMesh) + { + LogWarning("Encountered unknown mesh in *MESH_SOFTSKINVERTS section"); + + // Skip the mesh data - until we find a new mesh + // or the end of the *MESH_SOFTSKINVERTS section + while (true) + { + SkipSpacesAndLineEnd(&filePtr); + if (*filePtr == '}') + {++filePtr;return;} + else if (!IsNumeric(*filePtr)) + break; + + SkipLine(&filePtr); + } + } + else + { + SkipSpacesAndLineEnd(&filePtr); + ParseLV4MeshLong(numVerts); + + // Reserve enough storage + curMesh->mBoneVertices.reserve(numVerts); + + for (unsigned int i = 0; i < numVerts;++i) + { + SkipSpacesAndLineEnd(&filePtr); + unsigned int numWeights; + ParseLV4MeshLong(numWeights); + + curMesh->mBoneVertices.push_back(ASE::BoneVertex()); + ASE::BoneVertex& vert = curMesh->mBoneVertices.back(); + + // Reserve enough storage + vert.mBoneWeights.reserve(numWeights); + + std::string bone; + for (unsigned int w = 0; w < numWeights;++w) { + bone.clear(); + ParseString(bone,"*MESH_SOFTSKINVERTS.Bone"); + + // Find the bone in the mesh's list + std::pair me; + me.first = -1; + + for (unsigned int n = 0; n < curMesh->mBones.size();++n) { + if (curMesh->mBones[n].mName == bone) { + me.first = n; + break; + } + } + if (-1 == me.first) { + // We don't have this bone yet, so add it to the list + me.first = static_cast( curMesh->mBones.size() ); + curMesh->mBones.push_back( ASE::Bone( bone ) ); + } + ParseLV4MeshFloat( me.second ); + + // Add the new bone weight to list + vert.mBoneWeights.push_back(me); + } + } + } + } + } + ++filePtr; + SkipSpacesAndLineEnd(&filePtr); + } +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV1SceneBlock() +{ + AI_ASE_PARSER_INIT(); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + if (TokenMatch(filePtr,"SCENE_BACKGROUND_STATIC",23)) + + { + // parse a color triple and assume it is really the bg color + ParseLV4MeshFloatTriple( &m_clrBackground.r ); + continue; + } + if (TokenMatch(filePtr,"SCENE_AMBIENT_STATIC",20)) + + { + // parse a color triple and assume it is really the bg color + ParseLV4MeshFloatTriple( &m_clrAmbient.r ); + continue; + } + if (TokenMatch(filePtr,"SCENE_FIRSTFRAME",16)) + { + ParseLV4MeshLong(iFirstFrame); + continue; + } + if (TokenMatch(filePtr,"SCENE_LASTFRAME",15)) + { + ParseLV4MeshLong(iLastFrame); + continue; + } + if (TokenMatch(filePtr,"SCENE_FRAMESPEED",16)) + { + ParseLV4MeshLong(iFrameSpeed); + continue; + } + if (TokenMatch(filePtr,"SCENE_TICKSPERFRAME",19)) + { + ParseLV4MeshLong(iTicksPerFrame); + continue; + } + } + AI_ASE_HANDLE_TOP_LEVEL_SECTION(); + } +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV1MaterialListBlock() +{ + AI_ASE_PARSER_INIT(); + + unsigned int iMaterialCount = 0; + unsigned int iOldMaterialCount = (unsigned int)m_vMaterials.size(); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + if (TokenMatch(filePtr,"MATERIAL_COUNT",14)) + { + ParseLV4MeshLong(iMaterialCount); + + // now allocate enough storage to hold all materials + m_vMaterials.resize(iOldMaterialCount+iMaterialCount, Material("INVALID")); + continue; + } + if (TokenMatch(filePtr,"MATERIAL",8)) + { + unsigned int iIndex = 0; + ParseLV4MeshLong(iIndex); + + if (iIndex >= iMaterialCount) + { + LogWarning("Out of range: material index is too large"); + iIndex = iMaterialCount-1; + } + + // get a reference to the material + Material& sMat = m_vMaterials[iIndex+iOldMaterialCount]; + // parse the material block + ParseLV2MaterialBlock(sMat); + continue; + } + } + AI_ASE_HANDLE_TOP_LEVEL_SECTION(); + } +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV2MaterialBlock(ASE::Material& mat) +{ + AI_ASE_PARSER_INIT(); + + unsigned int iNumSubMaterials = 0; + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + if (TokenMatch(filePtr,"MATERIAL_NAME",13)) + { + if (!ParseString(mat.mName,"*MATERIAL_NAME")) + SkipToNextToken(); + continue; + } + // ambient material color + if (TokenMatch(filePtr,"MATERIAL_AMBIENT",16)) + { + ParseLV4MeshFloatTriple(&mat.mAmbient.r); + continue; + } + // diffuse material color + if (TokenMatch(filePtr,"MATERIAL_DIFFUSE",16) ) + { + ParseLV4MeshFloatTriple(&mat.mDiffuse.r); + continue; + } + // specular material color + if (TokenMatch(filePtr,"MATERIAL_SPECULAR",17)) + { + ParseLV4MeshFloatTriple(&mat.mSpecular.r); + continue; + } + // material shading type + if (TokenMatch(filePtr,"MATERIAL_SHADING",16)) + { + if (TokenMatch(filePtr,"Blinn",5)) + { + mat.mShading = Discreet3DS::Blinn; + } + else if (TokenMatch(filePtr,"Phong",5)) + { + mat.mShading = Discreet3DS::Phong; + } + else if (TokenMatch(filePtr,"Flat",4)) + { + mat.mShading = Discreet3DS::Flat; + } + else if (TokenMatch(filePtr,"Wire",4)) + { + mat.mShading = Discreet3DS::Wire; + } + else + { + // assume gouraud shading + mat.mShading = Discreet3DS::Gouraud; + SkipToNextToken(); + } + continue; + } + // material transparency + if (TokenMatch(filePtr,"MATERIAL_TRANSPARENCY",21)) + { + ParseLV4MeshFloat(mat.mTransparency); + mat.mTransparency = ai_real( 1.0 ) - mat.mTransparency; + continue; + } + // material self illumination + if (TokenMatch(filePtr,"MATERIAL_SELFILLUM",18)) + { + ai_real f = 0.0; + ParseLV4MeshFloat(f); + + mat.mEmissive.r = f; + mat.mEmissive.g = f; + mat.mEmissive.b = f; + continue; + } + // material shininess + if (TokenMatch(filePtr,"MATERIAL_SHINE",14) ) + { + ParseLV4MeshFloat(mat.mSpecularExponent); + mat.mSpecularExponent *= 15; + continue; + } + // two-sided material + if (TokenMatch(filePtr,"MATERIAL_TWOSIDED",17) ) + { + mat.mTwoSided = true; + continue; + } + // material shininess strength + if (TokenMatch(filePtr,"MATERIAL_SHINESTRENGTH",22)) + { + ParseLV4MeshFloat(mat.mShininessStrength); + continue; + } + // diffuse color map + if (TokenMatch(filePtr,"MAP_DIFFUSE",11)) + { + // parse the texture block + ParseLV3MapBlock(mat.sTexDiffuse); + continue; + } + // ambient color map + if (TokenMatch(filePtr,"MAP_AMBIENT",11)) + { + // parse the texture block + ParseLV3MapBlock(mat.sTexAmbient); + continue; + } + // specular color map + if (TokenMatch(filePtr,"MAP_SPECULAR",12)) + { + // parse the texture block + ParseLV3MapBlock(mat.sTexSpecular); + continue; + } + // opacity map + if (TokenMatch(filePtr,"MAP_OPACITY",11)) + { + // parse the texture block + ParseLV3MapBlock(mat.sTexOpacity); + continue; + } + // emissive map + if (TokenMatch(filePtr,"MAP_SELFILLUM",13)) + { + // parse the texture block + ParseLV3MapBlock(mat.sTexEmissive); + continue; + } + // bump map + if (TokenMatch(filePtr,"MAP_BUMP",8)) + { + // parse the texture block + ParseLV3MapBlock(mat.sTexBump); + } + // specular/shininess map + if (TokenMatch(filePtr,"MAP_SHINESTRENGTH",17)) + { + // parse the texture block + ParseLV3MapBlock(mat.sTexShininess); + continue; + } + // number of submaterials + if (TokenMatch(filePtr,"NUMSUBMTLS",10)) + { + ParseLV4MeshLong(iNumSubMaterials); + + // allocate enough storage + mat.avSubMaterials.resize(iNumSubMaterials, Material("INVALID SUBMATERIAL")); + } + // submaterial chunks + if (TokenMatch(filePtr,"SUBMATERIAL",11)) + { + + unsigned int iIndex = 0; + ParseLV4MeshLong(iIndex); + + if (iIndex >= iNumSubMaterials) + { + LogWarning("Out of range: submaterial index is too large"); + iIndex = iNumSubMaterials-1; + } + + // get a reference to the material + Material& sMat = mat.avSubMaterials[iIndex]; + + // parse the material block + ParseLV2MaterialBlock(sMat); + continue; + } + } + AI_ASE_HANDLE_SECTION("2","*MATERIAL"); + } +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MapBlock(Texture& map) +{ + AI_ASE_PARSER_INIT(); + + // *********************************************************** + // *BITMAP should not be there if *MAP_CLASS is not BITMAP, + // but we need to expect that case ... if the path is + // empty the texture won't be used later. + // *********************************************************** + bool parsePath = true; + std::string temp; + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + // type of map + if (TokenMatch(filePtr,"MAP_CLASS" ,9)) + { + temp.clear(); + if(!ParseString(temp,"*MAP_CLASS")) + SkipToNextToken(); + if (temp != "Bitmap" && temp != "Normal Bump") + { + ASSIMP_LOG_WARN_F("ASE: Skipping unknown map type: ", temp); + parsePath = false; + } + continue; + } + // path to the texture + if (parsePath && TokenMatch(filePtr,"BITMAP" ,6)) + { + if(!ParseString(map.mMapName,"*BITMAP")) + SkipToNextToken(); + + if (map.mMapName == "None") + { + // Files with 'None' as map name are produced by + // an Maja to ASE exporter which name I forgot .. + ASSIMP_LOG_WARN("ASE: Skipping invalid map entry"); + map.mMapName = ""; + } + + continue; + } + // offset on the u axis + if (TokenMatch(filePtr,"UVW_U_OFFSET" ,12)) + { + ParseLV4MeshFloat(map.mOffsetU); + continue; + } + // offset on the v axis + if (TokenMatch(filePtr,"UVW_V_OFFSET" ,12)) + { + ParseLV4MeshFloat(map.mOffsetV); + continue; + } + // tiling on the u axis + if (TokenMatch(filePtr,"UVW_U_TILING" ,12)) + { + ParseLV4MeshFloat(map.mScaleU); + continue; + } + // tiling on the v axis + if (TokenMatch(filePtr,"UVW_V_TILING" ,12)) + { + ParseLV4MeshFloat(map.mScaleV); + continue; + } + // rotation around the z-axis + if (TokenMatch(filePtr,"UVW_ANGLE" ,9)) + { + ParseLV4MeshFloat(map.mRotation); + continue; + } + // map blending factor + if (TokenMatch(filePtr,"MAP_AMOUNT" ,10)) + { + ParseLV4MeshFloat(map.mTextureBlend); + continue; + } + } + AI_ASE_HANDLE_SECTION("3","*MAP_XXXXXX"); + } + return; +} + +// ------------------------------------------------------------------------------------------------ +bool Parser::ParseString(std::string& out,const char* szName) +{ + char szBuffer[1024]; + if (!SkipSpaces(&filePtr)) + { + + ai_snprintf(szBuffer, 1024, "Unable to parse %s block: Unexpected EOL",szName); + LogWarning(szBuffer); + return false; + } + // there must be '"' + if ('\"' != *filePtr) + { + + ai_snprintf(szBuffer, 1024, "Unable to parse %s block: Strings are expected " + "to be enclosed in double quotation marks",szName); + LogWarning(szBuffer); + return false; + } + ++filePtr; + const char* sz = filePtr; + while (true) + { + if ('\"' == *sz)break; + else if ('\0' == *sz) + { + ai_snprintf(szBuffer, 1024, "Unable to parse %s block: Strings are expected to " + "be enclosed in double quotation marks but EOF was reached before " + "a closing quotation mark was encountered",szName); + LogWarning(szBuffer); + return false; + } + sz++; + } + out = std::string(filePtr,(uintptr_t)sz-(uintptr_t)filePtr); + filePtr = sz+1; + return true; +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV1ObjectBlock(ASE::BaseNode& node) +{ + AI_ASE_PARSER_INIT(); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + // first process common tokens such as node name and transform + // name of the mesh/node + if (TokenMatch(filePtr,"NODE_NAME" ,9)) + { + if(!ParseString(node.mName,"*NODE_NAME")) + SkipToNextToken(); + continue; + } + // name of the parent of the node + if (TokenMatch(filePtr,"NODE_PARENT" ,11) ) + { + if(!ParseString(node.mParent,"*NODE_PARENT")) + SkipToNextToken(); + continue; + } + // transformation matrix of the node + if (TokenMatch(filePtr,"NODE_TM" ,7)) + { + ParseLV2NodeTransformBlock(node); + continue; + } + // animation data of the node + if (TokenMatch(filePtr,"TM_ANIMATION" ,12)) + { + ParseLV2AnimationBlock(node); + continue; + } + + if (node.mType == BaseNode::Light) + { + // light settings + if (TokenMatch(filePtr,"LIGHT_SETTINGS" ,14)) + { + ParseLV2LightSettingsBlock((ASE::Light&)node); + continue; + } + // type of the light source + if (TokenMatch(filePtr,"LIGHT_TYPE" ,10)) + { + if (!ASSIMP_strincmp("omni",filePtr,4)) + { + ((ASE::Light&)node).mLightType = ASE::Light::OMNI; + } + else if (!ASSIMP_strincmp("target",filePtr,6)) + { + ((ASE::Light&)node).mLightType = ASE::Light::TARGET; + } + else if (!ASSIMP_strincmp("free",filePtr,4)) + { + ((ASE::Light&)node).mLightType = ASE::Light::FREE; + } + else if (!ASSIMP_strincmp("directional",filePtr,11)) + { + ((ASE::Light&)node).mLightType = ASE::Light::DIRECTIONAL; + } + else + { + LogWarning("Unknown kind of light source"); + } + continue; + } + } + else if (node.mType == BaseNode::Camera) + { + // Camera settings + if (TokenMatch(filePtr,"CAMERA_SETTINGS" ,15)) + { + ParseLV2CameraSettingsBlock((ASE::Camera&)node); + continue; + } + else if (TokenMatch(filePtr,"CAMERA_TYPE" ,11)) + { + if (!ASSIMP_strincmp("target",filePtr,6)) + { + ((ASE::Camera&)node).mCameraType = ASE::Camera::TARGET; + } + else if (!ASSIMP_strincmp("free",filePtr,4)) + { + ((ASE::Camera&)node).mCameraType = ASE::Camera::FREE; + } + else + { + LogWarning("Unknown kind of camera"); + } + continue; + } + } + else if (node.mType == BaseNode::Mesh) + { + // mesh data + // FIX: Older files use MESH_SOFTSKIN + if (TokenMatch(filePtr,"MESH" ,4) || + TokenMatch(filePtr,"MESH_SOFTSKIN",13)) + { + ParseLV2MeshBlock((ASE::Mesh&)node); + continue; + } + // mesh material index + if (TokenMatch(filePtr,"MATERIAL_REF" ,12)) + { + ParseLV4MeshLong(((ASE::Mesh&)node).iMaterialIndex); + continue; + } + } + } + AI_ASE_HANDLE_TOP_LEVEL_SECTION(); + } + return; +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV2CameraSettingsBlock(ASE::Camera& camera) +{ + AI_ASE_PARSER_INIT(); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + if (TokenMatch(filePtr,"CAMERA_NEAR" ,11)) + { + ParseLV4MeshFloat(camera.mNear); + continue; + } + if (TokenMatch(filePtr,"CAMERA_FAR" ,10)) + { + ParseLV4MeshFloat(camera.mFar); + continue; + } + if (TokenMatch(filePtr,"CAMERA_FOV" ,10)) + { + ParseLV4MeshFloat(camera.mFOV); + continue; + } + } + AI_ASE_HANDLE_SECTION("2","CAMERA_SETTINGS"); + } + return; +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV2LightSettingsBlock(ASE::Light& light) +{ + AI_ASE_PARSER_INIT(); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + if (TokenMatch(filePtr,"LIGHT_COLOR" ,11)) + { + ParseLV4MeshFloatTriple(&light.mColor.r); + continue; + } + if (TokenMatch(filePtr,"LIGHT_INTENS" ,12)) + { + ParseLV4MeshFloat(light.mIntensity); + continue; + } + if (TokenMatch(filePtr,"LIGHT_HOTSPOT" ,13)) + { + ParseLV4MeshFloat(light.mAngle); + continue; + } + if (TokenMatch(filePtr,"LIGHT_FALLOFF" ,13)) + { + ParseLV4MeshFloat(light.mFalloff); + continue; + } + } + AI_ASE_HANDLE_SECTION("2","LIGHT_SETTINGS"); + } + return; +} + +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV2AnimationBlock(ASE::BaseNode& mesh) +{ + AI_ASE_PARSER_INIT(); + + ASE::Animation* anim = &mesh.mAnim; + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + if (TokenMatch(filePtr,"NODE_NAME" ,9)) + { + std::string temp; + if(!ParseString(temp,"*NODE_NAME")) + SkipToNextToken(); + + // If the name of the node contains .target it + // represents an animated camera or spot light + // target. + if (std::string::npos != temp.find(".Target")) + { + if ((mesh.mType != BaseNode::Camera || ((ASE::Camera&)mesh).mCameraType != ASE::Camera::TARGET) && + ( mesh.mType != BaseNode::Light || ((ASE::Light&)mesh).mLightType != ASE::Light::TARGET)) + { + + ASSIMP_LOG_ERROR("ASE: Found target animation channel " + "but the node is neither a camera nor a spot light"); + anim = NULL; + } + else anim = &mesh.mTargetAnim; + } + continue; + } + + // position keyframes + if (TokenMatch(filePtr,"CONTROL_POS_TRACK" ,17) || + TokenMatch(filePtr,"CONTROL_POS_BEZIER" ,18) || + TokenMatch(filePtr,"CONTROL_POS_TCB" ,15)) + { + if (!anim)SkipSection(); + else ParseLV3PosAnimationBlock(*anim); + continue; + } + // scaling keyframes + if (TokenMatch(filePtr,"CONTROL_SCALE_TRACK" ,19) || + TokenMatch(filePtr,"CONTROL_SCALE_BEZIER" ,20) || + TokenMatch(filePtr,"CONTROL_SCALE_TCB" ,17)) + { + if (!anim || anim == &mesh.mTargetAnim) + { + // Target animation channels may have no rotation channels + ASSIMP_LOG_ERROR("ASE: Ignoring scaling channel in target animation"); + SkipSection(); + } + else ParseLV3ScaleAnimationBlock(*anim); + continue; + } + // rotation keyframes + if (TokenMatch(filePtr,"CONTROL_ROT_TRACK" ,17) || + TokenMatch(filePtr,"CONTROL_ROT_BEZIER" ,18) || + TokenMatch(filePtr,"CONTROL_ROT_TCB" ,15)) + { + if (!anim || anim == &mesh.mTargetAnim) + { + // Target animation channels may have no rotation channels + ASSIMP_LOG_ERROR("ASE: Ignoring rotation channel in target animation"); + SkipSection(); + } + else ParseLV3RotAnimationBlock(*anim); + continue; + } + } + AI_ASE_HANDLE_SECTION("2","TM_ANIMATION"); + } +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3ScaleAnimationBlock(ASE::Animation& anim) +{ + AI_ASE_PARSER_INIT(); + unsigned int iIndex; + + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + bool b = false; + + // For the moment we're just reading the three floats - + // we ignore the additional information for bezier's and TCBs + + // simple scaling keyframe + if (TokenMatch(filePtr,"CONTROL_SCALE_SAMPLE" ,20)) + { + b = true; + anim.mScalingType = ASE::Animation::TRACK; + } + + // Bezier scaling keyframe + if (TokenMatch(filePtr,"CONTROL_BEZIER_SCALE_KEY" ,24)) + { + b = true; + anim.mScalingType = ASE::Animation::BEZIER; + } + // TCB scaling keyframe + if (TokenMatch(filePtr,"CONTROL_TCB_SCALE_KEY" ,21)) + { + b = true; + anim.mScalingType = ASE::Animation::TCB; + } + if (b) + { + anim.akeyScaling.push_back(aiVectorKey()); + aiVectorKey& key = anim.akeyScaling.back(); + ParseLV4MeshFloatTriple(&key.mValue.x,iIndex); + key.mTime = (double)iIndex; + } + } + AI_ASE_HANDLE_SECTION("3","*CONTROL_POS_TRACK"); + } +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3PosAnimationBlock(ASE::Animation& anim) +{ + AI_ASE_PARSER_INIT(); + unsigned int iIndex; + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + bool b = false; + + // For the moment we're just reading the three floats - + // we ignore the additional information for bezier's and TCBs + + // simple scaling keyframe + if (TokenMatch(filePtr,"CONTROL_POS_SAMPLE" ,18)) + { + b = true; + anim.mPositionType = ASE::Animation::TRACK; + } + + // Bezier scaling keyframe + if (TokenMatch(filePtr,"CONTROL_BEZIER_POS_KEY" ,22)) + { + b = true; + anim.mPositionType = ASE::Animation::BEZIER; + } + // TCB scaling keyframe + if (TokenMatch(filePtr,"CONTROL_TCB_POS_KEY" ,19)) + { + b = true; + anim.mPositionType = ASE::Animation::TCB; + } + if (b) + { + anim.akeyPositions.push_back(aiVectorKey()); + aiVectorKey& key = anim.akeyPositions.back(); + ParseLV4MeshFloatTriple(&key.mValue.x,iIndex); + key.mTime = (double)iIndex; + } + } + AI_ASE_HANDLE_SECTION("3","*CONTROL_POS_TRACK"); + } +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3RotAnimationBlock(ASE::Animation& anim) +{ + AI_ASE_PARSER_INIT(); + unsigned int iIndex; + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + bool b = false; + + // For the moment we're just reading the floats - + // we ignore the additional information for bezier's and TCBs + + // simple scaling keyframe + if (TokenMatch(filePtr,"CONTROL_ROT_SAMPLE" ,18)) + { + b = true; + anim.mRotationType = ASE::Animation::TRACK; + } + + // Bezier scaling keyframe + if (TokenMatch(filePtr,"CONTROL_BEZIER_ROT_KEY" ,22)) + { + b = true; + anim.mRotationType = ASE::Animation::BEZIER; + } + // TCB scaling keyframe + if (TokenMatch(filePtr,"CONTROL_TCB_ROT_KEY" ,19)) + { + b = true; + anim.mRotationType = ASE::Animation::TCB; + } + if (b) + { + anim.akeyRotations.push_back(aiQuatKey()); + aiQuatKey& key = anim.akeyRotations.back(); + aiVector3D v;ai_real f; + ParseLV4MeshFloatTriple(&v.x,iIndex); + ParseLV4MeshFloat(f); + key.mTime = (double)iIndex; + key.mValue = aiQuaternion(v,f); + } + } + AI_ASE_HANDLE_SECTION("3","*CONTROL_ROT_TRACK"); + } +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV2NodeTransformBlock(ASE::BaseNode& mesh) +{ + AI_ASE_PARSER_INIT(); + int mode = 0; + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + // name of the node + if (TokenMatch(filePtr,"NODE_NAME" ,9)) + { + std::string temp; + if(!ParseString(temp,"*NODE_NAME")) + SkipToNextToken(); + + std::string::size_type s; + if (temp == mesh.mName) + { + mode = 1; + } + else if (std::string::npos != (s = temp.find(".Target")) && + mesh.mName == temp.substr(0,s)) + { + // This should be either a target light or a target camera + if ( (mesh.mType == BaseNode::Light && ((ASE::Light&)mesh) .mLightType == ASE::Light::TARGET) || + (mesh.mType == BaseNode::Camera && ((ASE::Camera&)mesh).mCameraType == ASE::Camera::TARGET)) + { + mode = 2; + } + else { + ASSIMP_LOG_ERROR("ASE: Ignoring target transform, " + "this is no spot light or target camera"); + } + } + else + { + ASSIMP_LOG_ERROR("ASE: Unknown node transformation: " + temp); + // mode = 0 + } + continue; + } + if (mode) + { + // fourth row of the transformation matrix - and also the + // only information here that is interesting for targets + if (TokenMatch(filePtr,"TM_ROW3" ,7)) + { + ParseLV4MeshFloatTriple((mode == 1 ? mesh.mTransform[3] : &mesh.mTargetPosition.x)); + continue; + } + if (mode == 1) + { + // first row of the transformation matrix + if (TokenMatch(filePtr,"TM_ROW0" ,7)) + { + ParseLV4MeshFloatTriple(mesh.mTransform[0]); + continue; + } + // second row of the transformation matrix + if (TokenMatch(filePtr,"TM_ROW1" ,7)) + { + ParseLV4MeshFloatTriple(mesh.mTransform[1]); + continue; + } + // third row of the transformation matrix + if (TokenMatch(filePtr,"TM_ROW2" ,7)) + { + ParseLV4MeshFloatTriple(mesh.mTransform[2]); + continue; + } + // inherited position axes + if (TokenMatch(filePtr,"INHERIT_POS" ,11)) + { + unsigned int aiVal[3]; + ParseLV4MeshLongTriple(aiVal); + + for (unsigned int i = 0; i < 3;++i) + mesh.inherit.abInheritPosition[i] = aiVal[i] != 0; + continue; + } + // inherited rotation axes + if (TokenMatch(filePtr,"INHERIT_ROT" ,11)) + { + unsigned int aiVal[3]; + ParseLV4MeshLongTriple(aiVal); + + for (unsigned int i = 0; i < 3;++i) + mesh.inherit.abInheritRotation[i] = aiVal[i] != 0; + continue; + } + // inherited scaling axes + if (TokenMatch(filePtr,"INHERIT_SCL" ,11)) + { + unsigned int aiVal[3]; + ParseLV4MeshLongTriple(aiVal); + + for (unsigned int i = 0; i < 3;++i) + mesh.inherit.abInheritScaling[i] = aiVal[i] != 0; + continue; + } + } + } + } + AI_ASE_HANDLE_SECTION("2","*NODE_TM"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV2MeshBlock(ASE::Mesh& mesh) +{ + AI_ASE_PARSER_INIT(); + + unsigned int iNumVertices = 0; + unsigned int iNumFaces = 0; + unsigned int iNumTVertices = 0; + unsigned int iNumTFaces = 0; + unsigned int iNumCVertices = 0; + unsigned int iNumCFaces = 0; + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + // Number of vertices in the mesh + if (TokenMatch(filePtr,"MESH_NUMVERTEX" ,14)) + { + ParseLV4MeshLong(iNumVertices); + continue; + } + // Number of texture coordinates in the mesh + if (TokenMatch(filePtr,"MESH_NUMTVERTEX" ,15)) + { + ParseLV4MeshLong(iNumTVertices); + continue; + } + // Number of vertex colors in the mesh + if (TokenMatch(filePtr,"MESH_NUMCVERTEX" ,15)) + { + ParseLV4MeshLong(iNumCVertices); + continue; + } + // Number of regular faces in the mesh + if (TokenMatch(filePtr,"MESH_NUMFACES" ,13)) + { + ParseLV4MeshLong(iNumFaces); + continue; + } + // Number of UVWed faces in the mesh + if (TokenMatch(filePtr,"MESH_NUMTVFACES" ,15)) + { + ParseLV4MeshLong(iNumTFaces); + continue; + } + // Number of colored faces in the mesh + if (TokenMatch(filePtr,"MESH_NUMCVFACES" ,15)) + { + ParseLV4MeshLong(iNumCFaces); + continue; + } + // mesh vertex list block + if (TokenMatch(filePtr,"MESH_VERTEX_LIST" ,16)) + { + ParseLV3MeshVertexListBlock(iNumVertices,mesh); + continue; + } + // mesh face list block + if (TokenMatch(filePtr,"MESH_FACE_LIST" ,14)) + { + ParseLV3MeshFaceListBlock(iNumFaces,mesh); + continue; + } + // mesh texture vertex list block + if (TokenMatch(filePtr,"MESH_TVERTLIST" ,14)) + { + ParseLV3MeshTListBlock(iNumTVertices,mesh); + continue; + } + // mesh texture face block + if (TokenMatch(filePtr,"MESH_TFACELIST" ,14)) + { + ParseLV3MeshTFaceListBlock(iNumTFaces,mesh); + continue; + } + // mesh color vertex list block + if (TokenMatch(filePtr,"MESH_CVERTLIST" ,14)) + { + ParseLV3MeshCListBlock(iNumCVertices,mesh); + continue; + } + // mesh color face block + if (TokenMatch(filePtr,"MESH_CFACELIST" ,14)) + { + ParseLV3MeshCFaceListBlock(iNumCFaces,mesh); + continue; + } + // mesh normals + if (TokenMatch(filePtr,"MESH_NORMALS" ,12)) + { + ParseLV3MeshNormalListBlock(mesh); + continue; + } + // another mesh UV channel ... + if (TokenMatch(filePtr,"MESH_MAPPINGCHANNEL" ,19)) { + unsigned int iIndex( 0 ); + ParseLV4MeshLong(iIndex); + if ( 0 == iIndex ) { + LogWarning( "Mapping channel has an invalid index. Skipping UV channel" ); + // skip it ... + SkipSection(); + } else { + if ( iIndex < 2 ) { + LogWarning( "Mapping channel has an invalid index. Skipping UV channel" ); + // skip it ... + SkipSection(); + } + if ( iIndex > AI_MAX_NUMBER_OF_TEXTURECOORDS ) { + LogWarning( "Too many UV channels specified. Skipping channel .." ); + // skip it ... + SkipSection(); + } else { + // parse the mapping channel + ParseLV3MappingChannel( iIndex - 1, mesh ); + } + continue; + } + } + // mesh animation keyframe. Not supported + if (TokenMatch(filePtr,"MESH_ANIMATION" ,14)) + { + + LogWarning("Found *MESH_ANIMATION element in ASE/ASK file. " + "Keyframe animation is not supported by Assimp, this element " + "will be ignored"); + //SkipSection(); + continue; + } + if (TokenMatch(filePtr,"MESH_WEIGHTS" ,12)) + { + ParseLV3MeshWeightsBlock(mesh);continue; + } + } + AI_ASE_HANDLE_SECTION("2","*MESH"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshWeightsBlock(ASE::Mesh& mesh) +{ + AI_ASE_PARSER_INIT(); + + unsigned int iNumVertices = 0, iNumBones = 0; + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + // Number of bone vertices ... + if (TokenMatch(filePtr,"MESH_NUMVERTEX" ,14)) + { + ParseLV4MeshLong(iNumVertices); + continue; + } + // Number of bones + if (TokenMatch(filePtr,"MESH_NUMBONE" ,12)) + { + ParseLV4MeshLong(iNumBones); + continue; + } + // parse the list of bones + if (TokenMatch(filePtr,"MESH_BONE_LIST" ,14)) + { + ParseLV4MeshBones(iNumBones,mesh); + continue; + } + // parse the list of bones vertices + if (TokenMatch(filePtr,"MESH_BONE_VERTEX_LIST" ,21) ) + { + ParseLV4MeshBonesVertices(iNumVertices,mesh); + continue; + } + } + AI_ASE_HANDLE_SECTION("3","*MESH_WEIGHTS"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshBones(unsigned int iNumBones,ASE::Mesh& mesh) +{ + AI_ASE_PARSER_INIT(); + mesh.mBones.resize(iNumBones, Bone("UNNAMED")); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + // Mesh bone with name ... + if (TokenMatch(filePtr,"MESH_BONE_NAME" ,14)) + { + // parse an index ... + if(SkipSpaces(&filePtr)) + { + unsigned int iIndex = strtoul10(filePtr,&filePtr); + if (iIndex >= iNumBones) + { + LogWarning("Bone index is out of bounds"); + continue; + } + if (!ParseString(mesh.mBones[iIndex].mName,"*MESH_BONE_NAME")) + SkipToNextToken(); + continue; + } + } + } + AI_ASE_HANDLE_SECTION("3","*MESH_BONE_LIST"); + } +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshBonesVertices(unsigned int iNumVertices,ASE::Mesh& mesh) +{ + AI_ASE_PARSER_INIT(); + mesh.mBoneVertices.resize(iNumVertices); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + // Mesh bone vertex + if (TokenMatch(filePtr,"MESH_BONE_VERTEX" ,16)) + { + // read the vertex index + unsigned int iIndex = strtoul10(filePtr,&filePtr); + if (iIndex >= mesh.mPositions.size()) + { + iIndex = (unsigned int)mesh.mPositions.size()-1; + LogWarning("Bone vertex index is out of bounds. Using the largest valid " + "bone vertex index instead"); + } + + // --- ignored + ai_real afVert[3]; + ParseLV4MeshFloatTriple(afVert); + + std::pair pairOut; + while (true) + { + // first parse the bone index ... + if (!SkipSpaces(&filePtr))break; + pairOut.first = strtoul10(filePtr,&filePtr); + + // then parse the vertex weight + if (!SkipSpaces(&filePtr))break; + filePtr = fast_atoreal_move(filePtr,pairOut.second); + + // -1 marks unused entries + if (-1 != pairOut.first) + { + mesh.mBoneVertices[iIndex].mBoneWeights.push_back(pairOut); + } + } + continue; + } + } + AI_ASE_HANDLE_SECTION("4","*MESH_BONE_VERTEX"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshVertexListBlock( + unsigned int iNumVertices, ASE::Mesh& mesh) +{ + AI_ASE_PARSER_INIT(); + + // allocate enough storage in the array + mesh.mPositions.resize(iNumVertices); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + // Vertex entry + if (TokenMatch(filePtr,"MESH_VERTEX" ,11)) + { + + aiVector3D vTemp; + unsigned int iIndex; + ParseLV4MeshFloatTriple(&vTemp.x,iIndex); + + if (iIndex >= iNumVertices) + { + LogWarning("Invalid vertex index. It will be ignored"); + } + else mesh.mPositions[iIndex] = vTemp; + continue; + } + } + AI_ASE_HANDLE_SECTION("3","*MESH_VERTEX_LIST"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshFaceListBlock(unsigned int iNumFaces, ASE::Mesh& mesh) +{ + AI_ASE_PARSER_INIT(); + + // allocate enough storage in the face array + mesh.mFaces.resize(iNumFaces); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + // Face entry + if (TokenMatch(filePtr,"MESH_FACE" ,9)) + { + + ASE::Face mFace; + ParseLV4MeshFace(mFace); + + if (mFace.iFace >= iNumFaces) + { + LogWarning("Face has an invalid index. It will be ignored"); + } + else mesh.mFaces[mFace.iFace] = mFace; + continue; + } + } + AI_ASE_HANDLE_SECTION("3","*MESH_FACE_LIST"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshTListBlock(unsigned int iNumVertices, + ASE::Mesh& mesh, unsigned int iChannel) +{ + AI_ASE_PARSER_INIT(); + + // allocate enough storage in the array + mesh.amTexCoords[iChannel].resize(iNumVertices); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + // Vertex entry + if (TokenMatch(filePtr,"MESH_TVERT" ,10)) + { + aiVector3D vTemp; + unsigned int iIndex; + ParseLV4MeshFloatTriple(&vTemp.x,iIndex); + + if (iIndex >= iNumVertices) + { + LogWarning("Tvertex has an invalid index. It will be ignored"); + } + else mesh.amTexCoords[iChannel][iIndex] = vTemp; + + if (0.0f != vTemp.z) + { + // we need 3 coordinate channels + mesh.mNumUVComponents[iChannel] = 3; + } + continue; + } + } + AI_ASE_HANDLE_SECTION("3","*MESH_TVERT_LIST"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshTFaceListBlock(unsigned int iNumFaces, + ASE::Mesh& mesh, unsigned int iChannel) +{ + AI_ASE_PARSER_INIT(); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + // Face entry + if (TokenMatch(filePtr,"MESH_TFACE" ,10)) + { + unsigned int aiValues[3]; + unsigned int iIndex = 0; + + ParseLV4MeshLongTriple(aiValues,iIndex); + if (iIndex >= iNumFaces || iIndex >= mesh.mFaces.size()) + { + LogWarning("UV-Face has an invalid index. It will be ignored"); + } + else + { + // copy UV indices + mesh.mFaces[iIndex].amUVIndices[iChannel][0] = aiValues[0]; + mesh.mFaces[iIndex].amUVIndices[iChannel][1] = aiValues[1]; + mesh.mFaces[iIndex].amUVIndices[iChannel][2] = aiValues[2]; + } + continue; + } + } + AI_ASE_HANDLE_SECTION("3","*MESH_TFACE_LIST"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MappingChannel(unsigned int iChannel, ASE::Mesh& mesh) +{ + AI_ASE_PARSER_INIT(); + + unsigned int iNumTVertices = 0; + unsigned int iNumTFaces = 0; + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + // Number of texture coordinates in the mesh + if (TokenMatch(filePtr,"MESH_NUMTVERTEX" ,15)) + { + ParseLV4MeshLong(iNumTVertices); + continue; + } + // Number of UVWed faces in the mesh + if (TokenMatch(filePtr,"MESH_NUMTVFACES" ,15)) + { + ParseLV4MeshLong(iNumTFaces); + continue; + } + // mesh texture vertex list block + if (TokenMatch(filePtr,"MESH_TVERTLIST" ,14)) + { + ParseLV3MeshTListBlock(iNumTVertices,mesh,iChannel); + continue; + } + // mesh texture face block + if (TokenMatch(filePtr,"MESH_TFACELIST" ,14)) + { + ParseLV3MeshTFaceListBlock(iNumTFaces,mesh, iChannel); + continue; + } + } + AI_ASE_HANDLE_SECTION("3","*MESH_MAPPING_CHANNEL"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshCListBlock(unsigned int iNumVertices, ASE::Mesh& mesh) +{ + AI_ASE_PARSER_INIT(); + + // allocate enough storage in the array + mesh.mVertexColors.resize(iNumVertices); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + // Vertex entry + if (TokenMatch(filePtr,"MESH_VERTCOL" ,12)) + { + aiColor4D vTemp; + vTemp.a = 1.0f; + unsigned int iIndex; + ParseLV4MeshFloatTriple(&vTemp.r,iIndex); + + if (iIndex >= iNumVertices) + { + LogWarning("Vertex color has an invalid index. It will be ignored"); + } + else mesh.mVertexColors[iIndex] = vTemp; + continue; + } + } + AI_ASE_HANDLE_SECTION("3","*MESH_CVERTEX_LIST"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshCFaceListBlock(unsigned int iNumFaces, ASE::Mesh& mesh) +{ + AI_ASE_PARSER_INIT(); + while (true) + { + if ('*' == *filePtr) + { + ++filePtr; + + // Face entry + if (TokenMatch(filePtr,"MESH_CFACE" ,10)) + { + unsigned int aiValues[3]; + unsigned int iIndex = 0; + + ParseLV4MeshLongTriple(aiValues,iIndex); + if (iIndex >= iNumFaces || iIndex >= mesh.mFaces.size()) + { + LogWarning("UV-Face has an invalid index. It will be ignored"); + } + else + { + // copy color indices + mesh.mFaces[iIndex].mColorIndices[0] = aiValues[0]; + mesh.mFaces[iIndex].mColorIndices[1] = aiValues[1]; + mesh.mFaces[iIndex].mColorIndices[2] = aiValues[2]; + } + continue; + } + } + AI_ASE_HANDLE_SECTION("3","*MESH_CFACE_LIST"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV3MeshNormalListBlock(ASE::Mesh& sMesh) +{ + AI_ASE_PARSER_INIT(); + + // Allocate enough storage for the normals + sMesh.mNormals.resize(sMesh.mFaces.size()*3,aiVector3D( 0.f, 0.f, 0.f )); + unsigned int index, faceIdx = UINT_MAX; + + // FIXME: rewrite this and find out how to interpret the normals + // correctly. This is crap. + + // Smooth the vertex and face normals together. The result + // will be edgy then, but otherwise everything would be soft ... + while (true) { + if ('*' == *filePtr) { + ++filePtr; + if (faceIdx != UINT_MAX && TokenMatch(filePtr,"MESH_VERTEXNORMAL",17)) { + aiVector3D vNormal; + ParseLV4MeshFloatTriple(&vNormal.x,index); + if (faceIdx >= sMesh.mFaces.size()) + continue; + + // Make sure we assign it to the correct face + const ASE::Face& face = sMesh.mFaces[faceIdx]; + if (index == face.mIndices[0]) + index = 0; + else if (index == face.mIndices[1]) + index = 1; + else if (index == face.mIndices[2]) + index = 2; + else { + ASSIMP_LOG_ERROR("ASE: Invalid vertex index in MESH_VERTEXNORMAL section"); + continue; + } + // We'll renormalize later + sMesh.mNormals[faceIdx*3+index] += vNormal; + continue; + } + if (TokenMatch(filePtr,"MESH_FACENORMAL",15)) { + aiVector3D vNormal; + ParseLV4MeshFloatTriple(&vNormal.x,faceIdx); + + if (faceIdx >= sMesh.mFaces.size()) { + ASSIMP_LOG_ERROR("ASE: Invalid vertex index in MESH_FACENORMAL section"); + continue; + } + + // We'll renormalize later + sMesh.mNormals[faceIdx*3] += vNormal; + sMesh.mNormals[faceIdx*3+1] += vNormal; + sMesh.mNormals[faceIdx*3+2] += vNormal; + continue; + } + } + AI_ASE_HANDLE_SECTION("3","*MESH_NORMALS"); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFace(ASE::Face& out) +{ + // skip spaces and tabs + if(!SkipSpaces(&filePtr)) + { + LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL [#1]"); + SkipToNextToken(); + return; + } + + // parse the face index + out.iFace = strtoul10(filePtr,&filePtr); + + // next character should be ':' + if(!SkipSpaces(&filePtr)) + { + // FIX: there are some ASE files which haven't got : here .... + LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL. \':\' expected [#2]"); + SkipToNextToken(); + return; + } + // FIX: There are some ASE files which haven't got ':' here + if(':' == *filePtr)++filePtr; + + // Parse all mesh indices + for (unsigned int i = 0; i < 3;++i) + { + unsigned int iIndex = 0; + if(!SkipSpaces(&filePtr)) + { + LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL"); + SkipToNextToken(); + return; + } + switch (*filePtr) + { + case 'A': + case 'a': + break; + case 'B': + case 'b': + iIndex = 1; + break; + case 'C': + case 'c': + iIndex = 2; + break; + default: + LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL. " + "A,B or C expected [#3]"); + SkipToNextToken(); + return; + }; + ++filePtr; + + // next character should be ':' + if(!SkipSpaces(&filePtr) || ':' != *filePtr) + { + LogWarning("Unable to parse *MESH_FACE Element: " + "Unexpected EOL. \':\' expected [#2]"); + SkipToNextToken(); + return; + } + + ++filePtr; + if(!SkipSpaces(&filePtr)) + { + LogWarning("Unable to parse *MESH_FACE Element: Unexpected EOL. " + "Vertex index ecpected [#4]"); + SkipToNextToken(); + return; + } + out.mIndices[iIndex] = strtoul10(filePtr,&filePtr); + } + + // now we need to skip the AB, BC, CA blocks. + while (true) + { + if ('*' == *filePtr)break; + if (IsLineEnd(*filePtr)) + { + //iLineNumber++; + return; + } + filePtr++; + } + + // parse the smoothing group of the face + if (TokenMatch(filePtr,"*MESH_SMOOTHING",15)) + { + if(!SkipSpaces(&filePtr)) + { + LogWarning("Unable to parse *MESH_SMOOTHING Element: " + "Unexpected EOL. Smoothing group(s) expected [#5]"); + SkipToNextToken(); + return; + } + + // Parse smoothing groups until we don't anymore see commas + // FIX: There needn't always be a value, sad but true + while (true) + { + if (*filePtr < '9' && *filePtr >= '0') + { + out.iSmoothGroup |= (1 << strtoul10(filePtr,&filePtr)); + } + SkipSpaces(&filePtr); + if (',' != *filePtr) + { + break; + } + ++filePtr; + SkipSpaces(&filePtr); + } + } + + // *MESH_MTLID is optional, too + while (true) + { + if ('*' == *filePtr)break; + if (IsLineEnd(*filePtr)) + { + return; + } + filePtr++; + } + + if (TokenMatch(filePtr,"*MESH_MTLID",11)) + { + if(!SkipSpaces(&filePtr)) + { + LogWarning("Unable to parse *MESH_MTLID Element: Unexpected EOL. " + "Material index expected [#6]"); + SkipToNextToken(); + return; + } + out.iMaterial = strtoul10(filePtr,&filePtr); + } + return; +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshLongTriple(unsigned int* apOut) +{ + ai_assert(NULL != apOut); + + for (unsigned int i = 0; i < 3;++i) + ParseLV4MeshLong(apOut[i]); +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshLongTriple(unsigned int* apOut, unsigned int& rIndexOut) +{ + ai_assert(NULL != apOut); + + // parse the index + ParseLV4MeshLong(rIndexOut); + + // parse the three others + ParseLV4MeshLongTriple(apOut); +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFloatTriple(ai_real* apOut, unsigned int& rIndexOut) +{ + ai_assert(NULL != apOut); + + // parse the index + ParseLV4MeshLong(rIndexOut); + + // parse the three others + ParseLV4MeshFloatTriple(apOut); +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFloatTriple(ai_real* apOut) +{ + ai_assert(NULL != apOut); + + for (unsigned int i = 0; i < 3;++i) + ParseLV4MeshFloat(apOut[i]); +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshFloat(ai_real& fOut) +{ + // skip spaces and tabs + if(!SkipSpaces(&filePtr)) + { + // LOG + LogWarning("Unable to parse float: unexpected EOL [#1]"); + fOut = 0.0; + ++iLineNumber; + return; + } + // parse the first float + filePtr = fast_atoreal_move(filePtr,fOut); +} +// ------------------------------------------------------------------------------------------------ +void Parser::ParseLV4MeshLong(unsigned int& iOut) +{ + // Skip spaces and tabs + if(!SkipSpaces(&filePtr)) + { + // LOG + LogWarning("Unable to parse long: unexpected EOL [#1]"); + iOut = 0; + ++iLineNumber; + return; + } + // parse the value + iOut = strtoul10(filePtr,&filePtr); +} + +#endif // ASSIMP_BUILD_NO_3DS_IMPORTER + +#endif // !! ASSIMP_BUILD_NO_BASE_IMPORTER diff --git a/thirdparty/assimp/code/ASE/ASEParser.h b/thirdparty/assimp/code/ASE/ASEParser.h new file mode 100644 index 0000000..988cbda --- /dev/null +++ b/thirdparty/assimp/code/ASE/ASEParser.h @@ -0,0 +1,693 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + + +/** @file Defines the helper data structures for importing ASE files */ +#ifndef AI_ASEFILEHELPER_H_INC +#define AI_ASEFILEHELPER_H_INC + +// public ASSIMP headers +#include +#include +#include + +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +// for some helper routines like IsSpace() +#include +#include + +// ASE is quite similar to 3ds. We can reuse some structures +#include "3DS/3DSLoader.h" + +namespace Assimp { +namespace ASE { + +using namespace D3DS; + +// --------------------------------------------------------------------------- +/** Helper structure representing an ASE material */ +struct Material : public D3DS::Material +{ + //! Default constructor has been deleted + Material() = delete; + + //! Constructor with explicit name + explicit Material(const std::string &name) + : D3DS::Material(name) + , pcInstance(NULL) + , bNeed (false) { + // empty + } + + Material(const Material &other) = default; + Material &operator=(const Material &other) = default; + + + //! Move constructor. This is explicitly written because MSVC doesn't support defaulting it + Material(Material &&other) AI_NO_EXCEPT + : D3DS::Material(std::move(other)) + , avSubMaterials(std::move(other.avSubMaterials)) + , pcInstance(std::move(other.pcInstance)) + , bNeed(std::move(other.bNeed)) + { + other.pcInstance = nullptr; + } + + + Material &operator=(Material &&other) AI_NO_EXCEPT { + if (this == &other) { + return *this; + } + + D3DS::Material::operator=(std::move(other)); + + avSubMaterials = std::move(other.avSubMaterials); + pcInstance = std::move(other.pcInstance); + bNeed = std::move(other.bNeed); + + other.pcInstance = nullptr; + + return *this; + } + + + ~Material() {} + + + //! Contains all sub materials of this material + std::vector avSubMaterials; + + //! aiMaterial object + aiMaterial* pcInstance; + + //! Can we remove this material? + bool bNeed; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE file face */ +struct Face : public FaceWithSmoothingGroup { + //! Default constructor. Initializes everything with 0 + Face() AI_NO_EXCEPT + : iMaterial(DEFAULT_MATINDEX) + , iFace(0) { + // empty + } + + //! special value to indicate that no material index has + //! been assigned to a face. The default material index + //! will replace this value later. + static const unsigned int DEFAULT_MATINDEX = 0xFFFFFFFF; + + //! Indices into each list of texture coordinates + unsigned int amUVIndices[AI_MAX_NUMBER_OF_TEXTURECOORDS][3]; + + //! Index into the list of vertex colors + unsigned int mColorIndices[3]; + + //! (Sub)Material index to be assigned to this face + unsigned int iMaterial; + + //! Index of the face. It is not specified whether it is + //! a requirement of the file format that all faces are + //! written in sequential order, so we have to expect this case + unsigned int iFace; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE file bone */ +struct Bone { + //! Constructor + Bone() = delete; + + //! Construction from an existing name + explicit Bone( const std::string& name) + : mName(name) { + // empty + } + + //! Name of the bone + std::string mName; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE file bone vertex */ +struct BoneVertex { + //! Bone and corresponding vertex weight. + //! -1 for unrequired bones .... + std::vector > mBoneWeights; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE file animation */ +struct Animation { + enum Type { + TRACK = 0x0, + BEZIER = 0x1, + TCB = 0x2 + } mRotationType, mScalingType, mPositionType; + + Animation() AI_NO_EXCEPT + : mRotationType (TRACK) + , mScalingType (TRACK) + , mPositionType (TRACK) { + // empty + } + + //! List of track rotation keyframes + std::vector< aiQuatKey > akeyRotations; + + //! List of track position keyframes + std::vector< aiVectorKey > akeyPositions; + + //! List of track scaling keyframes + std::vector< aiVectorKey > akeyScaling; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent the inheritance information of an ASE node */ +struct InheritanceInfo { + //! Default constructor + InheritanceInfo() AI_NO_EXCEPT { + for ( size_t i=0; i<3; ++i ) { + abInheritPosition[i] = abInheritRotation[i] = abInheritScaling[i] = true; + } + } + + //! Inherit the parent's position?, axis order is x,y,z + bool abInheritPosition[3]; + + //! Inherit the parent's rotation?, axis order is x,y,z + bool abInheritRotation[3]; + + //! Inherit the parent's scaling?, axis order is x,y,z + bool abInheritScaling[3]; +}; + +// --------------------------------------------------------------------------- +/** Represents an ASE file node. Base class for mesh, light and cameras */ +struct BaseNode { + enum Type { + Light, + Camera, + Mesh, + Dummy + } mType; + + //! Construction from an existing name + BaseNode(Type _mType, const std::string &name) + : mType (_mType) + , mName (name) + , mProcessed (false) { + // Set mTargetPosition to qnan + const ai_real qnan = get_qnan(); + mTargetPosition.x = qnan; + } + + //! Name of the mesh + std::string mName; + + //! Name of the parent of the node + //! "" if there is no parent ... + std::string mParent; + + //! Transformation matrix of the node + aiMatrix4x4 mTransform; + + //! Target position (target lights and cameras) + aiVector3D mTargetPosition; + + //! Specifies which axes transformations a node inherits + //! from its parent ... + InheritanceInfo inherit; + + //! Animation channels for the node + Animation mAnim; + + //! Needed for lights and cameras: target animation channel + //! Should contain position keys only. + Animation mTargetAnim; + + bool mProcessed; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE file mesh */ +struct Mesh : public MeshWithSmoothingGroups, public BaseNode { + //! Default constructor has been deleted + Mesh() = delete; + + //! Construction from an existing name + explicit Mesh(const std::string &name) + : BaseNode( BaseNode::Mesh, name ) + , mVertexColors() + , mBoneVertices() + , mBones() + , iMaterialIndex(Face::DEFAULT_MATINDEX) + , bSkip (false) { + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) { + this->mNumUVComponents[c] = 2; + } + } + + //! List of all texture coordinate sets + std::vector amTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + + //! List of all vertex color sets. + std::vector mVertexColors; + + //! List of all bone vertices + std::vector mBoneVertices; + + //! List of all bones + std::vector mBones; + + //! Material index of the mesh + unsigned int iMaterialIndex; + + //! Number of vertex components for each UVW set + unsigned int mNumUVComponents[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + + //! used internally + bool bSkip; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE light source */ +struct Light : public BaseNode +{ + enum LightType + { + OMNI, + TARGET, + FREE, + DIRECTIONAL + }; + + //! Default constructor has been deleted + Light() = delete; + + //! Construction from an existing name + explicit Light(const std::string &name) + : BaseNode (BaseNode::Light, name) + , mLightType (OMNI) + , mColor (1.f,1.f,1.f) + , mIntensity (1.f) // light is white by default + , mAngle (45.f) + , mFalloff (0.f) + { + } + + + LightType mLightType; + aiColor3D mColor; + ai_real mIntensity; + ai_real mAngle; // in degrees + ai_real mFalloff; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE camera */ +struct Camera : public BaseNode +{ + enum CameraType + { + FREE, + TARGET + }; + + //! Default constructor has been deleted + Camera() = delete; + + + //! Construction from an existing name + explicit Camera(const std::string &name) + : BaseNode (BaseNode::Camera, name) + , mFOV (0.75f) // in radians + , mNear (0.1f) + , mFar (1000.f) // could be zero + , mCameraType (FREE) + { + } + + + ai_real mFOV, mNear, mFar; + CameraType mCameraType; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent an ASE helper object (dummy) */ +struct Dummy : public BaseNode { + //! Constructor + Dummy() AI_NO_EXCEPT + : BaseNode (BaseNode::Dummy, "DUMMY") { + // empty + } +}; + +// Parameters to Parser::Parse() +#define AI_ASE_NEW_FILE_FORMAT 200 +#define AI_ASE_OLD_FILE_FORMAT 110 + +// Internally we're a little bit more tolerant +#define AI_ASE_IS_NEW_FILE_FORMAT() (iFileFormat >= 200) +#define AI_ASE_IS_OLD_FILE_FORMAT() (iFileFormat < 200) + +// ------------------------------------------------------------------------------- +/** \brief Class to parse ASE files + */ +class Parser { +private: + Parser() AI_NO_EXCEPT { + // empty + } + +public: + + // ------------------------------------------------------------------- + //! Construct a parser from a given input file which is + //! guaranteed to be terminated with zero. + //! @param szFile Input file + //! @param fileFormatDefault Assumed file format version. If the + //! file format is specified in the file the new value replaces + //! the default value. + Parser (const char* szFile, unsigned int fileFormatDefault); + + // ------------------------------------------------------------------- + //! Parses the file into the parsers internal representation + void Parse(); + + +private: + + // ------------------------------------------------------------------- + //! Parse the *SCENE block in a file + void ParseLV1SceneBlock(); + + // ------------------------------------------------------------------- + //! Parse the *MESH_SOFTSKINVERTS block in a file + void ParseLV1SoftSkinBlock(); + + // ------------------------------------------------------------------- + //! Parse the *MATERIAL_LIST block in a file + void ParseLV1MaterialListBlock(); + + // ------------------------------------------------------------------- + //! Parse a *OBJECT block in a file + //! \param mesh Node to be filled + void ParseLV1ObjectBlock(BaseNode& mesh); + + // ------------------------------------------------------------------- + //! Parse a *MATERIAL blocks in a material list + //! \param mat Material structure to be filled + void ParseLV2MaterialBlock(Material& mat); + + // ------------------------------------------------------------------- + //! Parse a *NODE_TM block in a file + //! \param mesh Node (!) object to be filled + void ParseLV2NodeTransformBlock(BaseNode& mesh); + + // ------------------------------------------------------------------- + //! Parse a *TM_ANIMATION block in a file + //! \param mesh Mesh object to be filled + void ParseLV2AnimationBlock(BaseNode& mesh); + void ParseLV3PosAnimationBlock(ASE::Animation& anim); + void ParseLV3ScaleAnimationBlock(ASE::Animation& anim); + void ParseLV3RotAnimationBlock(ASE::Animation& anim); + + // ------------------------------------------------------------------- + //! Parse a *MESH block in a file + //! \param mesh Mesh object to be filled + void ParseLV2MeshBlock(Mesh& mesh); + + // ------------------------------------------------------------------- + //! Parse a *LIGHT_SETTINGS block in a file + //! \param light Light object to be filled + void ParseLV2LightSettingsBlock(Light& light); + + // ------------------------------------------------------------------- + //! Parse a *CAMERA_SETTINGS block in a file + //! \param cam Camera object to be filled + void ParseLV2CameraSettingsBlock(Camera& cam); + + // ------------------------------------------------------------------- + //! Parse the *MAP_XXXXXX blocks in a material + //! \param map Texture structure to be filled + void ParseLV3MapBlock(Texture& map); + + // ------------------------------------------------------------------- + //! Parse a *MESH_VERTEX_LIST block in a file + //! \param iNumVertices Value of *MESH_NUMVERTEX, if present. + //! Otherwise zero. This is used to check the consistency of the file. + //! A warning is sent to the logger if the validations fails. + //! \param mesh Mesh object to be filled + void ParseLV3MeshVertexListBlock( + unsigned int iNumVertices,Mesh& mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_FACE_LIST block in a file + //! \param iNumFaces Value of *MESH_NUMFACES, if present. + //! Otherwise zero. This is used to check the consistency of the file. + //! A warning is sent to the logger if the validations fails. + //! \param mesh Mesh object to be filled + void ParseLV3MeshFaceListBlock( + unsigned int iNumFaces,Mesh& mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_TVERT_LIST block in a file + //! \param iNumVertices Value of *MESH_NUMTVERTEX, if present. + //! Otherwise zero. This is used to check the consistency of the file. + //! A warning is sent to the logger if the validations fails. + //! \param mesh Mesh object to be filled + //! \param iChannel Output UVW channel + void ParseLV3MeshTListBlock( + unsigned int iNumVertices,Mesh& mesh, unsigned int iChannel = 0); + + // ------------------------------------------------------------------- + //! Parse a *MESH_TFACELIST block in a file + //! \param iNumFaces Value of *MESH_NUMTVFACES, if present. + //! Otherwise zero. This is used to check the consistency of the file. + //! A warning is sent to the logger if the validations fails. + //! \param mesh Mesh object to be filled + //! \param iChannel Output UVW channel + void ParseLV3MeshTFaceListBlock( + unsigned int iNumFaces,Mesh& mesh, unsigned int iChannel = 0); + + // ------------------------------------------------------------------- + //! Parse an additional mapping channel + //! (specified via *MESH_MAPPINGCHANNEL) + //! \param iChannel Channel index to be filled + //! \param mesh Mesh object to be filled + void ParseLV3MappingChannel( + unsigned int iChannel, Mesh& mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_CVERTLIST block in a file + //! \param iNumVertices Value of *MESH_NUMCVERTEX, if present. + //! Otherwise zero. This is used to check the consistency of the file. + //! A warning is sent to the logger if the validations fails. + //! \param mesh Mesh object to be filled + void ParseLV3MeshCListBlock( + unsigned int iNumVertices, Mesh& mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_CFACELIST block in a file + //! \param iNumFaces Value of *MESH_NUMCVFACES, if present. + //! Otherwise zero. This is used to check the consistency of the file. + //! A warning is sent to the logger if the validations fails. + //! \param mesh Mesh object to be filled + void ParseLV3MeshCFaceListBlock( + unsigned int iNumFaces, Mesh& mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_NORMALS block in a file + //! \param mesh Mesh object to be filled + void ParseLV3MeshNormalListBlock(Mesh& mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_WEIGHTSblock in a file + //! \param mesh Mesh object to be filled + void ParseLV3MeshWeightsBlock(Mesh& mesh); + + // ------------------------------------------------------------------- + //! Parse the bone list of a file + //! \param mesh Mesh object to be filled + //! \param iNumBones Number of bones in the mesh + void ParseLV4MeshBones(unsigned int iNumBones,Mesh& mesh); + + // ------------------------------------------------------------------- + //! Parse the bone vertices list of a file + //! \param mesh Mesh object to be filled + //! \param iNumVertices Number of vertices to be parsed + void ParseLV4MeshBonesVertices(unsigned int iNumVertices,Mesh& mesh); + + // ------------------------------------------------------------------- + //! Parse a *MESH_FACE block in a file + //! \param out receive the face data + void ParseLV4MeshFace(ASE::Face& out); + + // ------------------------------------------------------------------- + //! Parse a *MESH_VERT block in a file + //! (also works for MESH_TVERT, MESH_CFACE, MESH_VERTCOL ...) + //! \param apOut Output buffer (3 floats) + //! \param rIndexOut Output index + void ParseLV4MeshFloatTriple(ai_real* apOut, unsigned int& rIndexOut); + + // ------------------------------------------------------------------- + //! Parse a *MESH_VERT block in a file + //! (also works for MESH_TVERT, MESH_CFACE, MESH_VERTCOL ...) + //! \param apOut Output buffer (3 floats) + void ParseLV4MeshFloatTriple(ai_real* apOut); + + // ------------------------------------------------------------------- + //! Parse a *MESH_TFACE block in a file + //! (also works for MESH_CFACE) + //! \param apOut Output buffer (3 ints) + //! \param rIndexOut Output index + void ParseLV4MeshLongTriple(unsigned int* apOut, unsigned int& rIndexOut); + + // ------------------------------------------------------------------- + //! Parse a *MESH_TFACE block in a file + //! (also works for MESH_CFACE) + //! \param apOut Output buffer (3 ints) + void ParseLV4MeshLongTriple(unsigned int* apOut); + + // ------------------------------------------------------------------- + //! Parse a single float element + //! \param fOut Output float + void ParseLV4MeshFloat(ai_real& fOut); + + // ------------------------------------------------------------------- + //! Parse a single int element + //! \param iOut Output integer + void ParseLV4MeshLong(unsigned int& iOut); + + // ------------------------------------------------------------------- + //! Skip everything to the next: '*' or '\0' + bool SkipToNextToken(); + + // ------------------------------------------------------------------- + //! Skip the current section until the token after the closing }. + //! This function handles embedded subsections correctly + bool SkipSection(); + + // ------------------------------------------------------------------- + //! Output a warning to the logger + //! \param szWarn Warn message + void LogWarning(const char* szWarn); + + // ------------------------------------------------------------------- + //! Output a message to the logger + //! \param szWarn Message + void LogInfo(const char* szWarn); + + // ------------------------------------------------------------------- + //! Output an error to the logger + //! \param szWarn Error message + AI_WONT_RETURN void LogError(const char* szWarn) AI_WONT_RETURN_SUFFIX; + + // ------------------------------------------------------------------- + //! Parse a string, enclosed in double quotation marks + //! \param out Output string + //! \param szName Name of the enclosing element -> used in error + //! messages. + //! \return false if an error occurred + bool ParseString(std::string& out,const char* szName); + +public: + + //! Pointer to current data + const char* filePtr; + + //! background color to be passed to the viewer + //! QNAN if none was found + aiColor3D m_clrBackground; + + //! Base ambient color to be passed to all materials + //! QNAN if none was found + aiColor3D m_clrAmbient; + + //! List of all materials found in the file + std::vector m_vMaterials; + + //! List of all meshes found in the file + std::vector m_vMeshes; + + //! List of all dummies found in the file + std::vector m_vDummies; + + //! List of all lights found in the file + std::vector m_vLights; + + //! List of all cameras found in the file + std::vector m_vCameras; + + //! Current line in the file + unsigned int iLineNumber; + + //! First frame + unsigned int iFirstFrame; + + //! Last frame + unsigned int iLastFrame; + + //! Frame speed - frames per second + unsigned int iFrameSpeed; + + //! Ticks per frame + unsigned int iTicksPerFrame; + + //! true if the last character read was an end-line character + bool bLastWasEndLine; + + //! File format version + unsigned int iFileFormat; +}; + + +} // Namespace ASE +} // Namespace ASSIMP + +#endif // ASSIMP_BUILD_NO_3DS_IMPORTER + +#endif // !! include guard diff --git a/thirdparty/assimp/code/Assbin/AssbinExporter.cpp b/thirdparty/assimp/code/Assbin/AssbinExporter.cpp new file mode 100644 index 0000000..76c823f --- /dev/null +++ b/thirdparty/assimp/code/Assbin/AssbinExporter.cpp @@ -0,0 +1,846 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +/** @file AssbinExporter.cpp + * ASSBIN exporter main code + */ + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER + +#include "Common/assbin_chunks.h" +#include "PostProcessing/ProcessHelper.h" + +#include +#include +#include +#include +#include + +#ifdef ASSIMP_BUILD_NO_OWN_ZLIB +# include +#else +# include "../contrib/zlib/zlib.h" +#endif + +#include + +namespace Assimp { + +template +size_t Write(IOStream * stream, const T& v) { + return stream->Write( &v, sizeof(T), 1 ); +} + +// ----------------------------------------------------------------------------------- +// Serialize an aiString +template <> +inline +size_t Write(IOStream * stream, const aiString& s) { + const size_t s2 = (uint32_t)s.length; + stream->Write(&s,4,1); + stream->Write(s.data,s2,1); + + return s2+4; +} + +// ----------------------------------------------------------------------------------- +// Serialize an unsigned int as uint32_t +template <> +inline +size_t Write(IOStream * stream, const unsigned int& w) { + const uint32_t t = (uint32_t)w; + if (w > t) { + // this shouldn't happen, integers in Assimp data structures never exceed 2^32 + throw DeadlyExportError("loss of data due to 64 -> 32 bit integer conversion"); + } + + stream->Write(&t,4,1); + + return 4; +} + +// ----------------------------------------------------------------------------------- +// Serialize an unsigned int as uint16_t +template <> +inline +size_t Write(IOStream * stream, const uint16_t& w) { + static_assert(sizeof(uint16_t)==2, "sizeof(uint16_t)==2"); + stream->Write(&w,2,1); + + return 2; +} + +// ----------------------------------------------------------------------------------- +// Serialize a float +template <> +inline +size_t Write(IOStream * stream, const float& f) { + static_assert(sizeof(float)==4, "sizeof(float)==4"); + stream->Write(&f,4,1); + + return 4; +} + +// ----------------------------------------------------------------------------------- +// Serialize a double +template <> +inline +size_t Write(IOStream * stream, const double& f) { + static_assert(sizeof(double)==8, "sizeof(double)==8"); + stream->Write(&f,8,1); + + return 8; +} + +// ----------------------------------------------------------------------------------- +// Serialize a vec3 +template <> +inline +size_t Write(IOStream * stream, const aiVector3D& v) { + size_t t = Write(stream,v.x); + t += Write(stream,v.y); + t += Write(stream,v.z); + + return t; +} + +// ----------------------------------------------------------------------------------- +// Serialize a color value +template <> +inline +size_t Write(IOStream * stream, const aiColor3D& v) { + size_t t = Write(stream,v.r); + t += Write(stream,v.g); + t += Write(stream,v.b); + + return t; +} + +// ----------------------------------------------------------------------------------- +// Serialize a color value +template <> +inline +size_t Write(IOStream * stream, const aiColor4D& v) { + size_t t = Write(stream,v.r); + t += Write(stream,v.g); + t += Write(stream,v.b); + t += Write(stream,v.a); + + return t; +} + +// ----------------------------------------------------------------------------------- +// Serialize a quaternion +template <> +inline +size_t Write(IOStream * stream, const aiQuaternion& v) { + size_t t = Write(stream,v.w); + t += Write(stream,v.x); + t += Write(stream,v.y); + t += Write(stream,v.z); + ai_assert(t == 16); + + return 16; +} + +// ----------------------------------------------------------------------------------- +// Serialize a vertex weight +template <> +inline +size_t Write(IOStream * stream, const aiVertexWeight& v) { + size_t t = Write(stream,v.mVertexId); + + return t+Write(stream,v.mWeight); +} + +// ----------------------------------------------------------------------------------- +// Serialize a mat4x4 +template <> +inline +size_t Write(IOStream * stream, const aiMatrix4x4& m) { + for (unsigned int i = 0; i < 4;++i) { + for (unsigned int i2 = 0; i2 < 4;++i2) { + Write(stream,m[i][i2]); + } + } + + return 64; +} + +// ----------------------------------------------------------------------------------- +// Serialize an aiVectorKey +template <> +inline +size_t Write(IOStream * stream, const aiVectorKey& v) { + const size_t t = Write(stream,v.mTime); + return t + Write(stream,v.mValue); +} + +// ----------------------------------------------------------------------------------- +// Serialize an aiQuatKey +template <> +inline +size_t Write(IOStream * stream, const aiQuatKey& v) { + const size_t t = Write(stream,v.mTime); + return t + Write(stream,v.mValue); +} + +template +inline +size_t WriteBounds(IOStream * stream, const T* in, unsigned int size) { + T minc, maxc; + ArrayBounds(in,size,minc,maxc); + + const size_t t = Write(stream,minc); + return t + Write(stream,maxc); +} + +// We use this to write out non-byte arrays so that we write using the specializations. +// This way we avoid writing out extra bytes that potentially come from struct alignment. +template +inline +size_t WriteArray(IOStream * stream, const T* in, unsigned int size) { + size_t n = 0; + for (unsigned int i=0; i(stream,in[i]); + + return n; +} + +// ---------------------------------------------------------------------------------- +/** @class AssbinChunkWriter + * @brief Chunk writer mechanism for the .assbin file structure + * + * This is a standard in-memory IOStream (most of the code is based on BlobIOStream), + * the difference being that this takes another IOStream as a "container" in the + * constructor, and when it is destroyed, it appends the magic number, the chunk size, + * and the chunk contents to the container stream. This allows relatively easy chunk + * chunk construction, even recursively. + */ +class AssbinChunkWriter : public IOStream +{ +private: + + uint8_t* buffer; + uint32_t magic; + IOStream * container; + size_t cur_size, cursor, initial; + +private: + // ------------------------------------------------------------------- + void Grow(size_t need = 0) + { + size_t new_size = std::max(initial, std::max( need, cur_size+(cur_size>>1) )); + + const uint8_t* const old = buffer; + buffer = new uint8_t[new_size]; + + if (old) { + memcpy(buffer,old,cur_size); + delete[] old; + } + + cur_size = new_size; + } + +public: + + AssbinChunkWriter( IOStream * container, uint32_t magic, size_t initial = 4096) + : buffer(NULL), magic(magic), container(container), cur_size(0), cursor(0), initial(initial) + { + } + + virtual ~AssbinChunkWriter() + { + if (container) { + container->Write( &magic, sizeof(uint32_t), 1 ); + container->Write( &cursor, sizeof(uint32_t), 1 ); + container->Write( buffer, 1, cursor ); + } + if (buffer) delete[] buffer; + } + + void * GetBufferPointer() { return buffer; } + + // ------------------------------------------------------------------- + virtual size_t Read(void* /*pvBuffer*/, size_t /*pSize*/, size_t /*pCount*/) { + return 0; + } + virtual aiReturn Seek(size_t /*pOffset*/, aiOrigin /*pOrigin*/) { + return aiReturn_FAILURE; + } + virtual size_t Tell() const { + return cursor; + } + virtual void Flush() { + // not implemented + } + + virtual size_t FileSize() const { + return cursor; + } + + // ------------------------------------------------------------------- + virtual size_t Write(const void* pvBuffer, size_t pSize, size_t pCount) { + pSize *= pCount; + if (cursor + pSize > cur_size) { + Grow(cursor + pSize); + } + + memcpy(buffer+cursor, pvBuffer, pSize); + cursor += pSize; + + return pCount; + } + +}; + +// ---------------------------------------------------------------------------------- +/** @class AssbinExport + * @brief Assbin exporter class + * + * This class performs the .assbin exporting, and is responsible for the file layout. + */ +class AssbinExport +{ +private: + bool shortened; + bool compressed; + +protected: + // ----------------------------------------------------------------------------------- + void WriteBinaryNode( IOStream * container, const aiNode* node) + { + AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AINODE ); + + unsigned int nb_metadata = (node->mMetaData != NULL ? node->mMetaData->mNumProperties : 0); + + Write(&chunk,node->mName); + Write(&chunk,node->mTransformation); + Write(&chunk,node->mNumChildren); + Write(&chunk,node->mNumMeshes); + Write(&chunk,nb_metadata); + + for (unsigned int i = 0; i < node->mNumMeshes;++i) { + Write(&chunk,node->mMeshes[i]); + } + + for (unsigned int i = 0; i < node->mNumChildren;++i) { + WriteBinaryNode( &chunk, node->mChildren[i] ); + } + + for (unsigned int i = 0; i < nb_metadata; ++i) { + const aiString& key = node->mMetaData->mKeys[i]; + aiMetadataType type = node->mMetaData->mValues[i].mType; + void* value = node->mMetaData->mValues[i].mData; + + Write(&chunk, key); + Write(&chunk, type); + + switch (type) { + case AI_BOOL: + Write(&chunk, *((bool*) value)); + break; + case AI_INT32: + Write(&chunk, *((int32_t*) value)); + break; + case AI_UINT64: + Write(&chunk, *((uint64_t*) value)); + break; + case AI_FLOAT: + Write(&chunk, *((float*) value)); + break; + case AI_DOUBLE: + Write(&chunk, *((double*) value)); + break; + case AI_AISTRING: + Write(&chunk, *((aiString*) value)); + break; + case AI_AIVECTOR3D: + Write(&chunk, *((aiVector3D*) value)); + break; +#ifdef SWIG + case FORCE_32BIT: +#endif // SWIG + default: + break; + } + } + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryTexture(IOStream * container, const aiTexture* tex) + { + AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AITEXTURE ); + + Write(&chunk,tex->mWidth); + Write(&chunk,tex->mHeight); + chunk.Write( tex->achFormatHint, sizeof(char), 4 ); + + if(!shortened) { + if (!tex->mHeight) { + chunk.Write(tex->pcData,1,tex->mWidth); + } + else { + chunk.Write(tex->pcData,1,tex->mWidth*tex->mHeight*4); + } + } + + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryBone(IOStream * container, const aiBone* b) + { + AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AIBONE ); + + Write(&chunk,b->mName); + Write(&chunk,b->mNumWeights); + Write(&chunk,b->mOffsetMatrix); + + // for the moment we write dumb min/max values for the bones, too. + // maybe I'll add a better, hash-like solution later + if (shortened) { + WriteBounds(&chunk,b->mWeights,b->mNumWeights); + } // else write as usual + else WriteArray(&chunk,b->mWeights,b->mNumWeights); + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryMesh(IOStream * container, const aiMesh* mesh) + { + AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AIMESH ); + + Write(&chunk,mesh->mPrimitiveTypes); + Write(&chunk,mesh->mNumVertices); + Write(&chunk,mesh->mNumFaces); + Write(&chunk,mesh->mNumBones); + Write(&chunk,mesh->mMaterialIndex); + + // first of all, write bits for all existent vertex components + unsigned int c = 0; + if (mesh->mVertices) { + c |= ASSBIN_MESH_HAS_POSITIONS; + } + if (mesh->mNormals) { + c |= ASSBIN_MESH_HAS_NORMALS; + } + if (mesh->mTangents && mesh->mBitangents) { + c |= ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS; + } + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n) { + if (!mesh->mTextureCoords[n]) { + break; + } + c |= ASSBIN_MESH_HAS_TEXCOORD(n); + } + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS;++n) { + if (!mesh->mColors[n]) { + break; + } + c |= ASSBIN_MESH_HAS_COLOR(n); + } + Write(&chunk,c); + + aiVector3D minVec, maxVec; + if (mesh->mVertices) { + if (shortened) { + WriteBounds(&chunk,mesh->mVertices,mesh->mNumVertices); + } // else write as usual + else WriteArray(&chunk,mesh->mVertices,mesh->mNumVertices); + } + if (mesh->mNormals) { + if (shortened) { + WriteBounds(&chunk,mesh->mNormals,mesh->mNumVertices); + } // else write as usual + else WriteArray(&chunk,mesh->mNormals,mesh->mNumVertices); + } + if (mesh->mTangents && mesh->mBitangents) { + if (shortened) { + WriteBounds(&chunk,mesh->mTangents,mesh->mNumVertices); + WriteBounds(&chunk,mesh->mBitangents,mesh->mNumVertices); + } // else write as usual + else { + WriteArray(&chunk,mesh->mTangents,mesh->mNumVertices); + WriteArray(&chunk,mesh->mBitangents,mesh->mNumVertices); + } + } + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS;++n) { + if (!mesh->mColors[n]) + break; + + if (shortened) { + WriteBounds(&chunk,mesh->mColors[n],mesh->mNumVertices); + } // else write as usual + else WriteArray(&chunk,mesh->mColors[n],mesh->mNumVertices); + } + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n) { + if (!mesh->mTextureCoords[n]) + break; + + // write number of UV components + Write(&chunk,mesh->mNumUVComponents[n]); + + if (shortened) { + WriteBounds(&chunk,mesh->mTextureCoords[n],mesh->mNumVertices); + } // else write as usual + else WriteArray(&chunk,mesh->mTextureCoords[n],mesh->mNumVertices); + } + + // write faces. There are no floating-point calculations involved + // in these, so we can write a simple hash over the face data + // to the dump file. We generate a single 32 Bit hash for 512 faces + // using Assimp's standard hashing function. + if (shortened) { + unsigned int processed = 0; + for (unsigned int job;(job = std::min(mesh->mNumFaces-processed,512u));processed += job) { + + uint32_t hash = 0; + for (unsigned int a = 0; a < job;++a) { + + const aiFace& f = mesh->mFaces[processed+a]; + uint32_t tmp = f.mNumIndices; + hash = SuperFastHash(reinterpret_cast(&tmp),sizeof tmp,hash); + for (unsigned int i = 0; i < f.mNumIndices; ++i) { + static_assert(AI_MAX_VERTICES <= 0xffffffff, "AI_MAX_VERTICES <= 0xffffffff"); + tmp = static_cast( f.mIndices[i] ); + hash = SuperFastHash(reinterpret_cast(&tmp),sizeof tmp,hash); + } + } + Write(&chunk,hash); + } + } + else // else write as usual + { + // if there are less than 2^16 vertices, we can simply use 16 bit integers ... + for (unsigned int i = 0; i < mesh->mNumFaces;++i) { + const aiFace& f = mesh->mFaces[i]; + + static_assert(AI_MAX_FACE_INDICES <= 0xffff, "AI_MAX_FACE_INDICES <= 0xffff"); + Write(&chunk,f.mNumIndices); + + for (unsigned int a = 0; a < f.mNumIndices;++a) { + if (mesh->mNumVertices < (1u<<16)) { + Write(&chunk,f.mIndices[a]); + } + else Write(&chunk,f.mIndices[a]); + } + } + } + + // write bones + if (mesh->mNumBones) { + for (unsigned int a = 0; a < mesh->mNumBones;++a) { + const aiBone* b = mesh->mBones[a]; + WriteBinaryBone(&chunk,b); + } + } + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryMaterialProperty(IOStream * container, const aiMaterialProperty* prop) + { + AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AIMATERIALPROPERTY ); + + Write(&chunk,prop->mKey); + Write(&chunk,prop->mSemantic); + Write(&chunk,prop->mIndex); + + Write(&chunk,prop->mDataLength); + Write(&chunk,(unsigned int)prop->mType); + chunk.Write(prop->mData,1,prop->mDataLength); + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryMaterial(IOStream * container, const aiMaterial* mat) + { + AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AIMATERIAL); + + Write(&chunk,mat->mNumProperties); + for (unsigned int i = 0; i < mat->mNumProperties;++i) { + WriteBinaryMaterialProperty( &chunk, mat->mProperties[i]); + } + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryNodeAnim(IOStream * container, const aiNodeAnim* nd) + { + AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AINODEANIM ); + + Write(&chunk,nd->mNodeName); + Write(&chunk,nd->mNumPositionKeys); + Write(&chunk,nd->mNumRotationKeys); + Write(&chunk,nd->mNumScalingKeys); + Write(&chunk,nd->mPreState); + Write(&chunk,nd->mPostState); + + if (nd->mPositionKeys) { + if (shortened) { + WriteBounds(&chunk,nd->mPositionKeys,nd->mNumPositionKeys); + + } // else write as usual + else WriteArray(&chunk,nd->mPositionKeys,nd->mNumPositionKeys); + } + if (nd->mRotationKeys) { + if (shortened) { + WriteBounds(&chunk,nd->mRotationKeys,nd->mNumRotationKeys); + + } // else write as usual + else WriteArray(&chunk,nd->mRotationKeys,nd->mNumRotationKeys); + } + if (nd->mScalingKeys) { + if (shortened) { + WriteBounds(&chunk,nd->mScalingKeys,nd->mNumScalingKeys); + + } // else write as usual + else WriteArray(&chunk,nd->mScalingKeys,nd->mNumScalingKeys); + } + } + + + // ----------------------------------------------------------------------------------- + void WriteBinaryAnim( IOStream * container, const aiAnimation* anim ) + { + AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AIANIMATION ); + + Write(&chunk,anim->mName); + Write(&chunk,anim->mDuration); + Write(&chunk,anim->mTicksPerSecond); + Write(&chunk,anim->mNumChannels); + + for (unsigned int a = 0; a < anim->mNumChannels;++a) { + const aiNodeAnim* nd = anim->mChannels[a]; + WriteBinaryNodeAnim(&chunk,nd); + } + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryLight( IOStream * container, const aiLight* l ) + { + AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AILIGHT ); + + Write(&chunk,l->mName); + Write(&chunk,l->mType); + + if (l->mType != aiLightSource_DIRECTIONAL) { + Write(&chunk,l->mAttenuationConstant); + Write(&chunk,l->mAttenuationLinear); + Write(&chunk,l->mAttenuationQuadratic); + } + + Write(&chunk,l->mColorDiffuse); + Write(&chunk,l->mColorSpecular); + Write(&chunk,l->mColorAmbient); + + if (l->mType == aiLightSource_SPOT) { + Write(&chunk,l->mAngleInnerCone); + Write(&chunk,l->mAngleOuterCone); + } + + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryCamera( IOStream * container, const aiCamera* cam ) + { + AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AICAMERA ); + + Write(&chunk,cam->mName); + Write(&chunk,cam->mPosition); + Write(&chunk,cam->mLookAt); + Write(&chunk,cam->mUp); + Write(&chunk,cam->mHorizontalFOV); + Write(&chunk,cam->mClipPlaneNear); + Write(&chunk,cam->mClipPlaneFar); + Write(&chunk,cam->mAspect); + } + + // ----------------------------------------------------------------------------------- + void WriteBinaryScene( IOStream * container, const aiScene* scene) + { + AssbinChunkWriter chunk( container, ASSBIN_CHUNK_AISCENE ); + + // basic scene information + Write(&chunk,scene->mFlags); + Write(&chunk,scene->mNumMeshes); + Write(&chunk,scene->mNumMaterials); + Write(&chunk,scene->mNumAnimations); + Write(&chunk,scene->mNumTextures); + Write(&chunk,scene->mNumLights); + Write(&chunk,scene->mNumCameras); + + // write node graph + WriteBinaryNode( &chunk, scene->mRootNode ); + + // write all meshes + for (unsigned int i = 0; i < scene->mNumMeshes;++i) { + const aiMesh* mesh = scene->mMeshes[i]; + WriteBinaryMesh( &chunk,mesh); + } + + // write materials + for (unsigned int i = 0; i< scene->mNumMaterials; ++i) { + const aiMaterial* mat = scene->mMaterials[i]; + WriteBinaryMaterial(&chunk,mat); + } + + // write all animations + for (unsigned int i = 0; i < scene->mNumAnimations;++i) { + const aiAnimation* anim = scene->mAnimations[i]; + WriteBinaryAnim(&chunk,anim); + } + + + // write all textures + for (unsigned int i = 0; i < scene->mNumTextures;++i) { + const aiTexture* mesh = scene->mTextures[i]; + WriteBinaryTexture(&chunk,mesh); + } + + // write lights + for (unsigned int i = 0; i < scene->mNumLights;++i) { + const aiLight* l = scene->mLights[i]; + WriteBinaryLight(&chunk,l); + } + + // write cameras + for (unsigned int i = 0; i < scene->mNumCameras;++i) { + const aiCamera* cam = scene->mCameras[i]; + WriteBinaryCamera(&chunk,cam); + } + + } + +public: + AssbinExport() + : shortened(false), compressed(false) // temporary settings until properties are introduced for exporters + { + } + + // ----------------------------------------------------------------------------------- + // Write a binary model dump + void WriteBinaryDump(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene) + { + IOStream * out = pIOSystem->Open( pFile, "wb" ); + if (!out) return; + + time_t tt = time(NULL); +#if _WIN32 + tm* p = gmtime(&tt); +#else + struct tm now; + tm* p = gmtime_r(&tt, &now); +#endif + + // header + char s[64]; + memset( s, 0, 64 ); +#if _MSC_VER >= 1400 + sprintf_s(s,"ASSIMP.binary-dump.%s",asctime(p)); +#else + ai_snprintf(s,64,"ASSIMP.binary-dump.%s",asctime(p)); +#endif + out->Write( s, 44, 1 ); + // == 44 bytes + + Write( out, ASSBIN_VERSION_MAJOR ); + Write( out, ASSBIN_VERSION_MINOR ); + Write( out, aiGetVersionRevision() ); + Write( out, aiGetCompileFlags() ); + Write( out, shortened ); + Write( out, compressed ); + // == 20 bytes + + char buff[256]; + strncpy(buff,pFile,256); + out->Write(buff,sizeof(char),256); + + char cmd[] = "\0"; + strncpy(buff,cmd,128); + out->Write(buff,sizeof(char),128); + + // leave 64 bytes free for future extensions + memset(buff,0xcd,64); + out->Write(buff,sizeof(char),64); + // == 435 bytes + + // ==== total header size: 512 bytes + ai_assert( out->Tell() == ASSBIN_HEADER_LENGTH ); + + // Up to here the data is uncompressed. For compressed files, the rest + // is compressed using standard DEFLATE from zlib. + if (compressed) + { + AssbinChunkWriter uncompressedStream( NULL, 0 ); + WriteBinaryScene( &uncompressedStream, pScene ); + + uLongf uncompressedSize = static_cast(uncompressedStream.Tell()); + uLongf compressedSize = (uLongf)compressBound(uncompressedSize); + uint8_t* compressedBuffer = new uint8_t[ compressedSize ]; + + int res = compress2( compressedBuffer, &compressedSize, (const Bytef*)uncompressedStream.GetBufferPointer(), uncompressedSize, 9 ); + if(res != Z_OK) + { + delete [] compressedBuffer; + pIOSystem->Close(out); + throw DeadlyExportError("Compression failed."); + } + + out->Write( &uncompressedSize, sizeof(uint32_t), 1 ); + out->Write( compressedBuffer, sizeof(char), compressedSize ); + + delete[] compressedBuffer; + } + else + { + WriteBinaryScene( out, pScene ); + } + + pIOSystem->Close( out ); + } +}; + +void ExportSceneAssbin(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) { + AssbinExport exporter; + exporter.WriteBinaryDump( pFile, pIOSystem, pScene ); +} +} // end of namespace Assimp + +#endif // ASSIMP_BUILD_NO_ASSBIN_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/thirdparty/assimp/code/Assbin/AssbinExporter.h b/thirdparty/assimp/code/Assbin/AssbinExporter.h new file mode 100644 index 0000000..3e13639 --- /dev/null +++ b/thirdparty/assimp/code/Assbin/AssbinExporter.h @@ -0,0 +1,58 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file AssbinExporter.h + * ASSBIN Exporter Main Header + */ +#ifndef AI_ASSBINEXPORTER_H_INC +#define AI_ASSBINEXPORTER_H_INC + +#include + +// nothing really needed here - reserved for future use like properties +namespace Assimp { + +void ASSIMP_API ExportSceneAssbin(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/); + +} + +#endif // AI_ASSBINEXPORTER_H_INC diff --git a/thirdparty/assimp/code/Assbin/AssbinLoader.cpp b/thirdparty/assimp/code/Assbin/AssbinLoader.cpp new file mode 100644 index 0000000..becc3f8 --- /dev/null +++ b/thirdparty/assimp/code/Assbin/AssbinLoader.cpp @@ -0,0 +1,733 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/** @file AssbinLoader.cpp + * @brief Implementation of the .assbin importer class + * + * see assbin_chunks.h + */ + +#ifndef ASSIMP_BUILD_NO_ASSBIN_IMPORTER + +// internal headers +#include "Assbin/AssbinLoader.h" +#include "Common/assbin_chunks.h" +#include +#include +#include +#include +#include +#include + +#ifdef ASSIMP_BUILD_NO_OWN_ZLIB +# include +#else +# include +#endif + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Assimp Binary Importer", + "Gargaj / Conspiracy", + "", + "", + aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_SupportCompressedFlavour, + 0, + 0, + 0, + 0, + "assbin" +}; + +// ----------------------------------------------------------------------------------- +const aiImporterDesc* AssbinImporter::GetInfo() const { + return &desc; +} + +// ----------------------------------------------------------------------------------- +bool AssbinImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/ ) const { + IOStream * in = pIOHandler->Open(pFile); + if (nullptr == in) { + return false; + } + + char s[32]; + in->Read( s, sizeof(char), 32 ); + + pIOHandler->Close(in); + + return strncmp( s, "ASSIMP.binary-dump.", 19 ) == 0; +} + +// ----------------------------------------------------------------------------------- +template +T Read(IOStream * stream) { + T t; + size_t res = stream->Read( &t, sizeof(T), 1 ); + if(res != 1) + throw DeadlyImportError("Unexpected EOF"); + return t; +} + +// ----------------------------------------------------------------------------------- +template <> +aiVector3D Read(IOStream * stream) { + aiVector3D v; + v.x = Read(stream); + v.y = Read(stream); + v.z = Read(stream); + return v; +} + +// ----------------------------------------------------------------------------------- +template <> +aiColor4D Read(IOStream * stream) { + aiColor4D c; + c.r = Read(stream); + c.g = Read(stream); + c.b = Read(stream); + c.a = Read(stream); + return c; +} + +// ----------------------------------------------------------------------------------- +template <> +aiQuaternion Read(IOStream * stream) { + aiQuaternion v; + v.w = Read(stream); + v.x = Read(stream); + v.y = Read(stream); + v.z = Read(stream); + return v; +} + +// ----------------------------------------------------------------------------------- +template <> +aiString Read(IOStream * stream) { + aiString s; + stream->Read(&s.length,4,1); + if(s.length) + stream->Read(s.data,s.length,1); + s.data[s.length] = 0; + return s; +} + +// ----------------------------------------------------------------------------------- +template <> +aiVertexWeight Read(IOStream * stream) { + aiVertexWeight w; + w.mVertexId = Read(stream); + w.mWeight = Read(stream); + return w; +} + +// ----------------------------------------------------------------------------------- +template <> +aiMatrix4x4 Read(IOStream * stream) { + aiMatrix4x4 m; + for (unsigned int i = 0; i < 4;++i) { + for (unsigned int i2 = 0; i2 < 4;++i2) { + m[i][i2] = Read(stream); + } + } + return m; +} + +// ----------------------------------------------------------------------------------- +template <> +aiVectorKey Read(IOStream * stream) { + aiVectorKey v; + v.mTime = Read(stream); + v.mValue = Read(stream); + return v; +} + +// ----------------------------------------------------------------------------------- +template <> +aiQuatKey Read(IOStream * stream) { + aiQuatKey v; + v.mTime = Read(stream); + v.mValue = Read(stream); + return v; +} + +// ----------------------------------------------------------------------------------- +template +void ReadArray( IOStream *stream, T * out, unsigned int size) { + ai_assert( nullptr != stream ); + ai_assert( nullptr != out ); + + for (unsigned int i=0; i(stream); + } +} + +// ----------------------------------------------------------------------------------- +template +void ReadBounds( IOStream * stream, T* /*p*/, unsigned int n ) { + // not sure what to do here, the data isn't really useful. + stream->Seek( sizeof(T) * n, aiOrigin_CUR ); +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryNode( IOStream * stream, aiNode** onode, aiNode* parent ) { + if(Read(stream) != ASSBIN_CHUNK_AINODE) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/ Read(stream); + + std::unique_ptr node(new aiNode()); + + node->mName = Read(stream); + node->mTransformation = Read(stream); + unsigned numChildren = Read(stream); + unsigned numMeshes = Read(stream); + unsigned int nb_metadata = Read(stream); + + if(parent) { + node->mParent = parent; + } + + if (numMeshes) + { + node->mMeshes = new unsigned int[numMeshes]; + for (unsigned int i = 0; i < numMeshes; ++i) { + node->mMeshes[i] = Read(stream); + node->mNumMeshes++; + } + } + + if (numChildren) { + node->mChildren = new aiNode*[numChildren]; + for (unsigned int i = 0; i < numChildren; ++i) { + ReadBinaryNode( stream, &node->mChildren[i], node.get() ); + node->mNumChildren++; + } + } + + if ( nb_metadata > 0 ) { + node->mMetaData = aiMetadata::Alloc(nb_metadata); + for (unsigned int i = 0; i < nb_metadata; ++i) { + node->mMetaData->mKeys[i] = Read(stream); + node->mMetaData->mValues[i].mType = (aiMetadataType) Read(stream); + void* data = nullptr; + + switch (node->mMetaData->mValues[i].mType) { + case AI_BOOL: + data = new bool(Read(stream)); + break; + case AI_INT32: + data = new int32_t(Read(stream)); + break; + case AI_UINT64: + data = new uint64_t(Read(stream)); + break; + case AI_FLOAT: + data = new float(Read(stream)); + break; + case AI_DOUBLE: + data = new double(Read(stream)); + break; + case AI_AISTRING: + data = new aiString(Read(stream)); + break; + case AI_AIVECTOR3D: + data = new aiVector3D(Read(stream)); + break; +#ifndef SWIG + case FORCE_32BIT: +#endif // SWIG + default: + break; + } + + node->mMetaData->mValues[i].mData = data; + } + } + *onode = node.release(); +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryBone( IOStream * stream, aiBone* b ) { + if(Read(stream) != ASSBIN_CHUNK_AIBONE) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/ Read(stream); + + b->mName = Read(stream); + b->mNumWeights = Read(stream); + b->mOffsetMatrix = Read(stream); + + // for the moment we write dumb min/max values for the bones, too. + // maybe I'll add a better, hash-like solution later + if (shortened) { + ReadBounds(stream,b->mWeights,b->mNumWeights); + } else { + // else write as usual + b->mWeights = new aiVertexWeight[b->mNumWeights]; + ReadArray(stream,b->mWeights,b->mNumWeights); + } +} + +// ----------------------------------------------------------------------------------- +static bool fitsIntoUI16(unsigned int mNumVertices) { + return ( mNumVertices < (1u<<16) ); +} +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryMesh( IOStream * stream, aiMesh* mesh ) { + if(Read(stream) != ASSBIN_CHUNK_AIMESH) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/ Read(stream); + + mesh->mPrimitiveTypes = Read(stream); + mesh->mNumVertices = Read(stream); + mesh->mNumFaces = Read(stream); + mesh->mNumBones = Read(stream); + mesh->mMaterialIndex = Read(stream); + + // first of all, write bits for all existent vertex components + unsigned int c = Read(stream); + + if (c & ASSBIN_MESH_HAS_POSITIONS) { + if (shortened) { + ReadBounds(stream,mesh->mVertices,mesh->mNumVertices); + } else { + // else write as usual + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + ReadArray(stream,mesh->mVertices,mesh->mNumVertices); + } + } + if (c & ASSBIN_MESH_HAS_NORMALS) { + if (shortened) { + ReadBounds(stream,mesh->mNormals,mesh->mNumVertices); + } else { + // else write as usual + mesh->mNormals = new aiVector3D[mesh->mNumVertices]; + ReadArray(stream,mesh->mNormals,mesh->mNumVertices); + } + } + if (c & ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS) { + if (shortened) { + ReadBounds(stream,mesh->mTangents,mesh->mNumVertices); + ReadBounds(stream,mesh->mBitangents,mesh->mNumVertices); + } else { + // else write as usual + mesh->mTangents = new aiVector3D[mesh->mNumVertices]; + ReadArray(stream,mesh->mTangents,mesh->mNumVertices); + mesh->mBitangents = new aiVector3D[mesh->mNumVertices]; + ReadArray(stream,mesh->mBitangents,mesh->mNumVertices); + } + } + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS;++n) { + if (!(c & ASSBIN_MESH_HAS_COLOR(n))) { + break; + } + + if (shortened) { + ReadBounds(stream,mesh->mColors[n],mesh->mNumVertices); + } else { + // else write as usual + mesh->mColors[n] = new aiColor4D[mesh->mNumVertices]; + ReadArray(stream,mesh->mColors[n],mesh->mNumVertices); + } + } + for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS;++n) { + if (!(c & ASSBIN_MESH_HAS_TEXCOORD(n))) { + break; + } + + // write number of UV components + mesh->mNumUVComponents[n] = Read(stream); + + if (shortened) { + ReadBounds(stream,mesh->mTextureCoords[n],mesh->mNumVertices); + } else { + // else write as usual + mesh->mTextureCoords[n] = new aiVector3D[mesh->mNumVertices]; + ReadArray(stream,mesh->mTextureCoords[n],mesh->mNumVertices); + } + } + + // write faces. There are no floating-point calculations involved + // in these, so we can write a simple hash over the face data + // to the dump file. We generate a single 32 Bit hash for 512 faces + // using Assimp's standard hashing function. + if (shortened) { + Read(stream); + } else { + // else write as usual + // if there are less than 2^16 vertices, we can simply use 16 bit integers ... + mesh->mFaces = new aiFace[mesh->mNumFaces]; + for (unsigned int i = 0; i < mesh->mNumFaces;++i) { + aiFace& f = mesh->mFaces[i]; + + static_assert(AI_MAX_FACE_INDICES <= 0xffff, "AI_MAX_FACE_INDICES <= 0xffff"); + f.mNumIndices = Read(stream); + f.mIndices = new unsigned int[f.mNumIndices]; + + for (unsigned int a = 0; a < f.mNumIndices;++a) { + // Check if unsigned short ( 16 bit ) are big enought for the indices + if ( fitsIntoUI16( mesh->mNumVertices ) ) { + f.mIndices[a] = Read(stream); + } else { + f.mIndices[a] = Read(stream); + } + } + } + } + + // write bones + if (mesh->mNumBones) { + mesh->mBones = new C_STRUCT aiBone*[mesh->mNumBones]; + for (unsigned int a = 0; a < mesh->mNumBones;++a) { + mesh->mBones[a] = new aiBone(); + ReadBinaryBone(stream,mesh->mBones[a]); + } + } +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryMaterialProperty(IOStream * stream, aiMaterialProperty* prop) { + if(Read(stream) != ASSBIN_CHUNK_AIMATERIALPROPERTY) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/ Read(stream); + + prop->mKey = Read(stream); + prop->mSemantic = Read(stream); + prop->mIndex = Read(stream); + + prop->mDataLength = Read(stream); + prop->mType = (aiPropertyTypeInfo)Read(stream); + prop->mData = new char [ prop->mDataLength ]; + stream->Read(prop->mData,1,prop->mDataLength); +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryMaterial(IOStream * stream, aiMaterial* mat) { + if(Read(stream) != ASSBIN_CHUNK_AIMATERIAL) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/ Read(stream); + + mat->mNumAllocated = mat->mNumProperties = Read(stream); + if (mat->mNumProperties) + { + if (mat->mProperties) + { + delete[] mat->mProperties; + } + mat->mProperties = new aiMaterialProperty*[mat->mNumProperties]; + for (unsigned int i = 0; i < mat->mNumProperties;++i) { + mat->mProperties[i] = new aiMaterialProperty(); + ReadBinaryMaterialProperty( stream, mat->mProperties[i]); + } + } +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryNodeAnim(IOStream * stream, aiNodeAnim* nd) { + if(Read(stream) != ASSBIN_CHUNK_AINODEANIM) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/ Read(stream); + + nd->mNodeName = Read(stream); + nd->mNumPositionKeys = Read(stream); + nd->mNumRotationKeys = Read(stream); + nd->mNumScalingKeys = Read(stream); + nd->mPreState = (aiAnimBehaviour)Read(stream); + nd->mPostState = (aiAnimBehaviour)Read(stream); + + if (nd->mNumPositionKeys) { + if (shortened) { + ReadBounds(stream,nd->mPositionKeys,nd->mNumPositionKeys); + + } // else write as usual + else { + nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys]; + ReadArray(stream,nd->mPositionKeys,nd->mNumPositionKeys); + } + } + if (nd->mNumRotationKeys) { + if (shortened) { + ReadBounds(stream,nd->mRotationKeys,nd->mNumRotationKeys); + + } else { + // else write as usual + nd->mRotationKeys = new aiQuatKey[nd->mNumRotationKeys]; + ReadArray(stream,nd->mRotationKeys,nd->mNumRotationKeys); + } + } + if (nd->mNumScalingKeys) { + if (shortened) { + ReadBounds(stream,nd->mScalingKeys,nd->mNumScalingKeys); + + } else { + // else write as usual + nd->mScalingKeys = new aiVectorKey[nd->mNumScalingKeys]; + ReadArray(stream,nd->mScalingKeys,nd->mNumScalingKeys); + } + } +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryAnim( IOStream * stream, aiAnimation* anim ) { + if(Read(stream) != ASSBIN_CHUNK_AIANIMATION) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/ Read(stream); + + anim->mName = Read (stream); + anim->mDuration = Read (stream); + anim->mTicksPerSecond = Read (stream); + anim->mNumChannels = Read(stream); + + if (anim->mNumChannels) { + anim->mChannels = new aiNodeAnim*[ anim->mNumChannels ]; + for (unsigned int a = 0; a < anim->mNumChannels;++a) { + anim->mChannels[a] = new aiNodeAnim(); + ReadBinaryNodeAnim(stream,anim->mChannels[a]); + } + } +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryTexture(IOStream * stream, aiTexture* tex) { + if(Read(stream) != ASSBIN_CHUNK_AITEXTURE) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/ Read(stream); + + tex->mWidth = Read(stream); + tex->mHeight = Read(stream); + stream->Read( tex->achFormatHint, sizeof(char), 4 ); + + if(!shortened) { + if (!tex->mHeight) { + tex->pcData = new aiTexel[ tex->mWidth ]; + stream->Read(tex->pcData,1,tex->mWidth); + } else { + tex->pcData = new aiTexel[ tex->mWidth*tex->mHeight ]; + stream->Read(tex->pcData,1,tex->mWidth*tex->mHeight*4); + } + } +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryLight( IOStream * stream, aiLight* l ) { + if(Read(stream) != ASSBIN_CHUNK_AILIGHT) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/ Read(stream); + + l->mName = Read(stream); + l->mType = (aiLightSourceType)Read(stream); + + if (l->mType != aiLightSource_DIRECTIONAL) { + l->mAttenuationConstant = Read(stream); + l->mAttenuationLinear = Read(stream); + l->mAttenuationQuadratic = Read(stream); + } + + l->mColorDiffuse = Read(stream); + l->mColorSpecular = Read(stream); + l->mColorAmbient = Read(stream); + + if (l->mType == aiLightSource_SPOT) { + l->mAngleInnerCone = Read(stream); + l->mAngleOuterCone = Read(stream); + } +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryCamera( IOStream * stream, aiCamera* cam ) { + if(Read(stream) != ASSBIN_CHUNK_AICAMERA) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/ Read(stream); + + cam->mName = Read(stream); + cam->mPosition = Read(stream); + cam->mLookAt = Read(stream); + cam->mUp = Read(stream); + cam->mHorizontalFOV = Read(stream); + cam->mClipPlaneNear = Read(stream); + cam->mClipPlaneFar = Read(stream); + cam->mAspect = Read(stream); +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::ReadBinaryScene( IOStream * stream, aiScene* scene ) { + if(Read(stream) != ASSBIN_CHUNK_AISCENE) + throw DeadlyImportError("Magic chunk identifiers are wrong!"); + /*uint32_t size =*/ Read(stream); + + scene->mFlags = Read(stream); + scene->mNumMeshes = Read(stream); + scene->mNumMaterials = Read(stream); + scene->mNumAnimations = Read(stream); + scene->mNumTextures = Read(stream); + scene->mNumLights = Read(stream); + scene->mNumCameras = Read(stream); + + // Read node graph + //scene->mRootNode = new aiNode[1]; + ReadBinaryNode( stream, &scene->mRootNode, (aiNode*)NULL ); + + // Read all meshes + if (scene->mNumMeshes) { + scene->mMeshes = new aiMesh*[scene->mNumMeshes]; + memset(scene->mMeshes, 0, scene->mNumMeshes*sizeof(aiMesh*)); + for (unsigned int i = 0; i < scene->mNumMeshes;++i) { + scene->mMeshes[i] = new aiMesh(); + ReadBinaryMesh( stream,scene->mMeshes[i]); + } + } + + // Read materials + if (scene->mNumMaterials) { + scene->mMaterials = new aiMaterial*[scene->mNumMaterials]; + memset(scene->mMaterials, 0, scene->mNumMaterials*sizeof(aiMaterial*)); + for (unsigned int i = 0; i< scene->mNumMaterials; ++i) { + scene->mMaterials[i] = new aiMaterial(); + ReadBinaryMaterial(stream,scene->mMaterials[i]); + } + } + + // Read all animations + if (scene->mNumAnimations) { + scene->mAnimations = new aiAnimation*[scene->mNumAnimations]; + memset(scene->mAnimations, 0, scene->mNumAnimations*sizeof(aiAnimation*)); + for (unsigned int i = 0; i < scene->mNumAnimations;++i) { + scene->mAnimations[i] = new aiAnimation(); + ReadBinaryAnim(stream,scene->mAnimations[i]); + } + } + + // Read all textures + if (scene->mNumTextures) { + scene->mTextures = new aiTexture*[scene->mNumTextures]; + memset(scene->mTextures, 0, scene->mNumTextures*sizeof(aiTexture*)); + for (unsigned int i = 0; i < scene->mNumTextures;++i) { + scene->mTextures[i] = new aiTexture(); + ReadBinaryTexture(stream,scene->mTextures[i]); + } + } + + // Read lights + if (scene->mNumLights) { + scene->mLights = new aiLight*[scene->mNumLights]; + memset(scene->mLights, 0, scene->mNumLights*sizeof(aiLight*)); + for (unsigned int i = 0; i < scene->mNumLights;++i) { + scene->mLights[i] = new aiLight(); + ReadBinaryLight(stream,scene->mLights[i]); + } + } + + // Read cameras + if (scene->mNumCameras) { + scene->mCameras = new aiCamera*[scene->mNumCameras]; + memset(scene->mCameras, 0, scene->mNumCameras*sizeof(aiCamera*)); + for (unsigned int i = 0; i < scene->mNumCameras;++i) { + scene->mCameras[i] = new aiCamera(); + ReadBinaryCamera(stream,scene->mCameras[i]); + } + } + +} + +// ----------------------------------------------------------------------------------- +void AssbinImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler ) { + IOStream * stream = pIOHandler->Open(pFile,"rb"); + if (nullptr == stream) { + return; + } + + // signature + stream->Seek( 44, aiOrigin_CUR ); + + unsigned int versionMajor = Read(stream); + unsigned int versionMinor = Read(stream); + if (versionMinor != ASSBIN_VERSION_MINOR || versionMajor != ASSBIN_VERSION_MAJOR) { + throw DeadlyImportError( "Invalid version, data format not compatible!" ); + } + + /*unsigned int versionRevision =*/ Read(stream); + /*unsigned int compileFlags =*/ Read(stream); + + shortened = Read(stream) > 0; + compressed = Read(stream) > 0; + + if (shortened) + throw DeadlyImportError( "Shortened binaries are not supported!" ); + + stream->Seek( 256, aiOrigin_CUR ); // original filename + stream->Seek( 128, aiOrigin_CUR ); // options + stream->Seek( 64, aiOrigin_CUR ); // padding + + if (compressed) { + uLongf uncompressedSize = Read(stream); + uLongf compressedSize = static_cast(stream->FileSize() - stream->Tell()); + + unsigned char * compressedData = new unsigned char[ compressedSize ]; + size_t len = stream->Read( compressedData, 1, compressedSize ); + ai_assert(len == compressedSize); + + unsigned char * uncompressedData = new unsigned char[ uncompressedSize ]; + + int res = uncompress( uncompressedData, &uncompressedSize, compressedData, (uLong) len ); + if(res != Z_OK) + { + delete [] uncompressedData; + delete [] compressedData; + pIOHandler->Close(stream); + throw DeadlyImportError("Zlib decompression failed."); + } + + MemoryIOStream io( uncompressedData, uncompressedSize ); + + ReadBinaryScene(&io,pScene); + + delete[] uncompressedData; + delete[] compressedData; + } else { + ReadBinaryScene(stream,pScene); + } + + pIOHandler->Close(stream); +} + +#endif // !! ASSIMP_BUILD_NO_ASSBIN_IMPORTER diff --git a/thirdparty/assimp/code/Assbin/AssbinLoader.h b/thirdparty/assimp/code/Assbin/AssbinLoader.h new file mode 100644 index 0000000..9f2dde1 --- /dev/null +++ b/thirdparty/assimp/code/Assbin/AssbinLoader.h @@ -0,0 +1,106 @@ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file AssbinLoader.h + * @brief .assbin File format loader + */ +#ifndef AI_ASSBINIMPORTER_H_INC +#define AI_ASSBINIMPORTER_H_INC + +#include + +struct aiMesh; +struct aiNode; +struct aiBone; +struct aiMaterial; +struct aiMaterialProperty; +struct aiNodeAnim; +struct aiAnimation; +struct aiTexture; +struct aiLight; +struct aiCamera; + +#ifndef ASSIMP_BUILD_NO_ASSBIN_IMPORTER + +namespace Assimp { + +// --------------------------------------------------------------------------------- +/** Importer class for 3D Studio r3 and r4 3DS files + */ +class AssbinImporter : public BaseImporter +{ +private: + bool shortened; + bool compressed; + +public: + virtual bool CanRead( + const std::string& pFile, + IOSystem* pIOHandler, + bool checkSig + ) const; + virtual const aiImporterDesc* GetInfo() const; + virtual void InternReadFile( + const std::string& pFile, + aiScene* pScene, + IOSystem* pIOHandler + ); + void ReadHeader(); + void ReadBinaryScene( IOStream * stream, aiScene* pScene ); + void ReadBinaryNode( IOStream * stream, aiNode** mRootNode, aiNode* parent ); + void ReadBinaryMesh( IOStream * stream, aiMesh* mesh ); + void ReadBinaryBone( IOStream * stream, aiBone* bone ); + void ReadBinaryMaterial(IOStream * stream, aiMaterial* mat); + void ReadBinaryMaterialProperty(IOStream * stream, aiMaterialProperty* prop); + void ReadBinaryNodeAnim(IOStream * stream, aiNodeAnim* nd); + void ReadBinaryAnim( IOStream * stream, aiAnimation* anim ); + void ReadBinaryTexture(IOStream * stream, aiTexture* tex); + void ReadBinaryLight( IOStream * stream, aiLight* l ); + void ReadBinaryCamera( IOStream * stream, aiCamera* cam ); +}; + +} // end of namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_ASSBIN_IMPORTER + +#endif // AI_ASSBINIMPORTER_H_INC diff --git a/thirdparty/assimp/code/Assjson/cencode.c b/thirdparty/assimp/code/Assjson/cencode.c new file mode 100644 index 0000000..db99e7e --- /dev/null +++ b/thirdparty/assimp/code/Assjson/cencode.c @@ -0,0 +1,109 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include "cencode.h" // changed from + +const int CHARS_PER_LINE = 72; + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return codechar - code_out; + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + if (state_in->stepcount == CHARS_PER_LINE/4) + { + *codechar++ = '\n'; + state_in->stepcount = 0; + } + } + } + /* control should not reach here */ + return codechar - code_out; +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + *codechar++ = '\n'; + + return codechar - code_out; +} + diff --git a/thirdparty/assimp/code/Assjson/cencode.h b/thirdparty/assimp/code/Assjson/cencode.h new file mode 100644 index 0000000..c1e3464 --- /dev/null +++ b/thirdparty/assimp/code/Assjson/cencode.h @@ -0,0 +1,31 @@ +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +#endif /* BASE64_CENCODE_H */ diff --git a/thirdparty/assimp/code/Assjson/json_exporter.cpp b/thirdparty/assimp/code/Assjson/json_exporter.cpp new file mode 100644 index 0000000..e9fa724 --- /dev/null +++ b/thirdparty/assimp/code/Assjson/json_exporter.cpp @@ -0,0 +1,809 @@ +/* +Assimp2Json +Copyright (c) 2011, Alexander C. Gessler + +Licensed under a 3-clause BSD license. See the LICENSE file for more information. + +*/ + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define CURRENT_FORMAT_VERSION 100 + +// grab scoped_ptr from assimp to avoid a dependency on boost. +//#include + +#include "mesh_splitter.h" + +extern "C" { + #include "cencode.h" +} +namespace Assimp { + +void ExportAssimp2Json(const char*, Assimp::IOSystem*, const aiScene*, const Assimp::ExportProperties*); + +// small utility class to simplify serializing the aiScene to Json +class JSONWriter { +public: + enum { + Flag_DoNotIndent = 0x1, + Flag_WriteSpecialFloats = 0x2, + }; + + JSONWriter(Assimp::IOStream& out, unsigned int flags = 0u) + : out(out) + , first() + , flags(flags) { + // make sure that all formatting happens using the standard, C locale and not the user's current locale + buff.imbue(std::locale("C")); + } + + ~JSONWriter() { + Flush(); + } + + void Flush() { + const std::string s = buff.str(); + out.Write(s.c_str(), s.length(), 1); + buff.clear(); + } + + void PushIndent() { + indent += '\t'; + } + + void PopIndent() { + indent.erase(indent.end() - 1); + } + + void Key(const std::string& name) { + AddIndentation(); + Delimit(); + buff << '\"' + name + "\": "; + } + + template + void Element(const Literal& name) { + AddIndentation(); + Delimit(); + + LiteralToString(buff, name) << '\n'; + } + + template + void SimpleValue(const Literal& s) { + LiteralToString(buff, s) << '\n'; + } + + void SimpleValue(const void* buffer, size_t len) { + base64_encodestate s; + base64_init_encodestate(&s); + + char* const out = new char[std::max(len * 2, static_cast(16u))]; + const int n = base64_encode_block(reinterpret_cast(buffer), static_cast(len), out, &s); + out[n + base64_encode_blockend(out + n, &s)] = '\0'; + + // base64 encoding may add newlines, but JSON strings may not contain 'real' newlines + // (only escaped ones). Remove any newlines in out. + for (char* cur = out; *cur; ++cur) { + if (*cur == '\n') { + *cur = ' '; + } + } + + buff << '\"' << out << "\"\n"; + delete[] out; + } + + void StartObj(bool is_element = false) { + // if this appears as a plain array element, we need to insert a delimiter and we should also indent it + if (is_element) { + AddIndentation(); + if (!first) { + buff << ','; + } + } + first = true; + buff << "{\n"; + PushIndent(); + } + + void EndObj() { + PopIndent(); + AddIndentation(); + first = false; + buff << "}\n"; + } + + void StartArray(bool is_element = false) { + // if this appears as a plain array element, we need to insert a delimiter and we should also indent it + if (is_element) { + AddIndentation(); + if (!first) { + buff << ','; + } + } + first = true; + buff << "[\n"; + PushIndent(); + } + + void EndArray() { + PopIndent(); + AddIndentation(); + buff << "]\n"; + first = false; + } + + void AddIndentation() { + if (!(flags & Flag_DoNotIndent)) { + buff << indent; + } + } + + void Delimit() { + if (!first) { + buff << ','; + } + else { + buff << ' '; + first = false; + } + } + +private: + template + std::stringstream& LiteralToString(std::stringstream& stream, const Literal& s) { + stream << s; + return stream; + } + + std::stringstream& LiteralToString(std::stringstream& stream, const aiString& s) { + std::string t; + + // escape backslashes and single quotes, both would render the JSON invalid if left as is + t.reserve(s.length); + for (size_t i = 0; i < s.length; ++i) { + + if (s.data[i] == '\\' || s.data[i] == '\'' || s.data[i] == '\"') { + t.push_back('\\'); + } + + t.push_back(s.data[i]); + } + stream << "\""; + stream << t; + stream << "\""; + return stream; + } + + std::stringstream& LiteralToString(std::stringstream& stream, float f) { + if (!std::numeric_limits::is_iec559) { + // on a non IEEE-754 platform, we make no assumptions about the representation or existence + // of special floating-point numbers. + stream << f; + return stream; + } + + // JSON does not support writing Inf/Nan + // [RFC 4672: "Numeric values that cannot be represented as sequences of digits + // (such as Infinity and NaN) are not permitted."] + // Nevertheless, many parsers will accept the special keywords Infinity, -Infinity and NaN + if (std::numeric_limits::infinity() == fabs(f)) { + if (flags & Flag_WriteSpecialFloats) { + stream << (f < 0 ? "\"-" : "\"") + std::string("Infinity\""); + return stream; + } + // we should print this warning, but we can't - this is called from within a generic assimp exporter, we cannot use cerr + // std::cerr << "warning: cannot represent infinite number literal, substituting 0 instead (use -i flag to enforce Infinity/NaN)" << std::endl; + stream << "0.0"; + return stream; + } + // f!=f is the most reliable test for NaNs that I know of + else if (f != f) { + if (flags & Flag_WriteSpecialFloats) { + stream << "\"NaN\""; + return stream; + } + // we should print this warning, but we can't - this is called from within a generic assimp exporter, we cannot use cerr + // std::cerr << "warning: cannot represent infinite number literal, substituting 0 instead (use -i flag to enforce Infinity/NaN)" << std::endl; + stream << "0.0"; + return stream; + } + + stream << f; + return stream; + } + +private: + Assimp::IOStream& out; + std::string indent, newline; + std::stringstream buff; + bool first; + + unsigned int flags; +}; + +void Write(JSONWriter& out, const aiVector3D& ai, bool is_elem = true) { + out.StartArray(is_elem); + out.Element(ai.x); + out.Element(ai.y); + out.Element(ai.z); + out.EndArray(); +} + +void Write(JSONWriter& out, const aiQuaternion& ai, bool is_elem = true) { + out.StartArray(is_elem); + out.Element(ai.w); + out.Element(ai.x); + out.Element(ai.y); + out.Element(ai.z); + out.EndArray(); +} + +void Write(JSONWriter& out, const aiColor3D& ai, bool is_elem = true) { + out.StartArray(is_elem); + out.Element(ai.r); + out.Element(ai.g); + out.Element(ai.b); + out.EndArray(); +} + +void Write(JSONWriter& out, const aiMatrix4x4& ai, bool is_elem = true) { + out.StartArray(is_elem); + for (unsigned int x = 0; x < 4; ++x) { + for (unsigned int y = 0; y < 4; ++y) { + out.Element(ai[x][y]); + } + } + out.EndArray(); +} + +void Write(JSONWriter& out, const aiBone& ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mName); + + out.Key("offsetmatrix"); + Write(out, ai.mOffsetMatrix, false); + + out.Key("weights"); + out.StartArray(); + for (unsigned int i = 0; i < ai.mNumWeights; ++i) { + out.StartArray(true); + out.Element(ai.mWeights[i].mVertexId); + out.Element(ai.mWeights[i].mWeight); + out.EndArray(); + } + out.EndArray(); + out.EndObj(); +} + +void Write(JSONWriter& out, const aiFace& ai, bool is_elem = true) { + out.StartArray(is_elem); + for (unsigned int i = 0; i < ai.mNumIndices; ++i) { + out.Element(ai.mIndices[i]); + } + out.EndArray(); +} + +void Write(JSONWriter& out, const aiMesh& ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mName); + + out.Key("materialindex"); + out.SimpleValue(ai.mMaterialIndex); + + out.Key("primitivetypes"); + out.SimpleValue(ai.mPrimitiveTypes); + + out.Key("vertices"); + out.StartArray(); + for (unsigned int i = 0; i < ai.mNumVertices; ++i) { + out.Element(ai.mVertices[i].x); + out.Element(ai.mVertices[i].y); + out.Element(ai.mVertices[i].z); + } + out.EndArray(); + + if (ai.HasNormals()) { + out.Key("normals"); + out.StartArray(); + for (unsigned int i = 0; i < ai.mNumVertices; ++i) { + out.Element(ai.mNormals[i].x); + out.Element(ai.mNormals[i].y); + out.Element(ai.mNormals[i].z); + } + out.EndArray(); + } + + if (ai.HasTangentsAndBitangents()) { + out.Key("tangents"); + out.StartArray(); + for (unsigned int i = 0; i < ai.mNumVertices; ++i) { + out.Element(ai.mTangents[i].x); + out.Element(ai.mTangents[i].y); + out.Element(ai.mTangents[i].z); + } + out.EndArray(); + + out.Key("bitangents"); + out.StartArray(); + for (unsigned int i = 0; i < ai.mNumVertices; ++i) { + out.Element(ai.mBitangents[i].x); + out.Element(ai.mBitangents[i].y); + out.Element(ai.mBitangents[i].z); + } + out.EndArray(); + } + + if (ai.GetNumUVChannels()) { + out.Key("numuvcomponents"); + out.StartArray(); + for (unsigned int n = 0; n < ai.GetNumUVChannels(); ++n) { + out.Element(ai.mNumUVComponents[n]); + } + out.EndArray(); + + out.Key("texturecoords"); + out.StartArray(); + for (unsigned int n = 0; n < ai.GetNumUVChannels(); ++n) { + const unsigned int numc = ai.mNumUVComponents[n] ? ai.mNumUVComponents[n] : 2; + + out.StartArray(true); + for (unsigned int i = 0; i < ai.mNumVertices; ++i) { + for (unsigned int c = 0; c < numc; ++c) { + out.Element(ai.mTextureCoords[n][i][c]); + } + } + out.EndArray(); + } + out.EndArray(); + } + + if (ai.GetNumColorChannels()) { + out.Key("colors"); + out.StartArray(); + for (unsigned int n = 0; n < ai.GetNumColorChannels(); ++n) { + out.StartArray(true); + for (unsigned int i = 0; i < ai.mNumVertices; ++i) { + out.Element(ai.mColors[n][i].r); + out.Element(ai.mColors[n][i].g); + out.Element(ai.mColors[n][i].b); + out.Element(ai.mColors[n][i].a); + } + out.EndArray(); + } + out.EndArray(); + } + + if (ai.mNumBones) { + out.Key("bones"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumBones; ++n) { + Write(out, *ai.mBones[n]); + } + out.EndArray(); + } + + out.Key("faces"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumFaces; ++n) { + Write(out, ai.mFaces[n]); + } + out.EndArray(); + + out.EndObj(); +} + +void Write(JSONWriter& out, const aiNode& ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mName); + + out.Key("transformation"); + Write(out, ai.mTransformation, false); + + if (ai.mNumMeshes) { + out.Key("meshes"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumMeshes; ++n) { + out.Element(ai.mMeshes[n]); + } + out.EndArray(); + } + + if (ai.mNumChildren) { + out.Key("children"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumChildren; ++n) { + Write(out, *ai.mChildren[n]); + } + out.EndArray(); + } + + out.EndObj(); +} + +void Write(JSONWriter& out, const aiMaterial& ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("properties"); + out.StartArray(); + for (unsigned int i = 0; i < ai.mNumProperties; ++i) { + const aiMaterialProperty* const prop = ai.mProperties[i]; + out.StartObj(true); + out.Key("key"); + out.SimpleValue(prop->mKey); + out.Key("semantic"); + out.SimpleValue(prop->mSemantic); + out.Key("index"); + out.SimpleValue(prop->mIndex); + + out.Key("type"); + out.SimpleValue(prop->mType); + + out.Key("value"); + switch (prop->mType) { + case aiPTI_Float: + if (prop->mDataLength / sizeof(float) > 1) { + out.StartArray(); + for (unsigned int i = 0; i < prop->mDataLength / sizeof(float); ++i) { + out.Element(reinterpret_cast(prop->mData)[i]); + } + out.EndArray(); + } + else { + out.SimpleValue(*reinterpret_cast(prop->mData)); + } + break; + + case aiPTI_Integer: + if (prop->mDataLength / sizeof(int) > 1) { + out.StartArray(); + for (unsigned int i = 0; i < prop->mDataLength / sizeof(int); ++i) { + out.Element(reinterpret_cast(prop->mData)[i]); + } + out.EndArray(); + } else { + out.SimpleValue(*reinterpret_cast(prop->mData)); + } + break; + + case aiPTI_String: + { + aiString s; + aiGetMaterialString(&ai, prop->mKey.data, prop->mSemantic, prop->mIndex, &s); + out.SimpleValue(s); + } + break; + case aiPTI_Buffer: + { + // binary data is written as series of hex-encoded octets + out.SimpleValue(prop->mData, prop->mDataLength); + } + break; + default: + assert(false); + } + + out.EndObj(); + } + + out.EndArray(); + out.EndObj(); +} + +void Write(JSONWriter& out, const aiTexture& ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("width"); + out.SimpleValue(ai.mWidth); + + out.Key("height"); + out.SimpleValue(ai.mHeight); + + out.Key("formathint"); + out.SimpleValue(aiString(ai.achFormatHint)); + + out.Key("data"); + if (!ai.mHeight) { + out.SimpleValue(ai.pcData, ai.mWidth); + } + else { + out.StartArray(); + for (unsigned int y = 0; y < ai.mHeight; ++y) { + out.StartArray(true); + for (unsigned int x = 0; x < ai.mWidth; ++x) { + const aiTexel& tx = ai.pcData[y*ai.mWidth + x]; + out.StartArray(true); + out.Element(static_cast(tx.r)); + out.Element(static_cast(tx.g)); + out.Element(static_cast(tx.b)); + out.Element(static_cast(tx.a)); + out.EndArray(); + } + out.EndArray(); + } + out.EndArray(); + } + + out.EndObj(); +} + +void Write(JSONWriter& out, const aiLight& ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mName); + + out.Key("type"); + out.SimpleValue(ai.mType); + + if (ai.mType == aiLightSource_SPOT || ai.mType == aiLightSource_UNDEFINED) { + out.Key("angleinnercone"); + out.SimpleValue(ai.mAngleInnerCone); + + out.Key("angleoutercone"); + out.SimpleValue(ai.mAngleOuterCone); + } + + out.Key("attenuationconstant"); + out.SimpleValue(ai.mAttenuationConstant); + + out.Key("attenuationlinear"); + out.SimpleValue(ai.mAttenuationLinear); + + out.Key("attenuationquadratic"); + out.SimpleValue(ai.mAttenuationQuadratic); + + out.Key("diffusecolor"); + Write(out, ai.mColorDiffuse, false); + + out.Key("specularcolor"); + Write(out, ai.mColorSpecular, false); + + out.Key("ambientcolor"); + Write(out, ai.mColorAmbient, false); + + if (ai.mType != aiLightSource_POINT) { + out.Key("direction"); + Write(out, ai.mDirection, false); + + } + + if (ai.mType != aiLightSource_DIRECTIONAL) { + out.Key("position"); + Write(out, ai.mPosition, false); + } + + out.EndObj(); +} + +void Write(JSONWriter& out, const aiNodeAnim& ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mNodeName); + + out.Key("prestate"); + out.SimpleValue(ai.mPreState); + + out.Key("poststate"); + out.SimpleValue(ai.mPostState); + + if (ai.mNumPositionKeys) { + out.Key("positionkeys"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumPositionKeys; ++n) { + const aiVectorKey& pos = ai.mPositionKeys[n]; + out.StartArray(true); + out.Element(pos.mTime); + Write(out, pos.mValue); + out.EndArray(); + } + out.EndArray(); + } + + if (ai.mNumRotationKeys) { + out.Key("rotationkeys"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumRotationKeys; ++n) { + const aiQuatKey& rot = ai.mRotationKeys[n]; + out.StartArray(true); + out.Element(rot.mTime); + Write(out, rot.mValue); + out.EndArray(); + } + out.EndArray(); + } + + if (ai.mNumScalingKeys) { + out.Key("scalingkeys"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumScalingKeys; ++n) { + const aiVectorKey& scl = ai.mScalingKeys[n]; + out.StartArray(true); + out.Element(scl.mTime); + Write(out, scl.mValue); + out.EndArray(); + } + out.EndArray(); + } + out.EndObj(); +} + +void Write(JSONWriter& out, const aiAnimation& ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mName); + + out.Key("tickspersecond"); + out.SimpleValue(ai.mTicksPerSecond); + + out.Key("duration"); + out.SimpleValue(ai.mDuration); + + out.Key("channels"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumChannels; ++n) { + Write(out, *ai.mChannels[n]); + } + out.EndArray(); + out.EndObj(); +} + +void Write(JSONWriter& out, const aiCamera& ai, bool is_elem = true) { + out.StartObj(is_elem); + + out.Key("name"); + out.SimpleValue(ai.mName); + + out.Key("aspect"); + out.SimpleValue(ai.mAspect); + + out.Key("clipplanefar"); + out.SimpleValue(ai.mClipPlaneFar); + + out.Key("clipplanenear"); + out.SimpleValue(ai.mClipPlaneNear); + + out.Key("horizontalfov"); + out.SimpleValue(ai.mHorizontalFOV); + + out.Key("up"); + Write(out, ai.mUp, false); + + out.Key("lookat"); + Write(out, ai.mLookAt, false); + + out.EndObj(); +} + +void WriteFormatInfo(JSONWriter& out) { + out.StartObj(); + out.Key("format"); + out.SimpleValue("\"assimp2json\""); + out.Key("version"); + out.SimpleValue(CURRENT_FORMAT_VERSION); + out.EndObj(); +} + +void Write(JSONWriter& out, const aiScene& ai) { + out.StartObj(); + + out.Key("__metadata__"); + WriteFormatInfo(out); + + out.Key("rootnode"); + Write(out, *ai.mRootNode, false); + + out.Key("flags"); + out.SimpleValue(ai.mFlags); + + if (ai.HasMeshes()) { + out.Key("meshes"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumMeshes; ++n) { + Write(out, *ai.mMeshes[n]); + } + out.EndArray(); + } + + if (ai.HasMaterials()) { + out.Key("materials"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumMaterials; ++n) { + Write(out, *ai.mMaterials[n]); + } + out.EndArray(); + } + + if (ai.HasAnimations()) { + out.Key("animations"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumAnimations; ++n) { + Write(out, *ai.mAnimations[n]); + } + out.EndArray(); + } + + if (ai.HasLights()) { + out.Key("lights"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumLights; ++n) { + Write(out, *ai.mLights[n]); + } + out.EndArray(); + } + + if (ai.HasCameras()) { + out.Key("cameras"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumCameras; ++n) { + Write(out, *ai.mCameras[n]); + } + out.EndArray(); + } + + if (ai.HasTextures()) { + out.Key("textures"); + out.StartArray(); + for (unsigned int n = 0; n < ai.mNumTextures; ++n) { + Write(out, *ai.mTextures[n]); + } + out.EndArray(); + } + out.EndObj(); +} + + +void ExportAssimp2Json(const char* file, Assimp::IOSystem* io, const aiScene* scene, const Assimp::ExportProperties*) { + std::unique_ptr str(io->Open(file, "wt")); + if (!str) { + //throw Assimp::DeadlyExportError("could not open output file"); + } + + // get a copy of the scene so we can modify it + aiScene* scenecopy_tmp; + aiCopyScene(scene, &scenecopy_tmp); + + try { + // split meshes so they fit into a 16 bit index buffer + MeshSplitter splitter; + splitter.SetLimit(1 << 16); + splitter.Execute(scenecopy_tmp); + + // XXX Flag_WriteSpecialFloats is turned on by default, right now we don't have a configuration interface for exporters + JSONWriter s(*str, JSONWriter::Flag_WriteSpecialFloats); + Write(s, *scenecopy_tmp); + + } + catch (...) { + aiFreeScene(scenecopy_tmp); + throw; + } + aiFreeScene(scenecopy_tmp); +} + +} + +#endif // ASSIMP_BUILD_NO_ASSJSON_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/thirdparty/assimp/code/Assjson/mesh_splitter.cpp b/thirdparty/assimp/code/Assjson/mesh_splitter.cpp new file mode 100644 index 0000000..24385f9 --- /dev/null +++ b/thirdparty/assimp/code/Assjson/mesh_splitter.cpp @@ -0,0 +1,320 @@ +/* +Assimp2Json +Copyright (c) 2011, Alexander C. Gessler + +Licensed under a 3-clause BSD license. See the LICENSE file for more information. + +*/ + +#include "mesh_splitter.h" + +#include + +// ---------------------------------------------------------------------------- +// Note: this is largely based on assimp's SplitLargeMeshes_Vertex process. +// it is refactored and the coding style is slightly improved, though. +// ---------------------------------------------------------------------------- + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void MeshSplitter::Execute( aiScene* pScene) { + std::vector > source_mesh_map; + + for( unsigned int a = 0; a < pScene->mNumMeshes; a++) { + SplitMesh(a, pScene->mMeshes[a],source_mesh_map); + } + + const unsigned int size = static_cast(source_mesh_map.size()); + if (size != pScene->mNumMeshes) { + // it seems something has been split. rebuild the mesh list + delete[] pScene->mMeshes; + pScene->mNumMeshes = size; + pScene->mMeshes = new aiMesh*[size](); + + for (unsigned int i = 0; i < size;++i) { + pScene->mMeshes[i] = source_mesh_map[i].first; + } + + // now we need to update all nodes + UpdateNode(pScene->mRootNode,source_mesh_map); + } +} + + +// ------------------------------------------------------------------------------------------------ +void MeshSplitter::UpdateNode(aiNode* pcNode, const std::vector >& source_mesh_map) { + // TODO: should better use std::(multi)set for source_mesh_map. + + // for every index in out list build a new entry + std::vector aiEntries; + aiEntries.reserve(pcNode->mNumMeshes + 1); + for (unsigned int i = 0; i < pcNode->mNumMeshes;++i) { + for (unsigned int a = 0, end = static_cast(source_mesh_map.size()); a < end;++a) { + if (source_mesh_map[a].second == pcNode->mMeshes[i]) { + aiEntries.push_back(a); + } + } + } + + // now build the new list + delete pcNode->mMeshes; + pcNode->mNumMeshes = static_cast(aiEntries.size()); + pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes]; + + for (unsigned int b = 0; b < pcNode->mNumMeshes;++b) { + pcNode->mMeshes[b] = aiEntries[b]; + } + + // recursively update children + for (unsigned int i = 0, end = pcNode->mNumChildren; i < end;++i) { + UpdateNode ( pcNode->mChildren[i], source_mesh_map ); + } + return; +} + +#define WAS_NOT_COPIED 0xffffffff + +typedef std::pair PerVertexWeight; +typedef std::vector VertexWeightTable; + +// ------------------------------------------------------------------------------------------------ +VertexWeightTable* ComputeVertexBoneWeightTable(const aiMesh* pMesh) { + if (!pMesh || !pMesh->mNumVertices || !pMesh->mNumBones) { + return nullptr; + } + + VertexWeightTable* const avPerVertexWeights = new VertexWeightTable[pMesh->mNumVertices]; + for (unsigned int i = 0; i < pMesh->mNumBones;++i) { + + aiBone* bone = pMesh->mBones[i]; + for (unsigned int a = 0; a < bone->mNumWeights;++a) { + const aiVertexWeight& weight = bone->mWeights[a]; + avPerVertexWeights[weight.mVertexId].push_back( std::make_pair(i,weight.mWeight) ); + } + } + return avPerVertexWeights; +} + +// ------------------------------------------------------------------------------------------------ +void MeshSplitter :: SplitMesh(unsigned int a, aiMesh* in_mesh, std::vector >& source_mesh_map) { + // TODO: should better use std::(multi)set for source_mesh_map. + + if (in_mesh->mNumVertices <= LIMIT) { + source_mesh_map.push_back(std::make_pair(in_mesh,a)); + return; + } + + // build a per-vertex weight list if necessary + VertexWeightTable* avPerVertexWeights = ComputeVertexBoneWeightTable(in_mesh); + + // we need to split this mesh into sub meshes. Estimate submesh size + const unsigned int sub_meshes = (in_mesh->mNumVertices / LIMIT) + 1; + + // create a std::vector to remember which vertices have already + // been copied and to which position (i.e. output index) + std::vector was_copied_to; + was_copied_to.resize(in_mesh->mNumVertices,WAS_NOT_COPIED); + + // Try to find a good estimate for the number of output faces + // per mesh. Add 12.5% as buffer + unsigned int size_estimated = in_mesh->mNumFaces / sub_meshes; + size_estimated += size_estimated / 8; + + // now generate all submeshes + unsigned int base = 0; + while (true) { + const unsigned int out_vertex_index = LIMIT; + + aiMesh* out_mesh = new aiMesh(); + out_mesh->mNumVertices = 0; + out_mesh->mMaterialIndex = in_mesh->mMaterialIndex; + + // the name carries the adjacency information between the meshes + out_mesh->mName = in_mesh->mName; + + typedef std::vector BoneWeightList; + if (in_mesh->HasBones()) { + out_mesh->mBones = new aiBone*[in_mesh->mNumBones](); + } + + // clear the temporary helper array + if (base) { + std::fill(was_copied_to.begin(), was_copied_to.end(), WAS_NOT_COPIED); + } + + std::vector vFaces; + + // reserve enough storage for most cases + if (in_mesh->HasPositions()) { + out_mesh->mVertices = new aiVector3D[out_vertex_index]; + } + + if (in_mesh->HasNormals()) { + out_mesh->mNormals = new aiVector3D[out_vertex_index]; + } + + if (in_mesh->HasTangentsAndBitangents()) { + out_mesh->mTangents = new aiVector3D[out_vertex_index]; + out_mesh->mBitangents = new aiVector3D[out_vertex_index]; + } + + for (unsigned int c = 0; in_mesh->HasVertexColors(c);++c) { + out_mesh->mColors[c] = new aiColor4D[out_vertex_index]; + } + + for (unsigned int c = 0; in_mesh->HasTextureCoords(c);++c) { + out_mesh->mNumUVComponents[c] = in_mesh->mNumUVComponents[c]; + out_mesh->mTextureCoords[c] = new aiVector3D[out_vertex_index]; + } + vFaces.reserve(size_estimated); + + // (we will also need to copy the array of indices) + while (base < in_mesh->mNumFaces) { + const unsigned int iNumIndices = in_mesh->mFaces[base].mNumIndices; + + // doesn't catch degenerates but is quite fast + unsigned int iNeed = 0; + for (unsigned int v = 0; v < iNumIndices;++v) { + unsigned int index = in_mesh->mFaces[base].mIndices[v]; + + // check whether we do already have this vertex + if (WAS_NOT_COPIED == was_copied_to[index]) { + iNeed++; + } + } + if (out_mesh->mNumVertices + iNeed > out_vertex_index) { + // don't use this face + break; + } + + vFaces.push_back(aiFace()); + aiFace& rFace = vFaces.back(); + + // setup face type and number of indices + rFace.mNumIndices = iNumIndices; + rFace.mIndices = new unsigned int[iNumIndices]; + + // need to update the output primitive types + switch (rFace.mNumIndices) + { + case 1: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + case 2: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + case 3: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + default: + out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + } + + // and copy the contents of the old array, offset them by current base + for (unsigned int v = 0; v < iNumIndices;++v) { + const unsigned int index = in_mesh->mFaces[base].mIndices[v]; + + // check whether we do already have this vertex + if (WAS_NOT_COPIED != was_copied_to[index]) { + rFace.mIndices[v] = was_copied_to[index]; + continue; + } + + // copy positions + out_mesh->mVertices[out_mesh->mNumVertices] = (in_mesh->mVertices[index]); + + // copy normals + if (in_mesh->HasNormals()) { + out_mesh->mNormals[out_mesh->mNumVertices] = (in_mesh->mNormals[index]); + } + + // copy tangents/bi-tangents + if (in_mesh->HasTangentsAndBitangents()) { + out_mesh->mTangents[out_mesh->mNumVertices] = (in_mesh->mTangents[index]); + out_mesh->mBitangents[out_mesh->mNumVertices] = (in_mesh->mBitangents[index]); + } + + // texture coordinates + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) { + if (in_mesh->HasTextureCoords( c)) { + out_mesh->mTextureCoords[c][out_mesh->mNumVertices] = in_mesh->mTextureCoords[c][index]; + } + } + // vertex colors + for (unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS;++c) { + if (in_mesh->HasVertexColors( c)) { + out_mesh->mColors[c][out_mesh->mNumVertices] = in_mesh->mColors[c][index]; + } + } + // check whether we have bone weights assigned to this vertex + rFace.mIndices[v] = out_mesh->mNumVertices; + if (avPerVertexWeights) { + VertexWeightTable& table = avPerVertexWeights[ out_mesh->mNumVertices ]; + for (VertexWeightTable::const_iterator iter = table.begin(), end = table.end(); iter != end;++iter) { + // allocate the bone weight array if necessary and store it in the mBones field (HACK!) + BoneWeightList* weight_list = reinterpret_cast(out_mesh->mBones[(*iter).first]); + if (!weight_list) { + weight_list = new BoneWeightList(); + out_mesh->mBones[(*iter).first] = reinterpret_cast(weight_list); + } + weight_list->push_back(aiVertexWeight(out_mesh->mNumVertices,(*iter).second)); + } + } + + was_copied_to[index] = out_mesh->mNumVertices; + out_mesh->mNumVertices++; + } + base++; + if(out_mesh->mNumVertices == out_vertex_index) { + // break here. The face is only added if it was complete + break; + } + } + + // check which bones we'll need to create for this submesh + if (in_mesh->HasBones()) { + aiBone** ppCurrent = out_mesh->mBones; + for (unsigned int k = 0; k < in_mesh->mNumBones;++k) { + // check whether the bone exists + BoneWeightList* const weight_list = reinterpret_cast(out_mesh->mBones[k]); + + if (weight_list) { + const aiBone* const bone_in = in_mesh->mBones[k]; + aiBone* const bone_out = new aiBone(); + *ppCurrent++ = bone_out; + bone_out->mName = aiString(bone_in->mName); + bone_out->mOffsetMatrix =bone_in->mOffsetMatrix; + bone_out->mNumWeights = (unsigned int)weight_list->size(); + bone_out->mWeights = new aiVertexWeight[bone_out->mNumWeights]; + + // copy the vertex weights + ::memcpy(bone_out->mWeights, &(*weight_list)[0],bone_out->mNumWeights * sizeof(aiVertexWeight)); + + delete weight_list; + out_mesh->mNumBones++; + } + } + } + + // copy the face list to the mesh + out_mesh->mFaces = new aiFace[vFaces.size()]; + out_mesh->mNumFaces = (unsigned int)vFaces.size(); + + for (unsigned int p = 0; p < out_mesh->mNumFaces;++p) { + out_mesh->mFaces[p] = vFaces[p]; + } + + // add the newly created mesh to the list + source_mesh_map.push_back(std::make_pair(out_mesh,a)); + + if (base == in_mesh->mNumFaces) { + break; + } + } + + // delete the per-vertex weight list again + delete[] avPerVertexWeights; + + // now delete the old mesh data + delete in_mesh; +} diff --git a/thirdparty/assimp/code/Assjson/mesh_splitter.h b/thirdparty/assimp/code/Assjson/mesh_splitter.h new file mode 100644 index 0000000..326f73b --- /dev/null +++ b/thirdparty/assimp/code/Assjson/mesh_splitter.h @@ -0,0 +1,61 @@ +/* +Assimp2Json +Copyright (c) 2011, Alexander C. Gessler + +Licensed under a 3-clause BSD license. See the LICENSE file for more information. + +*/ + +#ifndef INCLUDED_MESH_SPLITTER +#define INCLUDED_MESH_SPLITTER + +// ---------------------------------------------------------------------------- +// Note: this is largely based on assimp's SplitLargeMeshes_Vertex process. +// it is refactored and the coding style is slightly improved, though. +// ---------------------------------------------------------------------------- + +#include + +struct aiScene; +struct aiMesh; +struct aiNode; + +// --------------------------------------------------------------------------- +/** Splits meshes of unique vertices into meshes with no more vertices than + * a given, configurable threshold value. + */ +class MeshSplitter +{ + +public: + + void SetLimit(unsigned int l) { + LIMIT = l; + } + + unsigned int GetLimit() const { + return LIMIT; + } + +public: + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + + +private: + + void UpdateNode(aiNode* pcNode, const std::vector >& source_mesh_map); + void SplitMesh (unsigned int index, aiMesh* mesh, std::vector >& source_mesh_map); + +public: + + unsigned int LIMIT; +}; + +#endif // INCLUDED_MESH_SPLITTER + diff --git a/thirdparty/assimp/code/Assxml/AssxmlExporter.cpp b/thirdparty/assimp/code/Assxml/AssxmlExporter.cpp new file mode 100644 index 0000000..afdecba --- /dev/null +++ b/thirdparty/assimp/code/Assxml/AssxmlExporter.cpp @@ -0,0 +1,655 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +/** @file AssxmlExporter.cpp + * ASSXML exporter main code + */ + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER + +#include "PostProcessing/ProcessHelper.h" + +#include +#include +#include +#include + +#include + +#ifdef ASSIMP_BUILD_NO_OWN_ZLIB +# include +#else +# include +#endif + +#include +#include + +using namespace Assimp; + +namespace Assimp { + +namespace AssxmlExport { + +// ----------------------------------------------------------------------------------- +static int ioprintf( IOStream * io, const char *format, ... ) { + using namespace std; + if ( nullptr == io ) { + return -1; + } + + static const int Size = 4096; + char sz[ Size ]; + ::memset( sz, '\0', Size ); + va_list va; + va_start( va, format ); + const unsigned int nSize = vsnprintf( sz, Size-1, format, va ); + ai_assert( nSize < Size ); + va_end( va ); + + io->Write( sz, sizeof(char), nSize ); + + return nSize; +} + +// ----------------------------------------------------------------------------------- +// Convert a name to standard XML format +static void ConvertName(aiString& out, const aiString& in) { + out.length = 0; + for (unsigned int i = 0; i < in.length; ++i) { + switch (in.data[i]) { + case '<': + out.Append("<");break; + case '>': + out.Append(">");break; + case '&': + out.Append("&");break; + case '\"': + out.Append(""");break; + case '\'': + out.Append("'");break; + default: + out.data[out.length++] = in.data[i]; + } + } + out.data[out.length] = 0; +} + +// ----------------------------------------------------------------------------------- +// Write a single node as text dump +static void WriteNode(const aiNode* node, IOStream * io, unsigned int depth) { + char prefix[512]; + for (unsigned int i = 0; i < depth;++i) + prefix[i] = '\t'; + prefix[depth] = '\0'; + + const aiMatrix4x4& m = node->mTransformation; + + aiString name; + ConvertName(name,node->mName); + ioprintf(io,"%s \n" + "%s\t \n" + "%s\t\t%0 6f %0 6f %0 6f %0 6f\n" + "%s\t\t%0 6f %0 6f %0 6f %0 6f\n" + "%s\t\t%0 6f %0 6f %0 6f %0 6f\n" + "%s\t\t%0 6f %0 6f %0 6f %0 6f\n" + "%s\t \n", + prefix,name.data,prefix, + prefix,m.a1,m.a2,m.a3,m.a4, + prefix,m.b1,m.b2,m.b3,m.b4, + prefix,m.c1,m.c2,m.c3,m.c4, + prefix,m.d1,m.d2,m.d3,m.d4,prefix); + + if (node->mNumMeshes) { + ioprintf(io, "%s\t\n%s\t", + prefix,node->mNumMeshes,prefix); + + for (unsigned int i = 0; i < node->mNumMeshes;++i) { + ioprintf(io,"%i ",node->mMeshes[i]); + } + ioprintf(io,"\n%s\t\n",prefix); + } + + if (node->mNumChildren) { + ioprintf(io,"%s\t\n", + prefix,node->mNumChildren); + + for (unsigned int i = 0; i < node->mNumChildren;++i) { + WriteNode(node->mChildren[i],io,depth+2); + } + ioprintf(io,"%s\t\n",prefix); + } + ioprintf(io,"%s\n",prefix); +} + + +// ----------------------------------------------------------------------------------- +// Some chuncks of text will need to be encoded for XML +// http://stackoverflow.com/questions/5665231/most-efficient-way-to-escape-xml-html-in-c-string#5665377 +static std::string encodeXML(const std::string& data) { + std::string buffer; + buffer.reserve(data.size()); + for(size_t pos = 0; pos != data.size(); ++pos) { + switch(data[pos]) { + case '&': buffer.append("&"); break; + case '\"': buffer.append("""); break; + case '\'': buffer.append("'"); break; + case '<': buffer.append("<"); break; + case '>': buffer.append(">"); break; + default: buffer.append(&data[pos], 1); break; + } + } + return buffer; +} + +// ----------------------------------------------------------------------------------- +// Write a text model dump +static +void WriteDump(const aiScene* scene, IOStream* io, bool shortened) { + time_t tt = ::time( NULL ); +#if _WIN32 + tm* p = gmtime(&tt); +#else + struct tm now; + tm* p = gmtime_r(&tt, &now); +#endif + ai_assert(nullptr != p); + + // write header + std::string header( + "\n" + "\n\n" + "" + " \n\n" + "\n" + ); + + const unsigned int majorVersion( aiGetVersionMajor() ); + const unsigned int minorVersion( aiGetVersionMinor() ); + const unsigned int rev( aiGetVersionRevision() ); + const char *curtime( asctime( p ) ); + ioprintf( io, header.c_str(), majorVersion, minorVersion, rev, curtime, scene->mFlags, 0 ); + + // write the node graph + WriteNode(scene->mRootNode, io, 0); + +#if 0 + // write cameras + for (unsigned int i = 0; i < scene->mNumCameras;++i) { + aiCamera* cam = scene->mCameras[i]; + ConvertName(name,cam->mName); + + // camera header + ioprintf(io,"\t\n" + "\t\t %0 8f %0 8f %0 8f \n" + "\t\t %0 8f %0 8f %0 8f \n" + "\t\t %0 8f %0 8f %0 8f \n" + "\t\t %f \n" + "\t\t %f \n" + "\t\t %f \n" + "\t\t %f \n" + "\t\n", + name.data, + cam->mUp.x,cam->mUp.y,cam->mUp.z, + cam->mLookAt.x,cam->mLookAt.y,cam->mLookAt.z, + cam->mPosition.x,cam->mPosition.y,cam->mPosition.z, + cam->mHorizontalFOV,cam->mAspect,cam->mClipPlaneNear,cam->mClipPlaneFar,i); + } + + // write lights + for (unsigned int i = 0; i < scene->mNumLights;++i) { + aiLight* l = scene->mLights[i]; + ConvertName(name,l->mName); + + // light header + ioprintf(io,"\t type=\"%s\"\n" + "\t\t %0 8f %0 8f %0 8f \n" + "\t\t %0 8f %0 8f %0 8f \n" + "\t\t %0 8f %0 8f %0 8f \n", + name.data, + (l->mType == aiLightSource_DIRECTIONAL ? "directional" : + (l->mType == aiLightSource_POINT ? "point" : "spot" )), + l->mColorDiffuse.r, l->mColorDiffuse.g, l->mColorDiffuse.b, + l->mColorSpecular.r,l->mColorSpecular.g,l->mColorSpecular.b, + l->mColorAmbient.r, l->mColorAmbient.g, l->mColorAmbient.b); + + if (l->mType != aiLightSource_DIRECTIONAL) { + ioprintf(io, + "\t\t %0 8f %0 8f %0 8f \n" + "\t\t %f \n" + "\t\t %f \n" + "\t\t %f \n", + l->mPosition.x,l->mPosition.y,l->mPosition.z, + l->mAttenuationConstant,l->mAttenuationLinear,l->mAttenuationQuadratic); + } + + if (l->mType != aiLightSource_POINT) { + ioprintf(io, + "\t\t %0 8f %0 8f %0 8f \n", + l->mDirection.x,l->mDirection.y,l->mDirection.z); + } + + if (l->mType == aiLightSource_SPOT) { + ioprintf(io, + "\t\t %f \n" + "\t\t %f \n", + l->mAngleOuterCone,l->mAngleInnerCone); + } + ioprintf(io,"\t\n"); + } +#endif + aiString name; + + // write textures + if (scene->mNumTextures) { + ioprintf(io,"\n",scene->mNumTextures); + for (unsigned int i = 0; i < scene->mNumTextures;++i) { + aiTexture* tex = scene->mTextures[i]; + bool compressed = (tex->mHeight == 0); + + // mesh header + ioprintf(io,"\t \n", + (compressed ? -1 : tex->mWidth),(compressed ? -1 : tex->mHeight), + (compressed ? "true" : "false")); + + if (compressed) { + ioprintf(io,"\t\t \n",tex->mWidth); + + if (!shortened) { + for (unsigned int n = 0; n < tex->mWidth;++n) { + ioprintf(io,"\t\t\t%2x",reinterpret_cast(tex->pcData)[n]); + if (n && !(n % 50)) { + ioprintf(io,"\n"); + } + } + } + } + else if (!shortened){ + ioprintf(io,"\t\t \n",tex->mWidth*tex->mHeight*4); + + // const unsigned int width = (unsigned int)std::log10((double)std::max(tex->mHeight,tex->mWidth))+1; + for (unsigned int y = 0; y < tex->mHeight;++y) { + for (unsigned int x = 0; x < tex->mWidth;++x) { + aiTexel* tx = tex->pcData + y*tex->mWidth+x; + unsigned int r = tx->r,g=tx->g,b=tx->b,a=tx->a; + ioprintf(io,"\t\t\t%2x %2x %2x %2x",r,g,b,a); + + // group by four for readability + if ( 0 == ( x + y*tex->mWidth ) % 4 ) { + ioprintf( io, "\n" ); + } + } + } + } + ioprintf(io,"\t\t\n\t\n"); + } + ioprintf(io,"\n"); + } + + // write materials + if (scene->mNumMaterials) { + ioprintf(io,"\n",scene->mNumMaterials); + for (unsigned int i = 0; i< scene->mNumMaterials; ++i) { + const aiMaterial* mat = scene->mMaterials[i]; + + ioprintf(io,"\t\n"); + ioprintf(io,"\t\t\n",mat->mNumProperties); + for (unsigned int n = 0; n < mat->mNumProperties;++n) { + + const aiMaterialProperty* prop = mat->mProperties[n]; + const char* sz = ""; + if (prop->mType == aiPTI_Float) { + sz = "float"; + } + else if (prop->mType == aiPTI_Integer) { + sz = "integer"; + } + else if (prop->mType == aiPTI_String) { + sz = "string"; + } + else if (prop->mType == aiPTI_Buffer) { + sz = "binary_buffer"; + } + + ioprintf(io,"\t\t\tmKey.data, sz, + ::TextureTypeToString((aiTextureType)prop->mSemantic),prop->mIndex); + + if (prop->mType == aiPTI_Float) { + ioprintf(io," size=\"%i\">\n\t\t\t\t", + static_cast(prop->mDataLength/sizeof(float))); + + for (unsigned int p = 0; p < prop->mDataLength/sizeof(float);++p) { + ioprintf(io,"%f ",*((float*)(prop->mData+p*sizeof(float)))); + } + } + else if (prop->mType == aiPTI_Integer) { + ioprintf(io," size=\"%i\">\n\t\t\t\t", + static_cast(prop->mDataLength/sizeof(int))); + + for (unsigned int p = 0; p < prop->mDataLength/sizeof(int);++p) { + ioprintf(io,"%i ",*((int*)(prop->mData+p*sizeof(int)))); + } + } + else if (prop->mType == aiPTI_Buffer) { + ioprintf(io," size=\"%i\">\n\t\t\t\t", + static_cast(prop->mDataLength)); + + for (unsigned int p = 0; p < prop->mDataLength;++p) { + ioprintf(io,"%2x ",prop->mData[p]); + if (p && 0 == p%30) { + ioprintf(io,"\n\t\t\t\t"); + } + } + } + else if (prop->mType == aiPTI_String) { + ioprintf(io,">\n\t\t\t\t\"%s\"",encodeXML(prop->mData+4).c_str() /* skip length */); + } + ioprintf(io,"\n\t\t\t\n"); + } + ioprintf(io,"\t\t\n"); + ioprintf(io,"\t\n"); + } + ioprintf(io,"\n"); + } + + // write animations + if (scene->mNumAnimations) { + ioprintf(io,"\n",scene->mNumAnimations); + for (unsigned int i = 0; i < scene->mNumAnimations;++i) { + aiAnimation* anim = scene->mAnimations[i]; + + // anim header + ConvertName(name,anim->mName); + ioprintf(io,"\t\n", + name.data, anim->mDuration, anim->mTicksPerSecond); + + // write bone animation channels + if (anim->mNumChannels) { + ioprintf(io,"\t\t\n",anim->mNumChannels); + for (unsigned int n = 0; n < anim->mNumChannels;++n) { + aiNodeAnim* nd = anim->mChannels[n]; + + // node anim header + ConvertName(name,nd->mNodeName); + ioprintf(io,"\t\t\t\n",name.data); + + if (!shortened) { + // write position keys + if (nd->mNumPositionKeys) { + ioprintf(io,"\t\t\t\t\n",nd->mNumPositionKeys); + for (unsigned int a = 0; a < nd->mNumPositionKeys;++a) { + aiVectorKey* vc = nd->mPositionKeys+a; + ioprintf(io,"\t\t\t\t\t\n" + "\t\t\t\t\t\t%0 8f %0 8f %0 8f\n\t\t\t\t\t\n", + vc->mTime,vc->mValue.x,vc->mValue.y,vc->mValue.z); + } + ioprintf(io,"\t\t\t\t\n"); + } + + // write scaling keys + if (nd->mNumScalingKeys) { + ioprintf(io,"\t\t\t\t\n",nd->mNumScalingKeys); + for (unsigned int a = 0; a < nd->mNumScalingKeys;++a) { + aiVectorKey* vc = nd->mScalingKeys+a; + ioprintf(io,"\t\t\t\t\t\n" + "\t\t\t\t\t\t%0 8f %0 8f %0 8f\n\t\t\t\t\t\n", + vc->mTime,vc->mValue.x,vc->mValue.y,vc->mValue.z); + } + ioprintf(io,"\t\t\t\t\n"); + } + + // write rotation keys + if (nd->mNumRotationKeys) { + ioprintf(io,"\t\t\t\t\n",nd->mNumRotationKeys); + for (unsigned int a = 0; a < nd->mNumRotationKeys;++a) { + aiQuatKey* vc = nd->mRotationKeys+a; + ioprintf(io,"\t\t\t\t\t\n" + "\t\t\t\t\t\t%0 8f %0 8f %0 8f %0 8f\n\t\t\t\t\t\n", + vc->mTime,vc->mValue.x,vc->mValue.y,vc->mValue.z,vc->mValue.w); + } + ioprintf(io,"\t\t\t\t\n"); + } + } + ioprintf(io,"\t\t\t\n"); + } + ioprintf(io,"\t\t\n"); + } + ioprintf(io,"\t\n"); + } + ioprintf(io,"\n"); + } + + // write meshes + if (scene->mNumMeshes) { + ioprintf(io,"\n",scene->mNumMeshes); + for (unsigned int i = 0; i < scene->mNumMeshes;++i) { + aiMesh* mesh = scene->mMeshes[i]; + // const unsigned int width = (unsigned int)std::log10((double)mesh->mNumVertices)+1; + + // mesh header + ioprintf(io,"\t\n", + (mesh->mPrimitiveTypes & aiPrimitiveType_POINT ? "points" : ""), + (mesh->mPrimitiveTypes & aiPrimitiveType_LINE ? "lines" : ""), + (mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE ? "triangles" : ""), + (mesh->mPrimitiveTypes & aiPrimitiveType_POLYGON ? "polygons" : ""), + mesh->mMaterialIndex); + + // bones + if (mesh->mNumBones) { + ioprintf(io,"\t\t\n",mesh->mNumBones); + + for (unsigned int n = 0; n < mesh->mNumBones;++n) { + aiBone* bone = mesh->mBones[n]; + + ConvertName(name,bone->mName); + // bone header + ioprintf(io,"\t\t\t\n" + "\t\t\t\t \n" + "\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n" + "\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n" + "\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n" + "\t\t\t\t\t%0 6f %0 6f %0 6f %0 6f\n" + "\t\t\t\t \n", + name.data, + bone->mOffsetMatrix.a1,bone->mOffsetMatrix.a2,bone->mOffsetMatrix.a3,bone->mOffsetMatrix.a4, + bone->mOffsetMatrix.b1,bone->mOffsetMatrix.b2,bone->mOffsetMatrix.b3,bone->mOffsetMatrix.b4, + bone->mOffsetMatrix.c1,bone->mOffsetMatrix.c2,bone->mOffsetMatrix.c3,bone->mOffsetMatrix.c4, + bone->mOffsetMatrix.d1,bone->mOffsetMatrix.d2,bone->mOffsetMatrix.d3,bone->mOffsetMatrix.d4); + + if (!shortened && bone->mNumWeights) { + ioprintf(io,"\t\t\t\t\n",bone->mNumWeights); + + // bone weights + for (unsigned int a = 0; a < bone->mNumWeights;++a) { + aiVertexWeight* wght = bone->mWeights+a; + + ioprintf(io,"\t\t\t\t\t\n\t\t\t\t\t\t%f\n\t\t\t\t\t\n", + wght->mVertexId,wght->mWeight); + } + ioprintf(io,"\t\t\t\t\n"); + } + ioprintf(io,"\t\t\t\n"); + } + ioprintf(io,"\t\t\n"); + } + + // faces + if (!shortened && mesh->mNumFaces) { + ioprintf(io,"\t\t\n",mesh->mNumFaces); + for (unsigned int n = 0; n < mesh->mNumFaces; ++n) { + aiFace& f = mesh->mFaces[n]; + ioprintf(io,"\t\t\t\n" + "\t\t\t\t",f.mNumIndices); + + for (unsigned int j = 0; j < f.mNumIndices;++j) + ioprintf(io,"%i ",f.mIndices[j]); + + ioprintf(io,"\n\t\t\t\n"); + } + ioprintf(io,"\t\t\n"); + } + + // vertex positions + if (mesh->HasPositions()) { + ioprintf(io,"\t\t \n",mesh->mNumVertices); + if (!shortened) { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io,"\t\t%0 8f %0 8f %0 8f\n", + mesh->mVertices[n].x, + mesh->mVertices[n].y, + mesh->mVertices[n].z); + } + } + ioprintf(io,"\t\t\n"); + } + + // vertex normals + if (mesh->HasNormals()) { + ioprintf(io,"\t\t \n",mesh->mNumVertices); + if (!shortened) { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io,"\t\t%0 8f %0 8f %0 8f\n", + mesh->mNormals[n].x, + mesh->mNormals[n].y, + mesh->mNormals[n].z); + } + } + ioprintf(io,"\t\t\n"); + } + + // vertex tangents and bitangents + if (mesh->HasTangentsAndBitangents()) { + ioprintf(io,"\t\t \n",mesh->mNumVertices); + if (!shortened) { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io,"\t\t%0 8f %0 8f %0 8f\n", + mesh->mTangents[n].x, + mesh->mTangents[n].y, + mesh->mTangents[n].z); + } + } + ioprintf(io,"\t\t\n"); + + ioprintf(io,"\t\t \n",mesh->mNumVertices); + if (!shortened) { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io,"\t\t%0 8f %0 8f %0 8f\n", + mesh->mBitangents[n].x, + mesh->mBitangents[n].y, + mesh->mBitangents[n].z); + } + } + ioprintf(io,"\t\t\n"); + } + + // texture coordinates + for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) { + if (!mesh->mTextureCoords[a]) + break; + + ioprintf(io,"\t\t \n",mesh->mNumVertices, + a,mesh->mNumUVComponents[a]); + + if (!shortened) { + if (mesh->mNumUVComponents[a] == 3) { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io,"\t\t%0 8f %0 8f %0 8f\n", + mesh->mTextureCoords[a][n].x, + mesh->mTextureCoords[a][n].y, + mesh->mTextureCoords[a][n].z); + } + } + else { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io,"\t\t%0 8f %0 8f\n", + mesh->mTextureCoords[a][n].x, + mesh->mTextureCoords[a][n].y); + } + } + } + ioprintf(io,"\t\t\n"); + } + + // vertex colors + for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) { + if (!mesh->mColors[a]) + break; + ioprintf(io,"\t\t \n",mesh->mNumVertices,a); + if (!shortened) { + for (unsigned int n = 0; n < mesh->mNumVertices; ++n) { + ioprintf(io,"\t\t%0 8f %0 8f %0 8f %0 8f\n", + mesh->mColors[a][n].r, + mesh->mColors[a][n].g, + mesh->mColors[a][n].b, + mesh->mColors[a][n].a); + } + } + ioprintf(io,"\t\t\n"); + } + ioprintf(io,"\t\n"); + } + ioprintf(io,"\n"); + } + ioprintf(io,"\n"); +} + +} // end of namespace AssxmlExport + +void ExportSceneAssxml(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) +{ + IOStream * out = pIOSystem->Open( pFile, "wt" ); + if (!out) return; + + bool shortened = false; + AssxmlExport::WriteDump( pScene, out, shortened ); + + pIOSystem->Close( out ); +} + +} // end of namespace Assimp + +#endif // ASSIMP_BUILD_NO_ASSXML_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/thirdparty/assimp/code/Assxml/AssxmlExporter.h b/thirdparty/assimp/code/Assxml/AssxmlExporter.h new file mode 100644 index 0000000..8ca887e --- /dev/null +++ b/thirdparty/assimp/code/Assxml/AssxmlExporter.h @@ -0,0 +1,51 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file AssxmlExporter.h + * ASSXML Exporter Main Header + */ +#ifndef AI_ASSXMLEXPORTER_H_INC +#define AI_ASSXMLEXPORTER_H_INC + +// nothing really needed here - reserved for future use like properties + +#endif diff --git a/thirdparty/assimp/code/B3D/B3DImporter.cpp b/thirdparty/assimp/code/B3D/B3DImporter.cpp new file mode 100644 index 0000000..ba484ca --- /dev/null +++ b/thirdparty/assimp/code/B3D/B3DImporter.cpp @@ -0,0 +1,740 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/** @file B3DImporter.cpp + * @brief Implementation of the b3d importer class + */ + + +#ifndef ASSIMP_BUILD_NO_B3D_IMPORTER + +// internal headers +#include "B3D/B3DImporter.h" +#include "PostProcessing/TextureTransform.h" +#include "PostProcessing/ConvertToLHProcess.h" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace Assimp; +using namespace std; + +static const aiImporterDesc desc = { + "BlitzBasic 3D Importer", + "", + "", + "http://www.blitzbasic.com/", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "b3d" +}; + +// (fixme, Aramis) quick workaround to get rid of all those signed to unsigned warnings +#ifdef _MSC_VER +# pragma warning (disable: 4018) +#endif + +//#define DEBUG_B3D + +template +void DeleteAllBarePointers(std::vector& x) +{ + for(auto p : x) + { + delete p; + } +} + +B3DImporter::~B3DImporter() +{ +} + +// ------------------------------------------------------------------------------------------------ +bool B3DImporter::CanRead( const std::string& pFile, IOSystem* /*pIOHandler*/, bool /*checkSig*/) const{ + + size_t pos=pFile.find_last_of( '.' ); + if( pos==string::npos ) return false; + + string ext=pFile.substr( pos+1 ); + if( ext.size()!=3 ) return false; + + return (ext[0]=='b' || ext[0]=='B') && (ext[1]=='3') && (ext[2]=='d' || ext[2]=='D'); +} + +// ------------------------------------------------------------------------------------------------ +// Loader meta information +const aiImporterDesc* B3DImporter::GetInfo () const +{ + return &desc; +} + +#ifdef DEBUG_B3D + extern "C"{ void _stdcall AllocConsole(); } +#endif +// ------------------------------------------------------------------------------------------------ +void B3DImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler){ + +#ifdef DEBUG_B3D + AllocConsole(); + freopen( "conin$","r",stdin ); + freopen( "conout$","w",stdout ); + freopen( "conout$","w",stderr ); + cout<<"Hello world from the B3DImporter!"< file( pIOHandler->Open( pFile)); + + // Check whether we can read from the file + if( file.get() == NULL) + throw DeadlyImportError( "Failed to open B3D file " + pFile + "."); + + // check whether the .b3d file is large enough to contain + // at least one chunk. + size_t fileSize = file->FileSize(); + if( fileSize<8 ) throw DeadlyImportError( "B3D File is too small."); + + _pos=0; + _buf.resize( fileSize ); + file->Read( &_buf[0],1,fileSize ); + _stack.clear(); + + ReadBB3D( pScene ); +} + +// ------------------------------------------------------------------------------------------------ +AI_WONT_RETURN void B3DImporter::Oops(){ + throw DeadlyImportError( "B3D Importer - INTERNAL ERROR" ); +} + +// ------------------------------------------------------------------------------------------------ +AI_WONT_RETURN void B3DImporter::Fail( string str ){ +#ifdef DEBUG_B3D + cout<<"Error in B3D file data: "< +T *B3DImporter::to_array( const vector &v ){ + if( v.empty() ) { + return 0; + } + T *p=new T[ v.size() ]; + for( size_t i=0;i +T **unique_to_array( vector > &v ){ + if( v.empty() ) { + return 0; + } + T **p = new T*[ v.size() ]; + for( size_t i = 0; i < v.size(); ++i ){ + p[i] = v[i].release(); + } + return p; +} + + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadTEXS(){ + while( ChunkSize() ){ + string name=ReadString(); + /*int flags=*/ReadInt(); + /*int blend=*/ReadInt(); + /*aiVector2D pos=*/ReadVec2(); + /*aiVector2D scale=*/ReadVec2(); + /*float rot=*/ReadFloat(); + + _textures.push_back( name ); + } +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadBRUS(){ + int n_texs=ReadInt(); + if( n_texs<0 || n_texs>8 ){ + Fail( "Bad texture count" ); + } + while( ChunkSize() ){ + string name=ReadString(); + aiVector3D color=ReadVec3(); + float alpha=ReadFloat(); + float shiny=ReadFloat(); + /*int blend=**/ReadInt(); + int fx=ReadInt(); + + std::unique_ptr mat(new aiMaterial); + + // Name + aiString ainame( name ); + mat->AddProperty( &ainame,AI_MATKEY_NAME ); + + // Diffuse color + mat->AddProperty( &color,1,AI_MATKEY_COLOR_DIFFUSE ); + + // Opacity + mat->AddProperty( &alpha,1,AI_MATKEY_OPACITY ); + + // Specular color + aiColor3D speccolor( shiny,shiny,shiny ); + mat->AddProperty( &speccolor,1,AI_MATKEY_COLOR_SPECULAR ); + + // Specular power + float specpow=shiny*128; + mat->AddProperty( &specpow,1,AI_MATKEY_SHININESS ); + + // Double sided + if( fx & 0x10 ){ + int i=1; + mat->AddProperty( &i,1,AI_MATKEY_TWOSIDED ); + } + + //Textures + for( int i=0;i=0 && texid>=static_cast(_textures.size())) ){ + Fail( "Bad texture id" ); + } + if( i==0 && texid>=0 ){ + aiString texname( _textures[texid] ); + mat->AddProperty( &texname,AI_MATKEY_TEXTURE_DIFFUSE(0) ); + } + } + _materials.emplace_back( std::move(mat) ); + } +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadVRTS(){ + _vflags=ReadInt(); + _tcsets=ReadInt(); + _tcsize=ReadInt(); + if( _tcsets<0 || _tcsets>4 || _tcsize<0 || _tcsize>4 ){ + Fail( "Bad texcoord data" ); + } + + int sz=12+(_vflags&1?12:0)+(_vflags&2?16:0)+(_tcsets*_tcsize*4); + int n_verts=ChunkSize()/sz; + + int v0=static_cast(_vertices.size()); + _vertices.resize( v0+n_verts ); + + for( int i=0;i=(int)_materials.size() ){ +#ifdef DEBUG_B3D + cout<<"material id="< mesh(new aiMesh); + + mesh->mMaterialIndex=matid; + mesh->mNumFaces=0; + mesh->mPrimitiveTypes=aiPrimitiveType_TRIANGLE; + + int n_tris=ChunkSize()/12; + aiFace *face=mesh->mFaces=new aiFace[n_tris]; + + for( int i=0;i=(int)_vertices.size() || i1<0 || i1>=(int)_vertices.size() || i2<0 || i2>=(int)_vertices.size() ){ +#ifdef DEBUG_B3D + cout<<"Bad triangle index: i0="<mNumIndices=3; + face->mIndices=new unsigned[3]; + face->mIndices[0]=i0; + face->mIndices[1]=i1; + face->mIndices[2]=i2; + ++mesh->mNumFaces; + ++face; + } + + _meshes.emplace_back( std::move(mesh) ); +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadMESH(){ + /*int matid=*/ReadInt(); + + int v0= static_cast(_vertices.size()); + + while( ChunkSize() ){ + string t=ReadChunk(); + if( t=="VRTS" ){ + ReadVRTS(); + }else if( t=="TRIS" ){ + ReadTRIS( v0 ); + } + ExitChunk(); + } +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadBONE( int id ){ + while( ChunkSize() ){ + int vertex=ReadInt(); + float weight=ReadFloat(); + if( vertex<0 || vertex>=(int)_vertices.size() ){ + Fail( "Bad vertex index" ); + } + + Vertex &v=_vertices[vertex]; + int i; + for( i=0;i<4;++i ){ + if( !v.weights[i] ){ + v.bones[i]=id; + v.weights[i]=weight; + break; + } + } +#ifdef DEBUG_B3D + if( i==4 ){ + cout<<"Too many bone weights"< trans,scale; + vector rot; + int flags=ReadInt(); + while( ChunkSize() ){ + int frame=ReadInt(); + if( flags & 1 ){ + trans.push_back( aiVectorKey( frame,ReadVec3() ) ); + } + if( flags & 2 ){ + scale.push_back( aiVectorKey( frame,ReadVec3() ) ); + } + if( flags & 4 ){ + rot.push_back( aiQuatKey( frame,ReadQuat() ) ); + } + } + + if( flags & 1 ){ + nodeAnim->mNumPositionKeys=static_cast(trans.size()); + nodeAnim->mPositionKeys=to_array( trans ); + } + + if( flags & 2 ){ + nodeAnim->mNumScalingKeys=static_cast(scale.size()); + nodeAnim->mScalingKeys=to_array( scale ); + } + + if( flags & 4 ){ + nodeAnim->mNumRotationKeys=static_cast(rot.size()); + nodeAnim->mRotationKeys=to_array( rot ); + } +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadANIM(){ + /*int flags=*/ReadInt(); + int frames=ReadInt(); + float fps=ReadFloat(); + + std::unique_ptr anim(new aiAnimation); + + anim->mDuration=frames; + anim->mTicksPerSecond=fps; + _animations.emplace_back( std::move(anim) ); +} + +// ------------------------------------------------------------------------------------------------ +aiNode *B3DImporter::ReadNODE( aiNode *parent ){ + + string name=ReadString(); + aiVector3D t=ReadVec3(); + aiVector3D s=ReadVec3(); + aiQuaternion r=ReadQuat(); + + aiMatrix4x4 trans,scale,rot; + + aiMatrix4x4::Translation( t,trans ); + aiMatrix4x4::Scaling( s,scale ); + rot=aiMatrix4x4( r.GetMatrix() ); + + aiMatrix4x4 tform=trans * rot * scale; + + int nodeid=static_cast(_nodes.size()); + + aiNode *node=new aiNode( name ); + _nodes.push_back( node ); + + node->mParent=parent; + node->mTransformation=tform; + + std::unique_ptr nodeAnim; + vector meshes; + vector children; + + while( ChunkSize() ){ + string t=ReadChunk(); + if( t=="MESH" ){ + unsigned int n= static_cast(_meshes.size()); + ReadMESH(); + for( unsigned int i=n;i(_meshes.size());++i ){ + meshes.push_back( i ); + } + }else if( t=="BONE" ){ + ReadBONE( nodeid ); + }else if( t=="ANIM" ){ + ReadANIM(); + }else if( t=="KEYS" ){ + if( !nodeAnim ){ + nodeAnim.reset(new aiNodeAnim); + nodeAnim->mNodeName=node->mName; + } + ReadKEYS( nodeAnim.get() ); + }else if( t=="NODE" ){ + aiNode *child=ReadNODE( node ); + children.push_back( child ); + } + ExitChunk(); + } + + if (nodeAnim) { + _nodeAnims.emplace_back( std::move(nodeAnim) ); + } + + node->mNumMeshes= static_cast(meshes.size()); + node->mMeshes=to_array( meshes ); + + node->mNumChildren=static_cast(children.size()); + node->mChildren=to_array( children ); + + return node; +} + +// ------------------------------------------------------------------------------------------------ +void B3DImporter::ReadBB3D( aiScene *scene ){ + + _textures.clear(); + + _materials.clear(); + + _vertices.clear(); + + _meshes.clear(); + + DeleteAllBarePointers(_nodes); + _nodes.clear(); + + _nodeAnims.clear(); + + _animations.clear(); + + string t=ReadChunk(); + if( t=="BB3D" ){ + int version=ReadInt(); + + if (!DefaultLogger::isNullLogger()) { + char dmp[128]; + ai_snprintf(dmp, 128, "B3D file format version: %i",version); + ASSIMP_LOG_INFO(dmp); + } + + while( ChunkSize() ){ + string t=ReadChunk(); + if( t=="TEXS" ){ + ReadTEXS(); + }else if( t=="BRUS" ){ + ReadBRUS(); + }else if( t=="NODE" ){ + ReadNODE( 0 ); + } + ExitChunk(); + } + } + ExitChunk(); + + if( !_nodes.size() ) Fail( "No nodes" ); + + if( !_meshes.size() ) Fail( "No meshes" ); + + //Fix nodes/meshes/bones + for(size_t i=0;i<_nodes.size();++i ){ + aiNode *node=_nodes[i]; + + for( size_t j=0;jmNumMeshes;++j ){ + aiMesh *mesh = _meshes[node->mMeshes[j]].get(); + + int n_tris=mesh->mNumFaces; + int n_verts=mesh->mNumVertices=n_tris * 3; + + aiVector3D *mv=mesh->mVertices=new aiVector3D[ n_verts ],*mn=0,*mc=0; + if( _vflags & 1 ) mn=mesh->mNormals=new aiVector3D[ n_verts ]; + if( _tcsets ) mc=mesh->mTextureCoords[0]=new aiVector3D[ n_verts ]; + + aiFace *face=mesh->mFaces; + + vector< vector > vweights( _nodes.size() ); + + for( int i=0;imIndices[j]]; + + *mv++=v.vertex; + if( mn ) *mn++=v.normal; + if( mc ) *mc++=v.texcoords; + + face->mIndices[j]=i+j; + + for( int k=0;k<4;++k ){ + if( !v.weights[k] ) break; + + int bone=v.bones[k]; + float weight=v.weights[k]; + + vweights[bone].push_back( aiVertexWeight(i+j,weight) ); + } + } + ++face; + } + + vector bones; + for(size_t i=0;i &weights=vweights[i]; + if( !weights.size() ) continue; + + aiBone *bone=new aiBone; + bones.push_back( bone ); + + aiNode *bnode=_nodes[i]; + + bone->mName=bnode->mName; + bone->mNumWeights= static_cast(weights.size()); + bone->mWeights=to_array( weights ); + + aiMatrix4x4 mat=bnode->mTransformation; + while( bnode->mParent ){ + bnode=bnode->mParent; + mat=bnode->mTransformation * mat; + } + bone->mOffsetMatrix=mat.Inverse(); + } + mesh->mNumBones= static_cast(bones.size()); + mesh->mBones=to_array( bones ); + } + } + + //nodes + scene->mRootNode=_nodes[0]; + _nodes.clear(); // node ownership now belongs to scene + + //material + if( !_materials.size() ){ + _materials.emplace_back( std::unique_ptr(new aiMaterial) ); + } + scene->mNumMaterials= static_cast(_materials.size()); + scene->mMaterials = unique_to_array( _materials ); + + //meshes + scene->mNumMeshes= static_cast(_meshes.size()); + scene->mMeshes = unique_to_array( _meshes ); + + //animations + if( _animations.size()==1 && _nodeAnims.size() ){ + + aiAnimation *anim = _animations.back().get(); + anim->mNumChannels=static_cast(_nodeAnims.size()); + anim->mChannels = unique_to_array( _nodeAnims ); + + scene->mNumAnimations=static_cast(_animations.size()); + scene->mAnimations=unique_to_array( _animations ); + } + + // convert to RH + MakeLeftHandedProcess makeleft; + makeleft.Execute( scene ); + + FlipWindingOrderProcess flip; + flip.Execute( scene ); +} + +#endif // !! ASSIMP_BUILD_NO_B3D_IMPORTER diff --git a/thirdparty/assimp/code/B3D/B3DImporter.h b/thirdparty/assimp/code/B3D/B3DImporter.h new file mode 100644 index 0000000..d52dac3 --- /dev/null +++ b/thirdparty/assimp/code/B3D/B3DImporter.h @@ -0,0 +1,134 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file Definition of the .b3d importer class. */ + +#ifndef AI_B3DIMPORTER_H_INC +#define AI_B3DIMPORTER_H_INC + +#include +#include +#include +#include + +#include +#include + +struct aiNodeAnim; +struct aiNode; +struct aiAnimation; + +namespace Assimp{ + +class B3DImporter : public BaseImporter{ +public: + B3DImporter() = default; + virtual ~B3DImporter(); + + virtual bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const; + +protected: + + virtual const aiImporterDesc* GetInfo () const; + virtual void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); + +private: + + int ReadByte(); + int ReadInt(); + float ReadFloat(); + aiVector2D ReadVec2(); + aiVector3D ReadVec3(); + aiQuaternion ReadQuat(); + std::string ReadString(); + std::string ReadChunk(); + void ExitChunk(); + unsigned ChunkSize(); + + template + T *to_array( const std::vector &v ); + + struct Vertex{ + aiVector3D vertex; + aiVector3D normal; + aiVector3D texcoords; + unsigned char bones[4]; + float weights[4]; + }; + + AI_WONT_RETURN void Oops() AI_WONT_RETURN_SUFFIX; + AI_WONT_RETURN void Fail( std::string str ) AI_WONT_RETURN_SUFFIX; + + void ReadTEXS(); + void ReadBRUS(); + + void ReadVRTS(); + void ReadTRIS( int v0 ); + void ReadMESH(); + void ReadBONE( int id ); + void ReadKEYS( aiNodeAnim *nodeAnim ); + void ReadANIM(); + + aiNode *ReadNODE( aiNode *parent ); + + void ReadBB3D( aiScene *scene ); + + unsigned _pos; +// unsigned _size; + std::vector _buf; + std::vector _stack; + + std::vector _textures; + std::vector > _materials; + + int _vflags,_tcsets,_tcsize; + std::vector _vertices; + + std::vector _nodes; + std::vector > _meshes; + std::vector > _nodeAnims; + std::vector > _animations; +}; + +} + +#endif diff --git a/thirdparty/assimp/code/BVH/BVHLoader.cpp b/thirdparty/assimp/code/BVH/BVHLoader.cpp new file mode 100644 index 0000000..cd9ab08 --- /dev/null +++ b/thirdparty/assimp/code/BVH/BVHLoader.cpp @@ -0,0 +1,578 @@ +/** Implementation of the BVH loader */ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + + +#ifndef ASSIMP_BUILD_NO_BVH_IMPORTER + +#include "BVHLoader.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Assimp; +using namespace Assimp::Formatter; + +static const aiImporterDesc desc = { + "BVH Importer (MoCap)", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "bvh" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +BVHLoader::BVHLoader() + : mLine(), + mAnimTickDuration(), + mAnimNumFrames(), + noSkeletonMesh() +{} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +BVHLoader::~BVHLoader() +{} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool BVHLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const +{ + // check file extension + const std::string extension = GetExtension(pFile); + + if( extension == "bvh") + return true; + + if ((!extension.length() || cs) && pIOHandler) { + const char* tokens[] = {"HIERARCHY"}; + return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1); + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +void BVHLoader::SetupProperties(const Importer* pImp) +{ + noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Loader meta information +const aiImporterDesc* BVHLoader::GetInfo () const +{ + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void BVHLoader::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) +{ + mFileName = pFile; + + // read file into memory + std::unique_ptr file( pIOHandler->Open( pFile)); + if( file.get() == NULL) + throw DeadlyImportError( "Failed to open file " + pFile + "."); + + size_t fileSize = file->FileSize(); + if( fileSize == 0) + throw DeadlyImportError( "File is too small."); + + mBuffer.resize( fileSize); + file->Read( &mBuffer.front(), 1, fileSize); + + // start reading + mReader = mBuffer.begin(); + mLine = 1; + ReadStructure( pScene); + + if (!noSkeletonMesh) { + // build a dummy mesh for the skeleton so that we see something at least + SkeletonMeshBuilder meshBuilder( pScene); + } + + // construct an animation from all the motion data we read + CreateAnimation( pScene); +} + +// ------------------------------------------------------------------------------------------------ +// Reads the file +void BVHLoader::ReadStructure( aiScene* pScene) +{ + // first comes hierarchy + std::string header = GetNextToken(); + if( header != "HIERARCHY") + ThrowException( "Expected header string \"HIERARCHY\"."); + ReadHierarchy( pScene); + + // then comes the motion data + std::string motion = GetNextToken(); + if( motion != "MOTION") + ThrowException( "Expected beginning of motion data \"MOTION\"."); + ReadMotion( pScene); +} + +// ------------------------------------------------------------------------------------------------ +// Reads the hierarchy +void BVHLoader::ReadHierarchy( aiScene* pScene) +{ + std::string root = GetNextToken(); + if( root != "ROOT") + ThrowException( "Expected root node \"ROOT\"."); + + // Go read the hierarchy from here + pScene->mRootNode = ReadNode(); +} + +// ------------------------------------------------------------------------------------------------ +// Reads a node and recursively its childs and returns the created node; +aiNode* BVHLoader::ReadNode() +{ + // first token is name + std::string nodeName = GetNextToken(); + if( nodeName.empty() || nodeName == "{") + ThrowException( format() << "Expected node name, but found \"" << nodeName << "\"." ); + + // then an opening brace should follow + std::string openBrace = GetNextToken(); + if( openBrace != "{") + ThrowException( format() << "Expected opening brace \"{\", but found \"" << openBrace << "\"." ); + + // Create a node + aiNode* node = new aiNode( nodeName); + std::vector childNodes; + + // and create an bone entry for it + mNodes.push_back( Node( node)); + Node& internNode = mNodes.back(); + + // now read the node's contents + std::string siteToken; + while( 1) + { + std::string token = GetNextToken(); + + // node offset to parent node + if( token == "OFFSET") + ReadNodeOffset( node); + else if( token == "CHANNELS") + ReadNodeChannels( internNode); + else if( token == "JOINT") + { + // child node follows + aiNode* child = ReadNode(); + child->mParent = node; + childNodes.push_back( child); + } + else if( token == "End") + { + // The real symbol is "End Site". Second part comes in a separate token + siteToken.clear(); + siteToken = GetNextToken(); + if( siteToken != "Site") + ThrowException( format() << "Expected \"End Site\" keyword, but found \"" << token << " " << siteToken << "\"." ); + + aiNode* child = ReadEndSite( nodeName); + child->mParent = node; + childNodes.push_back( child); + } + else if( token == "}") + { + // we're done with that part of the hierarchy + break; + } else + { + // everything else is a parse error + ThrowException( format() << "Unknown keyword \"" << token << "\"." ); + } + } + + // add the child nodes if there are any + if( childNodes.size() > 0) + { + node->mNumChildren = static_cast(childNodes.size()); + node->mChildren = new aiNode*[node->mNumChildren]; + std::copy( childNodes.begin(), childNodes.end(), node->mChildren); + } + + // and return the sub-hierarchy we built here + return node; +} + +// ------------------------------------------------------------------------------------------------ +// Reads an end node and returns the created node. +aiNode* BVHLoader::ReadEndSite( const std::string& pParentName) +{ + // check opening brace + std::string openBrace = GetNextToken(); + if( openBrace != "{") + ThrowException( format() << "Expected opening brace \"{\", but found \"" << openBrace << "\"."); + + // Create a node + aiNode* node = new aiNode( "EndSite_" + pParentName); + + // now read the node's contents. Only possible entry is "OFFSET" + std::string token; + while( 1) { + token.clear(); + token = GetNextToken(); + + // end node's offset + if( token == "OFFSET") { + ReadNodeOffset( node); + } else if( token == "}") { + // we're done with the end node + break; + } else { + // everything else is a parse error + ThrowException( format() << "Unknown keyword \"" << token << "\"." ); + } + } + + // and return the sub-hierarchy we built here + return node; +} +// ------------------------------------------------------------------------------------------------ +// Reads a node offset for the given node +void BVHLoader::ReadNodeOffset( aiNode* pNode) +{ + // Offset consists of three floats to read + aiVector3D offset; + offset.x = GetNextTokenAsFloat(); + offset.y = GetNextTokenAsFloat(); + offset.z = GetNextTokenAsFloat(); + + // build a transformation matrix from it + pNode->mTransformation = aiMatrix4x4( 1.0f, 0.0f, 0.0f, offset.x, + 0.0f, 1.0f, 0.0f, offset.y, + 0.0f, 0.0f, 1.0f, offset.z, + 0.0f, 0.0f, 0.0f, 1.0f); +} + +// ------------------------------------------------------------------------------------------------ +// Reads the animation channels for the given node +void BVHLoader::ReadNodeChannels( BVHLoader::Node& pNode) +{ + // number of channels. Use the float reader because we're lazy + float numChannelsFloat = GetNextTokenAsFloat(); + unsigned int numChannels = (unsigned int) numChannelsFloat; + + for( unsigned int a = 0; a < numChannels; a++) + { + std::string channelToken = GetNextToken(); + + if( channelToken == "Xposition") + pNode.mChannels.push_back( Channel_PositionX); + else if( channelToken == "Yposition") + pNode.mChannels.push_back( Channel_PositionY); + else if( channelToken == "Zposition") + pNode.mChannels.push_back( Channel_PositionZ); + else if( channelToken == "Xrotation") + pNode.mChannels.push_back( Channel_RotationX); + else if( channelToken == "Yrotation") + pNode.mChannels.push_back( Channel_RotationY); + else if( channelToken == "Zrotation") + pNode.mChannels.push_back( Channel_RotationZ); + else + ThrowException( format() << "Invalid channel specifier \"" << channelToken << "\"." ); + } +} + +// ------------------------------------------------------------------------------------------------ +// Reads the motion data +void BVHLoader::ReadMotion( aiScene* /*pScene*/) +{ + // Read number of frames + std::string tokenFrames = GetNextToken(); + if( tokenFrames != "Frames:") + ThrowException( format() << "Expected frame count \"Frames:\", but found \"" << tokenFrames << "\"."); + + float numFramesFloat = GetNextTokenAsFloat(); + mAnimNumFrames = (unsigned int) numFramesFloat; + + // Read frame duration + std::string tokenDuration1 = GetNextToken(); + std::string tokenDuration2 = GetNextToken(); + if( tokenDuration1 != "Frame" || tokenDuration2 != "Time:") + ThrowException( format() << "Expected frame duration \"Frame Time:\", but found \"" << tokenDuration1 << " " << tokenDuration2 << "\"." ); + + mAnimTickDuration = GetNextTokenAsFloat(); + + // resize value vectors for each node + for( std::vector::iterator it = mNodes.begin(); it != mNodes.end(); ++it) + it->mChannelValues.reserve( it->mChannels.size() * mAnimNumFrames); + + // now read all the data and store it in the corresponding node's value vector + for( unsigned int frame = 0; frame < mAnimNumFrames; ++frame) + { + // on each line read the values for all nodes + for( std::vector::iterator it = mNodes.begin(); it != mNodes.end(); ++it) + { + // get as many values as the node has channels + for( unsigned int c = 0; c < it->mChannels.size(); ++c) + it->mChannelValues.push_back( GetNextTokenAsFloat()); + } + + // after one frame worth of values for all nodes there should be a newline, but we better don't rely on it + } +} + +// ------------------------------------------------------------------------------------------------ +// Retrieves the next token +std::string BVHLoader::GetNextToken() +{ + // skip any preceding whitespace + while( mReader != mBuffer.end()) + { + if( !isspace( *mReader)) + break; + + // count lines + if( *mReader == '\n') + mLine++; + + ++mReader; + } + + // collect all chars till the next whitespace. BVH is easy in respect to that. + std::string token; + while( mReader != mBuffer.end()) + { + if( isspace( *mReader)) + break; + + token.push_back( *mReader); + ++mReader; + + // little extra logic to make sure braces are counted correctly + if( token == "{" || token == "}") + break; + } + + // empty token means end of file, which is just fine + return token; +} + +// ------------------------------------------------------------------------------------------------ +// Reads the next token as a float +float BVHLoader::GetNextTokenAsFloat() +{ + std::string token = GetNextToken(); + if( token.empty()) + ThrowException( "Unexpected end of file while trying to read a float"); + + // check if the float is valid by testing if the atof() function consumed every char of the token + const char* ctoken = token.c_str(); + float result = 0.0f; + ctoken = fast_atoreal_move( ctoken, result); + + if( ctoken != token.c_str() + token.length()) + ThrowException( format() << "Expected a floating point number, but found \"" << token << "\"." ); + + return result; +} + +// ------------------------------------------------------------------------------------------------ +// Aborts the file reading with an exception +AI_WONT_RETURN void BVHLoader::ThrowException( const std::string& pError) +{ + throw DeadlyImportError( format() << mFileName << ":" << mLine << " - " << pError); +} + +// ------------------------------------------------------------------------------------------------ +// Constructs an animation for the motion data and stores it in the given scene +void BVHLoader::CreateAnimation( aiScene* pScene) +{ + // create the animation + pScene->mNumAnimations = 1; + pScene->mAnimations = new aiAnimation*[1]; + aiAnimation* anim = new aiAnimation; + pScene->mAnimations[0] = anim; + + // put down the basic parameters + anim->mName.Set( "Motion"); + anim->mTicksPerSecond = 1.0 / double( mAnimTickDuration); + anim->mDuration = double( mAnimNumFrames - 1); + + // now generate the tracks for all nodes + anim->mNumChannels = static_cast(mNodes.size()); + anim->mChannels = new aiNodeAnim*[anim->mNumChannels]; + + // FIX: set the array elements to NULL to ensure proper deletion if an exception is thrown + for (unsigned int i = 0; i < anim->mNumChannels;++i) + anim->mChannels[i] = NULL; + + for( unsigned int a = 0; a < anim->mNumChannels; a++) + { + const Node& node = mNodes[a]; + const std::string nodeName = std::string( node.mNode->mName.data ); + aiNodeAnim* nodeAnim = new aiNodeAnim; + anim->mChannels[a] = nodeAnim; + nodeAnim->mNodeName.Set( nodeName); + std::map channelMap; + + //Build map of channels + for (unsigned int channel = 0; channel < node.mChannels.size(); ++channel) + { + channelMap[node.mChannels[channel]] = channel; + } + + // translational part, if given + if( node.mChannels.size() == 6) + { + nodeAnim->mNumPositionKeys = mAnimNumFrames; + nodeAnim->mPositionKeys = new aiVectorKey[mAnimNumFrames]; + aiVectorKey* poskey = nodeAnim->mPositionKeys; + for( unsigned int fr = 0; fr < mAnimNumFrames; ++fr) + { + poskey->mTime = double( fr); + + // Now compute all translations + for(BVHLoader::ChannelType channel = Channel_PositionX; channel <= Channel_PositionZ; channel = (BVHLoader::ChannelType)(channel +1)) + { + //Find channel in node + std::map::iterator mapIter = channelMap.find(channel); + + if (mapIter == channelMap.end()) + throw DeadlyImportError("Missing position channel in node " + nodeName); + else { + int channelIdx = mapIter->second; + switch (channel) { + case Channel_PositionX: + poskey->mValue.x = node.mChannelValues[fr * node.mChannels.size() + channelIdx]; + break; + case Channel_PositionY: + poskey->mValue.y = node.mChannelValues[fr * node.mChannels.size() + channelIdx]; + break; + case Channel_PositionZ: + poskey->mValue.z = node.mChannelValues[fr * node.mChannels.size() + channelIdx]; + break; + + default: + break; + } + + } + } + ++poskey; + } + } else + { + // if no translation part is given, put a default sequence + aiVector3D nodePos( node.mNode->mTransformation.a4, node.mNode->mTransformation.b4, node.mNode->mTransformation.c4); + nodeAnim->mNumPositionKeys = 1; + nodeAnim->mPositionKeys = new aiVectorKey[1]; + nodeAnim->mPositionKeys[0].mTime = 0.0; + nodeAnim->mPositionKeys[0].mValue = nodePos; + } + + // rotation part. Always present. First find value offsets + { + + // Then create the number of rotation keys + nodeAnim->mNumRotationKeys = mAnimNumFrames; + nodeAnim->mRotationKeys = new aiQuatKey[mAnimNumFrames]; + aiQuatKey* rotkey = nodeAnim->mRotationKeys; + for( unsigned int fr = 0; fr < mAnimNumFrames; ++fr) + { + aiMatrix4x4 temp; + aiMatrix3x3 rotMatrix; + for (BVHLoader::ChannelType channel = Channel_RotationX; channel <= Channel_RotationZ; channel = (BVHLoader::ChannelType)(channel + 1)) + { + //Find channel in node + std::map::iterator mapIter = channelMap.find(channel); + + if (mapIter == channelMap.end()) + throw DeadlyImportError("Missing rotation channel in node " + nodeName); + else { + int channelIdx = mapIter->second; + // translate ZXY euler angels into a quaternion + const float angle = node.mChannelValues[fr * node.mChannels.size() + channelIdx] * float(AI_MATH_PI) / 180.0f; + + // Compute rotation transformations in the right order + switch (channel) + { + case Channel_RotationX: + aiMatrix4x4::RotationX(angle, temp); rotMatrix *= aiMatrix3x3(temp); + break; + case Channel_RotationY: + aiMatrix4x4::RotationY(angle, temp); rotMatrix *= aiMatrix3x3(temp); + break; + case Channel_RotationZ: aiMatrix4x4::RotationZ(angle, temp); rotMatrix *= aiMatrix3x3(temp); + break; + default: + break; + } + } + } + + rotkey->mTime = double( fr); + rotkey->mValue = aiQuaternion( rotMatrix); + ++rotkey; + } + } + + // scaling part. Always just a default track + { + nodeAnim->mNumScalingKeys = 1; + nodeAnim->mScalingKeys = new aiVectorKey[1]; + nodeAnim->mScalingKeys[0].mTime = 0.0; + nodeAnim->mScalingKeys[0].mValue.Set( 1.0f, 1.0f, 1.0f); + } + } +} + +#endif // !! ASSIMP_BUILD_NO_BVH_IMPORTER diff --git a/thirdparty/assimp/code/BVH/BVHLoader.h b/thirdparty/assimp/code/BVH/BVHLoader.h new file mode 100644 index 0000000..33b4e24 --- /dev/null +++ b/thirdparty/assimp/code/BVH/BVHLoader.h @@ -0,0 +1,176 @@ +/** Defines the BHV motion capturing loader class */ + +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file BVHLoader.h + * @brief Biovision BVH import + */ + +#ifndef AI_BVHLOADER_H_INC +#define AI_BVHLOADER_H_INC + +#include + +struct aiNode; + +namespace Assimp +{ + +// -------------------------------------------------------------------------------- +/** Loader class to read Motion Capturing data from a .bvh file. + * + * This format only contains a hierarchy of joints and a series of keyframes for + * the hierarchy. It contains no actual mesh data, but we generate a dummy mesh + * inside the loader just to be able to see something. +*/ +class BVHLoader : public BaseImporter +{ + + /** Possible animation channels for which the motion data holds the values */ + enum ChannelType + { + Channel_PositionX, + Channel_PositionY, + Channel_PositionZ, + Channel_RotationX, + Channel_RotationY, + Channel_RotationZ + }; + + /** Collected list of node. Will be bones of the dummy mesh some day, addressed by their array index */ + struct Node + { + const aiNode* mNode; + std::vector mChannels; + std::vector mChannelValues; // motion data values for that node. Of size NumChannels * NumFrames + + Node() + : mNode(nullptr) + { } + + explicit Node( const aiNode* pNode) : mNode( pNode) { } + }; + +public: + + BVHLoader(); + ~BVHLoader(); + +public: + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool cs) const; + + void SetupProperties(const Importer* pImp); + const aiImporterDesc* GetInfo () const; + +protected: + + + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); + +protected: + /** Reads the file */ + void ReadStructure( aiScene* pScene); + + /** Reads the hierarchy */ + void ReadHierarchy( aiScene* pScene); + + /** Reads a node and recursively its childs and returns the created node. */ + aiNode* ReadNode(); + + /** Reads an end node and returns the created node. */ + aiNode* ReadEndSite( const std::string& pParentName); + + /** Reads a node offset for the given node */ + void ReadNodeOffset( aiNode* pNode); + + /** Reads the animation channels into the given node */ + void ReadNodeChannels( BVHLoader::Node& pNode); + + /** Reads the motion data */ + void ReadMotion( aiScene* pScene); + + /** Retrieves the next token */ + std::string GetNextToken(); + + /** Reads the next token as a float */ + float GetNextTokenAsFloat(); + + /** Aborts the file reading with an exception */ + AI_WONT_RETURN void ThrowException( const std::string& pError) AI_WONT_RETURN_SUFFIX; + + /** Constructs an animation for the motion data and stores it in the given scene */ + void CreateAnimation( aiScene* pScene); + +protected: + /** Filename, for a verbose error message */ + std::string mFileName; + + /** Buffer to hold the loaded file */ + std::vector mBuffer; + + /** Next char to read from the buffer */ + std::vector::const_iterator mReader; + + /** Current line, for error messages */ + unsigned int mLine; + + /** Collected list of nodes. Will be bones of the dummy mesh some day, addressed by their array index. + * Also contain the motion data for the node's channels + */ + std::vector mNodes; + + /** basic Animation parameters */ + float mAnimTickDuration; + unsigned int mAnimNumFrames; + + bool noSkeletonMesh; +}; + +} // end of namespace Assimp + +#endif // AI_BVHLOADER_H_INC diff --git a/thirdparty/assimp/code/Blender/BlenderBMesh.cpp b/thirdparty/assimp/code/Blender/BlenderBMesh.cpp new file mode 100644 index 0000000..8a13819 --- /dev/null +++ b/thirdparty/assimp/code/Blender/BlenderBMesh.cpp @@ -0,0 +1,206 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2013, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file BlenderBMesh.cpp + * @brief Conversion of Blender's new BMesh stuff + */ + + +#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER + +#include "BlenderDNA.h" +#include "BlenderScene.h" +#include "BlenderBMesh.h" +#include "BlenderTessellator.h" + +namespace Assimp +{ + template< > const char* LogFunctions< BlenderBMeshConverter >::Prefix() + { + static auto prefix = "BLEND_BMESH: "; + return prefix; + } +} + +using namespace Assimp; +using namespace Assimp::Blender; +using namespace Assimp::Formatter; + +// ------------------------------------------------------------------------------------------------ +BlenderBMeshConverter::BlenderBMeshConverter( const Mesh* mesh ): + BMesh( mesh ), + triMesh( NULL ) +{ +} + +// ------------------------------------------------------------------------------------------------ +BlenderBMeshConverter::~BlenderBMeshConverter( ) +{ + DestroyTriMesh( ); +} + +// ------------------------------------------------------------------------------------------------ +bool BlenderBMeshConverter::ContainsBMesh( ) const +{ + // TODO - Should probably do some additional verification here + return BMesh->totpoly && BMesh->totloop && BMesh->totvert; +} + +// ------------------------------------------------------------------------------------------------ +const Mesh* BlenderBMeshConverter::TriangulateBMesh( ) +{ + AssertValidMesh( ); + AssertValidSizes( ); + PrepareTriMesh( ); + + for ( int i = 0; i < BMesh->totpoly; ++i ) + { + const MPoly& poly = BMesh->mpoly[ i ]; + ConvertPolyToFaces( poly ); + } + + return triMesh; +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::AssertValidMesh( ) +{ + if ( !ContainsBMesh( ) ) + { + ThrowException( "BlenderBMeshConverter requires a BMesh with \"polygons\" - please call BlenderBMeshConverter::ContainsBMesh to check this first" ); + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::AssertValidSizes( ) +{ + if ( BMesh->totpoly != static_cast( BMesh->mpoly.size( ) ) ) + { + ThrowException( "BMesh poly array has incorrect size" ); + } + if ( BMesh->totloop != static_cast( BMesh->mloop.size( ) ) ) + { + ThrowException( "BMesh loop array has incorrect size" ); + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::PrepareTriMesh( ) +{ + if ( triMesh ) + { + DestroyTriMesh( ); + } + + triMesh = new Mesh( *BMesh ); + triMesh->totface = 0; + triMesh->mface.clear( ); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::DestroyTriMesh( ) +{ + delete triMesh; + triMesh = NULL; +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::ConvertPolyToFaces( const MPoly& poly ) +{ + const MLoop* polyLoop = &BMesh->mloop[ poly.loopstart ]; + + if ( poly.totloop == 3 || poly.totloop == 4 ) + { + AddFace( polyLoop[ 0 ].v, polyLoop[ 1 ].v, polyLoop[ 2 ].v, poly.totloop == 4 ? polyLoop[ 3 ].v : 0 ); + + // UVs are optional, so only convert when present. + if ( BMesh->mloopuv.size() ) + { + if ( (poly.loopstart + poly.totloop ) > static_cast( BMesh->mloopuv.size() ) ) + { + ThrowException( "BMesh uv loop array has incorrect size" ); + } + const MLoopUV* loopUV = &BMesh->mloopuv[ poly.loopstart ]; + AddTFace( loopUV[ 0 ].uv, loopUV[ 1 ].uv, loopUV[ 2 ].uv, poly.totloop == 4 ? loopUV[ 3 ].uv : 0 ); + } + } + else if ( poly.totloop > 4 ) + { +#if ASSIMP_BLEND_WITH_GLU_TESSELLATE + BlenderTessellatorGL tessGL( *this ); + tessGL.Tessellate( polyLoop, poly.totloop, triMesh->mvert ); +#elif ASSIMP_BLEND_WITH_POLY_2_TRI + BlenderTessellatorP2T tessP2T( *this ); + tessP2T.Tessellate( polyLoop, poly.totloop, triMesh->mvert ); +#endif + } +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::AddFace( int v1, int v2, int v3, int v4 ) +{ + MFace face; + face.v1 = v1; + face.v2 = v2; + face.v3 = v3; + face.v4 = v4; + // TODO - Work out how materials work + face.mat_nr = 0; + triMesh->mface.push_back( face ); + triMesh->totface = static_cast(triMesh->mface.size( )); +} + +// ------------------------------------------------------------------------------------------------ +void BlenderBMeshConverter::AddTFace( const float* uv1, const float *uv2, const float *uv3, const float* uv4 ) +{ + MTFace mtface; + memcpy( &mtface.uv[ 0 ], uv1, sizeof(float) * 2 ); + memcpy( &mtface.uv[ 1 ], uv2, sizeof(float) * 2 ); + memcpy( &mtface.uv[ 2 ], uv3, sizeof(float) * 2 ); + + if ( uv4 ) + { + memcpy( &mtface.uv[ 3 ], uv4, sizeof(float) * 2 ); + } + + triMesh->mtface.push_back( mtface ); +} + +#endif // ASSIMP_BUILD_NO_BLEND_IMPORTER diff --git a/thirdparty/assimp/code/Blender/BlenderBMesh.h b/thirdparty/assimp/code/Blender/BlenderBMesh.h new file mode 100644 index 0000000..5b65fb5 --- /dev/null +++ b/thirdparty/assimp/code/Blender/BlenderBMesh.h @@ -0,0 +1,94 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2013, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file BlenderBMesh.h + * @brief Conversion of Blender's new BMesh stuff + */ +#ifndef INCLUDED_AI_BLEND_BMESH_H +#define INCLUDED_AI_BLEND_BMESH_H + +#include + +namespace Assimp +{ + // TinyFormatter.h + namespace Formatter + { + template < typename T,typename TR, typename A > class basic_formatter; + typedef class basic_formatter< char, std::char_traits< char >, std::allocator< char > > format; + } + + // BlenderScene.h + namespace Blender + { + struct Mesh; + struct MPoly; + struct MLoop; + } + + class BlenderBMeshConverter: public LogFunctions< BlenderBMeshConverter > + { + public: + BlenderBMeshConverter( const Blender::Mesh* mesh ); + ~BlenderBMeshConverter( ); + + bool ContainsBMesh( ) const; + + const Blender::Mesh* TriangulateBMesh( ); + + private: + void AssertValidMesh( ); + void AssertValidSizes( ); + void PrepareTriMesh( ); + void DestroyTriMesh( ); + void ConvertPolyToFaces( const Blender::MPoly& poly ); + void AddFace( int v1, int v2, int v3, int v4 = 0 ); + void AddTFace( const float* uv1, const float* uv2, const float *uv3, const float* uv4 = 0 ); + + const Blender::Mesh* BMesh; + Blender::Mesh* triMesh; + + friend class BlenderTessellatorGL; + friend class BlenderTessellatorP2T; + }; + +} // end of namespace Assimp + +#endif // INCLUDED_AI_BLEND_BMESH_H diff --git a/thirdparty/assimp/code/Blender/BlenderCustomData.cpp b/thirdparty/assimp/code/Blender/BlenderCustomData.cpp new file mode 100644 index 0000000..6561eaf --- /dev/null +++ b/thirdparty/assimp/code/Blender/BlenderCustomData.cpp @@ -0,0 +1,189 @@ +#include "BlenderCustomData.h" +#include "BlenderDNA.h" +#include +#include + +namespace Assimp { + namespace Blender { + /** + * @brief read/convert of Structure array to memory + */ + template + bool read(const Structure &s, T *p, const size_t cnt, const FileDatabase &db) { + for (size_t i = 0; i < cnt; ++i) { + T read; + s.Convert(read, db); + *p = read; + p++; + } + return true; + } + + /** + * @brief pointer to function read memory for n CustomData types + */ + typedef bool (*PRead)(ElemBase *pOut, const size_t cnt, const FileDatabase &db); + typedef ElemBase * (*PCreate)(const size_t cnt); + typedef void(*PDestroy)(ElemBase *); + +#define IMPL_STRUCT_READ(ty) \ + bool read##ty(ElemBase *v, const size_t cnt, const FileDatabase &db) { \ + ty *ptr = dynamic_cast(v); \ + if (nullptr == ptr) { \ + return false; \ + } \ + return read(db.dna[#ty], ptr, cnt, db); \ + } + +#define IMPL_STRUCT_CREATE(ty) \ + ElemBase *create##ty(const size_t cnt) { \ + return new ty[cnt]; \ + } + +#define IMPL_STRUCT_DESTROY(ty) \ + void destroy##ty(ElemBase *pE) { \ + ty *p = dynamic_cast(pE); \ + delete[]p; \ + } + + /** + * @brief helper macro to define Structure functions + */ +#define IMPL_STRUCT(ty) \ + IMPL_STRUCT_READ(ty) \ + IMPL_STRUCT_CREATE(ty) \ + IMPL_STRUCT_DESTROY(ty) + + // supported structures for CustomData + IMPL_STRUCT(MVert) + IMPL_STRUCT(MEdge) + IMPL_STRUCT(MFace) + IMPL_STRUCT(MTFace) + IMPL_STRUCT(MTexPoly) + IMPL_STRUCT(MLoopUV) + IMPL_STRUCT(MLoopCol) + IMPL_STRUCT(MPoly) + IMPL_STRUCT(MLoop) + + /** + * @brief describes the size of data and the read function to be used for single CustomerData.type + */ + struct CustomDataTypeDescription { + PRead Read; ///< function to read one CustomData type element + PCreate Create; ///< function to allocate n type elements + PDestroy Destroy; + + CustomDataTypeDescription(PRead read, PCreate create, PDestroy destroy) + : Read(read) + , Create(create) + , Destroy(destroy) + {} + }; + + + /** + * @brief helper macro to define Structure type specific CustomDataTypeDescription + * @note IMPL_STRUCT_READ for same ty must be used earlier to implement the typespecific read function + */ +#define DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(ty) \ + CustomDataTypeDescription{&read##ty, &create##ty, &destroy##ty} + + /** + * @brief helper macro to define CustomDataTypeDescription for UNSUPPORTED type + */ +#define DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION \ + CustomDataTypeDescription{nullptr, nullptr, nullptr} + + /** + * @brief descriptors for data pointed to from CustomDataLayer.data + * @note some of the CustomData uses already well defined Structures + * other (like CD_ORCO, ...) uses arrays of rawtypes or even arrays of Structures + * use a special readfunction for that cases + */ + std::array customDataTypeDescriptions = { { + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MVert), + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MEdge), + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MFace), + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MTFace), + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MTexPoly), + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoopUV), + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoopCol), + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MPoly), + DECL_STRUCT_CUSTOMDATATYPEDESCRIPTION(MLoop), + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION, + DECL_UNSUPPORTED_CUSTOMDATATYPEDESCRIPTION + }}; + + + bool isValidCustomDataType(const int cdtype) { + return cdtype >= 0 && cdtype < CD_NUMTYPES; + } + + bool readCustomData(std::shared_ptr &out, const int cdtype, const size_t cnt, const FileDatabase &db) { + if (!isValidCustomDataType(cdtype)) { + throw Error((Formatter::format(), "CustomData.type ", cdtype, " out of index")); + } + + const CustomDataTypeDescription cdtd = customDataTypeDescriptions[cdtype]; + if (cdtd.Read && cdtd.Create && cdtd.Destroy && cnt > 0) { + // allocate cnt elements and parse them from file + out.reset(cdtd.Create(cnt), cdtd.Destroy); + return cdtd.Read(out.get(), cnt, db); + } + return false; + } + + std::shared_ptr getCustomDataLayer(const CustomData &customdata, const CustomDataType cdtype, const std::string &name) { + for (auto it = customdata.layers.begin(); it != customdata.layers.end(); ++it) { + if (it->get()->type == cdtype && name == it->get()->name) { + return *it; + } + } + return nullptr; + } + + const ElemBase * getCustomDataLayerData(const CustomData &customdata, const CustomDataType cdtype, const std::string &name) + { + const std::shared_ptr pLayer = getCustomDataLayer(customdata, cdtype, name); + if (pLayer && pLayer->data) { + return pLayer->data.get(); + } + return nullptr; + } + } +} diff --git a/thirdparty/assimp/code/Blender/BlenderCustomData.h b/thirdparty/assimp/code/Blender/BlenderCustomData.h new file mode 100644 index 0000000..f61d79a --- /dev/null +++ b/thirdparty/assimp/code/Blender/BlenderCustomData.h @@ -0,0 +1,89 @@ +#pragma once + +#include "BlenderDNA.h" +#include "BlenderScene.h" +#include + +namespace Assimp { + namespace Blender { + /* CustomData.type from Blender (2.79b) */ + enum CustomDataType { + CD_AUTO_FROM_NAME = -1, + CD_MVERT = 0, +#ifdef DNA_DEPRECATED + CD_MSTICKY = 1, /* DEPRECATED */ +#endif + CD_MDEFORMVERT = 2, + CD_MEDGE = 3, + CD_MFACE = 4, + CD_MTFACE = 5, + CD_MCOL = 6, + CD_ORIGINDEX = 7, + CD_NORMAL = 8, + /* CD_POLYINDEX = 9, */ + CD_PROP_FLT = 10, + CD_PROP_INT = 11, + CD_PROP_STR = 12, + CD_ORIGSPACE = 13, /* for modifier stack face location mapping */ + CD_ORCO = 14, + CD_MTEXPOLY = 15, + CD_MLOOPUV = 16, + CD_MLOOPCOL = 17, + CD_TANGENT = 18, + CD_MDISPS = 19, + CD_PREVIEW_MCOL = 20, /* for displaying weightpaint colors */ + /* CD_ID_MCOL = 21, */ + CD_TEXTURE_MLOOPCOL = 22, + CD_CLOTH_ORCO = 23, + CD_RECAST = 24, + + /* BMESH ONLY START */ + CD_MPOLY = 25, + CD_MLOOP = 26, + CD_SHAPE_KEYINDEX = 27, + CD_SHAPEKEY = 28, + CD_BWEIGHT = 29, + CD_CREASE = 30, + CD_ORIGSPACE_MLOOP = 31, + CD_PREVIEW_MLOOPCOL = 32, + CD_BM_ELEM_PYPTR = 33, + /* BMESH ONLY END */ + + CD_PAINT_MASK = 34, + CD_GRID_PAINT_MASK = 35, + CD_MVERT_SKIN = 36, + CD_FREESTYLE_EDGE = 37, + CD_FREESTYLE_FACE = 38, + CD_MLOOPTANGENT = 39, + CD_TESSLOOPNORMAL = 40, + CD_CUSTOMLOOPNORMAL = 41, + + CD_NUMTYPES = 42 + }; + + /** + * @brief check if given cdtype is valid (ie >= 0 and < CD_NUMTYPES) + * @param[in] cdtype to check + * @return true when valid + */ + bool isValidCustomDataType(const int cdtype); + + /** + * @brief returns CustomDataLayer ptr for given cdtype and name + * @param[in] customdata CustomData to search for wanted layer + * @param[in] cdtype to search for + * @param[in] name to search for + * @return CustomDataLayer * or nullptr if not found + */ + std::shared_ptr getCustomDataLayer(const CustomData &customdata, CustomDataType cdtype, const std::string &name); + + /** + * @brief returns CustomDataLayer data ptr for given cdtype and name + * @param[in] customdata CustomData to search for wanted layer + * @param[in] cdtype to search for + * @param[in] name to search for + * @return * to struct data or nullptr if not found + */ + const ElemBase * getCustomDataLayerData(const CustomData &customdata, CustomDataType cdtype, const std::string &name); + } +} diff --git a/thirdparty/assimp/code/Blender/BlenderDNA.cpp b/thirdparty/assimp/code/Blender/BlenderDNA.cpp new file mode 100644 index 0000000..f274e02 --- /dev/null +++ b/thirdparty/assimp/code/Blender/BlenderDNA.cpp @@ -0,0 +1,375 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file BlenderDNA.cpp + * @brief Implementation of the Blender `DNA`, that is its own + * serialized set of data structures. + */ + + +#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER +#include "BlenderDNA.h" +#include +#include +#include + +using namespace Assimp; +using namespace Assimp::Blender; +using namespace Assimp::Formatter; + +static bool match4(StreamReaderAny& stream, const char* string) { + ai_assert( nullptr != string ); + char tmp[4]; + tmp[ 0 ] = ( stream ).GetI1(); + tmp[ 1 ] = ( stream ).GetI1(); + tmp[ 2 ] = ( stream ).GetI1(); + tmp[ 3 ] = ( stream ).GetI1(); + return (tmp[0]==string[0] && tmp[1]==string[1] && tmp[2]==string[2] && tmp[3]==string[3]); +} + +struct Type { + size_t size; + std::string name; +}; + +// ------------------------------------------------------------------------------------------------ +void DNAParser::Parse () +{ + StreamReaderAny& stream = *db.reader.get(); + DNA& dna = db.dna; + + if(!match4(stream,"SDNA")) { + throw DeadlyImportError("BlenderDNA: Expected SDNA chunk"); + } + + // name dictionary + if(!match4(stream,"NAME")) { + throw DeadlyImportError("BlenderDNA: Expected NAME field"); + } + + std::vector names (stream.GetI4()); + for(std::string& s : names) { + while (char c = stream.GetI1()) { + s += c; + } + } + + // type dictionary + for (;stream.GetCurrentPos() & 0x3; stream.GetI1()); + if(!match4(stream,"TYPE")) { + throw DeadlyImportError("BlenderDNA: Expected TYPE field"); + } + + std::vector types (stream.GetI4()); + for(Type& s : types) { + while (char c = stream.GetI1()) { + s.name += c; + } + } + + // type length dictionary + for (;stream.GetCurrentPos() & 0x3; stream.GetI1()); + if(!match4(stream,"TLEN")) { + throw DeadlyImportError("BlenderDNA: Expected TLEN field"); + } + + for(Type& s : types) { + s.size = stream.GetI2(); + } + + // structures dictionary + for (;stream.GetCurrentPos() & 0x3; stream.GetI1()); + if(!match4(stream,"STRC")) { + throw DeadlyImportError("BlenderDNA: Expected STRC field"); + } + + size_t end = stream.GetI4(), fields = 0; + + dna.structures.reserve(end); + for(size_t i = 0; i != end; ++i) { + + uint16_t n = stream.GetI2(); + if (n >= types.size()) { + throw DeadlyImportError((format(), + "BlenderDNA: Invalid type index in structure name" ,n, + " (there are only ", types.size(), " entries)" + )); + } + + // maintain separate indexes + dna.indices[types[n].name] = dna.structures.size(); + + dna.structures.push_back(Structure()); + Structure& s = dna.structures.back(); + s.name = types[n].name; + //s.index = dna.structures.size()-1; + + n = stream.GetI2(); + s.fields.reserve(n); + + size_t offset = 0; + for (size_t m = 0; m < n; ++m, ++fields) { + + uint16_t j = stream.GetI2(); + if (j >= types.size()) { + throw DeadlyImportError((format(), + "BlenderDNA: Invalid type index in structure field ", j, + " (there are only ", types.size(), " entries)" + )); + } + s.fields.push_back(Field()); + Field& f = s.fields.back(); + f.offset = offset; + + f.type = types[j].name; + f.size = types[j].size; + + j = stream.GetI2(); + if (j >= names.size()) { + throw DeadlyImportError((format(), + "BlenderDNA: Invalid name index in structure field ", j, + " (there are only ", names.size(), " entries)" + )); + } + + f.name = names[j]; + f.flags = 0u; + + // pointers always specify the size of the pointee instead of their own. + // The pointer asterisk remains a property of the lookup name. + if (f.name[0] == '*') { + f.size = db.i64bit ? 8 : 4; + f.flags |= FieldFlag_Pointer; + } + + // arrays, however, specify the size of a single element so we + // need to parse the (possibly multi-dimensional) array declaration + // in order to obtain the actual size of the array in the file. + // Also we need to alter the lookup name to include no array + // brackets anymore or size fixup won't work (if our size does + // not match the size read from the DNA). + if (*f.name.rbegin() == ']') { + const std::string::size_type rb = f.name.find('['); + if (rb == std::string::npos) { + throw DeadlyImportError((format(), + "BlenderDNA: Encountered invalid array declaration ", + f.name + )); + } + + f.flags |= FieldFlag_Array; + DNA::ExtractArraySize(f.name,f.array_sizes); + f.name = f.name.substr(0,rb); + + f.size *= f.array_sizes[0] * f.array_sizes[1]; + } + + // maintain separate indexes + s.indices[f.name] = s.fields.size()-1; + offset += f.size; + } + s.size = offset; + } + + ASSIMP_LOG_DEBUG_F( "BlenderDNA: Got ", dna.structures.size()," structures with totally ",fields," fields"); + +#ifdef ASSIMP_BUILD_BLENDER_DEBUG + dna.DumpToFile(); +#endif + + dna.AddPrimitiveStructures(); + dna.RegisterConverters(); +} + + +#ifdef ASSIMP_BUILD_BLENDER_DEBUG + +#include +// ------------------------------------------------------------------------------------------------ +void DNA :: DumpToFile() +{ + // we don't bother using the VFS here for this is only for debugging. + // (and all your bases are belong to us). + + std::ofstream f("dna.txt"); + if (f.fail()) { + ASSIMP_LOG_ERROR("Could not dump dna to dna.txt"); + return; + } + f << "Field format: type name offset size" << "\n"; + f << "Structure format: name size" << "\n"; + + for(const Structure& s : structures) { + f << s.name << " " << s.size << "\n\n"; + for(const Field& ff : s.fields) { + f << "\t" << ff.type << " " << ff.name << " " << ff.offset << " " << ff.size << "\n"; + } + f << "\n"; + } + f << std::flush; + + ASSIMP_LOG_INFO("BlenderDNA: Dumped dna to dna.txt"); +} +#endif + +// ------------------------------------------------------------------------------------------------ +/*static*/ void DNA :: ExtractArraySize( + const std::string& out, + size_t array_sizes[2] +) +{ + array_sizes[0] = array_sizes[1] = 1; + std::string::size_type pos = out.find('['); + if (pos++ == std::string::npos) { + return; + } + array_sizes[0] = strtoul10(&out[pos]); + + pos = out.find('[',pos); + if (pos++ == std::string::npos) { + return; + } + array_sizes[1] = strtoul10(&out[pos]); +} + +// ------------------------------------------------------------------------------------------------ +std::shared_ptr< ElemBase > DNA :: ConvertBlobToStructure( + const Structure& structure, + const FileDatabase& db +) const +{ + std::map::const_iterator it = converters.find(structure.name); + if (it == converters.end()) { + return std::shared_ptr< ElemBase >(); + } + + std::shared_ptr< ElemBase > ret = (structure.*((*it).second.first))(); + (structure.*((*it).second.second))(ret,db); + + return ret; +} + +// ------------------------------------------------------------------------------------------------ +DNA::FactoryPair DNA :: GetBlobToStructureConverter( + const Structure& structure, + const FileDatabase& /*db*/ +) const +{ + std::map::const_iterator it = converters.find(structure.name); + return it == converters.end() ? FactoryPair() : (*it).second; +} + +// basing on http://www.blender.org/development/architecture/notes-on-sdna/ +// ------------------------------------------------------------------------------------------------ +void DNA :: AddPrimitiveStructures() +{ + // NOTE: these are just dummies. Their presence enforces + // Structure::Convert to be called on these + // empty structures. These converters are special + // overloads which scan the name of the structure and + // perform the required data type conversion if one + // of these special names is found in the structure + // in question. + + indices["int"] = structures.size(); + structures.push_back( Structure() ); + structures.back().name = "int"; + structures.back().size = 4; + + indices["short"] = structures.size(); + structures.push_back( Structure() ); + structures.back().name = "short"; + structures.back().size = 2; + + + indices["char"] = structures.size(); + structures.push_back( Structure() ); + structures.back().name = "char"; + structures.back().size = 1; + + + indices["float"] = structures.size(); + structures.push_back( Structure() ); + structures.back().name = "float"; + structures.back().size = 4; + + + indices["double"] = structures.size(); + structures.push_back( Structure() ); + structures.back().name = "double"; + structures.back().size = 8; + + // no long, seemingly. +} + +// ------------------------------------------------------------------------------------------------ +void SectionParser :: Next() +{ + stream.SetCurrentPos(current.start + current.size); + + const char tmp[] = { + (const char)stream.GetI1(), + (const char)stream.GetI1(), + (const char)stream.GetI1(), + (const char)stream.GetI1() + }; + current.id = std::string(tmp,tmp[3]?4:tmp[2]?3:tmp[1]?2:1); + + current.size = stream.GetI4(); + current.address.val = ptr64 ? stream.GetU8() : stream.GetU4(); + + current.dna_index = stream.GetI4(); + current.num = stream.GetI4(); + + current.start = stream.GetCurrentPos(); + if (stream.GetRemainingSizeToLimit() < current.size) { + throw DeadlyImportError("BLEND: invalid size of file block"); + } + +#ifdef ASSIMP_BUILD_BLENDER_DEBUG + ASSIMP_LOG_DEBUG(current.id); +#endif +} + + + +#endif diff --git a/thirdparty/assimp/code/Blender/BlenderDNA.h b/thirdparty/assimp/code/Blender/BlenderDNA.h new file mode 100644 index 0000000..375d0c4 --- /dev/null +++ b/thirdparty/assimp/code/Blender/BlenderDNA.h @@ -0,0 +1,844 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file BlenderDNA.h + * @brief Blender `DNA` (file format specification embedded in + * blend file itself) loader. + */ +#ifndef INCLUDED_AI_BLEND_DNA_H +#define INCLUDED_AI_BLEND_DNA_H + +#include +#include +#include +#include +#include +#include + +// enable verbose log output. really verbose, so be careful. +#ifdef ASSIMP_BUILD_DEBUG +# define ASSIMP_BUILD_BLENDER_DEBUG +#endif + +// #define ASSIMP_BUILD_BLENDER_NO_STATS + +namespace Assimp { + +template class StreamReader; +typedef StreamReader StreamReaderAny; + +namespace Blender { + +class FileDatabase; +struct FileBlockHead; + +template